From 6060831f6124198b671bb5649f42e13b0a929732 Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Tue, 22 Apr 2025 15:36:40 +0200 Subject: [PATCH] rwda ui implementation --- actix_mvc_app/src/controllers/asset.rs | 691 ++++++++++++++++++ actix_mvc_app/src/controllers/mod.rs | 1 + actix_mvc_app/src/models/asset.rs | 282 +++++++ actix_mvc_app/src/models/mod.rs | 1 + actix_mvc_app/src/routes/mod.rs | 16 + actix_mvc_app/src/utils/mod.rs | 70 +- actix_mvc_app/src/views/assets/create.html | 271 +++++++ actix_mvc_app/src/views/assets/detail.html | 556 ++++++++++++++ actix_mvc_app/src/views/assets/index.html | 140 ++++ actix_mvc_app/src/views/assets/list.html | 286 ++++++++ actix_mvc_app/src/views/assets/my_assets.html | 373 ++++++++++ actix_mvc_app/src/views/base.html | 8 +- actix_mvc_app/src/views/error.html | 2 +- actix_mvc_app/src/views/test.html | 10 + actix_mvc_app/src/views/test_base.html | 10 + 15 files changed, 2690 insertions(+), 27 deletions(-) create mode 100644 actix_mvc_app/src/controllers/asset.rs create mode 100644 actix_mvc_app/src/models/asset.rs create mode 100644 actix_mvc_app/src/views/assets/create.html create mode 100644 actix_mvc_app/src/views/assets/detail.html create mode 100644 actix_mvc_app/src/views/assets/index.html create mode 100644 actix_mvc_app/src/views/assets/list.html create mode 100644 actix_mvc_app/src/views/assets/my_assets.html create mode 100644 actix_mvc_app/src/views/test.html create mode 100644 actix_mvc_app/src/views/test_base.html diff --git a/actix_mvc_app/src/controllers/asset.rs b/actix_mvc_app/src/controllers/asset.rs new file mode 100644 index 0000000..b34249c --- /dev/null +++ b/actix_mvc_app/src/controllers/asset.rs @@ -0,0 +1,691 @@ +use actix_web::{web, HttpResponse, Result}; +use tera::{Context, Tera}; +use chrono::{Utc, Duration}; +use serde::Deserialize; + +use crate::models::asset::{Asset, AssetType, AssetStatus, BlockchainInfo, ValuationPoint, AssetTransaction, AssetStatistics}; +use crate::utils::render_template; + +#[derive(Debug, Deserialize)] +pub struct AssetForm { + pub name: String, + pub description: String, + pub asset_type: String, +} + +#[derive(Debug, Deserialize)] +pub struct ValuationForm { + pub value: f64, + pub currency: String, + pub source: String, + pub notes: Option, +} + +#[derive(Debug, Deserialize)] +pub struct TransactionForm { + pub transaction_type: String, + pub from_address: Option, + pub to_address: Option, + pub amount: Option, + pub currency: Option, + pub transaction_hash: Option, + pub notes: Option, +} + +pub struct AssetController; + +impl AssetController { + // Display the assets dashboard + pub async fn index(tmpl: web::Data) -> Result { + let mut context = Context::new(); + + println!("DEBUG: Starting assets dashboard rendering"); + + let assets = Self::get_mock_assets(); + println!("DEBUG: Generated {} mock assets", assets.len()); + + let stats = AssetStatistics::new(&assets); + println!("DEBUG: Generated asset statistics: {:?}", stats); + + // Add active_page for navigation highlighting + context.insert("active_page", &"assets"); + + // Add stats + context.insert("stats", &serde_json::to_value(stats).unwrap()); + println!("DEBUG: Added stats to context"); + + // Add recent assets + let recent_assets: Vec> = assets + .iter() + .take(5) + .map(|a| Self::asset_to_json(a)) + .collect(); + + context.insert("recent_assets", &recent_assets); + + // Add assets by type + let asset_types = vec![ + AssetType::NFT, + AssetType::Token, + AssetType::RealEstate, + AssetType::Commodity, + AssetType::Share, + AssetType::Bond, + AssetType::IntellectualProperty, + AssetType::Other, + ]; + + let assets_by_type: Vec> = asset_types + .iter() + .map(|asset_type| { + let mut map = serde_json::Map::new(); + let type_str = asset_type.as_str(); + let count = assets.iter().filter(|a| a.asset_type == *asset_type).count(); + + map.insert("type".to_string(), serde_json::Value::String(type_str.to_string())); + map.insert("count".to_string(), serde_json::Value::Number(serde_json::Number::from(count))); + + map + }) + .collect(); + + context.insert("assets_by_type", &assets_by_type); + + println!("DEBUG: Rendering assets dashboard template"); + let response = render_template(&tmpl, "assets/index.html", &context); + println!("DEBUG: Finished rendering assets dashboard template"); + response + } + + // Display the list of all assets + pub async fn list(tmpl: web::Data) -> Result { + let mut context = Context::new(); + + println!("DEBUG: Starting assets list rendering"); + + let assets = Self::get_mock_assets(); + println!("DEBUG: Generated {} mock assets", assets.len()); + + let assets_data: Vec> = assets + .iter() + .map(|a| Self::asset_to_json(a)) + .collect(); + + // Add active_page for navigation highlighting + context.insert("active_page", &"assets"); + + context.insert("assets", &assets_data); + context.insert("filter", &"all"); + + println!("DEBUG: Rendering assets list template"); + let response = render_template(&tmpl, "assets/list.html", &context); + println!("DEBUG: Finished rendering assets list template"); + response + } + + // Display the list of user's assets + pub async fn my_assets(tmpl: web::Data) -> Result { + let mut context = Context::new(); + + println!("DEBUG: Starting my assets rendering"); + + let assets = Self::get_mock_assets(); + println!("DEBUG: Generated {} mock assets", assets.len()); + + let assets_data: Vec> = assets + .iter() + .map(|a| Self::asset_to_json(a)) + .collect(); + + // Add active_page for navigation highlighting + context.insert("active_page", &"assets"); + + context.insert("assets", &assets_data); + + println!("DEBUG: Rendering my assets template"); + let response = render_template(&tmpl, "assets/my_assets.html", &context); + println!("DEBUG: Finished rendering my assets template"); + response + } + + // Display a specific asset + pub async fn detail(tmpl: web::Data, path: web::Path) -> Result { + let asset_id = path.into_inner(); + let mut context = Context::new(); + + println!("DEBUG: Starting asset detail rendering"); + + // Add active_page for navigation highlighting + context.insert("active_page", &"assets"); + + // Find the asset by ID + let assets = Self::get_mock_assets(); + let asset = assets.iter().find(|a| a.id == asset_id); + + match asset { + Some(asset) => { + println!("DEBUG: Found asset with ID {}", asset_id); + + // Convert asset to JSON + let asset_json = Self::asset_to_json(asset); + + context.insert("asset", &asset_json); + + // Add valuation history for chart + let valuation_history: Vec> = asset + .sorted_valuation_history() + .iter() + .map(|v| { + let mut map = serde_json::Map::new(); + map.insert("date".to_string(), serde_json::Value::String(v.date.format("%Y-%m-%d").to_string())); + map.insert("value".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(v.value).unwrap())); + map.insert("currency".to_string(), serde_json::Value::String(v.currency.clone())); + map + }) + .collect(); + + context.insert("valuation_history", &valuation_history); + + println!("DEBUG: Rendering asset detail template"); + let response = render_template(&tmpl, "assets/detail.html", &context); + println!("DEBUG: Finished rendering asset detail template"); + response + }, + None => { + println!("DEBUG: Asset not found with ID {}", asset_id); + Ok(HttpResponse::NotFound().finish()) + } + } + } + + // Display the create asset form + pub async fn create_form(tmpl: web::Data) -> Result { + let mut context = Context::new(); + + println!("DEBUG: Starting create asset form rendering"); + + // Add active_page for navigation highlighting + context.insert("active_page", &"assets"); + + // Add asset types for dropdown + let asset_types = vec![ + ("NFT", "NFT"), + ("Token", "Token"), + ("RealEstate", "Real Estate"), + ("Commodity", "Commodity"), + ("Share", "Share"), + ("Bond", "Bond"), + ("IntellectualProperty", "Intellectual Property"), + ("Other", "Other") + ]; + + context.insert("asset_types", &asset_types); + + println!("DEBUG: Rendering create asset form template"); + let response = render_template(&tmpl, "assets/create.html", &context); + println!("DEBUG: Finished rendering create asset form template"); + response + } + + // Process the create asset form + pub async fn create( + _tmpl: web::Data, + _form: web::Form, + ) -> Result { + println!("DEBUG: Processing create asset form"); + + // In a real application, we would save the asset to the database + // For now, we'll just redirect to the assets list + + Ok(HttpResponse::Found().append_header(("Location", "/assets")).finish()) + } + + // Add a valuation to an asset + pub async fn add_valuation( + _tmpl: web::Data, + path: web::Path, + _form: web::Form, + ) -> Result { + let asset_id = path.into_inner(); + + println!("DEBUG: Adding valuation to asset with ID {}", asset_id); + + // In a real application, we would update the asset in the database + // For now, we'll just redirect to the asset detail page + + Ok(HttpResponse::Found().append_header(("Location", format!("/assets/{}", asset_id))).finish()) + } + + // Add a transaction to an asset + pub async fn add_transaction( + _tmpl: web::Data, + path: web::Path, + _form: web::Form, + ) -> Result { + let asset_id = path.into_inner(); + + println!("DEBUG: Adding transaction to asset with ID {}", asset_id); + + // In a real application, we would update the asset in the database + // For now, we'll just redirect to the asset detail page + + Ok(HttpResponse::Found().append_header(("Location", format!("/assets/{}", asset_id))).finish()) + } + + // Update the status of an asset + pub async fn update_status( + _tmpl: web::Data, + path: web::Path<(String, String)>, + ) -> Result { + let (asset_id, _status) = path.into_inner(); + + println!("DEBUG: Updating status of asset with ID {}", asset_id); + + // In a real application, we would update the asset in the database + // For now, we'll just redirect to the asset detail page + + Ok(HttpResponse::Found().append_header(("Location", format!("/assets/{}", asset_id))).finish()) + } + + // Test method to render a simple test page + pub async fn test(tmpl: web::Data) -> Result { + println!("DEBUG: Starting test page rendering"); + + let mut context = Context::new(); + + let assets = Self::get_mock_assets(); + println!("DEBUG: Generated {} mock assets for test", assets.len()); + + let stats = AssetStatistics::new(&assets); + println!("DEBUG: Generated asset statistics for test: {:?}", stats); + + // Add active_page for navigation highlighting + context.insert("active_page", &"assets"); + + // Add stats + context.insert("stats", &serde_json::to_value(stats).unwrap()); + println!("DEBUG: Added stats to context for test"); + + // Add recent assets + let recent_assets: Vec> = assets + .iter() + .take(5) + .map(|a| Self::asset_to_json(a)) + .collect(); + + context.insert("recent_assets", &recent_assets); + + println!("DEBUG: Rendering test_base.html with full context"); + let response = render_template(&tmpl, "test_base.html", &context); + println!("DEBUG: Finished rendering test_base.html"); + response + } + + // Helper method to convert Asset to a JSON object for templates + fn asset_to_json(asset: &Asset) -> serde_json::Map { + let mut map = serde_json::Map::new(); + + map.insert("id".to_string(), serde_json::Value::String(asset.id.clone())); + map.insert("name".to_string(), serde_json::Value::String(asset.name.clone())); + map.insert("description".to_string(), serde_json::Value::String(asset.description.clone())); + map.insert("asset_type".to_string(), serde_json::Value::String(asset.asset_type.as_str().to_string())); + map.insert("status".to_string(), serde_json::Value::String(asset.status.as_str().to_string())); + map.insert("owner_id".to_string(), serde_json::Value::String(asset.owner_id.clone())); + map.insert("owner_name".to_string(), serde_json::Value::String(asset.owner_name.clone())); + map.insert("created_at".to_string(), serde_json::Value::String(asset.created_at.format("%Y-%m-%d").to_string())); + map.insert("updated_at".to_string(), serde_json::Value::String(asset.updated_at.format("%Y-%m-%d").to_string())); + + // Add current valuation if available + if let Some(current_valuation) = asset.current_valuation { + map.insert("current_valuation".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(current_valuation).unwrap())); + + if let Some(valuation_currency) = &asset.valuation_currency { + map.insert("valuation_currency".to_string(), serde_json::Value::String(valuation_currency.clone())); + } + + if let Some(valuation_date) = asset.valuation_date { + map.insert("valuation_date".to_string(), serde_json::Value::String(valuation_date.format("%Y-%m-%d").to_string())); + } + } + + // Add blockchain info if available + if let Some(blockchain_info) = &asset.blockchain_info { + let mut blockchain_map = serde_json::Map::new(); + blockchain_map.insert("blockchain".to_string(), serde_json::Value::String(blockchain_info.blockchain.clone())); + blockchain_map.insert("token_id".to_string(), serde_json::Value::String(blockchain_info.token_id.clone())); + blockchain_map.insert("contract_address".to_string(), serde_json::Value::String(blockchain_info.contract_address.clone())); + blockchain_map.insert("owner_address".to_string(), serde_json::Value::String(blockchain_info.owner_address.clone())); + + if let Some(transaction_hash) = &blockchain_info.transaction_hash { + blockchain_map.insert("transaction_hash".to_string(), serde_json::Value::String(transaction_hash.clone())); + } + + if let Some(block_number) = blockchain_info.block_number { + blockchain_map.insert("block_number".to_string(), serde_json::Value::Number(serde_json::Number::from(block_number))); + } + + if let Some(timestamp) = blockchain_info.timestamp { + blockchain_map.insert("timestamp".to_string(), serde_json::Value::String(timestamp.format("%Y-%m-%d").to_string())); + } + + map.insert("blockchain_info".to_string(), serde_json::Value::Object(blockchain_map)); + } + + // Add valuation history + let valuation_history: Vec = asset.valuation_history.iter() + .map(|v| { + let mut valuation_map = serde_json::Map::new(); + valuation_map.insert("id".to_string(), serde_json::Value::String(v.id.clone())); + valuation_map.insert("date".to_string(), serde_json::Value::String(v.date.format("%Y-%m-%d").to_string())); + valuation_map.insert("value".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(v.value).unwrap())); + valuation_map.insert("currency".to_string(), serde_json::Value::String(v.currency.clone())); + valuation_map.insert("source".to_string(), serde_json::Value::String(v.source.clone())); + + if let Some(notes) = &v.notes { + valuation_map.insert("notes".to_string(), serde_json::Value::String(notes.clone())); + } + + serde_json::Value::Object(valuation_map) + }) + .collect(); + + map.insert("valuation_history".to_string(), serde_json::Value::Array(valuation_history)); + + // Add transaction history + let transaction_history: Vec = asset.transaction_history.iter() + .map(|t| { + let mut transaction_map = serde_json::Map::new(); + transaction_map.insert("id".to_string(), serde_json::Value::String(t.id.clone())); + transaction_map.insert("transaction_type".to_string(), serde_json::Value::String(t.transaction_type.clone())); + transaction_map.insert("date".to_string(), serde_json::Value::String(t.date.format("%Y-%m-%d").to_string())); + + if let Some(from_address) = &t.from_address { + transaction_map.insert("from_address".to_string(), serde_json::Value::String(from_address.clone())); + } + + if let Some(to_address) = &t.to_address { + transaction_map.insert("to_address".to_string(), serde_json::Value::String(to_address.clone())); + } + + if let Some(amount) = t.amount { + transaction_map.insert("amount".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(amount).unwrap())); + } + + if let Some(currency) = &t.currency { + transaction_map.insert("currency".to_string(), serde_json::Value::String(currency.clone())); + } + + if let Some(transaction_hash) = &t.transaction_hash { + transaction_map.insert("transaction_hash".to_string(), serde_json::Value::String(transaction_hash.clone())); + } + + if let Some(notes) = &t.notes { + transaction_map.insert("notes".to_string(), serde_json::Value::String(notes.clone())); + } + + serde_json::Value::Object(transaction_map) + }) + .collect(); + + map.insert("transaction_history".to_string(), serde_json::Value::Array(transaction_history)); + + // Add image URL if available + if let Some(image_url) = &asset.image_url { + map.insert("image_url".to_string(), serde_json::Value::String(image_url.clone())); + } + + // Add external URL if available + if let Some(external_url) = &asset.external_url { + map.insert("external_url".to_string(), serde_json::Value::String(external_url.clone())); + } + + map + } + + // Generate mock assets for testing + fn get_mock_assets() -> Vec { + let now = Utc::now(); + let mut assets = Vec::new(); + + // Create NFT asset + let mut nft = Asset { + id: "asset-nft12345".to_string(), + name: "CryptoKitty #12345".to_string(), + description: "A unique digital cat collectible".to_string(), + asset_type: AssetType::NFT, + status: AssetStatus::Active, + owner_id: "user-123".to_string(), + owner_name: "John Doe".to_string(), + created_at: now - Duration::days(30), + updated_at: now, + blockchain_info: None, + current_valuation: Some(600.0), + valuation_currency: Some("USD".to_string()), + valuation_date: Some(now - Duration::days(1)), + valuation_history: Vec::new(), + transaction_history: Vec::new(), + metadata: serde_json::json!({}), + image_url: Some("https://example.com/cryptokitty12345.png".to_string()), + external_url: Some("https://www.cryptokitties.co/kitty/12345".to_string()), + }; + + nft.add_blockchain_info(BlockchainInfo { + blockchain: "Ethereum".to_string(), + token_id: "12345".to_string(), + contract_address: "0x06012c8cf97BEaD5deAe237070F9587f8E7A266d".to_string(), + owner_address: "0xb794f5ea0ba39494ce839613fffba74279579268".to_string(), + transaction_hash: Some("0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string()), + block_number: Some(12345678), + timestamp: Some(now - Duration::days(30)), + }); + + nft.add_valuation(500.0, "USD", "OpenSea", Some("Initial valuation".to_string())); + nft.add_valuation(550.0, "USD", "OpenSea", Some("Market increase".to_string())); + nft.add_valuation(600.0, "USD", "OpenSea", None); + + nft.add_transaction( + "Purchase", + Some("0xa1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2".to_string()), + Some("0xb794f5ea0ba39494ce839613fffba74279579268".to_string()), + Some(500.0), + Some("USD".to_string()), + Some("0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string()), + None, + ); + + assets.push(nft); + + // Create Real Estate asset + let mut real_estate = Asset { + id: "asset-realestate789".to_string(), + name: "Apartment 123, New York".to_string(), + description: "A luxury apartment in downtown Manhattan".to_string(), + asset_type: AssetType::RealEstate, + status: AssetStatus::Active, + owner_id: "user-456".to_string(), + owner_name: "Jane Smith".to_string(), + created_at: now - Duration::days(60), + updated_at: now, + blockchain_info: None, + current_valuation: Some(1050000.0), + valuation_currency: Some("USD".to_string()), + valuation_date: Some(now - Duration::days(5)), + valuation_history: Vec::new(), + transaction_history: Vec::new(), + metadata: serde_json::json!({}), + image_url: Some("https://example.com/apartment123.jpg".to_string()), + external_url: None, + }; + + real_estate.add_blockchain_info(BlockchainInfo { + blockchain: "Ethereum".to_string(), + token_id: "RE789".to_string(), + contract_address: "0x123456789abcdef123456789abcdef12345678ab".to_string(), + owner_address: "0xc3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4".to_string(), + transaction_hash: Some("0xabcdef123456789abcdef123456789abcdef123456789abcdef123456789abcd".to_string()), + block_number: Some(9876543), + timestamp: Some(now - Duration::days(60)), + }); + + real_estate.add_valuation(1000000.0, "USD", "Property Appraisal", Some("Initial valuation".to_string())); + real_estate.add_valuation(1050000.0, "USD", "Property Appraisal", Some("Annual update".to_string())); + + real_estate.add_transaction( + "Tokenization", + None, + Some("0xc3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4".to_string()), + Some(1000000.0), + Some("USD".to_string()), + Some("0xabcdef123456789abcdef123456789abcdef123456789abcdef123456789abcd".to_string()), + Some("Initial tokenization of property".to_string()), + ); + + assets.push(real_estate); + + // Create Token asset + let mut token = Asset { + id: "asset-token456".to_string(), + name: "OurWorld Token".to_string(), + description: "Utility token for the OurWorld platform".to_string(), + asset_type: AssetType::Token, + status: AssetStatus::Active, + owner_id: "user-789".to_string(), + owner_name: "Alice Johnson".to_string(), + created_at: now - Duration::days(90), + updated_at: now, + blockchain_info: None, + current_valuation: Some(110000.0), + valuation_currency: Some("TFT".to_string()), + valuation_date: Some(now - Duration::days(2)), + valuation_history: Vec::new(), + transaction_history: Vec::new(), + metadata: serde_json::json!({}), + image_url: Some("https://example.com/ourworld_token.png".to_string()), + external_url: Some("https://ourworld.tf/token".to_string()), + }; + + token.add_blockchain_info(BlockchainInfo { + blockchain: "ThreeFold".to_string(), + token_id: "TFT".to_string(), + contract_address: "0xf6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1".to_string(), + owner_address: "0xe5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6".to_string(), + transaction_hash: None, + block_number: None, + timestamp: Some(now - Duration::days(90)), + }); + + token.add_valuation(100000.0, "TFT", "ThreeFold Exchange", None); + token.add_valuation(120000.0, "TFT", "ThreeFold Exchange", None); + token.add_valuation(110000.0, "TFT", "ThreeFold Exchange", None); + + token.add_transaction( + "Transfer", + Some("0xd4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5".to_string()), + Some("0xe5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6".to_string()), + Some(100000.0), + Some("TFT".to_string()), + None, + Some("Initial allocation".to_string()), + ); + + assets.push(token); + + // Create Intellectual Property asset + let mut ip = Asset { + id: "asset-ip987654".to_string(), + name: "Patent #987654".to_string(), + description: "A patent for a new renewable energy technology".to_string(), + asset_type: AssetType::IntellectualProperty, + status: AssetStatus::Active, + owner_id: "user-321".to_string(), + owner_name: "Bob Williams".to_string(), + created_at: now - Duration::days(120), + updated_at: now, + blockchain_info: None, + current_valuation: Some(300000.0), + valuation_currency: Some("USD".to_string()), + valuation_date: Some(now - Duration::days(10)), + valuation_history: Vec::new(), + transaction_history: Vec::new(), + metadata: serde_json::json!({}), + image_url: None, + external_url: None, + }; + + ip.add_blockchain_info(BlockchainInfo { + blockchain: "Polygon".to_string(), + token_id: "IP987654".to_string(), + contract_address: "0x2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3".to_string(), + owner_address: "0x4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f".to_string(), + transaction_hash: Some("0x56789abcdef123456789abcdef123456789abcdef123456789abcdef12345678".to_string()), + block_number: Some(5432109), + timestamp: Some(now - Duration::days(120)), + }); + + ip.add_valuation(250000.0, "USD", "Patent Valuation Service", Some("Initial valuation".to_string())); + ip.add_valuation(300000.0, "USD", "Patent Valuation Service", Some("After successful testing".to_string())); + + ip.add_transaction( + "Licensing", + Some("0x4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f".to_string()), + Some("0x5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a".to_string()), + Some(50000.0), + Some("USD".to_string()), + Some("0x6789abcdef123456789abcdef123456789abcdef123456789abcdef123456789".to_string()), + Some("Annual licensing fee".to_string()), + ); + + assets.push(ip); + + // Create Share asset + let mut share = Asset { + id: "asset-share123".to_string(), + name: "OurWorld Inc. Shares".to_string(), + description: "Equity shares in OurWorld Inc.".to_string(), + asset_type: AssetType::Share, + status: AssetStatus::Active, + owner_id: "user-654".to_string(), + owner_name: "Charlie Brown".to_string(), + created_at: now - Duration::days(150), + updated_at: now, + blockchain_info: None, + current_valuation: Some(1300.0), + valuation_currency: Some("USD".to_string()), + valuation_date: Some(now - Duration::days(3)), + valuation_history: Vec::new(), + transaction_history: Vec::new(), + metadata: serde_json::json!({}), + image_url: None, + external_url: Some("https://ourworld.tf/shares".to_string()), + }; + + share.add_blockchain_info(BlockchainInfo { + blockchain: "Ethereum".to_string(), + token_id: "OWSHARE".to_string(), + contract_address: "0x3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4".to_string(), + owner_address: "0x6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b".to_string(), + transaction_hash: Some("0x789abcdef123456789abcdef123456789abcdef123456789abcdef123456789a".to_string()), + block_number: Some(7654321), + timestamp: Some(now - Duration::days(150)), + }); + + share.add_valuation(1000.0, "USD", "Stock Exchange", None); + share.add_valuation(1200.0, "USD", "Stock Exchange", None); + share.add_valuation(1150.0, "USD", "Stock Exchange", None); + share.add_valuation(1300.0, "USD", "Stock Exchange", None); + + share.add_transaction( + "Purchase", + Some("0x7b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c".to_string()), + Some("0x6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b".to_string()), + Some(1000.0), + Some("USD".to_string()), + Some("0x89abcdef123456789abcdef123456789abcdef123456789abcdef123456789ab".to_string()), + None, + ); + + assets.push(share); + + assets + } +} diff --git a/actix_mvc_app/src/controllers/mod.rs b/actix_mvc_app/src/controllers/mod.rs index 6baf3d6..22cf289 100644 --- a/actix_mvc_app/src/controllers/mod.rs +++ b/actix_mvc_app/src/controllers/mod.rs @@ -6,5 +6,6 @@ pub mod calendar; pub mod governance; pub mod flow; pub mod contract; +pub mod asset; // Re-export controllers for easier imports diff --git a/actix_mvc_app/src/models/asset.rs b/actix_mvc_app/src/models/asset.rs new file mode 100644 index 0000000..d48e35c --- /dev/null +++ b/actix_mvc_app/src/models/asset.rs @@ -0,0 +1,282 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +/// Asset types representing different categories of digital assets +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum AssetType { + NFT, + Token, + RealEstate, + Commodity, + Share, + Bond, + IntellectualProperty, + Other, +} + +impl AssetType { + pub fn as_str(&self) -> &str { + match self { + AssetType::NFT => "NFT", + AssetType::Token => "Token", + AssetType::RealEstate => "Real Estate", + AssetType::Commodity => "Commodity", + AssetType::Share => "Share", + AssetType::Bond => "Bond", + AssetType::IntellectualProperty => "Intellectual Property", + AssetType::Other => "Other", + } + } +} + +/// Status of an asset +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum AssetStatus { + Active, + Locked, + ForSale, + Transferred, + Archived, +} + +impl AssetStatus { + pub fn as_str(&self) -> &str { + match self { + AssetStatus::Active => "Active", + AssetStatus::Locked => "Locked", + AssetStatus::ForSale => "For Sale", + AssetStatus::Transferred => "Transferred", + AssetStatus::Archived => "Archived", + } + } +} + +/// Blockchain information for an asset +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BlockchainInfo { + pub blockchain: String, + pub token_id: String, + pub contract_address: String, + pub owner_address: String, + pub transaction_hash: Option, + pub block_number: Option, + pub timestamp: Option>, +} + +/// Valuation history point for an asset +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ValuationPoint { + pub id: String, + pub date: DateTime, + pub value: f64, + pub currency: String, + pub source: String, + pub notes: Option, +} + +/// Transaction history for an asset +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AssetTransaction { + pub id: String, + pub transaction_type: String, + pub date: DateTime, + pub from_address: Option, + pub to_address: Option, + pub amount: Option, + pub currency: Option, + pub transaction_hash: Option, + pub notes: Option, +} + +/// Main Asset model +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Asset { + pub id: String, + pub name: String, + pub description: String, + pub asset_type: AssetType, + pub status: AssetStatus, + pub owner_id: String, + pub owner_name: String, + pub created_at: DateTime, + pub updated_at: DateTime, + pub blockchain_info: Option, + pub current_valuation: Option, + pub valuation_currency: Option, + pub valuation_date: Option>, + pub valuation_history: Vec, + pub transaction_history: Vec, + pub metadata: serde_json::Value, + pub image_url: Option, + pub external_url: Option, +} + +impl Asset { + /// Creates a new asset + pub fn new( + name: &str, + description: &str, + asset_type: AssetType, + owner_id: &str, + owner_name: &str, + ) -> Self { + let now = Utc::now(); + Self { + id: format!("asset-{}", Uuid::new_v4().to_string()[..8].to_string()), + name: name.to_string(), + description: description.to_string(), + asset_type, + status: AssetStatus::Active, + owner_id: owner_id.to_string(), + owner_name: owner_name.to_string(), + created_at: now, + updated_at: now, + blockchain_info: None, + current_valuation: None, + valuation_currency: None, + valuation_date: None, + valuation_history: Vec::new(), + transaction_history: Vec::new(), + metadata: serde_json::json!({}), + image_url: None, + external_url: None, + } + } + + /// Adds blockchain information to the asset + pub fn add_blockchain_info(&mut self, blockchain_info: BlockchainInfo) { + self.blockchain_info = Some(blockchain_info); + self.updated_at = Utc::now(); + } + + /// Adds a valuation point to the asset's history + pub fn add_valuation(&mut self, value: f64, currency: &str, source: &str, notes: Option) { + let valuation = ValuationPoint { + id: format!("val-{}", Uuid::new_v4().to_string()[..8].to_string()), + date: Utc::now(), + value, + currency: currency.to_string(), + source: source.to_string(), + notes, + }; + + self.current_valuation = Some(value); + self.valuation_currency = Some(currency.to_string()); + self.valuation_date = Some(valuation.date); + self.valuation_history.push(valuation); + self.updated_at = Utc::now(); + } + + /// Adds a transaction to the asset's history + pub fn add_transaction( + &mut self, + transaction_type: &str, + from_address: Option, + to_address: Option, + amount: Option, + currency: Option, + transaction_hash: Option, + notes: Option, + ) { + let transaction = AssetTransaction { + id: format!("tx-{}", Uuid::new_v4().to_string()[..8].to_string()), + transaction_type: transaction_type.to_string(), + date: Utc::now(), + from_address, + to_address, + amount, + currency, + transaction_hash, + notes, + }; + + self.transaction_history.push(transaction); + self.updated_at = Utc::now(); + } + + /// Updates the status of the asset + pub fn update_status(&mut self, status: AssetStatus) { + self.status = status; + self.updated_at = Utc::now(); + } + + /// Gets the latest valuation point + pub fn latest_valuation(&self) -> Option<&ValuationPoint> { + self.valuation_history.last() + } + + /// Gets the latest transaction + pub fn latest_transaction(&self) -> Option<&AssetTransaction> { + self.transaction_history.last() + } + + /// Gets the valuation history sorted by date + pub fn sorted_valuation_history(&self) -> Vec<&ValuationPoint> { + let mut history = self.valuation_history.iter().collect::>(); + history.sort_by(|a, b| a.date.cmp(&b.date)); + history + } + + /// Gets the transaction history sorted by date + pub fn sorted_transaction_history(&self) -> Vec<&AssetTransaction> { + let mut history = self.transaction_history.iter().collect::>(); + history.sort_by(|a, b| a.date.cmp(&b.date)); + history + } +} + +/// Filter for assets +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AssetFilter { + pub asset_type: Option, + pub status: Option, + pub owner_id: Option, + pub min_valuation: Option, + pub max_valuation: Option, + pub valuation_currency: Option, + pub search_query: Option, +} + +/// Statistics for assets +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AssetStatistics { + pub total_assets: usize, + pub total_value: f64, + pub value_by_type: std::collections::HashMap, + pub assets_by_type: std::collections::HashMap, + pub assets_by_status: std::collections::HashMap, +} + +impl AssetStatistics { + pub fn new(assets: &[Asset]) -> Self { + let mut total_value = 0.0; + let mut value_by_type = std::collections::HashMap::new(); + let mut assets_by_type = std::collections::HashMap::new(); + let mut assets_by_status = std::collections::HashMap::new(); + + for asset in assets { + if let Some(valuation) = asset.current_valuation { + total_value += valuation; + + let asset_type = asset.asset_type.as_str().to_string(); + *value_by_type.entry(asset_type.clone()).or_insert(0.0) += valuation; + *assets_by_type.entry(asset_type).or_insert(0) += 1; + } else { + let asset_type = asset.asset_type.as_str().to_string(); + *assets_by_type.entry(asset_type).or_insert(0) += 1; + } + + let status = asset.status.as_str().to_string(); + *assets_by_status.entry(status).or_insert(0) += 1; + } + + Self { + total_assets: assets.len(), + total_value, + value_by_type, + assets_by_type, + assets_by_status, + } + } +} diff --git a/actix_mvc_app/src/models/mod.rs b/actix_mvc_app/src/models/mod.rs index 21e1148..b2cb5d7 100644 --- a/actix_mvc_app/src/models/mod.rs +++ b/actix_mvc_app/src/models/mod.rs @@ -5,6 +5,7 @@ pub mod calendar; pub mod governance; pub mod flow; pub mod contract; +pub mod asset; // Re-export models for easier imports pub use user::User; diff --git a/actix_mvc_app/src/routes/mod.rs b/actix_mvc_app/src/routes/mod.rs index 5eae7ea..42595b9 100644 --- a/actix_mvc_app/src/routes/mod.rs +++ b/actix_mvc_app/src/routes/mod.rs @@ -7,6 +7,7 @@ use crate::controllers::calendar::CalendarController; use crate::controllers::governance::GovernanceController; use crate::controllers::flow::FlowController; use crate::controllers::contract::ContractController; +use crate::controllers::asset::AssetController; use crate::middleware::JwtAuth; use crate::SESSION_KEY; @@ -89,6 +90,21 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) { .route("/create", web::get().to(ContractController::create_form)) .route("/create", web::post().to(ContractController::create)) ) + + // Asset routes + .service( + web::scope("/assets") + .route("", web::get().to(AssetController::index)) + .route("/list", web::get().to(AssetController::list)) + .route("/my", web::get().to(AssetController::my_assets)) + .route("/create", web::get().to(AssetController::create_form)) + .route("/create", web::post().to(AssetController::create)) + .route("/test", web::get().to(AssetController::test)) + .route("/{id}", web::get().to(AssetController::detail)) + .route("/{id}/valuation", web::post().to(AssetController::add_valuation)) + .route("/{id}/transaction", web::post().to(AssetController::add_transaction)) + .route("/{id}/status/{status}", web::post().to(AssetController::update_status)) + ) ); // Keep the /protected scope for any future routes that should be under that path diff --git a/actix_mvc_app/src/utils/mod.rs b/actix_mvc_app/src/utils/mod.rs index bb1e553..7c053f9 100644 --- a/actix_mvc_app/src/utils/mod.rs +++ b/actix_mvc_app/src/utils/mod.rs @@ -122,37 +122,57 @@ pub fn render_template( template_name: &str, ctx: &Context, ) -> Result { + println!("DEBUG: Attempting to render template: {}", template_name); + + // Print all context keys for debugging + let mut keys = Vec::new(); + for (key, _) in ctx.clone().into_json().as_object().unwrap().iter() { + keys.push(key.clone()); + } + println!("DEBUG: Context keys: {:?}", keys); + match tmpl.render(template_name, ctx) { - Ok(content) => Ok(HttpResponse::Ok().content_type("text/html").body(content)), + Ok(content) => { + println!("DEBUG: Successfully rendered template: {}", template_name); + Ok(HttpResponse::Ok().content_type("text/html").body(content)) + }, Err(e) => { // Log the error + println!("DEBUG: Template rendering error for {}: {}", template_name, e); + log::error!("Template rendering error: {}", e); - log::error!("Error details: {:?}", e); - log::error!("Context: {:?}", ctx); - // Create a context for the error template - let mut error_ctx = Context::new(); - error_ctx.insert("error", &format!("Template rendering error: {}", e)); - error_ctx.insert("error_details", &format!("{:?}", e)); - error_ctx.insert("error_location", &template_name); + // Create a simple error response instead of trying to render the error template + let error_html = format!( + r#" + + + Template Error + + + +
+

