Skip to main content

ui_grid_core/
grouping.rs

1use std::collections::BTreeMap;
2
3use crate::{
4    models::{
5        DisplayItem, ExpandableItem, GridColumnDef, GridOptions, GridRow, GroupItem, RowItem,
6    },
7    utils::{get_path_value, stringify_cell_value},
8};
9
10fn is_grouping_enabled(options: &GridOptions) -> bool {
11    options.enable_grouping && !options.enable_tree_view
12}
13
14fn can_expand_rows(options: &GridOptions) -> bool {
15    options.enable_expandable
16}
17
18fn build_row_display_items(rows: &[GridRow], options: &GridOptions) -> Vec<DisplayItem> {
19    let mut items = Vec::new();
20    for (visible_index, row) in rows.iter().enumerate() {
21        items.push(DisplayItem::Row(RowItem {
22            id: row.id.clone(),
23            row: row.clone(),
24            visible_index,
25        }));
26
27        if row.expanded && can_expand_rows(options) {
28            items.push(DisplayItem::Expandable(ExpandableItem {
29                id: format!("{}:expandable", row.id),
30                row: row.clone(),
31            }));
32        }
33    }
34    items
35}
36
37fn build_grouped_items(
38    rows: &[GridRow],
39    columns: &[GridColumnDef],
40    options: &GridOptions,
41    group_by: &[String],
42    collapsed_groups: &BTreeMap<String, bool>,
43    depth: usize,
44    path: &str,
45) -> Vec<DisplayItem> {
46    if group_by.is_empty() {
47        return build_row_display_items(rows, options);
48    }
49
50    let current_field = &group_by[0];
51    let mut groups: BTreeMap<String, Vec<GridRow>> = BTreeMap::new();
52
53    for row in rows {
54        let value = stringify_cell_value(
55            &get_path_value(&row.entity, current_field)
56                .or_else(|| {
57                    columns
58                        .iter()
59                        .find(|column| column.name == *current_field)
60                        .map(|column| crate::utils::get_cell_value(&row.entity, column))
61                })
62                .unwrap_or(serde_json::Value::Null),
63        );
64        let key = if value.is_empty() {
65            "Unassigned".to_string()
66        } else {
67            value
68        };
69        groups.entry(key).or_default().push(row.clone());
70    }
71
72    let mut items = Vec::new();
73    for (label, grouped_rows) in groups {
74        let group_id = format!("{}{}:{}", path, current_field, label);
75        let collapsed = collapsed_groups
76            .get(&group_id)
77            .copied()
78            .or_else(|| {
79                options
80                    .grouping
81                    .as_ref()
82                    .map(|grouping| grouping.start_collapsed)
83            })
84            .unwrap_or(false);
85
86        items.push(DisplayItem::Group(GroupItem {
87            id: group_id.clone(),
88            depth,
89            field: current_field.clone(),
90            label: label.clone(),
91            count: grouped_rows.len(),
92            collapsed,
93        }));
94
95        if !collapsed {
96            items.extend(build_grouped_items(
97                &grouped_rows,
98                columns,
99                options,
100                &group_by[1..],
101                collapsed_groups,
102                depth + 1,
103                &format!("{}|", group_id),
104            ));
105        }
106    }
107
108    items
109}
110
111pub fn build_grid_display_items(
112    rows: &[GridRow],
113    columns: &[GridColumnDef],
114    options: &GridOptions,
115    group_by: &[String],
116    collapsed_groups: &BTreeMap<String, bool>,
117) -> Vec<DisplayItem> {
118    if options.enable_tree_view {
119        return build_row_display_items(rows, options);
120    }
121
122    if !is_grouping_enabled(options) || group_by.is_empty() {
123        return build_row_display_items(rows, options);
124    }
125
126    build_grouped_items(rows, columns, options, group_by, collapsed_groups, 0, "")
127}