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',