edc-web-ui 0.1.0

A Rust Web interface with patternfly-yew client for EDC
Documentation
use crate::contexts::use_edc_connector_context;
use edc_connector_client::types::contract_negotiation::{
  ContractNegotiation, ContractNegotiationKind, ContractNegotiationState,
};
use edc_connector_client::types::query::Query;
use patternfly_yew::prelude::*;
use std::rc::Rc;
use yew::prelude::*;
use yew::suspense::use_future_with;

#[function_component]
pub fn ListContractNegotiations() -> Html {
  let fallback = html!("Loading ...");

  html!(
    <Suspense {fallback}>
      <ListContractNegotiationsInner />
    </Suspense>
  )
}

#[function_component]
pub fn ListContractNegotiationsInner() -> HtmlResult {
  let edc_connector_context = use_edc_connector_context();

  let offset = use_state(|| 0usize);
  let limit = use_state(|| 10usize);

  let contract_negotiation_list = use_future_with(
    (edc_connector_context, *limit, *offset),
    |parameters| async move {
      let (edc_connector_context, limit, offset) = &*parameters;

      let query = Query::builder()
        .limit(*limit as u32)
        .offset(*offset as u32)
        .build();

      if let Some(client) = edc_connector_context.get_client() {
        client.contract_negotiations().query(query).await
      } else {
        Ok(vec![])
      }
    },
  )?;

  let contract_negotiation_list = &(*contract_negotiation_list);

  let header = html_nested! {
    <TableHeader<Columns>>
      <TableColumn<Columns> label="ID" index={Columns::Id} />
      <TableColumn<Columns> label="Access Policy ID" index={Columns::AccessPolicyId} />
      <TableColumn<Columns> label="Contract Policy ID" index={Columns::ContractPolicyId} />
      <TableColumn<Columns> label="State" index={Columns::State} />
      <TableColumn<Columns> label="Contract Agreement ID" index={Columns::ContractAgreementId} />
      <TableColumn<Columns> label="Counter Party ID" index={Columns::CounterPartyId} />
      <TableColumn<Columns> label="Counter Party Address" index={Columns::CounterPartyAddress} />
      <TableColumn<Columns> label="Protocol" index={Columns::Protocol} />
      <TableColumn<Columns> label="Kind" index={Columns::Kind} />
    </TableHeader<Columns>>
  };

  let limit_callback = use_callback(limit.clone(), |number, limit| limit.set(number));

  let total_entries: Option<usize> = None;

  let nav_callback = use_callback(
    (offset.clone(), *limit, total_entries),
    |page: Navigation, (offset, limit, total_entries)| {
      let o = match page {
        Navigation::First => 0,
        Navigation::Last => (total_entries.unwrap_or_default().saturating_sub(1) / limit) * limit,
        Navigation::Previous => **offset - limit,
        Navigation::Next => **offset + limit,
        Navigation::Page(n) => n * limit,
      };
      offset.set(o);
    },
  );

  let rows = contract_negotiation_list
    .as_ref()
    .unwrap()
    .iter()
    .map(|contract_negotiation| ContractNegotiationRenderer(contract_negotiation.clone()))
    .collect();

  let (entries, _) = use_table_data(MemoizedTableModel::new(Rc::new(rows)));

  let table = html!(
    <>
      <Toolbar>
        <ToolbarContent>
          <ToolbarItem r#type={ToolbarItemType::Pagination}>
            <Pagination
              offset={*offset}
              entries_per_page_choices={vec![5, 10, 25, 50, 100]}
              selected_choice={*limit}
              onlimit={&limit_callback}
              onnavigation={&nav_callback}
            />
          </ToolbarItem>
        </ToolbarContent>
      </Toolbar>
      <Table<Columns, UseTableData<Columns, MemoizedTableModel<ContractNegotiationRenderer>>>
        mode={TableMode::Compact}
        {header}
        {entries}
        />
    </>
  );

  Ok(table)
}

#[derive(Clone, Debug, Eq, PartialEq)]
enum Columns {
  Id,
  AccessPolicyId,
  ContractPolicyId,
  State,
  ContractAgreementId,
  CounterPartyId,
  CounterPartyAddress,
  Protocol,
  Kind,
}

#[derive(Clone, Debug)]
struct ContractNegotiationRenderer(ContractNegotiation);

impl ContractNegotiationRenderer {}

impl TableEntryRenderer<Columns> for ContractNegotiationRenderer {
  fn render_cell(&self, context: CellContext<'_, Columns>) -> Cell {
    let contract_agreement_id = self
      .0
      .contract_agreement_id()
      .map(|contract_agreement_id| contract_agreement_id.to_string())
      .unwrap_or_default();

    let kind = match self.0.kind() {
      ContractNegotiationKind::Consumer => "Consumer",
      ContractNegotiationKind::Provider => "Provider",
    };

    let state = match self.0.state() {
      ContractNegotiationState::Initial => "Initial".to_string(),
      ContractNegotiationState::Requesting => "Requesting".to_string(),
      ContractNegotiationState::Requested => "Requested".to_string(),
      ContractNegotiationState::Offering => "Offering".to_string(),
      ContractNegotiationState::Offered => "Offered".to_string(),
      ContractNegotiationState::Accepting => "Accepting".to_string(),
      ContractNegotiationState::Accepted => "Accepted".to_string(),
      ContractNegotiationState::Agreeing => "Agreeing".to_string(),
      ContractNegotiationState::Agreed => "Agreed".to_string(),
      ContractNegotiationState::Verifying => "Verifying".to_string(),
      ContractNegotiationState::Verified => "Verified".to_string(),
      ContractNegotiationState::Finalizing => "Finalizing".to_string(),
      ContractNegotiationState::Finalized => "Finalized".to_string(),
      ContractNegotiationState::Terminating => "Terminating".to_string(),
      ContractNegotiationState::Terminated => "Terminated".to_string(),
      ContractNegotiationState::Other(other) => other.to_string(),
    };

    match context.column {
      Columns::Id => html! {self.0.id().to_string()},
      Columns::AccessPolicyId => html! {},
      Columns::ContractPolicyId => html! {},
      Columns::State => html! { state },
      Columns::ContractAgreementId => html! { contract_agreement_id },
      Columns::CounterPartyId => html! { self.0.counter_party_id() },
      Columns::CounterPartyAddress => html! { self.0.counter_party_address() },
      Columns::Protocol => html! { self.0.protocol() },
      Columns::Kind => html! { kind },
    }
    .into()
  }
}