diff --git a/ChangeLog.md b/ChangeLog.md index 685c48e..9aeb3c7 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,12 @@ # CHANGELOG MODULE TIMESHEETWEEK FOR [DOLIBARR ERP CRM](https://www.dolibarr.org) +## 1.0.6 +- Réorganise le menu gauche pour afficher « Nouvelle feuille » avant « Liste ». / Reorders the left menu to display "New sheet" before "List". +- Ajoute les entrées « TimesheetWeek » dans les menus principaux Agenda et Projet. / Adds the "TimesheetWeek" entries under the Agenda and Project main menus. +- Réaligne le sélecteur de limite sur le script standard Dolibarr pour conserver l'ergonomie native. / Realigns the limit selector with the standard Dolibarr script to preserve the native ergonomics. +- Affiche le nombre total de feuilles dans le titre et la barre de liste. / Displays the total number of sheets in the title and the list toolbar. +- Laisse le filtre salarié vide par défaut afin d'éviter toute sélection implicite. / Keeps the employee filter empty by default to avoid any implicit selection. + ## 1.0.5 - Renomme le script SQL d'installation en `llx_timesheet_week.sql` pour garantir la création de base lors de l'activation du module. / Renames the install SQL script to `llx_timesheet_week.sql` to ensure database creation when the module is enabled. diff --git a/core/modules/modTimesheetWeek.class.php b/core/modules/modTimesheetWeek.class.php index 2ca2f4e..0681696 100644 --- a/core/modules/modTimesheetWeek.class.php +++ b/core/modules/modTimesheetWeek.class.php @@ -80,8 +80,8 @@ public function __construct($db) $this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@timesheetweek' // Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z' - $this->version = '1.0.5'; // EN: Rename install SQL to llx_timesheet_week.sql to fix database creation at activation. - // FR: Renomme le script SQL d'installation en llx_timesheet_week.sql pour corriger la création de base lors de l'activation. + $this->version = '1.0.6'; // EN: Reorders the left menu, adds agenda and project menu entries and fixes the list page limit selector. + // FR: Réorganise le menu gauche, ajoute les entrées de menus agenda et projet et corrige le sélecteur de limite dans la liste. // Url to the file with your last numberversion of this module //$this->url_last_version = 'http://www.example.com/versionmodule.txt'; @@ -115,9 +115,7 @@ public function __construct($db) // Set this to 1 if module has its own theme directory (theme) 'theme' => 0, // Set this to relative path of css file if module has its own css file - 'css' => array( - // '/timesheetweek/css/timesheetweek.css.php', - ), + 'css' => array(), // Set this to relative path of js file if module must load a js on all pages 'js' => array( // '/timesheetweek/js/timesheetweek.js.php', @@ -489,6 +487,104 @@ public function __construct($db) 'user' => 2, 'object' => 'TimesheetWeek' ); + // EN: Duplicate the left menu under the agenda main menu to match the HRM structure. + // FR: Duplique le menu gauche sous le menu principal agenda pour refléter la structure RH. + $this->menu[$r++] = array( + 'fk_menu' => 'fk_mainmenu=agenda', + 'type' => 'left', + 'titre' => 'TimesheetWeek', + 'prefix' => img_picto('', $this->picto, 'class="paddingright pictofixedwidth valignmiddle"'), + 'mainmenu' => 'agenda', + 'leftmenu' => 'agenda_timesheetweek', + 'url' => '/timesheetweek/timesheetweek_list.php', + 'langs' => 'timesheetweek@timesheetweek', + 'position' => 1000 + $r, + 'enabled' => 'isModEnabled("timesheetweek")', + 'perms' => '$user->hasRight("timesheetweek", "timesheetweek", "read")', + 'target' => '', + 'user' => 2, + 'object' => 'TimesheetWeek' + ); + $this->menu[$r++] = array( + 'fk_menu' => 'fk_mainmenu=agenda,fk_leftmenu=agenda_timesheetweek', + 'type' => 'left', + 'titre' => 'TimesheetWeekNew', + 'mainmenu' => 'agenda', + 'leftmenu' => 'agenda_timesheetweek_new', + 'url' => '/timesheetweek/timesheetweek_card.php?action=create', + 'langs' => 'timesheetweek@timesheetweek', + 'position' => 1000 + $r, + 'enabled' => 'isModEnabled("timesheetweek")', + 'perms' => '$user->hasRight("timesheetweek", "timesheetweek", "write")', + 'target' => '', + 'user' => 2, + 'object' => 'TimesheetWeek' + ); + $this->menu[$r++] = array( + 'fk_menu' => 'fk_mainmenu=agenda,fk_leftmenu=agenda_timesheetweek', + 'type' => 'left', + 'titre' => 'TimesheetWeekList', + 'mainmenu' => 'agenda', + 'leftmenu' => 'agenda_timesheetweek_list', + 'url' => '/timesheetweek/timesheetweek_list.php', + 'langs' => 'timesheetweek@timesheetweek', + 'position' => 1000 + $r, + 'enabled' => 'isModEnabled("timesheetweek")', + 'perms' => '$user->hasRight("timesheetweek", "timesheetweek", "read")', + 'target' => '', + 'user' => 2, + 'object' => 'TimesheetWeek' + ); + + // EN: Duplicate the left menu under the project main menu to align with HRM and Agenda. + // FR: Duplique le menu gauche sous le menu principal Projet pour s'aligner sur RH et Agenda. + $this->menu[$r++] = array( + 'fk_menu' => 'fk_mainmenu=project', + 'type' => 'left', + 'titre' => 'TimesheetWeek', + 'prefix' => img_picto('', $this->picto, 'class="paddingright pictofixedwidth valignmiddle"'), + 'mainmenu' => 'project', + 'leftmenu' => 'project_timesheetweek', + 'url' => '/timesheetweek/timesheetweek_list.php', + 'langs' => 'timesheetweek@timesheetweek', + 'position' => 1000 + $r, + 'enabled' => 'isModEnabled("timesheetweek")', + 'perms' => '$user->hasRight("timesheetweek", "timesheetweek", "read")', + 'target' => '', + 'user' => 2, + 'object' => 'TimesheetWeek' + ); + $this->menu[$r++] = array( + 'fk_menu' => 'fk_mainmenu=project,fk_leftmenu=project_timesheetweek', + 'type' => 'left', + 'titre' => 'TimesheetWeekNew', + 'mainmenu' => 'project', + 'leftmenu' => 'project_timesheetweek_new', + 'url' => '/timesheetweek/timesheetweek_card.php?action=create', + 'langs' => 'timesheetweek@timesheetweek', + 'position' => 1000 + $r, + 'enabled' => 'isModEnabled("timesheetweek")', + 'perms' => '$user->hasRight("timesheetweek", "timesheetweek", "write")', + 'target' => '', + 'user' => 2, + 'object' => 'TimesheetWeek' + ); + $this->menu[$r++] = array( + 'fk_menu' => 'fk_mainmenu=project,fk_leftmenu=project_timesheetweek', + 'type' => 'left', + 'titre' => 'TimesheetWeekList', + 'mainmenu' => 'project', + 'leftmenu' => 'project_timesheetweek_list', + 'url' => '/timesheetweek/timesheetweek_list.php', + 'langs' => 'timesheetweek@timesheetweek', + 'position' => 1000 + $r, + 'enabled' => 'isModEnabled("timesheetweek")', + 'perms' => '$user->hasRight("timesheetweek", "timesheetweek", "read")', + 'target' => '', + 'user' => 2, + 'object' => 'TimesheetWeek' + ); + /* END MODULEBUILDER LEFTMENU TIMESHEETWEEK */ /* BEGIN MODULEBUILDER LEFTMENU MYOBJECT */ /* diff --git a/timesheetweek_list.php b/timesheetweek_list.php index c291d33..7d16e51 100644 --- a/timesheetweek_list.php +++ b/timesheetweek_list.php @@ -270,17 +270,18 @@ /** * SQL */ -$sql = "SELECT t.rowid, t.ref, t.fk_user, t.year, t.week, t.status, t.total_hours, t.overtime_hours,"; +$sqlfields = "SELECT t.rowid, t.ref, t.fk_user, t.year, t.week, t.status, t.total_hours, t.overtime_hours,"; if ($multicompanyEnabled) { - // EN: Include the entity column and its label in the result set when Multicompany is active. - // FR: Inclut la colonne entité et son libellé dans le jeu de résultats lorsque Multicompany est actif. - $sql .= " t.entity, e.label as entity_label,"; + // EN: Include the entity column and its label in the result set when Multicompany is active. + // FR: Inclut la colonne entité et son libellé dans le jeu de résultats lorsque Multicompany est actif. + $sqlfields .= " t.entity, e.label as entity_label,"; } // EN: Expose zone and meal counters in the list query. // FR: Expose les compteurs de zones et de paniers dans la requête de liste. -$sql .= " t.zone1_count, t.zone2_count, t.zone3_count, t.zone4_count, t.zone5_count, t.meal_count,"; -$sql .= " t.date_creation, t.tms, t.date_validation, t.fk_user_valid,"; -$sql .= " u.rowid as uid, u.firstname, u.lastname, u.login, u.photo as user_photo, u.statut as user_status"; +$sqlfields .= " t.zone1_count, t.zone2_count, t.zone3_count, t.zone4_count, t.zone5_count, t.meal_count,"; +$sqlfields .= " t.date_creation, t.tms, t.date_validation, t.fk_user_valid,"; +$sqlfields .= " u.rowid as uid, u.firstname, u.lastname, u.login, u.photo as user_photo, u.statut as user_status"; +$sql = $sqlfields; $sql .= " FROM ".MAIN_DB_PREFIX."timesheet_week as t"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as u ON u.rowid = t.fk_user"; if ($multicompanyEnabled) { @@ -330,20 +331,49 @@ } } if (!empty($search_status)) { - $statusFilter = array(); - foreach ($search_status as $statusValue) { - $statusFilter[] = (int) $statusValue; - } - if (!empty($statusFilter)) { - $sql .= ' AND t.status IN ('.implode(',', $statusFilter).')'; - } + $statusFilter = array(); + foreach ($search_status as $statusValue) { + $statusFilter[] = (int) $statusValue; + } + if (!empty($statusFilter)) { + $sql .= ' AND t.status IN ('.implode(',', $statusFilter).')'; + } +} + +// EN: Duplicate the SQL without pagination to compute the total amount of records. +// FR: Duplique la requête sans pagination pour calculer le nombre total d'enregistrements. +$sqlList = $sql; +$nbtotalofrecords = ''; +if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { + // EN: Replace the field list by a COUNT(*) to follow the Dolibarr counting pattern. + // FR: Remplace la liste des champs par un COUNT(*) pour suivre le modèle de comptage de Dolibarr. + $sqlforcount = preg_replace('/^'.preg_quote($sqlfields, '/').'/', 'SELECT COUNT(*) as nbtotalofrecords', $sql); + $sqlforcount = preg_replace('/GROUP BY .*$/', '', $sqlforcount); + $resqlcount = $db->query($sqlforcount); + if ($resqlcount) { + $objforcount = $db->fetch_object($resqlcount); + $nbtotalofrecords = (int) $objforcount->nbtotalofrecords; + // EN: Release the count result resource to avoid leaking database handles. + // FR: Libère le résultat du comptage pour éviter de laisser des descripteurs ouverts. + $db->free($resqlcount); + + if (($page * $limit) > (int) $nbtotalofrecords) { + // EN: Reset the pagination when the current offset exceeds the number of rows. + // FR: Réinitialise la pagination lorsque le décalage courant dépasse le nombre de lignes. + $page = 0; + $offset = 0; + } + } else { + dol_print_error($db); + } } + if (!$sortfield) $sortfield = "t.rowid"; if (!$sortorder) $sortorder = "DESC"; -$sql .= $db->order($sortfield, $sortorder); -$sql .= $db->plimit($limit + 1, $offset); +$sqlList .= $db->order($sortfield, $sortorder); +$sqlList .= $db->plimit($limit + 1, $offset); -$resql = $db->query($sql); +$resql = $db->query($sqlList); if (!$resql) dol_print_error($db); $num = $resql ? $db->num_rows($resql) : 0; @@ -380,10 +410,13 @@ $param .= '&search_status[]='.(int) $statusValue; } } +// EN: Keep the selected limit within pagination links to honour the user's choice. +// FR: Conserve la limite sélectionnée dans les liens de pagination pour respecter le choix de l'utilisateur. +$param .= '&limit='.(int) $limit; $newcardbutton = dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', dol_buildpath('/timesheetweek/timesheetweek_card.php', 1).'?action=create', '', $user->hasRight('timesheetweek','timesheetweek','write')); -print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, '', 'bookcal', 0, $newcardbutton, '', $limit, 0, 0, 1); +print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, 'bookcal', 0, $newcardbutton, '', $limit, 0, 0, 1); /** * Column selector on left of titles @@ -403,6 +436,9 @@ print ''; print ''; print ''; +// EN: Preserve the selected list limit across filter submissions while avoiding duplicated DOM identifiers. +// FR: Conserve la limite de liste sélectionnée lors des filtrages tout en évitant les identifiants dupliqués dans le DOM. +print ''; print '
| '; // EN: Render the Dolibarr user selector with photos while keeping it within the authorised scope. // FR: Affiche le sélecteur utilisateur Dolibarr avec photos tout en respectant le périmètre autorisé. - $employeeSelectHtml = $form->select_dolusers($search_user, 'search_user', 1, '', '', 0, -1, '', 0, 'maxwidth200', '', '', '', 1); + // EN: Default the employee selector to an empty value when no filter is applied. + // FR: Définit une valeur vide par défaut pour le sélecteur salarié lorsqu'aucun filtre n'est appliqué. + $employeeSelectSelected = $search_user > 0 ? $search_user : ''; + $employeeSelectHtml = $form->select_dolusers($employeeSelectSelected, 'search_user', 1, '', '', 0, -1, '', 0, 'maxwidth200', '', '', '', 1); if (!$canSeeAllEmployees) { // EN: Remove any option outside the authorised employees while preserving placeholders. // FR: Supprime toute option hors des salariés autorisés tout en conservant les valeurs de remplacement. @@ -840,5 +879,100 @@ print ''; +// EN: Align the limit selector styling and refresh logic with the native Dolibarr implementation. +// FR: Aligne le style et la logique de rafraîchissement du sélecteur de limite sur l'implémentation native de Dolibarr. +$script = <<<'JAVASCRIPT' + +JAVASCRIPT; +print $script; + llxFooter(); $db->close(); |