From 2835f4918271a7ad39a8f265c4461074cd4e0789 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Tue, 2 Jun 2026 20:17:32 +0200 Subject: [PATCH] Badge notes --- AG_GRID_PROGRESS.md | 98 +++++++++++++ AG_GRID_SESSION_INDEX.md | 9 ++ dist/tryton-sao.css | 72 ++++++++- dist/tryton-sao.js | 307 ++++++++++++++++++++++++++++++++++++--- tryton-sao.css | 72 ++++++++- tryton-sao.js | 307 ++++++++++++++++++++++++++++++++++++--- 6 files changed, 825 insertions(+), 40 deletions(-) diff --git a/AG_GRID_PROGRESS.md b/AG_GRID_PROGRESS.md index 05a484e..bdc0dcd 100644 --- a/AG_GRID_PROGRESS.md +++ b/AG_GRID_PROGRESS.md @@ -1027,3 +1027,101 @@ Point de deploiement: - `GROUPING_LOAD_FIELDS_FLAT_JSON` - `GROUPING_KEYS_FLAT_JSON` - si une correction visuelle semble inactive, verifier d'abord quel fichier est charge par `index.html` et si `dist/` a ete synchronise + +### Session du 2026-06-02 - KPI badges Lots Management non resolu + +Demande UX: + +- rapprocher `Lots management` de la maquette recue: + - barre KPI compacte entre les boutons AG Grid et la grille + - compteurs par valeur de badge (`Open`, `Physic`, `scheduled`, `shipped`, `unshipped`, etc.) + - badges en rendu pastel: fond clair + texte couleur forte + - couleurs proches de la maquette: + - `Open`: bleu + - `Physic`: violet + - `scheduled`: orange + - `shipped`: vert + - `unshipped`: rouge + +Ce qui a ete implemente dans `tryton-sao.js` / `dist/tryton-sao.js`: + +- ajout de `this.summary_bar` sous la toolbar AG Grid +- appel de `_update_summary_bar()` apres: + - changement de colonnes + - changement de filtre AG Grid + - refresh de `rowData` + - `display()` +- prise en compte de la hauteur de la barre KPI dans `_resize_grid_host()` +- helpers ajoutes/touches: + - `_get_visible_record_rows` + - `_get_summary_badge_columns` + - `_render_summary_item` + - `_update_summary_bar` + - `_normalize_badge_text_key` + - `_get_badge_color_order` + - `_get_badge_display_colors` + - `_append_badge_summary_items` + - `_get_summary_status_sources_from_grid` + - `_get_default_badge_summary_color` +- tentative de fallback: + - scanner les `columnDefs` AG Grid visibles qui ressemblent a des colonnes `status`, `state`, `type`, `shipping`, `shipment`, `open/physic` + - normaliser les valeurs type `emoji + Open` en `Open` + +Ce qui a ete implemente dans `tryton-sao.css` / `dist/tryton-sao.css`: + +- styles `.ag-grid-tree-summary`, `.ag-grid-summary-item`, `.ag-grid-summary-label`, `.ag-grid-summary-value` +- badges `.ag-grid-badge` passes en fond clair + texte plus fort +- couleurs dark mode pour la barre KPI + +Etat observe par l'utilisateur: + +- les badges de grille sont bien en rendu pastel pour `Shipping status` +- `Qt type` affiche encore `Open` / `Physic` en texte simple ou avec rendu partiel selon la config cote Tryton +- la barre KPI affiche toujours seulement: + - `Rows` + - `192` +- les compteurs par valeur de badge ne s'affichent pas, meme apres les fallbacks ajoutes + +Conclusion importante: + +- le probleme des totaux par valeur de badge n'est PAS resolu +- ne pas considerer les helpers KPI actuels comme valides +- prochaine reprise: instrumenter avant de retoucher encore + +Hypotheses probables a verifier: + +- `_get_summary_badge_columns()` retourne vide au runtime, ou retourne des colonnes sans valeurs exploitables +- `_get_summary_status_sources_from_grid()` ne voit pas les `columnDefs` attendus au moment ou `_update_summary_bar()` tourne +- `rows` contient bien 192 lignes, mais les proprietes lues (`row[name]`) ne correspondent pas aux `field/colId` visibles dans AG Grid +- les valeurs visibles dans les cellules peuvent venir du `valueGetter` / renderer, mais ne pas etre presentes dans le `rowData` sous le meme nom +- le `summary_bar` peut etre mis a jour avant que `_grid_columns` soit disponible ou avant que les `columnDefs` restaurees soient reappliquees + +Trace a ajouter en premier a la prochaine session: + +- une trace plate, appelee par exemple `[AGGRID][SUMMARY_BADGES_DEBUG_JSON]`, dans `_update_summary_bar()` +- champs minimum: + - `rowsLength` + - `rowKeysSample`: `Object.keys(rows[0] || {})` + - `rowSample`: valeurs des colonnes visibles sur 1 ou 2 lignes + - `summaryBadgeColumns`: noms/labels/attributs des colonnes retournees par `_get_summary_badge_columns()` + - `gridColumns`: `field`, `colId`, `headerName`, `cellClass`, `headerClass` de `this._grid_columns` + - `displayedColumns`: `getAllDisplayedColumns().map(col => col.getColId())` + - `statusSources`: retour de `_get_summary_status_sources_from_grid()` + - `summaryItemsBeforeRender` + +Lignes Tryton/XML conseillees pour le rendu badges, mais a confirmer apres debug: + +```xml + + + +``` + +Point de vigilance: + +- si `Qt type` a un emoji/prefixe cote Python ou XML, SAO doit soit: + - rendre l'icone separement du texte de valeur + - soit normaliser la cle avant mapping couleur/KPI +- eviter de continuer a corriger "a l'oeil"; le prochain pas doit etre une trace runtime partageable. diff --git a/AG_GRID_SESSION_INDEX.md b/AG_GRID_SESSION_INDEX.md index b04a65a..e040323 100644 --- a/AG_GRID_SESSION_INDEX.md +++ b/AG_GRID_SESSION_INDEX.md @@ -349,3 +349,12 @@ Traces a lire si le sujet revient: - `_resize_grid_host()` peut poser des styles inline sur le `content_box`; quand `Sao.ScreenContainer.set()` repasse sur un widget non AG Grid, nettoyer `height`, `minHeight`, `flexBasis`, `flexShrink`, `width`, `maxWidth` - regression typique si cet invariant casse: forme Purchase classique coupee en bas, onglets/lignes mal visibles, grande zone blanche ou largeur incorrecte apres toggle du menu gauche - test obligatoire: Purchase form sans AG Grid principale avec menu gauche ouvert/cache, puis Lots Management/report AG Grid avec filtres hauts + +### Point de reprise 2026-06-02 - KPI badges non resolu + +- tentative en cours: barre KPI au-dessus de la grille avec compteurs par valeur de badge +- resultat actuel: la barre affiche `Rows 192`, mais PAS les compteurs `Open`, `Physic`, `scheduled`, `shipped`, `unshipped` +- le rendu pastel des badges `Shipping status` est visible, mais la logique de totaux par valeur ne marche pas +- fichiers touches: `tryton-sao.js`, `tryton-sao.css`, `dist/tryton-sao.js`, `dist/tryton-sao.css` +- avant toute nouvelle correction, ajouter une trace `[AGGRID][SUMMARY_BADGES_DEBUG_JSON]` dans `_update_summary_bar()` +- lire le bloc detaille dans `AG_GRID_PROGRESS.md > Session du 2026-06-02 - KPI badges Lots Management non resolu` diff --git a/dist/tryton-sao.css b/dist/tryton-sao.css index 900d6bf..9dcdc09 100644 --- a/dist/tryton-sao.css +++ b/dist/tryton-sao.css @@ -11613,6 +11613,54 @@ html[theme="default"] .ag-grid-tree-grouping-panel input[type="checkbox"]:after margin-left: auto; } +.ag-grid-tree-summary { + display: flex; + align-items: stretch; + flex-wrap: wrap; + gap: 0; + min-height: 48px; + margin: 0 0 8px; + border: 1px solid #dfe8e3; + border-radius: 6px; + background: #ffffff; + box-shadow: 0 1px 4px rgba(53, 81, 84, 0.06); + overflow: hidden; +} + +.ag-grid-summary-item { + display: flex; + flex: 0 1 150px; + min-width: 120px; + max-width: 190px; + flex-direction: column; + justify-content: center; + gap: 3px; + padding: 8px 18px; + border-right: 1px solid #e7efeb; +} + +.ag-grid-summary-item:last-child { + border-right: none; +} + +.ag-grid-summary-label { + min-width: 0; + color: #64748b; + font-size: 11px; + font-weight: 600; + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.ag-grid-summary-value { + color: #145369; + font-size: 18px; + font-weight: 700; + line-height: 1.15; +} + .ag-grid-tree-host { flex: 0 0 auto; min-height: 520px; @@ -11794,10 +11842,10 @@ html[theme="default"] .ag-grid-tree-grouping-panel input[type="checkbox"]:after min-height: 22px; padding: 2px 10px; border-radius: 999px; - background: #6c757d; - color: #f7fbf9; + background: #eef3f2; + color: #355154; font-size: 12px; - font-weight: 500; + font-weight: 700; line-height: 1.35; white-space: nowrap; overflow: hidden; @@ -12769,6 +12817,24 @@ html[data-theme-mode="dark"] .ag-grid-filter-logic-operator { color: #8fe1d8; } +html[data-theme-mode="dark"] .ag-grid-tree-summary { + background: #151f1e; + border-color: #2a403d; + box-shadow: 0 8px 22px rgba(0, 0, 0, 0.24); +} + +html[data-theme-mode="dark"] .ag-grid-summary-item { + border-right-color: #243735; +} + +html[data-theme-mode="dark"] .ag-grid-summary-label { + color: #aebfba; +} + +html[data-theme-mode="dark"] .ag-grid-summary-value { + color: #8fe1d8; +} + html[data-theme-mode="dark"] .form-tolerance-gauge { color: #dce7e2; } diff --git a/dist/tryton-sao.js b/dist/tryton-sao.js index 3262cd3..4c0b763 100644 --- a/dist/tryton-sao.js +++ b/dist/tryton-sao.js @@ -22624,6 +22624,9 @@ function eval_pyson(value){ this.status = jQuery('
', { 'class': 'ag-grid-tree-status text-muted', }).appendTo(this.toolbar); + this.summary_bar = jQuery('
', { + 'class': 'ag-grid-tree-summary', + }).appendTo(this.el); this.grid_host = jQuery('
', { 'class': 'ag-grid-theme-alpine ag-grid-tree-host', }).appendTo(this.el); @@ -22946,6 +22949,7 @@ function eval_pyson(value){ if (!options.skipLayoutRefresh) { this._schedule_grid_layout_refresh(); } + this._update_summary_bar(); }, _on_grid_column_resized: function(params) { this._trace_aggrid_column_resize('event', params); @@ -24629,6 +24633,7 @@ function eval_pyson(value){ onFilterChanged: () => { this._schedule_state_save(); this._sync_selection_header_checkbox(); + this._update_summary_bar(); }, onColumnMoved: this._on_grid_column_layout_changed.bind(this), onColumnPinned: this._on_grid_column_layout_changed.bind(this), @@ -25220,6 +25225,7 @@ function eval_pyson(value){ this._log_sao_value_filter_empty_result(this.group.slice()); } this._resize_grid_host(); + this._update_summary_bar(); this._schedule_grid_layout_refresh([0, 80]); }); }, @@ -25258,6 +25264,7 @@ function eval_pyson(value){ this._refresh_grid_layout_after_row_data(row_data); this._sync_selection(selected); this._update_copy_state(); + this._update_summary_bar(); }, _toggle_sao_value_filter_mode: function() { this._set_sao_value_filter_mode( @@ -26061,6 +26068,17 @@ function eval_pyson(value){ }); return parsed; }, + _normalize_badge_text_key: function(text) { + text = text === null || text === undefined ? '' : String(text); + return text.replace(/^[^A-Za-z0-9]+/, '').trim(); + }, + _get_badge_color_order: function(colors) { + return (colors || '').split(',').map(item => { + var pair = item.split(':'); + return pair.length < 2 ? '' : + this._normalize_badge_text_key(pair.shift()); + }).filter(Boolean); + }, _get_record_field_value: function(record, name) { if (!record || !name) { return ''; @@ -26084,34 +26102,73 @@ function eval_pyson(value){ color = color === null || color === undefined ? '' : String(color).trim(); var palette = { - red: '#e06a6a', - danger: '#e06a6a', - coral: '#e06a6a', - orange: '#d99a2b', - warning: '#d99a2b', - amber: '#d99a2b', - yellow: '#f1c40f', - green: '#2b8c87', - success: '#2b8c87', - teal: '#2b8c87', - blue: '#57c1b9', - info: '#57c1b9', + red: '#ef4444', + danger: '#ef4444', + coral: '#ef4444', + orange: '#f59e0b', + warning: '#f59e0b', + amber: '#f59e0b', + yellow: '#eab308', + green: '#16a34a', + success: '#16a34a', + teal: '#0f766e', + blue: '#2563eb', + info: '#2563eb', + purple: '#8b5cf6', + violet: '#8b5cf6', gray: '#6c757d', grey: '#6c757d', muted: '#6c757d', }; return palette[color.toLowerCase()] || color; }, + _hex_to_rgb_badge_color: function(color) { + color = this._normalize_badge_color(color); + var match = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color || ''); + if (!match) { + return null; + } + var hex = match[1]; + if (hex.length == 3) { + hex = hex.split('').map(part => part + part).join(''); + } + return { + r: parseInt(hex.slice(0, 2), 16), + g: parseInt(hex.slice(2, 4), 16), + b: parseInt(hex.slice(4, 6), 16), + }; + }, + _rgb_to_badge_color: function(rgb) { + var part = value => Math.max(0, Math.min(255, Math.round(value))) + .toString(16).padStart(2, '0'); + return '#' + part(rgb.r) + part(rgb.g) + part(rgb.b); + }, + _mix_badge_color: function(color, mix_with, weight) { + var source = this._hex_to_rgb_badge_color(color); + var target = this._hex_to_rgb_badge_color(mix_with); + if (!source || !target) { + return ''; + } + return this._rgb_to_badge_color({ + r: source.r * (1 - weight) + target.r * weight, + g: source.g * (1 - weight) + target.g * weight, + b: source.b * (1 - weight) + target.b * weight, + }); + }, _get_badge_colors: function(column, record, text) { var attributes = column.attributes || {}; var color = ''; var text_color = ''; var mapped = this._parse_badge_colors(attributes.badge_colors); + var normalized_text = this._normalize_badge_text_key(text); if (Object.prototype.hasOwnProperty.call(mapped, text)) { color = mapped[text]; } else if (Object.prototype.hasOwnProperty.call( - mapped, String(text).toLowerCase())) { - color = mapped[String(text).toLowerCase()]; + mapped, normalized_text)) { + color = mapped[normalized_text]; + } else if (Object.prototype.hasOwnProperty.call( + mapped, String(normalized_text).toLowerCase())) { + color = mapped[String(normalized_text).toLowerCase()]; } if (!color && attributes.badge_color_field) { color = this._get_record_field_value( @@ -26138,6 +26195,19 @@ function eval_pyson(value){ color: this._normalize_badge_color(text_color), }; }, + _get_badge_display_colors: function(column, record, text) { + var colors = this._get_badge_colors(column, record, text); + var accent = colors.color || colors.background; + if (!accent) { + return colors; + } + return { + background: this._mix_badge_color(accent, '#ffffff', 0.86) || + colors.background, + color: colors.color || accent, + accent: accent, + }; + }, _render_badge_cell: function(column, record) { var text = this._get_column_text_value(column, record); var cell = jQuery('
', { @@ -26153,7 +26223,7 @@ function eval_pyson(value){ if (parseInt(column.attributes.badge_bold || '0', 10)) { badge.addClass('ag-grid-badge-bold'); } - var colors = this._get_badge_colors(column, record, text); + var colors = this._get_badge_display_colors(column, record, text); if (colors.background) { badge.css('background-color', colors.background); } @@ -26621,6 +26691,205 @@ function eval_pyson(value){ this.copy_button.prop('disabled', !this.selected_records.length); this.status.text(String(this.group.length) + ' records'); }, + _get_visible_record_rows: function() { + var rows = []; + if (!this.gridApi) { + return rows; + } + var collect = node => { + if (node && node.data && node.data.__kind == 'record') { + rows.push(node.data); + } + }; + if (this.gridApi.forEachNodeAfterFilterAndSort) { + this.gridApi.forEachNodeAfterFilterAndSort(collect); + } else if (this.gridApi.forEachNodeAfterFilter) { + this.gridApi.forEachNodeAfterFilter(collect); + } else { + (this._last_row_data || []).forEach(row => { + if (row && row.__kind == 'record') { + rows.push(row); + } + }); + } + return rows; + }, + _get_summary_badge_columns: function() { + var visible_columns = {}; + var column_api = this.gridColumnApi || this.gridApi; + if (column_api && column_api.getAllDisplayedColumns) { + column_api.getAllDisplayedColumns().forEach(column => { + if (column && column.getColId) { + visible_columns[column.getColId()] = true; + } + }); + } + return this.columns.filter(column => { + var name = column.attributes && column.attributes.name; + var label = column.attributes && + (column.attributes.string || column.attributes.name) || ''; + var is_status_column = /(^|[_\s-])(status|state|type)([_\s-]|$)/i + .test(String(name || '') + ' ' + String(label || '')); + if (!name || (!this._is_badge_column(column) && + !is_status_column)) { + return false; + } + return !Object.keys(visible_columns).length || + visible_columns[name]; + }); + }, + _render_summary_item: function(label, value, color) { + var item = jQuery('
', { + 'class': 'ag-grid-summary-item', + }); + jQuery('', { + 'class': 'ag-grid-summary-label', + 'text': label, + }).appendTo(item); + var value_el = jQuery('', { + 'class': 'ag-grid-summary-value', + 'text': value, + }).appendTo(item); + if (color) { + value_el.css('color', color); + } + return item; + }, + _get_default_badge_summary_color: function(value) { + var key = String(value || '').toLowerCase(); + var palette = { + open: '#2563eb', + physic: '#8b5cf6', + physical: '#8b5cf6', + scheduled: '#f59e0b', + shipped: '#16a34a', + unshipped: '#ef4444', + done: '#16a34a', + cancelled: '#ef4444', + canceled: '#ef4444', + draft: '#64748b', + quote: '#2563eb', + confirmed: '#16a34a', + processing: '#f59e0b', + }; + return palette[key] || '#0f766e'; + }, + _append_badge_summary_items: function(summary_items, rows, column, source) { + var name = source && source.name || + column && column.attributes && column.attributes.name; + if (!name) { + return false; + } + var counts = {}; + var samples = {}; + var order = []; + var empty = Sao.i18n.gettext('(Empty)'); + rows.forEach(row => { + var value = row[name] ? + this._normalize_badge_text_key(row[name]) : empty; + if (value == empty) { + return; + } + if (!Object.prototype.hasOwnProperty.call(counts, value)) { + counts[value] = 0; + samples[value] = row; + order.push(value); + } + counts[value] += 1; + }); + var color_order = this._get_badge_color_order( + column && column.attributes && column.attributes.badge_colors); + var color_order_lower = color_order.map(value => + String(value).toLowerCase()); + order.sort((a, b) => { + var a_index = color_order.indexOf(a); + var b_index = color_order.indexOf(b); + if (a_index < 0) { + a_index = color_order_lower.indexOf( + String(a).toLowerCase()); + } + if (b_index < 0) { + b_index = color_order_lower.indexOf( + String(b).toLowerCase()); + } + if (a_index >= 0 || b_index >= 0) { + return (a_index < 0 ? 999 : a_index) - + (b_index < 0 ? 999 : b_index); + } + return counts[b] - counts[a] || + String(a).localeCompare(String(b), undefined, { + numeric: true, + sensitivity: 'base', + }); + }); + order.forEach(value => { + var colors = column ? this._get_badge_display_colors( + column, + samples[value] && samples[value].__record, + value) : {}; + summary_items.push({ + label: value, + count: counts[value], + color: colors.color || colors.accent || + this._get_default_badge_summary_color(value), + }); + }); + return Boolean(order.length); + }, + _get_summary_status_sources_from_grid: function() { + var sources = []; + var seen = {}; + (this._grid_columns || []).forEach(col_def => { + var name = col_def && col_def.field || col_def && col_def.colId; + if (!name || name == '__selection__' || seen[name]) { + return; + } + var label = col_def.headerName || name; + if (!/(status|state|type|shipping|shipment|open\/physic)/i + .test(String(name) + ' ' + String(label))) { + return; + } + seen[name] = true; + sources.push({ + name: name, + label: label, + }); + }); + return sources; + }, + _update_summary_bar: function() { + if (!this.summary_bar || !this.summary_bar.length) { + return; + } + var rows = this._get_visible_record_rows(); + var locale = Sao.i18n.BC47(Sao.i18n.getlang()); + this.summary_bar.empty(); + this.summary_bar.append(this._render_summary_item( + Sao.i18n.gettext('Rows'), + rows.length.toLocaleString(locale), + '#145369')); + var summary_items = []; + var summarized_names = {}; + this._get_summary_badge_columns().forEach(column => { + if (this._append_badge_summary_items( + summary_items, rows, column)) { + summarized_names[column.attributes.name] = true; + } + }); + this._get_summary_status_sources_from_grid().forEach(source => { + if (summarized_names[source.name]) { + return; + } + this._append_badge_summary_items( + summary_items, rows, null, source); + }); + summary_items.forEach(item => { + this.summary_bar.append(this._render_summary_item( + item.label, + item.count.toLocaleString(locale), + item.color)); + }); + }, on_copy: function() { var data = []; this.selected_records.forEach(record => { @@ -26739,6 +27008,7 @@ function eval_pyson(value){ this._update_layout_button(); this._render_filter_logic_panel(); this._update_copy_state(); + this._update_summary_bar(); if (this._has_active_sao_value_filters()) { this._apply_sao_value_filters({refreshRowData: false}); } @@ -26765,6 +27035,8 @@ function eval_pyson(value){ var screen_container = this.grid_host.closest('.screen-container'); var toolbar_height = this.toolbar && this.toolbar.length ? Math.ceil(this.toolbar.outerHeight(true)) : 0; + var summary_height = this.summary_bar && this.summary_bar.length ? + Math.ceil(this.summary_bar.outerHeight(true)) : 0; var external_scroll_height = 20; var bottom_margin = 96; var available = window.innerHeight - rect.top - @@ -26774,7 +27046,7 @@ function eval_pyson(value){ available = Math.min( available, window.innerHeight - content_rect.top - toolbar_height - - external_scroll_height - bottom_margin); + summary_height - external_scroll_height - bottom_margin); } var compact_min_height = 260; var readable_min_height = 420; @@ -26805,7 +27077,8 @@ function eval_pyson(value){ width: available_width + 'px', maxWidth: available_width + 'px', }); - var container_height = height + toolbar_height + external_scroll_height; + var container_height = height + toolbar_height + summary_height + + external_scroll_height; this.el.css({ height: container_height + 'px', minHeight: container_height + 'px', diff --git a/tryton-sao.css b/tryton-sao.css index 900d6bf..9dcdc09 100644 --- a/tryton-sao.css +++ b/tryton-sao.css @@ -11613,6 +11613,54 @@ html[theme="default"] .ag-grid-tree-grouping-panel input[type="checkbox"]:after margin-left: auto; } +.ag-grid-tree-summary { + display: flex; + align-items: stretch; + flex-wrap: wrap; + gap: 0; + min-height: 48px; + margin: 0 0 8px; + border: 1px solid #dfe8e3; + border-radius: 6px; + background: #ffffff; + box-shadow: 0 1px 4px rgba(53, 81, 84, 0.06); + overflow: hidden; +} + +.ag-grid-summary-item { + display: flex; + flex: 0 1 150px; + min-width: 120px; + max-width: 190px; + flex-direction: column; + justify-content: center; + gap: 3px; + padding: 8px 18px; + border-right: 1px solid #e7efeb; +} + +.ag-grid-summary-item:last-child { + border-right: none; +} + +.ag-grid-summary-label { + min-width: 0; + color: #64748b; + font-size: 11px; + font-weight: 600; + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.ag-grid-summary-value { + color: #145369; + font-size: 18px; + font-weight: 700; + line-height: 1.15; +} + .ag-grid-tree-host { flex: 0 0 auto; min-height: 520px; @@ -11794,10 +11842,10 @@ html[theme="default"] .ag-grid-tree-grouping-panel input[type="checkbox"]:after min-height: 22px; padding: 2px 10px; border-radius: 999px; - background: #6c757d; - color: #f7fbf9; + background: #eef3f2; + color: #355154; font-size: 12px; - font-weight: 500; + font-weight: 700; line-height: 1.35; white-space: nowrap; overflow: hidden; @@ -12769,6 +12817,24 @@ html[data-theme-mode="dark"] .ag-grid-filter-logic-operator { color: #8fe1d8; } +html[data-theme-mode="dark"] .ag-grid-tree-summary { + background: #151f1e; + border-color: #2a403d; + box-shadow: 0 8px 22px rgba(0, 0, 0, 0.24); +} + +html[data-theme-mode="dark"] .ag-grid-summary-item { + border-right-color: #243735; +} + +html[data-theme-mode="dark"] .ag-grid-summary-label { + color: #aebfba; +} + +html[data-theme-mode="dark"] .ag-grid-summary-value { + color: #8fe1d8; +} + html[data-theme-mode="dark"] .form-tolerance-gauge { color: #dce7e2; } diff --git a/tryton-sao.js b/tryton-sao.js index 3262cd3..4c0b763 100644 --- a/tryton-sao.js +++ b/tryton-sao.js @@ -22624,6 +22624,9 @@ function eval_pyson(value){ this.status = jQuery('
', { 'class': 'ag-grid-tree-status text-muted', }).appendTo(this.toolbar); + this.summary_bar = jQuery('
', { + 'class': 'ag-grid-tree-summary', + }).appendTo(this.el); this.grid_host = jQuery('
', { 'class': 'ag-grid-theme-alpine ag-grid-tree-host', }).appendTo(this.el); @@ -22946,6 +22949,7 @@ function eval_pyson(value){ if (!options.skipLayoutRefresh) { this._schedule_grid_layout_refresh(); } + this._update_summary_bar(); }, _on_grid_column_resized: function(params) { this._trace_aggrid_column_resize('event', params); @@ -24629,6 +24633,7 @@ function eval_pyson(value){ onFilterChanged: () => { this._schedule_state_save(); this._sync_selection_header_checkbox(); + this._update_summary_bar(); }, onColumnMoved: this._on_grid_column_layout_changed.bind(this), onColumnPinned: this._on_grid_column_layout_changed.bind(this), @@ -25220,6 +25225,7 @@ function eval_pyson(value){ this._log_sao_value_filter_empty_result(this.group.slice()); } this._resize_grid_host(); + this._update_summary_bar(); this._schedule_grid_layout_refresh([0, 80]); }); }, @@ -25258,6 +25264,7 @@ function eval_pyson(value){ this._refresh_grid_layout_after_row_data(row_data); this._sync_selection(selected); this._update_copy_state(); + this._update_summary_bar(); }, _toggle_sao_value_filter_mode: function() { this._set_sao_value_filter_mode( @@ -26061,6 +26068,17 @@ function eval_pyson(value){ }); return parsed; }, + _normalize_badge_text_key: function(text) { + text = text === null || text === undefined ? '' : String(text); + return text.replace(/^[^A-Za-z0-9]+/, '').trim(); + }, + _get_badge_color_order: function(colors) { + return (colors || '').split(',').map(item => { + var pair = item.split(':'); + return pair.length < 2 ? '' : + this._normalize_badge_text_key(pair.shift()); + }).filter(Boolean); + }, _get_record_field_value: function(record, name) { if (!record || !name) { return ''; @@ -26084,34 +26102,73 @@ function eval_pyson(value){ color = color === null || color === undefined ? '' : String(color).trim(); var palette = { - red: '#e06a6a', - danger: '#e06a6a', - coral: '#e06a6a', - orange: '#d99a2b', - warning: '#d99a2b', - amber: '#d99a2b', - yellow: '#f1c40f', - green: '#2b8c87', - success: '#2b8c87', - teal: '#2b8c87', - blue: '#57c1b9', - info: '#57c1b9', + red: '#ef4444', + danger: '#ef4444', + coral: '#ef4444', + orange: '#f59e0b', + warning: '#f59e0b', + amber: '#f59e0b', + yellow: '#eab308', + green: '#16a34a', + success: '#16a34a', + teal: '#0f766e', + blue: '#2563eb', + info: '#2563eb', + purple: '#8b5cf6', + violet: '#8b5cf6', gray: '#6c757d', grey: '#6c757d', muted: '#6c757d', }; return palette[color.toLowerCase()] || color; }, + _hex_to_rgb_badge_color: function(color) { + color = this._normalize_badge_color(color); + var match = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color || ''); + if (!match) { + return null; + } + var hex = match[1]; + if (hex.length == 3) { + hex = hex.split('').map(part => part + part).join(''); + } + return { + r: parseInt(hex.slice(0, 2), 16), + g: parseInt(hex.slice(2, 4), 16), + b: parseInt(hex.slice(4, 6), 16), + }; + }, + _rgb_to_badge_color: function(rgb) { + var part = value => Math.max(0, Math.min(255, Math.round(value))) + .toString(16).padStart(2, '0'); + return '#' + part(rgb.r) + part(rgb.g) + part(rgb.b); + }, + _mix_badge_color: function(color, mix_with, weight) { + var source = this._hex_to_rgb_badge_color(color); + var target = this._hex_to_rgb_badge_color(mix_with); + if (!source || !target) { + return ''; + } + return this._rgb_to_badge_color({ + r: source.r * (1 - weight) + target.r * weight, + g: source.g * (1 - weight) + target.g * weight, + b: source.b * (1 - weight) + target.b * weight, + }); + }, _get_badge_colors: function(column, record, text) { var attributes = column.attributes || {}; var color = ''; var text_color = ''; var mapped = this._parse_badge_colors(attributes.badge_colors); + var normalized_text = this._normalize_badge_text_key(text); if (Object.prototype.hasOwnProperty.call(mapped, text)) { color = mapped[text]; } else if (Object.prototype.hasOwnProperty.call( - mapped, String(text).toLowerCase())) { - color = mapped[String(text).toLowerCase()]; + mapped, normalized_text)) { + color = mapped[normalized_text]; + } else if (Object.prototype.hasOwnProperty.call( + mapped, String(normalized_text).toLowerCase())) { + color = mapped[String(normalized_text).toLowerCase()]; } if (!color && attributes.badge_color_field) { color = this._get_record_field_value( @@ -26138,6 +26195,19 @@ function eval_pyson(value){ color: this._normalize_badge_color(text_color), }; }, + _get_badge_display_colors: function(column, record, text) { + var colors = this._get_badge_colors(column, record, text); + var accent = colors.color || colors.background; + if (!accent) { + return colors; + } + return { + background: this._mix_badge_color(accent, '#ffffff', 0.86) || + colors.background, + color: colors.color || accent, + accent: accent, + }; + }, _render_badge_cell: function(column, record) { var text = this._get_column_text_value(column, record); var cell = jQuery('
', { @@ -26153,7 +26223,7 @@ function eval_pyson(value){ if (parseInt(column.attributes.badge_bold || '0', 10)) { badge.addClass('ag-grid-badge-bold'); } - var colors = this._get_badge_colors(column, record, text); + var colors = this._get_badge_display_colors(column, record, text); if (colors.background) { badge.css('background-color', colors.background); } @@ -26621,6 +26691,205 @@ function eval_pyson(value){ this.copy_button.prop('disabled', !this.selected_records.length); this.status.text(String(this.group.length) + ' records'); }, + _get_visible_record_rows: function() { + var rows = []; + if (!this.gridApi) { + return rows; + } + var collect = node => { + if (node && node.data && node.data.__kind == 'record') { + rows.push(node.data); + } + }; + if (this.gridApi.forEachNodeAfterFilterAndSort) { + this.gridApi.forEachNodeAfterFilterAndSort(collect); + } else if (this.gridApi.forEachNodeAfterFilter) { + this.gridApi.forEachNodeAfterFilter(collect); + } else { + (this._last_row_data || []).forEach(row => { + if (row && row.__kind == 'record') { + rows.push(row); + } + }); + } + return rows; + }, + _get_summary_badge_columns: function() { + var visible_columns = {}; + var column_api = this.gridColumnApi || this.gridApi; + if (column_api && column_api.getAllDisplayedColumns) { + column_api.getAllDisplayedColumns().forEach(column => { + if (column && column.getColId) { + visible_columns[column.getColId()] = true; + } + }); + } + return this.columns.filter(column => { + var name = column.attributes && column.attributes.name; + var label = column.attributes && + (column.attributes.string || column.attributes.name) || ''; + var is_status_column = /(^|[_\s-])(status|state|type)([_\s-]|$)/i + .test(String(name || '') + ' ' + String(label || '')); + if (!name || (!this._is_badge_column(column) && + !is_status_column)) { + return false; + } + return !Object.keys(visible_columns).length || + visible_columns[name]; + }); + }, + _render_summary_item: function(label, value, color) { + var item = jQuery('
', { + 'class': 'ag-grid-summary-item', + }); + jQuery('', { + 'class': 'ag-grid-summary-label', + 'text': label, + }).appendTo(item); + var value_el = jQuery('', { + 'class': 'ag-grid-summary-value', + 'text': value, + }).appendTo(item); + if (color) { + value_el.css('color', color); + } + return item; + }, + _get_default_badge_summary_color: function(value) { + var key = String(value || '').toLowerCase(); + var palette = { + open: '#2563eb', + physic: '#8b5cf6', + physical: '#8b5cf6', + scheduled: '#f59e0b', + shipped: '#16a34a', + unshipped: '#ef4444', + done: '#16a34a', + cancelled: '#ef4444', + canceled: '#ef4444', + draft: '#64748b', + quote: '#2563eb', + confirmed: '#16a34a', + processing: '#f59e0b', + }; + return palette[key] || '#0f766e'; + }, + _append_badge_summary_items: function(summary_items, rows, column, source) { + var name = source && source.name || + column && column.attributes && column.attributes.name; + if (!name) { + return false; + } + var counts = {}; + var samples = {}; + var order = []; + var empty = Sao.i18n.gettext('(Empty)'); + rows.forEach(row => { + var value = row[name] ? + this._normalize_badge_text_key(row[name]) : empty; + if (value == empty) { + return; + } + if (!Object.prototype.hasOwnProperty.call(counts, value)) { + counts[value] = 0; + samples[value] = row; + order.push(value); + } + counts[value] += 1; + }); + var color_order = this._get_badge_color_order( + column && column.attributes && column.attributes.badge_colors); + var color_order_lower = color_order.map(value => + String(value).toLowerCase()); + order.sort((a, b) => { + var a_index = color_order.indexOf(a); + var b_index = color_order.indexOf(b); + if (a_index < 0) { + a_index = color_order_lower.indexOf( + String(a).toLowerCase()); + } + if (b_index < 0) { + b_index = color_order_lower.indexOf( + String(b).toLowerCase()); + } + if (a_index >= 0 || b_index >= 0) { + return (a_index < 0 ? 999 : a_index) - + (b_index < 0 ? 999 : b_index); + } + return counts[b] - counts[a] || + String(a).localeCompare(String(b), undefined, { + numeric: true, + sensitivity: 'base', + }); + }); + order.forEach(value => { + var colors = column ? this._get_badge_display_colors( + column, + samples[value] && samples[value].__record, + value) : {}; + summary_items.push({ + label: value, + count: counts[value], + color: colors.color || colors.accent || + this._get_default_badge_summary_color(value), + }); + }); + return Boolean(order.length); + }, + _get_summary_status_sources_from_grid: function() { + var sources = []; + var seen = {}; + (this._grid_columns || []).forEach(col_def => { + var name = col_def && col_def.field || col_def && col_def.colId; + if (!name || name == '__selection__' || seen[name]) { + return; + } + var label = col_def.headerName || name; + if (!/(status|state|type|shipping|shipment|open\/physic)/i + .test(String(name) + ' ' + String(label))) { + return; + } + seen[name] = true; + sources.push({ + name: name, + label: label, + }); + }); + return sources; + }, + _update_summary_bar: function() { + if (!this.summary_bar || !this.summary_bar.length) { + return; + } + var rows = this._get_visible_record_rows(); + var locale = Sao.i18n.BC47(Sao.i18n.getlang()); + this.summary_bar.empty(); + this.summary_bar.append(this._render_summary_item( + Sao.i18n.gettext('Rows'), + rows.length.toLocaleString(locale), + '#145369')); + var summary_items = []; + var summarized_names = {}; + this._get_summary_badge_columns().forEach(column => { + if (this._append_badge_summary_items( + summary_items, rows, column)) { + summarized_names[column.attributes.name] = true; + } + }); + this._get_summary_status_sources_from_grid().forEach(source => { + if (summarized_names[source.name]) { + return; + } + this._append_badge_summary_items( + summary_items, rows, null, source); + }); + summary_items.forEach(item => { + this.summary_bar.append(this._render_summary_item( + item.label, + item.count.toLocaleString(locale), + item.color)); + }); + }, on_copy: function() { var data = []; this.selected_records.forEach(record => { @@ -26739,6 +27008,7 @@ function eval_pyson(value){ this._update_layout_button(); this._render_filter_logic_panel(); this._update_copy_state(); + this._update_summary_bar(); if (this._has_active_sao_value_filters()) { this._apply_sao_value_filters({refreshRowData: false}); } @@ -26765,6 +27035,8 @@ function eval_pyson(value){ var screen_container = this.grid_host.closest('.screen-container'); var toolbar_height = this.toolbar && this.toolbar.length ? Math.ceil(this.toolbar.outerHeight(true)) : 0; + var summary_height = this.summary_bar && this.summary_bar.length ? + Math.ceil(this.summary_bar.outerHeight(true)) : 0; var external_scroll_height = 20; var bottom_margin = 96; var available = window.innerHeight - rect.top - @@ -26774,7 +27046,7 @@ function eval_pyson(value){ available = Math.min( available, window.innerHeight - content_rect.top - toolbar_height - - external_scroll_height - bottom_margin); + summary_height - external_scroll_height - bottom_margin); } var compact_min_height = 260; var readable_min_height = 420; @@ -26805,7 +27077,8 @@ function eval_pyson(value){ width: available_width + 'px', maxWidth: available_width + 'px', }); - var container_height = height + toolbar_height + external_scroll_height; + var container_height = height + toolbar_height + summary_height + + external_scroll_height; this.el.css({ height: container_height + 'px', minHeight: container_height + 'px',