edc_web_components/components/
list_contract_negotiations.rs1use crate::components::{AssetReference, ContractAgreementReference, DidLabel, MultiStateSelector};
2use crate::models::{ConsumerProvider, ContractNegotiationItem};
3use patternfly_yew::prelude::*;
4use std::collections::HashSet;
5use std::rc::Rc;
6use yew::prelude::*;
7
8#[derive(Clone, Debug, PartialEq, Properties)]
9pub struct ListContractNegotiationsProps {
10 pub contract_negotiation_items: Vec<ContractNegotiationItem>,
11 pub offset: usize,
12 pub limit: usize,
13 pub statuses: Vec<(String, bool)>,
14 pub on_offset: Callback<usize>,
15 pub on_limit: Callback<usize>,
16 pub on_show_contract_negotiation: Callback<String>,
17 pub on_statuses: Callback<Vec<(String, bool)>>,
18 #[prop_or(true)]
19 pub show_status_selector: bool,
20 #[prop_or_default]
21 pub consumer_provider: HashSet<ConsumerProvider>,
22 pub on_consumer_provider: Callback<HashSet<ConsumerProvider>>,
23}
24
25#[component]
26pub fn ListContractNegotiations(props: &ListContractNegotiationsProps) -> Html {
27 let header = html_nested! {
28 <TableHeader<Columns>>
29 <TableColumn<Columns> label="State" index={Columns::State} />
30 <TableColumn<Columns> label="Contract Agreement" index={Columns::ContractAgreementId} />
31 <TableColumn<Columns> label="Counter Party" index={Columns::CounterParty} />
32 <TableColumn<Columns> label="Asset" index={Columns::Asset} />
33 <TableColumn<Columns> label="Kind" index={Columns::Kind} />
34 <TableColumn<Columns> label="" index={Columns::Actions} />
35 </TableHeader<Columns>>
36 };
37
38 let total_entries: Option<usize> = None;
39
40 let nav_callback = use_callback(
41 (
42 props.offset,
43 props.limit,
44 total_entries,
45 props.on_offset.clone(),
46 ),
47 |page: Navigation, (offset, limit, total_entries, on_offset)| {
48 let offset = match page {
49 Navigation::First => 0,
50 Navigation::Last => (total_entries.unwrap_or_default().saturating_sub(1) / limit) * limit,
51 Navigation::Previous => *offset - limit,
52 Navigation::Next => *offset + limit,
53 Navigation::Page(n) => n * limit,
54 };
55 on_offset.emit(offset);
56 },
57 );
58
59 let rows = props
60 .contract_negotiation_items
61 .iter()
62 .map(
63 |contract_negotiation_item| ContractNegotiationItemRenderer {
64 item: contract_negotiation_item.clone(),
65 on_show_contract_negotiation: props.on_show_contract_negotiation.clone(),
66 },
67 )
68 .collect();
69
70 let (entries, _) = use_table_data(MemoizedTableModel::new(Rc::new(rows)));
71
72 let statuses_selector = if props.show_status_selector {
73 html!(
74 <MultiStateSelector
75 selectable_items={props.statuses.clone()}
76 on_selected={props.on_statuses.clone()}
77 none_selected_label="All States"
78 all_selected_label="All States"
79 />
80 )
81 } else {
82 html!()
83 };
84
85 let consumer_provider_select = {
86 let onclick_consumer = {
87 let consumer_provider = props.consumer_provider.clone();
88
89 props.on_consumer_provider.reform(move |_| {
90 let mut consumer_provider = consumer_provider.clone();
91 if consumer_provider.contains(&ConsumerProvider::Consumer) {
92 consumer_provider.remove(&ConsumerProvider::Consumer);
93 } else {
94 consumer_provider.insert(ConsumerProvider::Consumer);
95 }
96
97 consumer_provider
98 })
99 };
100
101 let onclick_provider = {
102 let consumer_provider = props.consumer_provider.clone();
103
104 props.on_consumer_provider.reform(move |_| {
105 let mut consumer_provider = consumer_provider.clone();
106 if consumer_provider.contains(&ConsumerProvider::Provider) {
107 consumer_provider.remove(&ConsumerProvider::Provider);
108 } else {
109 consumer_provider.insert(ConsumerProvider::Provider);
110 }
111
112 consumer_provider
113 })
114 };
115
116 let text = {
117 let state = if props.consumer_provider.is_empty() {
118 HashSet::from([ConsumerProvider::Consumer, ConsumerProvider::Provider])
119 } else {
120 props.consumer_provider.clone()
121 };
122
123 state
124 .into_iter()
125 .map(|consumer_provider| consumer_provider.to_string())
126 .collect::<Vec<String>>()
127 .join(", ")
128 };
129
130 html!(
131 <Dropdown {text}>
132 <MenuAction
133 onclick={onclick_consumer}
134 selected={props.consumer_provider.is_empty() || props.consumer_provider.contains(&ConsumerProvider::Consumer)}
135 >
136 { "Consumer" }
137 </MenuAction>
138 <MenuAction
139 onclick={onclick_provider}
140 selected={props.consumer_provider.is_empty() || props.consumer_provider.contains(&ConsumerProvider::Provider)}
141 >
142 { "Provider" }
143 </MenuAction>
144 </Dropdown>
145 )
146 };
147
148 html!(
149 <>
150 <Toolbar>
151 <ToolbarContent>
152 <ToolbarItem r#type={ToolbarItemType::BulkSelect}>
153 { consumer_provider_select }
154 { statuses_selector }
155 </ToolbarItem>
156 <ToolbarItem r#type={ToolbarItemType::Pagination}>
157 <Pagination
158 offset={props.offset}
159 entries_per_page_choices={vec![5, 10, 25, 50, 100]}
160 selected_choice={props.limit}
161 onlimit={&props.on_limit}
162 onnavigation={&nav_callback}
163 />
164 </ToolbarItem>
165 </ToolbarContent>
166 </Toolbar>
167 <Table<Columns, UseTableData<Columns, MemoizedTableModel<ContractNegotiationItemRenderer>>>
168 mode={TableMode::Compact}
169 {header}
170 {entries}
171 />
172 </>
173 )
174}
175
176#[derive(Clone, Debug, Eq, PartialEq)]
177enum Columns {
178 State,
179 ContractAgreementId,
180 CounterParty,
181 Asset,
182 Kind,
183 Actions,
184}
185
186#[derive(Clone, Debug)]
187struct ContractNegotiationItemRenderer {
188 item: ContractNegotiationItem,
189 on_show_contract_negotiation: Callback<String>,
190}
191
192impl TableEntryRenderer<Columns> for ContractNegotiationItemRenderer {
193 fn render_cell(&self, context: CellContext<'_, Columns>) -> Cell {
194 match context.column {
195 Columns::State => {
196 let color = match self.item.state.as_str() {
197 "Finalized" => Color::Green,
198 "Terminated" => Color::Red,
199 _ => Color::Blue,
200 };
201
202 html! { <Label label={self.item.state.to_string()} {color} /> }
203 },
204 Columns::ContractAgreementId => {
205 if self.item.contract_agreement_id.is_empty() {
206 html! { "-" }
207 }
208 else {
209 html! {
210 <ContractAgreementReference
211 contract_agreement_id={self.item.contract_agreement_id.to_string()}
212 />
213 }
214 }
215 },
216 Columns::CounterParty => html! { <DidLabel did={self.item.counter_party_id.to_string()} /> },
217 Columns::Asset => html! { <AssetReference asset_id={self.item.asset_id.to_string()} /> },
218 Columns::Kind => html! { self.item.kind.to_string() },
219 Columns::Actions => {
220 let contract_negotiation_id = self.item.id.clone();
221 html! {
222 <Button
223 variant={ButtonVariant::Primary}
224 onclick={self.on_show_contract_negotiation.clone().reform(move |_| contract_negotiation_id.clone())}
225 icon={Icon::Eye}
226 >
227 { "Show" }
228 </Button>
229 }
230 },
231 }
232 .into()
233 }
234}