Template Rendering Error

+

Template: {}

+

Error:

+
{}
+

Return to Home

+
+ + "#, + template_name, + e + ); - // Try to render the error template - match tmpl.render("error.html", &error_ctx) { - Ok(error_page) => { - // Return the error page with a 500 status code - Ok(HttpResponse::InternalServerError() - .content_type("text/html") - .body(error_page)) - } - Err(render_err) => { - // If we can't render the error template, log it and return a basic error - log::error!("Error rendering error template: {}", render_err); - Err(error::ErrorInternalServerError(format!( - "Template rendering error: {}. Failed to render error page: {}", - e, render_err - ))) - } - } + println!("DEBUG: Returning simple error page"); + Ok(HttpResponse::InternalServerError() + .content_type("text/html") + .body(error_html)) } } } diff --git a/actix_mvc_app/src/views/assets/create.html b/actix_mvc_app/src/views/assets/create.html new file mode 100644 index 0000000..b81c157 --- /dev/null +++ b/actix_mvc_app/src/views/assets/create.html @@ -0,0 +1,271 @@ +{% extends "base.html" %} + +{% block title %}Create New Digital Asset{% endblock %} + +{% block content %} +
+

Create New Digital Asset

+ + +
+
+ + Asset Details +
+
+
+ +
+
Basic Information
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + +
URL to an image representing this asset
+
+
+ + +
URL to an external resource for this asset
+
+
+
+ + +
+
Blockchain Information (optional)
+
+ + +
+ +
+ + +
+
Initial Valuation (optional)
+
+ + +
+ +
+ + +
+
Additional Metadata (optional)
+
+ + +
+ +
+ +
+ Cancel + +
+
+
+
+
+{% endblock %} + +{% block scripts %} + + + +{% endblock %} diff --git a/actix_mvc_app/src/views/assets/detail.html b/actix_mvc_app/src/views/assets/detail.html new file mode 100644 index 0000000..02de694 --- /dev/null +++ b/actix_mvc_app/src/views/assets/detail.html @@ -0,0 +1,556 @@ +{% extends "base.html" %} + +{% block title %}Asset Details - {{ asset.name }}{% endblock %} + +{% block content %} +
+

