460 lines
22 KiB
Rust
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>
|
|
{®istration.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>{®istration.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}
|
|
/>
|
|
}
|
|
} |