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}