Asset Details

+ + + +
+
+
+
+ + Asset Information +
+
+
+ {% if asset.image_url %} + {{ asset.name }} + {% else %} +
+ +
+ {% endif %} +

{{ asset.name }}

+
+ {% if asset.status == "Active" %} + {{ asset.status }} + {% elif asset.status == "For Sale" %} + {{ asset.status }} + {% elif asset.status == "Locked" %} + {{ asset.status }} + {% elif asset.status == "Transferred" %} + {{ asset.status }} + {% elif asset.status == "Archived" %} + {{ asset.status }} + {% else %} + {{ asset.status }} + {% endif %} + {{ asset.asset_type }} +
+
+ +
+
Description
+

{{ asset.description }}

+
+ +
+
Current Valuation
+ {% if asset.current_valuation %} +

{{ asset.valuation_currency }}{{ asset.current_valuation }}

+ Last updated: {{ asset.valuation_date }} + {% else %} +

No valuation available

+ {% endif %} +
+ +
+
Owner Information
+

Owner: {{ asset.owner_name }}

+

Owner ID: {{ asset.owner_id }}

+
+ +
+
Dates
+

Created: {{ asset.created_at }}

+

Last Updated: {{ asset.updated_at }}

+
+ + {% if asset.external_url %} + + {% endif %} +
+ +
+
+ +
+ + {% if asset.blockchain_info %} +
+
+ + Blockchain Information +
+
+
+
+

