diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css
index 0898786a719bf906b8ef815fe52af9cd475ca544..84b3e1708a76921a72cdbee5dc96a6242e5d81f3 100644
--- a/app/static/css/scodoc.css
+++ b/app/static/css/scodoc.css
@@ -3992,6 +3992,29 @@ div.table_recap {
   margin-top: 6px;
 }
 
+.table_recap .but_on {
+  background-color: rgb(177, 238, 162);
+  font-weight: bold;
+}
+
+.table_recap .but_off {
+  background-color: lightgray;
+}
+
+.table_recap button.but_on:hover:not(.disabled),
+.table_recap div.but_on:hover:not(.disabled),
+.table_recap a.but_on:hover:not(.disabled),
+.table_recap input.but_on:hover:not(.disabled) {
+  background: linear-gradient(to bottom, lightgray 0%, rgb(51, 255, 0) 100%);
+}
+
+.table_recap button.but_off:hover:not(.disabled),
+.table_recap div.but_off:hover:not(.disabled),
+.table_recap a.but_off:hover:not(.disabled),
+.table_recap input.but_off:hover:not(.disabled) {
+  background: linear-gradient(to bottom, rgb(51, 255, 0) 0%, lightgray 100%);
+}
+
 div.table_recap table.table_recap {
   width: auto;
   /* font-family: Consolas, monaco, monospace; */
diff --git a/app/static/js/table_recap.js b/app/static/js/table_recap.js
index 2c4882a9bbbd1822e1435e622049cc1f72df7d1d..bc3c1ef4c571183d46781d0036e999b04be7dc7a 100644
--- a/app/static/js/table_recap.js
+++ b/app/static/js/table_recap.js
@@ -32,146 +32,141 @@ $(function () {
             }
         }
 
-        // Les colonnes visibles étant mémorisées, il faut initialiser les titres des boutons
+        // Les colonnes visibles sont mémorisées, il faut initialiser l'état des boutons
         function update_buttons_labels(dt) {
-            console.log("update_buttons_labels");
-            dt.buttons('toggle_ident:name').text(dt.columns(".identite_detail").visible()[0] ? "Nom seul" : "Civ/Nom/Prénom");
-            dt.buttons('toggle_partitions:name').text(dt.columns(".partition_aux").visible()[0] ? "Cacher les groupes" : "Montrer groupes");
-            if (!$('table.table_recap').hasClass("table_jury_but")) {
-                // Bouton "rangs groupes", sauf pour table jury BUT
-                dt.buttons('toggle_partitions_rangs:name').text(dt.columns(".partition_rangs").visible()[0] ? "Cacher rangs groupes" : "Rangs groupes");
-                dt.buttons('toggle_admission:name').text(dt.columns(".admission").visible()[0] ? "Cacher infos admission" : "Montrer infos admission");
-            } else {
-                // table jury BUT: avec ou sans codes enregistrés
-                dt.buttons('toggle_recorded_code:name').text(dt.columns(".recorded_code").visible()[0] ? "Cacher codes jury" : "Code jury enregistrés");
-            }
-
-            // Boutons non visibles en mode jury:
-            if (!$('table.table_recap').hasClass("jury")) {
-                // Ces boutons dépendent du mode BUT ou classique:
-                if ($('table.table_recap').hasClass("apc")) {
-                    dt.buttons('toggle_res:name').text(dt.columns(".col_res").visible()[0] ? "Cacher les ressources" : "Montrer les ressources");
-                    dt.buttons('toggle_sae:name').text(dt.columns(".col_sae").visible()[0] ? "Cacher les SAÉs" : "Montrer les SAÉs");
-                } else {
-                    dt.buttons('toggle_mod:name').text(dt.columns(".col_mod:not(.col_empty)").visible()[0] ? "Cacher les modules" : "Montrer les modules");
+            // chaque bouton controle une classe stockée dans le data-group du span
+            document.querySelectorAll("button.dt-button").forEach(but => {
+                let g_span = but.querySelector("span > span");
+                if (g_span) {
+                    let group = g_span.dataset["group"];
+                    if (group) {
+                        // si le group (= la 1ere col.) est visible, but_on
+                        if (dt.columns("." + group).visible()[0]) {
+                            but.classList.add("but_on");
+                            but.classList.remove("but_off");
+                        } else {
+                            but.classList.add("but_off");
+                            but.classList.remove("but_on");
+                        }
+                    }
                 }
-                dt.buttons('toggle_col_empty:name').text(dt.columns(".col_empty").visible()[0] ? "Cacher mod. vides" : "Montrer mod. vides");
-            }
+            });
         }
 
+        // Changement visibilité groupes colonnes (boutons)
+        function toggle_col_but_visibility(e, dt, node, config) {
+            let group = node.children()[0].firstChild.dataset.group;
+            toggle_col_group_visibility(dt, group, node.hasClass("but_on"));
+        }
+        function toggle_col_ident_visibility(e, dt, node, config) {
+            let onoff = node.hasClass("but_on");
+            toggle_col_group_visibility(dt, "identite_detail", onoff);
+            toggle_col_group_visibility(dt, "identite_court", !onoff);
+        }
+        function toggle_col_ressources_visibility(e, dt, node, config) {
+            let onoff = node.hasClass("but_on");
+            toggle_col_group_visibility(dt, "col_res", onoff);
+            toggle_col_group_visibility(dt, "col_ue_bonus", onoff);
+            toggle_col_group_visibility(dt, "col_malus", onoff);
+        }
+        function toggle_col_group_visibility(dt, group, onoff) {
+            if (onoff) {
+                dt.columns('.' + group).visible(false);
+            } else {
+                dt.columns('.' + group).visible(true);
+            }
+            update_buttons_labels(dt);
+        }
+        // Definition des boutons au dessus de la table:
         let buttons = [
             {
-                name: "toggle_ident",
-                text: "Civ/Nom/Prénom",
+                extend: 'copyHtml5',
+                text: 'Copier',
+                exportOptions: { orthogonal: 'export' }
+            },
+            {
+                extend: 'excelHtml5',
+                // footer: true, // ne fonctionne pas ?
+                exportOptions: { orthogonal: 'export' },
+                title: document.querySelector('table.table_recap').dataset.filename
+            },
+            {
+                // force affichage de toutes les colonnes
+                text: '<a title="Afficher toutes les colonnes">&#10036;</a>',
                 action: function (e, dt, node, config) {
-                    let visible = dt.columns(".identite_detail").visible()[0];
-                    dt.columns(".identite_detail").visible(!visible);
-                    dt.columns(".identite_court").visible(visible);
+                    dt.columns().visible(true);
                     update_buttons_labels(dt);
                 }
             },
             {
-                name: "toggle_partitions",
-                text: "Montrer groupes",
+                text: '<a title="Rétablir l\'affichage par défaut">&#10135;</a>',
                 action: function (e, dt, node, config) {
-                    let visible = dt.columns(".partition_aux").visible()[0];
-                    dt.columns(".partition_aux").visible(!visible);
-                    update_buttons_labels(dt);
+                    localStorage.clear();
+                    console.log("cleared localStorage");
+                    location.reload();
                 }
             },
-        ];
-        // Bouton "rangs groupes", sauf pour table jury BUT
-        if (!$('table.table_recap').hasClass("table_jury_but")) {
+            {
+                text: '<span data-group="identite_detail">Civilité</span>',
+                action: toggle_col_ident_visibility,
+            },
+            {
+                text: '<span data-group="partition_aux">Groupes</span>',
+                action: toggle_col_but_visibility,
+            },
+            {
+                text: '<span data-group="partition_rangs">Rg</span>',
+                action: toggle_col_but_visibility,
+            },
+        ]; // fin des boutons communs à toutes les tables recap
+
+        if ($('table.table_recap').hasClass("jury")) {
+            // table jury: avec ou sans codes enregistrés
             buttons.push(
                 {
-                    name: "toggle_partitions_rangs",
-                    text: "Rangs groupes",
-                    action: function (e, dt, node, config) {
-                        let rangs_visible = dt.columns(".partition_rangs").visible()[0];
-                        dt.columns(".partition_rangs").visible(!rangs_visible);
-                        update_buttons_labels(dt);
-                    }
+                    text: '<span data-group="recorded_code">Code jurys</span>',
+                    action: toggle_col_but_visibility,
                 });
         } else {
-            // table jury BUT: avec ou sans codes enregistrés
-            buttons.push(
-                {
-                    name: "toggle_recorded_code",
-                    text: "Code jury enregistrés",
-                    action: function (e, dt, node, config) {
-                        let visible = dt.columns(".recorded_code").visible()[0];
-                        dt.columns(".recorded_code").visible(!visible);
-                        update_buttons_labels(dt);
-                    }
-                });
-        }
-
-        if (!$('table.table_recap').hasClass("jury")) {
+            // BOUTONS SPECIFIQUES A LA TABLE RECAP NON JURY
             buttons.push(
                 $('table.table_recap').hasClass("apc") ?
                     {
-                        name: "toggle_res",
-                        text: "Visibilité ressources",
-                        action: function (e, dt, node, config) {
-                            let visible = dt.columns(".col_res").visible()[0];
-                            dt.columns(".col_res").visible(!visible);
-                            dt.columns(".col_ue_bonus").visible(!visible);
-                            dt.columns(".col_malus").visible(!visible);
-                            update_buttons_labels(dt);
-                        }
+                        text: '<span data-group="col_res">Ressources</span>',
+                        action: toggle_col_ressources_visibility,
                     } : {
                         name: "toggle_mod",
                         text: "Cacher les modules",
                         action: function (e, dt, node, config) {
-                            let visible = dt.columns(".col_mod:not(.col_empty)").visible()[0];
-                            dt.columns(".col_mod:not(.col_empty)").visible(!visible);
-                            dt.columns(".col_ue_bonus").visible(!visible);
-                            dt.columns(".col_malus").visible(!visible);
-                            update_buttons_labels(dt);
+                            let onoff = node.hasClass("but_on");
+                            toggle_col_group_visibility(dt, "col_mod:not(.col_empty)", onoff);
+                            toggle_col_group_visibility(dt, "col_ue_bonus", onoff);
+                            toggle_col_group_visibility(dt, "col_malus", onoff);
                         }
                     }
             );
             if ($('table.table_recap').hasClass("apc")) {
                 buttons.push({
-                    name: "toggle_sae",
-                    text: "Visibilité SAÉs",
-                    action: function (e, dt, node, config) {
-                        let visible = dt.columns(".col_sae").visible()[0];
-                        dt.columns(".col_sae").visible(!visible);
-                        update_buttons_labels(dt);
-                    }
-                })
+                    text: '<span data-group="col_sae">SAÉs</span>',
+                    action: toggle_col_but_visibility,
+                });
             }
-            buttons.push({
-                name: "toggle_col_empty",
-                text: "Visibilité mod. vides",
-                action: function (e, dt, node, config) {
-                    let visible = dt.columns(".col_empty").visible()[0];
-                    dt.columns(".col_empty").visible(!visible);
-                    update_buttons_labels(dt);
-                }
-            })
-        }
-        // Boutons admission, sauf pour table jury BUT
-        if (!$('table.table_recap').hasClass("table_jury_but")) {
-            buttons.push({
-                name: "toggle_admission",
-                text: "Montrer infos admission",
-                action: function (e, dt, node, config) {
-                    let visible = dt.columns(".admission").visible()[0];
-                    dt.columns(".admission").visible(!visible);
-                    update_buttons_labels(dt);
-                }
+            buttons.push({ // modules vides
+                text: '<span data-group="col_empty">Vides</span>',
+                action: toggle_col_but_visibility,
             });
-        }
-        buttons.push({
-            name: "reset_table_display",
-            text: "Rétablir affichage par défaut",
-            action: function (e, dt, node, config) {
-                localStorage.clear();
-                location.reload();
+            // Boutons admission (pas en jury)
+            if (!$('table.table_recap').hasClass("jury")) {
+                buttons.push(
+                    {
+                        text: '<span data-group="admission">Admission</span>',
+                        action: toggle_col_but_visibility,
+                    }
+                );
             }
-        });
+        }
+
+        // ------------- LA TABLE ---------
         try {
             let table = $('table.table_recap').DataTable(
                 {
@@ -217,25 +212,7 @@ $(function () {
 
                     ],
                     dom: 'Bfrtip',
-                    buttons: [
-                        {
-                            extend: 'copyHtml5',
-                            text: 'Copier',
-                            exportOptions: { orthogonal: 'export' }
-                        },
-                        {
-                            extend: 'excelHtml5',
-                            // footer: true, // ne fonctionne pas ?
-                            exportOptions: { orthogonal: 'export' },
-                            title: document.querySelector('table.table_recap').dataset.filename
-                        },
-                        {
-                            extend: 'collection',
-                            text: 'Colonnes affichées',
-                            autoClose: true,
-                            buttons: buttons,
-                        },
-                    ],
+                    buttons: buttons,
                     "drawCallback": function (settings) {
                         // permet de conserver l'ordre de tri des colonnes
                         let order_info = JSON.stringify($('table.table_recap').DataTable().order());
diff --git a/app/tables/recap.py b/app/tables/recap.py
index c0f32e546c3f70cc7139d4dae5be8816a1e9abfe..42107b720b85265d6352f05f9a2cb8309928e781 100644
--- a/app/tables/recap.py
+++ b/app/tables/recap.py
@@ -82,6 +82,7 @@ class TableRecap(tb.Table):
         if res.formsemestre.etuds_inscriptions:  # table non vide
             # Fixe l'ordre des groupes de colonnes communs:
             groups = [
+                "etud_codes",
                 "rang",
                 "identite_court",
                 "identite_detail",