freezone/platform/src/components/accounting/expenses_tab.rs
2025-06-27 04:13:31 +02:00

801 lines
65 KiB
Rust

use yew::prelude::*;
use wasm_bindgen::JsCast;
use crate::components::accounting::models::*;
use js_sys;
#[derive(Properties, PartialEq)]
pub struct ExpensesTabProps {
pub state: UseStateHandle<AccountingState>,
}
#[function_component(ExpensesTab)]
pub fn expenses_tab(props: &ExpensesTabProps) -> Html {
let state = &props.state;
html! {
<div class="animate-fade-in-up">
// Expense Form Modal
{if state.show_expense_form {
html! {
<div class="modal fade show d-block" style="background-color: rgba(0,0,0,0.5);">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{"Add New Expense"}</h5>
<button type="button" class="btn-close" onclick={
let state = state.clone();
Callback::from(move |_| {
let mut new_state = (*state).clone();
new_state.show_expense_form = false;
state.set(new_state);
})
}></button>
</div>
<div class="modal-body">
<form>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">{"Receipt Number"}</label>
<input type="text" class="form-control" value={state.expense_form.receipt_number.clone()} readonly=true />
</div>
<div class="col-md-6">
<label class="form-label">{"Date"}</label>
<input type="date" class="form-control" value={state.expense_form.date.clone()} onchange={
let state = state.clone();
Callback::from(move |e: Event| {
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
let mut new_state = (*state).clone();
new_state.expense_form.date = input.value();
state.set(new_state);
})
} />
</div>
<div class="col-md-6">
<label class="form-label">{"Vendor Name"}</label>
<input type="text" class="form-control" value={state.expense_form.vendor_name.clone()} onchange={
let state = state.clone();
Callback::from(move |e: Event| {
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
let mut new_state = (*state).clone();
new_state.expense_form.vendor_name = input.value();
state.set(new_state);
})
} />
</div>
<div class="col-md-6">
<label class="form-label">{"Vendor Email"}</label>
<input type="email" class="form-control" value={state.expense_form.vendor_email.clone()} onchange={
let state = state.clone();
Callback::from(move |e: Event| {
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
let mut new_state = (*state).clone();
new_state.expense_form.vendor_email = input.value();
state.set(new_state);
})
} />
</div>
<div class="col-12">
<label class="form-label">{"Description"}</label>
<textarea class="form-control" rows="3" value={state.expense_form.description.clone()} onchange={
let state = state.clone();
Callback::from(move |e: Event| {
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
let mut new_state = (*state).clone();
new_state.expense_form.description = input.value();
state.set(new_state);
})
}></textarea>
</div>
<div class="col-md-6">
<label class="form-label">{"Amount"}</label>
<input type="number" step="0.01" class="form-control" value={state.expense_form.amount.to_string()} onchange={
let state = state.clone();
Callback::from(move |e: Event| {
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
let mut new_state = (*state).clone();
new_state.expense_form.amount = input.value().parse().unwrap_or(0.0);
state.set(new_state);
})
} />
</div>
<div class="col-md-6">
<label class="form-label">{"Tax Amount"}</label>
<input type="number" step="0.01" class="form-control" value={state.expense_form.tax_amount.to_string()} onchange={
let state = state.clone();
Callback::from(move |e: Event| {
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
let mut new_state = (*state).clone();
new_state.expense_form.tax_amount = input.value().parse().unwrap_or(0.0);
state.set(new_state);
})
} />
</div>
<div class="col-md-6">
<label class="form-label">{"Tax Deductible"}</label>
<select class="form-select" onchange={
let state = state.clone();
Callback::from(move |e: Event| {
let select: web_sys::HtmlSelectElement = e.target_unchecked_into();
let mut new_state = (*state).clone();
new_state.expense_form.is_deductible = select.value() == "true";
state.set(new_state);
})
}>
<option value="true" selected={state.expense_form.is_deductible}>{"Yes"}</option>
<option value="false" selected={!state.expense_form.is_deductible}>{"No"}</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">{"Project Code (Optional)"}</label>
<input type="text" class="form-control" value={state.expense_form.project_code.clone().unwrap_or_default()} onchange={
let state = state.clone();
Callback::from(move |e: Event| {
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
let mut new_state = (*state).clone();
let value = input.value();
new_state.expense_form.project_code = if value.is_empty() { None } else { Some(value) };
state.set(new_state);
})
} />
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick={
let state = state.clone();
Callback::from(move |_| {
let mut new_state = (*state).clone();
new_state.show_expense_form = false;
state.set(new_state);
})
}>{"Cancel"}</button>
<button type="button" class="btn btn-danger" onclick={
let state = state.clone();
Callback::from(move |_| {
let mut new_state = (*state).clone();
// Calculate total
new_state.expense_form.total_amount = new_state.expense_form.amount + new_state.expense_form.tax_amount;
// Add to entries
new_state.expense_entries.push(new_state.expense_form.clone());
// Reset form
new_state.show_expense_form = false;
new_state.expense_form = AccountingState::default().expense_form;
state.set(new_state);
})
}>{"Add Expense"}</button>
</div>
</div>
</div>
</div>
}
} else {
html! {}
}}
// Expense Detail Modal
{if state.show_expense_detail {
if let Some(expense_id) = &state.selected_expense_id {
if let Some(expense) = state.expense_entries.iter().find(|e| &e.id == expense_id) {
let expense_transactions: Vec<&PaymentTransaction> = state.payment_transactions.iter()
.filter(|t| t.expense_id.as_ref() == Some(expense_id))
.collect();
let total_paid: f64 = expense_transactions.iter().map(|t| t.amount).sum();
let remaining_balance = expense.total_amount - total_paid;
html! {
<div class="modal fade show d-block" style="background-color: rgba(0,0,0,0.5);">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{format!("Expense Details - {}", expense.receipt_number)}</h5>
<button type="button" class="btn-close" onclick={
let state = state.clone();
Callback::from(move |_| {
let mut new_state = (*state).clone();
new_state.show_expense_detail = false;
new_state.selected_expense_id = None;
state.set(new_state);
})
}></button>
</div>
<div class="modal-body">
<div class="row g-4">
// Expense Information
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h6 class="mb-0">{"Expense Information"}</h6>
</div>
<div class="card-body">
<div class="row g-2">
<div class="col-6"><strong>{"Receipt #:"}</strong></div>
<div class="col-6">{&expense.receipt_number}</div>
<div class="col-6"><strong>{"Date:"}</strong></div>
<div class="col-6">{&expense.date}</div>
<div class="col-6"><strong>{"Category:"}</strong></div>
<div class="col-6">{expense.category.to_string()}</div>
<div class="col-6"><strong>{"Status:"}</strong></div>
<div class="col-6">
<span class={format!("badge bg-{}", expense.payment_status.get_color())}>
{expense.payment_status.to_string()}
</span>
</div>
<div class="col-6"><strong>{"Total Amount:"}</strong></div>
<div class="col-6 fw-bold text-danger">{format!("${:.2}", expense.total_amount)}</div>
<div class="col-6"><strong>{"Amount Paid:"}</strong></div>
<div class="col-6 fw-bold text-primary">{format!("${:.2}", total_paid)}</div>
<div class="col-6"><strong>{"Remaining:"}</strong></div>
<div class="col-6 fw-bold text-warning">{format!("${:.2}", remaining_balance)}</div>
</div>
</div>
</div>
</div>
// Vendor Information
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h6 class="mb-0">{"Vendor Information"}</h6>
</div>
<div class="card-body">
<div class="row g-2">
<div class="col-4"><strong>{"Name:"}</strong></div>
<div class="col-8">{&expense.vendor_name}</div>
<div class="col-4"><strong>{"Email:"}</strong></div>
<div class="col-8">{&expense.vendor_email}</div>
<div class="col-4"><strong>{"Address:"}</strong></div>
<div class="col-8">{&expense.vendor_address}</div>
</div>
</div>
</div>
</div>
// Payment Transactions
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="mb-0">{"Payment Transactions"}</h6>
<button class="btn btn-sm btn-primary" onclick={
let state = state.clone();
let expense_id = expense.id.clone();
Callback::from(move |_| {
let mut new_state = (*state).clone();
new_state.show_transaction_form = true;
new_state.transaction_form.expense_id = Some(expense_id.clone());
state.set(new_state);
})
}>
<i class="bi bi-plus-circle me-1"></i>{"Record Payment"}
</button>
</div>
<div class="card-body p-0">
{if expense_transactions.is_empty() {
html! {
<div class="text-center py-4 text-muted">
<i class="bi bi-credit-card fs-1 mb-2 d-block"></i>
<p class="mb-0">{"No payments recorded yet"}</p>
</div>
}
} else {
html! {
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="bg-light">
<tr>
<th class="border-0 py-3">{"Date"}</th>
<th class="border-0 py-3">{"Amount"}</th>
<th class="border-0 py-3">{"Method"}</th>
<th class="border-0 py-3">{"Reference"}</th>
<th class="border-0 py-3">{"Status"}</th>
<th class="border-0 py-3">{"Notes"}</th>
</tr>
</thead>
<tbody>
{for expense_transactions.iter().map(|transaction| {
html! {
<tr>
<td class="py-3">{&transaction.date}</td>
<td class="py-3 fw-bold text-danger">{format!("${:.2}", transaction.amount)}</td>
<td class="py-3">
<div class="d-flex align-items-center">
<i class={format!("bi bi-{} text-{} me-2", transaction.payment_method.get_icon(), transaction.payment_method.get_color())}></i>
{transaction.payment_method.to_string()}
</div>
</td>
<td class="py-3">
{if let Some(hash) = &transaction.transaction_hash {
html! { <code class="small">{&hash[..12]}{"..."}</code> }
} else if let Some(ref_num) = &transaction.reference_number {
html! { <span>{ref_num}</span> }
} else {
html! { <span class="text-muted">{"-"}</span> }
}}
</td>
<td class="py-3">
<span class={format!("badge bg-{}", transaction.status.get_color())}>
{transaction.status.to_string()}
</span>
</td>
<td class="py-3">{&transaction.notes}</td>
</tr>
}
})}
</tbody>
</table>
</div>
}
}}
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick={
let state = state.clone();
Callback::from(move |_| {
let mut new_state = (*state).clone();
new_state.show_expense_detail = false;
new_state.selected_expense_id = None;
state.set(new_state);
})
}>{"Close"}</button>
<button type="button" class="btn btn-primary" onclick={
let state = state.clone();
let expense_id = expense.id.clone();
Callback::from(move |_| {
let mut new_state = (*state).clone();
new_state.show_transaction_form = true;
new_state.transaction_form.expense_id = Some(expense_id.clone());
state.set(new_state);
})
}>
<i class="bi bi-credit-card me-2"></i>{"Record Payment"}
</button>
</div>
</div>
</div>
</div>
}
} else {
html! {}
}
} else {
html! {}
}
} else {
html! {}
}}
// Transaction Form Modal (for expense payments)
{if state.show_transaction_form && state.transaction_form.expense_id.is_some() {
html! {
<div class="modal fade show d-block" style="background-color: rgba(0,0,0,0.5);">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{"Record Expense Payment"}</h5>
<button type="button" class="btn-close" onclick={
let state = state.clone();
Callback::from(move |_| {
let mut new_state = (*state).clone();
new_state.show_transaction_form = false;
state.set(new_state);
})
}></button>
</div>
<div class="modal-body">
<form>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">{"Expense Receipt Number"}</label>
<input type="text" class="form-control" value={state.transaction_form.expense_id.clone().unwrap_or_default()} readonly=true />
</div>
<div class="col-md-6">
<label class="form-label">{"Payment Amount"}</label>
<input type="number" step="0.01" class="form-control" value={state.transaction_form.amount.to_string()} onchange={
let state = state.clone();
Callback::from(move |e: Event| {
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
let mut new_state = (*state).clone();
new_state.transaction_form.amount = input.value().parse().unwrap_or(0.0);
state.set(new_state);
})
} />
</div>
<div class="col-12">
<label class="form-label">{"Payment Method"}</label>
<select class="form-select" onchange={
let state = state.clone();
Callback::from(move |e: Event| {
let select: web_sys::HtmlSelectElement = e.target_unchecked_into();
let mut new_state = (*state).clone();
new_state.transaction_form.payment_method = match select.value().as_str() {
"BankTransfer" => PaymentMethod::BankTransfer,
"CreditCard" => PaymentMethod::CreditCard,
"CryptoBitcoin" => PaymentMethod::CryptoBitcoin,
"CryptoEthereum" => PaymentMethod::CryptoEthereum,
"CryptoUSDC" => PaymentMethod::CryptoUSDC,
"Cash" => PaymentMethod::Cash,
"Check" => PaymentMethod::Check,
_ => PaymentMethod::BankTransfer,
};
state.set(new_state);
})
}>
<option value="BankTransfer" selected={matches!(state.transaction_form.payment_method, PaymentMethod::BankTransfer)}>{"Bank Transfer"}</option>
<option value="CreditCard" selected={matches!(state.transaction_form.payment_method, PaymentMethod::CreditCard)}>{"Credit Card"}</option>
<option value="CryptoBitcoin" selected={matches!(state.transaction_form.payment_method, PaymentMethod::CryptoBitcoin)}>{"Bitcoin"}</option>
<option value="CryptoEthereum" selected={matches!(state.transaction_form.payment_method, PaymentMethod::CryptoEthereum)}>{"Ethereum"}</option>
<option value="CryptoUSDC" selected={matches!(state.transaction_form.payment_method, PaymentMethod::CryptoUSDC)}>{"USDC"}</option>
<option value="Cash" selected={matches!(state.transaction_form.payment_method, PaymentMethod::Cash)}>{"Cash"}</option>
<option value="Check" selected={matches!(state.transaction_form.payment_method, PaymentMethod::Check)}>{"Check"}</option>
</select>
</div>
{if matches!(state.transaction_form.payment_method, PaymentMethod::CryptoBitcoin | PaymentMethod::CryptoEthereum | PaymentMethod::CryptoUSDC | PaymentMethod::CryptoOther) {
html! {
<div class="col-12">
<label class="form-label">{"Transaction Hash"}</label>
<input type="text" class="form-control" placeholder="0x..." value={state.transaction_form.transaction_hash.clone()} onchange={
let state = state.clone();
Callback::from(move |e: Event| {
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
let mut new_state = (*state).clone();
new_state.transaction_form.transaction_hash = input.value();
state.set(new_state);
})
} />
</div>
}
} else {
html! {
<div class="col-12">
<label class="form-label">{"Reference Number"}</label>
<input type="text" class="form-control" placeholder="REF-2024-001" value={state.transaction_form.reference_number.clone()} onchange={
let state = state.clone();
Callback::from(move |e: Event| {
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
let mut new_state = (*state).clone();
new_state.transaction_form.reference_number = input.value();
state.set(new_state);
})
} />
</div>
}
}}
<div class="col-12">
<label class="form-label">{"Notes"}</label>
<textarea class="form-control" rows="3" value={state.transaction_form.notes.clone()} onchange={
let state = state.clone();
Callback::from(move |e: Event| {
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
let mut new_state = (*state).clone();
new_state.transaction_form.notes = input.value();
state.set(new_state);
})
}></textarea>
</div>
<div class="col-12">
<label class="form-label">{"Attach Files"}</label>
<input type="file" class="form-control" multiple=true accept=".pdf,.jpg,.jpeg,.png" />
<small class="text-muted">{"Upload receipts, confirmations, or other supporting documents"}</small>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick={
let state = state.clone();
Callback::from(move |_| {
let mut new_state = (*state).clone();
new_state.show_transaction_form = false;
state.set(new_state);
})
}>{"Cancel"}</button>
<button type="button" class="btn btn-success" onclick={
let state = state.clone();
Callback::from(move |_| {
let mut new_state = (*state).clone();
// Create new transaction
let transaction_count = new_state.payment_transactions.len() + 1;
let new_transaction = PaymentTransaction {
id: format!("TXN-2024-{:03}", transaction_count),
invoice_id: None,
expense_id: new_state.transaction_form.expense_id.clone(),
date: js_sys::Date::new_0().to_iso_string().as_string().unwrap()[..10].to_string(),
amount: new_state.transaction_form.amount,
payment_method: new_state.transaction_form.payment_method.clone(),
transaction_hash: if new_state.transaction_form.transaction_hash.is_empty() { None } else { Some(new_state.transaction_form.transaction_hash.clone()) },
reference_number: if new_state.transaction_form.reference_number.is_empty() { None } else { Some(new_state.transaction_form.reference_number.clone()) },
notes: new_state.transaction_form.notes.clone(),
attached_files: new_state.transaction_form.attached_files.clone(),
status: TransactionStatus::Confirmed,
};
new_state.payment_transactions.push(new_transaction);
new_state.show_transaction_form = false;
new_state.transaction_form = TransactionForm::default();
state.set(new_state);
})
}>{"Record Payment"}</button>
</div>
</div>
</div>
</div>
}
} else {
html! {}
}}
// Expense Actions and Table
<div class="row g-4">
<div class="col-12">
<div class="card shadow-soft border-0">
<div class="card-header bg-white border-bottom-0 py-3">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="mb-0 fw-bold">{"Expense Entries"}</h5>
<small class="text-muted">{"Click on any row to view details"}</small>
</div>
<div class="d-flex gap-2">
<button class="btn btn-outline-primary btn-sm" onclick={
Callback::from(move |_| {
web_sys::window()
.unwrap()
.alert_with_message("Expense filter feature coming soon!")
.unwrap();
})
}>
<i class="bi bi-funnel me-2"></i>{"Filter"}
</button>
<button class="btn btn-outline-secondary btn-sm" onclick={
let expense_entries = state.expense_entries.clone();
Callback::from(move |_| {
// Create CSV content
let mut csv_content = "Receipt Number,Date,Vendor Name,Vendor Email,Description,Amount,Tax Amount,Total Amount,Category,Payment Method,Payment Status,Tax Deductible,Approval Status,Approved By,Notes,Project Code,Currency\n".to_string();
for entry in &expense_entries {
csv_content.push_str(&format!(
"{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}\n",
entry.receipt_number,
entry.date,
entry.vendor_name,
entry.vendor_email,
entry.description.replace(",", ";"),
entry.amount,
entry.tax_amount,
entry.total_amount,
entry.category.to_string(),
entry.payment_method.to_string(),
entry.payment_status.to_string(),
entry.is_deductible,
entry.approval_status.to_string(),
entry.approved_by.as_ref().unwrap_or(&"".to_string()),
entry.notes.replace(",", ";"),
entry.project_code.as_ref().unwrap_or(&"".to_string()),
entry.currency
));
}
// Create and download file
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let element = document.create_element("a").unwrap();
element.set_attribute("href", &format!("data:text/csv;charset=utf-8,{}", js_sys::encode_uri_component(&csv_content))).unwrap();
element.set_attribute("download", "expenses_export.csv").unwrap();
element.set_attribute("style", "display: none").unwrap();
document.body().unwrap().append_child(&element).unwrap();
let html_element: web_sys::HtmlElement = element.clone().dyn_into().unwrap();
html_element.click();
document.body().unwrap().remove_child(&element).unwrap();
})
}>
<i class="bi bi-download me-2"></i>{"Export"}
</button>
<button class="btn btn-danger btn-sm" onclick={
let state = state.clone();
Callback::from(move |_| {
let mut new_state = (*state).clone();
new_state.show_expense_form = true;
let expense_count = new_state.expense_entries.len() + 1;
new_state.expense_form.receipt_number = format!("EXP-2024-{:03}", expense_count);
new_state.expense_form.id = new_state.expense_form.receipt_number.clone();
state.set(new_state);
})
}>
<i class="bi bi-plus-circle me-2"></i>{"Add Expense"}
</button>
</div>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="bg-light">
<tr>
<th class="border-0 py-3 px-4">{"Receipt #"}</th>
<th class="border-0 py-3">{"Vendor"}</th>
<th class="border-0 py-3">{"Description"}</th>
<th class="border-0 py-3">{"Amount"}</th>
<th class="border-0 py-3">{"Payment Method"}</th>
<th class="border-0 py-3">{"Status"}</th>
<th class="border-0 py-3">{"Approval"}</th>
<th class="border-0 py-3">{"Actions"}</th>
</tr>
</thead>
<tbody>
{for state.expense_entries.iter().map(|entry| {
html! {
<tr class="border-bottom">
<td class="py-3 px-4 cursor-pointer" style="cursor: pointer;" onclick={
let state = state.clone();
let expense_id = entry.id.clone();
Callback::from(move |_| {
let mut new_state = (*state).clone();
new_state.show_expense_detail = true;
new_state.selected_expense_id = Some(expense_id.clone());
state.set(new_state);
})
}>
<div class="fw-bold text-primary">{&entry.receipt_number}</div>
<small class="text-muted">{&entry.date}</small>
</td>
<td class="py-3">
<div class="fw-semibold">{&entry.vendor_name}</div>
<small class="text-muted">{&entry.vendor_email}</small>
</td>
<td class="py-3">
<div class="fw-semibold">{&entry.description}</div>
<small class="text-muted">
<span class={format!("badge bg-{} bg-opacity-10 text-{} me-1", entry.category.get_color(), entry.category.get_color())}>
{entry.category.to_string()}
</span>
{if entry.is_deductible { "• Tax Deductible" } else { "" }}
{if let Some(project) = &entry.project_code {
html! { <span class="ms-1">{format!("{}", project)}</span> }
} else {
html! {}
}}
</small>
</td>
<td class="py-3">
<div class="fw-bold text-danger">{format!("${:.2}", entry.total_amount)}</div>
<small class="text-muted">{format!("${:.2} + ${:.2} tax", entry.amount, entry.tax_amount)}</small>
</td>
<td class="py-3">
<div class="d-flex align-items-center">
<i class={format!("bi bi-{} text-{} me-2", entry.payment_method.get_icon(), entry.payment_method.get_color())}></i>
<span class="small">{entry.payment_method.to_string()}</span>
</div>
</td>
<td class="py-3">
<span class={format!("badge bg-{} bg-opacity-10 text-{}", entry.payment_status.get_color(), entry.payment_status.get_color())}>
{entry.payment_status.to_string()}
</span>
</td>
<td class="py-3">
<span class={format!("badge bg-{} bg-opacity-10 text-{}", entry.approval_status.get_color(), entry.approval_status.get_color())}>
{entry.approval_status.to_string()}
</span>
{
if let Some(approver) = &entry.approved_by {
html! { <small class="d-block text-muted">{format!("by {}", approver)}</small> }
} else {
html! {}
}
}
</td>
<td class="py-3">
<div class="dropdown">
<button class="btn btn-sm btn-outline-secondary" data-bs-toggle="dropdown">
<i class="bi bi-three-dots-vertical"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick={
let state = state.clone();
let expense_id = entry.id.clone();
Callback::from(move |e: web_sys::MouseEvent| {
e.prevent_default();
e.stop_propagation();
let mut new_state = (*state).clone();
new_state.show_expense_detail = true;
new_state.selected_expense_id = Some(expense_id.clone());
state.set(new_state);
})
}><i class="bi bi-eye me-2"></i>{"View Details"}</a></li>
<li><a class="dropdown-item" href="#" onclick={
let state = state.clone();
let expense_id = entry.id.clone();
Callback::from(move |e: web_sys::MouseEvent| {
e.prevent_default();
e.stop_propagation();
let mut new_state = (*state).clone();
new_state.show_transaction_form = true;
new_state.transaction_form.expense_id = Some(expense_id.clone());
state.set(new_state);
})
}><i class="bi bi-credit-card me-2"></i>{"Record Payment"}</a></li>
<li><a class="dropdown-item" href="#" onclick={
Callback::from(move |e: web_sys::MouseEvent| {
e.prevent_default();
e.stop_propagation();
web_sys::window()
.unwrap()
.alert_with_message("Edit expense feature coming soon!")
.unwrap();
})
}><i class="bi bi-pencil me-2"></i>{"Edit"}</a></li>
<li><a class="dropdown-item" href="#" onclick={
let receipt_url = entry.receipt_url.clone();
Callback::from(move |e: web_sys::MouseEvent| {
e.prevent_default();
e.stop_propagation();
if let Some(url) = &receipt_url {
web_sys::window().unwrap().open_with_url(url).unwrap();
} else {
web_sys::window()
.unwrap()
.alert_with_message("No receipt available for this expense")
.unwrap();
}
})
}><i class="bi bi-file-earmark me-2"></i>{"View Receipt"}</a></li>
<li><a class="dropdown-item" href="#" onclick={
let state = state.clone();
let expense_id = entry.id.clone();
Callback::from(move |e: web_sys::MouseEvent| {
e.prevent_default();
e.stop_propagation();
let mut new_state = (*state).clone();
// Find and update the expense approval status
if let Some(expense) = new_state.expense_entries.iter_mut().find(|e| e.id == expense_id) {
expense.approval_status = ApprovalStatus::Approved;
expense.approved_by = Some("Current User".to_string());
}
state.set(new_state);
})
}><i class="bi bi-check-circle me-2"></i>{"Approve"}</a></li>
<li><hr class="dropdown-divider" /></li>
<li><a class="dropdown-item" href="#" onclick={
Callback::from(move |e: web_sys::MouseEvent| {
e.prevent_default();
e.stop_propagation();
web_sys::window()
.unwrap()
.alert_with_message("Duplicate expense feature coming soon!")
.unwrap();
})
}><i class="bi bi-files me-2"></i>{"Duplicate"}</a></li>
<li><a class="dropdown-item text-danger" href="#" onclick={
let state = state.clone();
let expense_id = entry.id.clone();
Callback::from(move |e: web_sys::MouseEvent| {
e.prevent_default();
e.stop_propagation();
if web_sys::window().unwrap().confirm_with_message("Are you sure you want to delete this expense?").unwrap() {
let mut new_state = (*state).clone();
new_state.expense_entries.retain(|e| e.id != expense_id);
state.set(new_state);
}
})
}><i class="bi bi-trash me-2"></i>{"Delete"}</a></li>
</ul>
</div>
</td>
</tr>
}
})}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
}
}