Blockchain: {{ asset.blockchain_info.blockchain }}

+

Token ID: {{ asset.blockchain_info.token_id }}

+

Contract Address: + {{ asset.blockchain_info.contract_address }} +

+
+
+

Owner Address: + {{ asset.blockchain_info.owner_address }} +

+ {% if asset.blockchain_info.transaction_hash %} +

Transaction Hash: + {{ asset.blockchain_info.transaction_hash }} +

+ {% endif %} + {% if asset.blockchain_info.block_number %} +

Block Number: {{ asset.blockchain_info.block_number }}

+ {% endif %} + {% if asset.blockchain_info.timestamp %} +

Timestamp: {{ asset.blockchain_info.timestamp }}

+ {% endif %} +
+
+
+
+ {% endif %} + + +
+
+ + Valuation History +
+
+ {% if valuation_history and valuation_history|length > 0 %} + + {% else %} +
+ No valuation history available for this asset. +
+ {% endif %} +
+
+ + +
+
+ + Valuation History +
+
+ {% if asset.valuation_history and asset.valuation_history|length > 0 %} +
+ + + + + + + + + + + + {% for valuation in asset.valuation_history %} + + + + + + + + {% endfor %} + +
DateValueCurrencySourceNotes
{{ valuation.date }}{{ valuation.value }}{{ valuation.currency }}{{ valuation.source }}{% if valuation.notes %}{{ valuation.notes }}{% else %}{% endif %}
+
+ {% else %} +
+ No valuation history available for this asset. +
+ {% endif %} +
+
+ + +
+
+ + Transaction History +
+
+ {% if asset.transaction_history and asset.transaction_history|length > 0 %} +
+ + + + + + + + + + + + + + {% for transaction in asset.transaction_history %} + + + + + + + + + + {% endfor %} + +
DateTypeFromToAmountTransaction HashNotes
{{ transaction.date }}{{ transaction.transaction_type }} + {% if transaction.from_address %} + {{ transaction.from_address }} + {% else %} + N/A + {% endif %} + + {% if transaction.to_address %} + {{ transaction.to_address }} + {% else %} + N/A + {% endif %} + + {% if transaction.amount %} + {{ transaction.currency }}{{ transaction.amount }} + {% else %} + N/A + {% endif %} + + {% if transaction.transaction_hash %} + {{ transaction.transaction_hash }} + {% else %} + N/A + {% endif %} + {% if transaction.notes %}{{ transaction.notes }}{% else %}{% endif %}
+
+ {% else %} +
+ No transaction history available for this asset. +
+ {% endif %} +
+
+
+
+
+ + + + + + + + + +{% endblock %} + +{% block scripts %} + + + + +{% endblock %} diff --git a/actix_mvc_app/src/views/assets/index.html b/actix_mvc_app/src/views/assets/index.html new file mode 100644 index 0000000..541f98c --- /dev/null +++ b/actix_mvc_app/src/views/assets/index.html @@ -0,0 +1,140 @@ +{% extends "base.html" %} + +{% block title %}Digital Assets Dashboard{% endblock %} + +{% block content %} +
+

