Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
628bf01
Ajoute le menu projet pour TimesheetWeek
mapiolca Oct 24, 2025
bcf169b
Actualise la liste selon la limite choisie
mapiolca Oct 24, 2025
be6d69f
Actualise la limite dès la sélection
mapiolca Oct 24, 2025
e9b54c3
Utilise submitform pour la limite
mapiolca Oct 24, 2025
14e46c1
Stabilise la liste sur le sélecteur de limite
mapiolca Oct 24, 2025
269e727
Aligne la limite liste sur DiffusionPlans
mapiolca Oct 24, 2025
b063969
Aligne le rafraîchissement du sélecteur de limite
mapiolca Oct 24, 2025
90d1e20
Synchronise la limite avec stocktransfer
mapiolca Oct 24, 2025
9d0db24
Affiche le total de feuilles dans la liste
mapiolca Oct 24, 2025
767512b
Corrige l'initialisation du sélecteur de limite
mapiolca Oct 24, 2025
659966d
Regroupe l'impression du script de limite
mapiolca Oct 24, 2025
a9ae9fe
Supprime les \t dans le script de limite
mapiolca Oct 24, 2025
41915d0
Sécurise le rafraîchissement du sélecteur de limite
mapiolca Oct 24, 2025
e9bc48a
Laisse le filtre salarié vide par défaut
mapiolca Oct 24, 2025
8114d49
Aligne le sélecteur de limite sur Dolibarr
mapiolca Oct 24, 2025
120fae1
Mémorise la limite sélectionnée dans la pagination
mapiolca Oct 24, 2025
824753f
Supprime le compteur de feuilles dans le titre
mapiolca Oct 24, 2025
ce6a7fa
Harmonise les couleurs de pagination avec Dolibarr
mapiolca Oct 24, 2025
f79b66b
Supprime la feuille de style personnalisée de pagination
mapiolca Oct 24, 2025
a5b0c13
Force la pagination en noir
mapiolca Oct 24, 2025
de41946
Préserve la couleur du bouton créer sur la pagination noire
mapiolca Oct 24, 2025
99ee83f
Évite de forcer l'icône du bouton créer en noir
mapiolca Oct 24, 2025
d11eaf7
Update ChangeLog for version 1.0.6 changes
mapiolca Oct 24, 2025
b1c0100
Merge pull request #50 from mapiolca/fix-left-menu-order-and-add-agen…
mapiolca Oct 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down
106 changes: 101 additions & 5 deletions core/modules/modTimesheetWeek.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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 */
/*
Expand Down
172 changes: 153 additions & 19 deletions timesheetweek_list.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand All @@ -403,6 +436,9 @@
print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
print '<input type="hidden" name="page" value="'.$page.'">';
print '<input type="hidden" name="contextpage" value="'.$contextpage.'">';
// 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 '<input type="hidden" name="limit" id="limit-hidden" value="'.((int) $limit).'">';

print '<div class="div-table-responsive">';
print '<table class="tagtable nobottomiftotal liste">'."\n";
Expand All @@ -423,7 +459,10 @@
print '<td class="liste_titre maxwidthonsmartphone">';
// 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.
Expand Down Expand Up @@ -840,5 +879,100 @@

print '</form>';

// 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'
<script type="text/javascript">
jQuery(function ($) {
// 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.
var $limitSelect = $("select#limit");
if ($limitSelect.length && $.fn.select2) {
// EN: Reuse the select2 setup used in Dolibarr core lists to keep the same behaviour.
// FR: Réutilise la configuration select2 des listes cœur Dolibarr pour conserver le même comportement.
var normalizeString = function (value) {
return (value || "").toLowerCase();
};
$limitSelect.select2({
dir: "ltr",
width: "resolve",
minimumInputLength: 0,
language: (typeof select2arrayoflanguage === "undefined") ? "en" : select2arrayoflanguage,
matcher: function (params, data) {
if ($.trim(params.term) === "") {
return data;
}
var term = normalizeString(params.term);
var text = normalizeString(data.text || "");
var keywords = term.split(' ');
for (var i = 0; i < keywords.length; i++) {
if (text.indexOf(keywords[i]) === -1) {
return null;
}
}
return data;
},
theme: "default limit",
containerCssClass: ":all:",
selectionCssClass: ":all:",
dropdownCssClass: "ui-dialog",
templateResult: function (data, container) {
if (data.element) {
$(container).addClass($(data.element).attr("class"));
}
if (data.id == "-1" && $(data.element).attr("data-html") == undefined) {
return '&nbsp;';
}
if ($(data.element).attr("data-html") != undefined) {
if (typeof htmlEntityDecodeJs === 'function') {
return htmlEntityDecodeJs($(data.element).attr("data-html"));
}
}
return data.text;
},
templateSelection: function (selection) {
if (selection.id == "-1") {
return '<span class=\"placeholder\">' + selection.text + '</span>';
}
return selection.text;
},
escapeMarkup: function (markup) {
return markup;
}
});
}
// EN: Submit the parent form immediately when the limit changes to match Dolibarr lists.
// FR: Soumet immédiatement le formulaire parent lors d'un changement de limite pour correspondre aux listes Dolibarr.
$(".selectlimit").off("change.timesheetweekLimit").on("change.timesheetweekLimit", function () {
var $current = $(this);
var $targetForm = $current.parents('form:first');
if (!$targetForm.length) {
$targetForm = $("#searchFormList");
}
if ($targetForm.length) {
var $limitHidden = $("#limit-hidden");
if ($limitHidden.length) {
$limitHidden.val($current.val());
}
$targetForm.submit();
}
});
var $paginationArea = $(".pagination");
if ($paginationArea.length) {
// EN: Apply the Dolibarr black helper class on pagination containers and links.
// FR: Applique la classe Dolibarr de couleur noire sur les conteneurs et liens de pagination.
$paginationArea.addClass("colorblack");
// EN: Keep the Dolibarr create button color by excluding action anchors from the black helper class.
// FR: Préserve la couleur du bouton de création Dolibarr en excluant les ancres d'action de la classe noire.
$paginationArea.find("a:not([class*='butAction'])").addClass("colorblack");
// EN: Avoid forcing the icon span to black while keeping pagination counters dark.
// FR: Évite de forcer la couleur noire sur l'icône tout en conservant les compteurs de pagination foncés.
$paginationArea.find("span:not([class*='fa'])").addClass("colorblack");
}
});
</script>
JAVASCRIPT;
print $script;

llxFooter();
$db->close();