freezone/platform/src/views/companies_view.rs
2025-06-27 04:13:31 +02:00

460 lines
22 KiB
Rust

use yew::prelude::*;
use std::collections::HashMap;
use crate::routing::AppView;
use crate::components::{ViewComponent, EmptyState, RegistrationWizard};
use crate::models::*;
use crate::services::{CompanyService, CompanyRegistration, RegistrationStatus};
#[derive(Properties, PartialEq)]
pub struct CompaniesViewProps {
pub on_navigate: Option<Callback<AppView>>,
#[prop_or_default]
pub show_registration: bool,
#[prop_or_default]
pub registration_success: Option<u32>,
#[prop_or_default]
pub registration_failure: bool,
}
pub enum CompaniesViewMsg {
LoadCompanies,
CompaniesLoaded(Vec<Company>),
LoadRegistrations,
RegistrationsLoaded(Vec<CompanyRegistration>),
SwitchToCompany(String),
ShowRegistration,
RegistrationComplete(Company),
BackToCompanies,
ViewCompany(u32),
ContinueRegistration(CompanyRegistration),
StartNewRegistration,
DeleteRegistration(u32),
ShowNewRegistrationForm,
HideNewRegistrationForm,
}
pub struct CompaniesView {
companies: Vec<Company>,
registrations: Vec<CompanyRegistration>,
loading: bool,
show_registration: bool,
current_registration: Option<CompanyRegistration>,
show_new_registration_form: bool,
}
impl Component for CompaniesView {
type Message = CompaniesViewMsg;
type Properties = CompaniesViewProps;
fn create(ctx: &Context<Self>) -> Self {
// Load companies and registrations on component creation
ctx.link().send_message(CompaniesViewMsg::LoadCompanies);
ctx.link().send_message(CompaniesViewMsg::LoadRegistrations);
Self {
companies: Vec::new(),
registrations: Vec::new(),
loading: true,
show_registration: ctx.props().show_registration,
current_registration: None,
show_new_registration_form: false,
}
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
CompaniesViewMsg::LoadCompanies => {
self.loading = true;
// Load companies from service
let companies = CompanyService::get_companies();
ctx.link().send_message(CompaniesViewMsg::CompaniesLoaded(companies));
false
}
CompaniesViewMsg::CompaniesLoaded(companies) => {
self.companies = companies;
self.loading = false;
true
}
CompaniesViewMsg::LoadRegistrations => {
// Load actual registrations from service
let registrations = CompanyService::get_registrations();
ctx.link().send_message(CompaniesViewMsg::RegistrationsLoaded(registrations));
false
}
CompaniesViewMsg::RegistrationsLoaded(registrations) => {
self.registrations = registrations;
true
}
CompaniesViewMsg::SwitchToCompany(company_id) => {
// Navigate to company view
if let Some(on_navigate) = &ctx.props().on_navigate {
if let Ok(id) = company_id.parse::<u32>() {
on_navigate.emit(AppView::CompanyView(id));
}
}
false
}
CompaniesViewMsg::ShowRegistration => {
self.show_registration = true;
self.current_registration = None; // Start fresh registration
true
}
CompaniesViewMsg::StartNewRegistration => {
self.show_registration = true;
self.current_registration = None; // Start fresh registration
true
}
CompaniesViewMsg::ContinueRegistration(registration) => {
self.show_registration = true;
self.current_registration = Some(registration);
true
}
CompaniesViewMsg::RegistrationComplete(company) => {
// Add new company to list and clear current registration
let company_id = company.id;
self.companies.push(company);
self.current_registration = None;
self.show_registration = false;
// Navigate to registration success step
if let Some(on_navigate) = &ctx.props().on_navigate {
on_navigate.emit(AppView::EntitiesRegisterSuccess(company_id));
}
true
}
CompaniesViewMsg::ViewCompany(company_id) => {
// Navigate to company view
if let Some(on_navigate) = &ctx.props().on_navigate {
on_navigate.emit(AppView::CompanyView(company_id));
}
false
}
CompaniesViewMsg::BackToCompanies => {
self.show_registration = false;
self.current_registration = None;
true
}
CompaniesViewMsg::DeleteRegistration(registration_id) => {
// Remove registration from list
self.registrations.retain(|r| r.id != registration_id);
// Update storage
let _ = CompanyService::save_registrations(&self.registrations);
true
}
CompaniesViewMsg::ShowNewRegistrationForm => {
self.show_new_registration_form = true;
true
}
CompaniesViewMsg::HideNewRegistrationForm => {
self.show_new_registration_form = false;
true
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let link = ctx.link();
// Check if we should show success state
if ctx.props().registration_success.is_some() {
// Show success state
html! {
<ViewComponent
title={Some("Registration Successful".to_string())}
description={Some("Your company registration has been completed successfully".to_string())}
>
<RegistrationWizard
on_registration_complete={link.callback(CompaniesViewMsg::RegistrationComplete)}
on_back_to_companies={link.callback(|_| CompaniesViewMsg::BackToCompanies)}
success_company_id={ctx.props().registration_success}
show_failure={false}
force_fresh_start={false}
continue_registration={None}
continue_step={None}
/>
</ViewComponent>
}
} else if self.show_registration {
// Registration view
html! {
<ViewComponent
title={Some("Register New Company".to_string())}
description={Some("Complete the registration process to create your new company".to_string())}
>
<RegistrationWizard
on_registration_complete={link.callback(CompaniesViewMsg::RegistrationComplete)}
on_back_to_companies={link.callback(|_| CompaniesViewMsg::BackToCompanies)}
success_company_id={None}
show_failure={ctx.props().registration_failure}
force_fresh_start={self.current_registration.is_none()}
continue_registration={self.current_registration.as_ref().map(|r| r.form_data.clone())}
continue_step={self.current_registration.as_ref().map(|r| r.current_step)}
/>
</ViewComponent>
}
} else {
// Main companies view with unified table
html! {
<ViewComponent
title={Some("Companies".to_string())}
description={Some("Manage your companies and registrations".to_string())}
>
{self.render_companies_content(ctx)}
</ViewComponent>
}
}
}
}
impl CompaniesView {
fn render_companies_content(&self, ctx: &Context<Self>) -> Html {
let link = ctx.link();
if self.loading {
return html! {
<div class="text-center py-5">
<div class="spinner-border text-primary mb-3" role="status">
<span class="visually-hidden">{"Loading..."}</span>
</div>
<p class="text-muted">{"Loading companies..."}</p>
</div>
};
}
if self.companies.is_empty() && self.registrations.is_empty() {
return html! {
<div class="text-center py-5">
<EmptyState
icon={"building".to_string()}
title={"No companies found".to_string()}
description={"Create and manage your owned companies and corporate entities for business operations.".to_string()}
primary_action={None}
secondary_action={None}
/>
<div class="mt-4">
<button
class="btn btn-success btn-lg"
onclick={link.callback(|_| CompaniesViewMsg::StartNewRegistration)}
>
<i class="bi bi-plus-circle me-2"></i>{"Register Your First Company"}
</button>
</div>
</div>
};
}
html! {
<div class="row">
<div class="col-12">
{self.render_companies_table(ctx)}
</div>
</div>
}
}
fn render_companies_table(&self, ctx: &Context<Self>) -> Html {
let link = ctx.link();
html! {
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<div>
<h5 class="mb-0">
<i class="bi bi-building me-2"></i>{"Companies & Registrations"}
</h5>
<small class="text-muted">
{format!("{} companies, {} pending registrations", self.companies.len(), self.registrations.len())}
</small>
</div>
<button
class="btn btn-success"
onclick={link.callback(|_| CompaniesViewMsg::StartNewRegistration)}
>
<i class="bi bi-plus-circle me-2"></i>{"New Registration"}
</button>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>{"Name"}</th>
<th>{"Type"}</th>
<th>{"Status"}</th>
<th>{"Date"}</th>
<th>{"Progress"}</th>
<th class="text-end">{"Actions"}</th>
</tr>
</thead>
<tbody>
// Render active companies first
{for self.companies.iter().map(|company| {
let company_id = company.id;
let on_view = {
let link = link.clone();
Callback::from(move |e: MouseEvent| {
e.prevent_default();
link.emit(CompaniesViewMsg::ViewCompany(company_id));
})
};
let on_switch = {
let link = link.clone();
Callback::from(move |e: MouseEvent| {
e.prevent_default();
link.emit(CompaniesViewMsg::SwitchToCompany(company_id.to_string()));
})
};
html! {
<tr key={format!("company-{}", company.id)}>
<td>
<div class="d-flex align-items-center">
<i class="bi bi-building text-success me-2"></i>
<strong>{&company.name}</strong>
</div>
</td>
<td>{company.company_type.to_string()}</td>
<td>
<span class={company.status.get_badge_class()}>
{company.status.to_string()}
</span>
</td>
<td>{&company.incorporation_date}</td>
<td>
<span class="badge bg-success">{"Complete"}</span>
</td>
<td class="text-end">
<div class="btn-group">
<button
class="btn btn-sm btn-outline-primary"
onclick={on_view}
title="View company details"
>
<i class="bi bi-eye"></i>
</button>
<button
class="btn btn-sm btn-primary"
onclick={on_switch}
title="Switch to this entity"
>
<i class="bi bi-box-arrow-in-right"></i>
</button>
</div>
</td>
</tr>
}
})}
// Render pending registrations
{for self.registrations.iter().map(|registration| {
let registration_clone = registration.clone();
let registration_id = registration.id;
let can_continue = matches!(registration.status, RegistrationStatus::Draft | RegistrationStatus::PendingPayment | RegistrationStatus::PaymentFailed);
let on_continue = {
let link = link.clone();
let reg = registration_clone.clone();
Callback::from(move |e: MouseEvent| {
e.prevent_default();
link.emit(CompaniesViewMsg::ContinueRegistration(reg.clone()));
})
};
let on_delete = {
let link = link.clone();
Callback::from(move |e: MouseEvent| {
e.prevent_default();
if web_sys::window()
.unwrap()
.confirm_with_message("Are you sure you want to delete this registration?")
.unwrap_or(false)
{
link.emit(CompaniesViewMsg::DeleteRegistration(registration_id));
}
})
};
html! {
<tr key={format!("registration-{}", registration.id)}>
<td>
<div class="d-flex align-items-center">
<i class="bi bi-file-earmark-text text-warning me-2"></i>
{&registration.company_name}
<small class="text-muted ms-2">{"(Registration)"}</small>
</div>
</td>
<td>{registration.company_type.to_string()}</td>
<td>
<span class={format!("badge {}", registration.status.get_badge_class())}>
{registration.status.to_string()}
</span>
</td>
<td>{&registration.created_at}</td>
<td>
<div class="d-flex align-items-center">
<div class="progress me-2" style="width: 60px; height: 8px;">
<div
class="progress-bar bg-info"
style={format!("width: {}%", (registration.current_step as f32 / 5.0 * 100.0))}
></div>
</div>
<small class="text-muted">{format!("{}/5", registration.current_step)}</small>
</div>
</td>
<td class="text-end">
<div class="btn-group">
{if can_continue {
html! {
<button
class="btn btn-sm btn-success"
onclick={on_continue}
title="Continue registration"
>
<i class="bi bi-play-circle"></i>
</button>
}
} else {
html! {
<button
class="btn btn-sm btn-outline-secondary"
disabled={true}
title="Registration complete"
>
<i class="bi bi-check-circle"></i>
</button>
}
}}
<button
class="btn btn-sm btn-outline-danger"
onclick={on_delete}
title="Delete registration"
>
<i class="bi bi-trash"></i>
</button>
</div>
</td>
</tr>
}
})}
</tbody>
</table>
</div>
</div>
</div>
}
}
}
#[function_component(CompaniesViewWrapper)]
pub fn companies_view(props: &CompaniesViewProps) -> Html {
html! {
<CompaniesView
on_navigate={props.on_navigate.clone()}
show_registration={props.show_registration}
registration_success={props.registration_success}
registration_failure={props.registration_failure}
/>
}
}