Digital Assets Dashboard

+ + + +
+
+
+
+

{{ stats.total_assets }}

+

Total Assets

+
+ +
+
+
+
+
+

${{ stats.total_value }}

+

Total Valuation

+
+ +
+
+
+
+
+

{{ stats.assets_by_status.Active }}

+

Active Assets

+
+ +
+
+
+
+
+

0

+

Pending Transactions

+
+ +
+
+
+ + +
+
+
+
+ + Asset Types Distribution +
+
+
+ + + + + + + + + {% for asset_type in assets_by_type %} + + + + + {% endfor %} + +
Asset TypeCount
{{ asset_type.type }}{{ asset_type.count }}
+
+
+
+
+
+ + +
+
+
+
+ + Recent Assets +
+
+
+ + + + + + + + + + + {% for asset in recent_assets %} + + + + + + + {% endfor %} + +
NameTypeStatusActions
{{ asset.name }}{{ asset.asset_type }}{{ asset.status }} + + View + +
+
+
+ +
+
+
+
+{% endblock %} diff --git a/actix_mvc_app/src/views/assets/list.html b/actix_mvc_app/src/views/assets/list.html new file mode 100644 index 0000000..53989f2 --- /dev/null +++ b/actix_mvc_app/src/views/assets/list.html @@ -0,0 +1,286 @@ +{% extends "base.html" %} + +{% block title %}Digital Assets List{% endblock %} + +{% block content %} +
+

