Skip to main content

edc_web_components/components/
list_contract_negotiations.rs

1use 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}