Digital Assets

+ + + +
+
+ + Filter Assets +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + All Digital Assets +
+ +
+
+
+
+ + + + + + + + + + + + + + {% for asset in assets %} + + + + + + + + + + {% endfor %} + +
NameTypeStatusOwnerValuationCreatedActions
+ {% if asset.image_url %} + {{ asset.name }} + {% endif %} + {{ asset.name }} + {{ asset.asset_type }} + {% if asset.status == "Active" %} + {{ asset.status }} + {% elif asset.status == "For Sale" %} + {{ asset.status }} + {% elif asset.status == "Locked" %} + {{ asset.status }} + {% elif asset.status == "Transferred" %} + {{ asset.status }} + {% elif asset.status == "Archived" %} + {{ asset.status }} + {% else %} + {{ asset.status }} + {% endif %} + {{ asset.owner_name }} + {% if asset.current_valuation %} + {{ asset.valuation_currency }}{{ asset.current_valuation }} + {% else %} + N/A + {% endif %} + {{ asset.created_at }} +
+ + + + {% if asset.status == "Active" %} + + {% endif %} +
+
+
+
+
+
+ + + +{% endblock %} + +{% block scripts %} + + + +{% endblock %} diff --git a/actix_mvc_app/src/views/assets/my_assets.html b/actix_mvc_app/src/views/assets/my_assets.html new file mode 100644 index 0000000..c299a22 --- /dev/null +++ b/actix_mvc_app/src/views/assets/my_assets.html @@ -0,0 +1,373 @@ +{% extends "base.html" %} + +{% block title %}My Digital Assets{% endblock %} + +{% block content %} +
+

My Digital Assets

+ + + +
+
+
+
+

{{ assets | length }}

+

Total Assets

+
+
+
+
+
+
+ {% set active_count = 0 %} + {% for asset in assets %} + {% if asset.status == "Active" %} + {% set active_count = active_count + 1 %} + {% endif %} + {% endfor %} +

{{ active_count }}

+

Active Assets

+
+
+
+
+
+
+ {% set for_sale_count = 0 %} + {% for asset in assets %} + {% if asset.status == "For Sale" %} + {% set for_sale_count = for_sale_count + 1 %} + {% endif %} + {% endfor %} +

{{ for_sale_count }}

+

For Sale

+
+
+
+
+
+
+ {% set total_value = 0 %} + {% for asset in assets %} + {% if asset.current_valuation %} + {% set total_value = total_value + asset.current_valuation %} + {% endif %} + {% endfor %} +

${% if total_value %}{{ total_value }}{% else %}0.00{% endif %}

+

Total Value

+
+
+
+
+ + +
+
+
+
+ + My Digital Assets +
+ +
+
+
+ {% if assets and assets|length > 0 %} +
+ + + + + + + + + + + + + {% for asset in assets %} + + + + + + + + + {% endfor %} + +
NameTypeStatusValuationCreatedActions
+ {% if asset.image_url %} + {{ asset.name }} + {% endif %} + {{ asset.name }} + {{ asset.asset_type }} + {% if asset.status == "Active" %} + {{ asset.status }} + {% elif asset.status == "For Sale" %} + {{ asset.status }} + {% elif asset.status == "Locked" %} + {{ asset.status }} + {% elif asset.status == "Transferred" %} + {{ asset.status }} + {% elif asset.status == "Archived" %} + {{ asset.status }} + {% else %} + {{ asset.status }} + {% endif %} + + {% if asset.current_valuation %} + {{ asset.valuation_currency }}{{ asset.current_valuation }} + {% else %} + N/A + {% endif %} + {{ asset.created_at }} +
+ + + + + +
+
+
+ {% else %} +
+

You don't have any digital assets yet.

+ Create Your First Asset +
+ {% endif %} +
+
+ + +
+
+
+
+ + Asset Types Distribution +
+
+ +
+
+
+
+
+
+ + Asset Value Distribution +
+
+ +
+
+
+
+
+ + + + + + +{% endblock %} + +{% block scripts %} + + + + +{% endblock %} diff --git a/actix_mvc_app/src/views/base.html b/actix_mvc_app/src/views/base.html index 7e57171..7aa38f7 100644 --- a/actix_mvc_app/src/views/base.html +++ b/actix_mvc_app/src/views/base.html @@ -76,6 +76,7 @@