rwda ui implementation
This commit is contained in:
		
							
								
								
									
										691
									
								
								actix_mvc_app/src/controllers/asset.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										691
									
								
								actix_mvc_app/src/controllers/asset.rs
									
									
									
									
									
										Normal file
									
								
							@@ -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<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
pub struct TransactionForm {
 | 
			
		||||
    pub transaction_type: String,
 | 
			
		||||
    pub from_address: Option<String>,
 | 
			
		||||
    pub to_address: Option<String>,
 | 
			
		||||
    pub amount: Option<f64>,
 | 
			
		||||
    pub currency: Option<String>,
 | 
			
		||||
    pub transaction_hash: Option<String>,
 | 
			
		||||
    pub notes: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct AssetController;
 | 
			
		||||
 | 
			
		||||
impl AssetController {
 | 
			
		||||
    // Display the assets dashboard
 | 
			
		||||
    pub async fn index(tmpl: web::Data<Tera>) -> Result<HttpResponse> {
 | 
			
		||||
        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<serde_json::Map<String, serde_json::Value>> = 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<serde_json::Map<String, serde_json::Value>> = 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<Tera>) -> Result<HttpResponse> {
 | 
			
		||||
        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<serde_json::Map<String, serde_json::Value>> = 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<Tera>) -> Result<HttpResponse> {
 | 
			
		||||
        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<serde_json::Map<String, serde_json::Value>> = 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<Tera>, path: web::Path<String>) -> Result<HttpResponse> {
 | 
			
		||||
        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<serde_json::Map<String, serde_json::Value>> = 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<Tera>) -> Result<HttpResponse> {
 | 
			
		||||
        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<Tera>,
 | 
			
		||||
        _form: web::Form<AssetForm>,
 | 
			
		||||
    ) -> Result<HttpResponse> {
 | 
			
		||||
        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<Tera>,
 | 
			
		||||
        path: web::Path<String>,
 | 
			
		||||
        _form: web::Form<ValuationForm>,
 | 
			
		||||
    ) -> Result<HttpResponse> {
 | 
			
		||||
        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<Tera>,
 | 
			
		||||
        path: web::Path<String>,
 | 
			
		||||
        _form: web::Form<TransactionForm>,
 | 
			
		||||
    ) -> Result<HttpResponse> {
 | 
			
		||||
        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<Tera>,
 | 
			
		||||
        path: web::Path<(String, String)>,
 | 
			
		||||
    ) -> Result<HttpResponse> {
 | 
			
		||||
        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<Tera>) -> Result<HttpResponse> {
 | 
			
		||||
        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<serde_json::Map<String, serde_json::Value>> = 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<String, serde_json::Value> {
 | 
			
		||||
        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<serde_json::Value> = 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<serde_json::Value> = 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<Asset> {
 | 
			
		||||
        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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										282
									
								
								actix_mvc_app/src/models/asset.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								actix_mvc_app/src/models/asset.rs
									
									
									
									
									
										Normal file
									
								
							@@ -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<String>,
 | 
			
		||||
    pub block_number: Option<u64>,
 | 
			
		||||
    pub timestamp: Option<DateTime<Utc>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Valuation history point for an asset
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct ValuationPoint {
 | 
			
		||||
    pub id: String,
 | 
			
		||||
    pub date: DateTime<Utc>,
 | 
			
		||||
    pub value: f64,
 | 
			
		||||
    pub currency: String,
 | 
			
		||||
    pub source: String,
 | 
			
		||||
    pub notes: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Transaction history for an asset
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct AssetTransaction {
 | 
			
		||||
    pub id: String,
 | 
			
		||||
    pub transaction_type: String,
 | 
			
		||||
    pub date: DateTime<Utc>,
 | 
			
		||||
    pub from_address: Option<String>,
 | 
			
		||||
    pub to_address: Option<String>,
 | 
			
		||||
    pub amount: Option<f64>,
 | 
			
		||||
    pub currency: Option<String>,
 | 
			
		||||
    pub transaction_hash: Option<String>,
 | 
			
		||||
    pub notes: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// 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<Utc>,
 | 
			
		||||
    pub updated_at: DateTime<Utc>,
 | 
			
		||||
    pub blockchain_info: Option<BlockchainInfo>,
 | 
			
		||||
    pub current_valuation: Option<f64>,
 | 
			
		||||
    pub valuation_currency: Option<String>,
 | 
			
		||||
    pub valuation_date: Option<DateTime<Utc>>,
 | 
			
		||||
    pub valuation_history: Vec<ValuationPoint>,
 | 
			
		||||
    pub transaction_history: Vec<AssetTransaction>,
 | 
			
		||||
    pub metadata: serde_json::Value,
 | 
			
		||||
    pub image_url: Option<String>,
 | 
			
		||||
    pub external_url: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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<String>) {
 | 
			
		||||
        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<String>,
 | 
			
		||||
        to_address: Option<String>,
 | 
			
		||||
        amount: Option<f64>,
 | 
			
		||||
        currency: Option<String>,
 | 
			
		||||
        transaction_hash: Option<String>,
 | 
			
		||||
        notes: Option<String>,
 | 
			
		||||
    ) {
 | 
			
		||||
        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::<Vec<_>>();
 | 
			
		||||
        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::<Vec<_>>();
 | 
			
		||||
        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<AssetType>,
 | 
			
		||||
    pub status: Option<AssetStatus>,
 | 
			
		||||
    pub owner_id: Option<String>,
 | 
			
		||||
    pub min_valuation: Option<f64>,
 | 
			
		||||
    pub max_valuation: Option<f64>,
 | 
			
		||||
    pub valuation_currency: Option<String>,
 | 
			
		||||
    pub search_query: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// 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<String, f64>,
 | 
			
		||||
    pub assets_by_type: std::collections::HashMap<String, usize>,
 | 
			
		||||
    pub assets_by_status: std::collections::HashMap<String, usize>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -122,37 +122,57 @@ pub fn render_template(
 | 
			
		||||
    template_name: &str,
 | 
			
		||||
    ctx: &Context,
 | 
			
		||||
) -> Result<HttpResponse, Error> {
 | 
			
		||||
    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#"<!DOCTYPE html>
 | 
			
		||||
                <html>
 | 
			
		||||
                <head>
 | 
			
		||||
                    <title>Template Error</title>
 | 
			
		||||
                    <style>
 | 
			
		||||
                        body {{ font-family: Arial, sans-serif; margin: 40px; }}
 | 
			
		||||
                        .error-container {{ border: 1px solid #dc3545; padding: 20px; border-radius: 5px; }}
 | 
			
		||||
                        h1 {{ color: #dc3545; }}
 | 
			
		||||
                        pre {{ background-color: #f8f9fa; padding: 15px; border-radius: 5px; overflow-x: auto; }}
 | 
			
		||||
                    </style>
 | 
			
		||||
                </head>
 | 
			
		||||
                <body>
 | 
			
		||||
                    <div class="error-container">
 | 
			
		||||
                        <h1>Template Rendering Error</h1>
 | 
			
		||||
                        <p><strong>Template:</strong> {}</p>
 | 
			
		||||
                        <p><strong>Error:</strong></p>
 | 
			
		||||
                        <pre>{}</pre>
 | 
			
		||||
                        <p><a href="/">Return to Home</a></p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </body>
 | 
			
		||||
                </html>"#,
 | 
			
		||||
                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))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										271
									
								
								actix_mvc_app/src/views/assets/create.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										271
									
								
								actix_mvc_app/src/views/assets/create.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,271 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}Create New Digital Asset{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container-fluid px-4">
 | 
			
		||||
    <h1 class="mt-4">Create New Digital Asset</h1>
 | 
			
		||||
    <ol class="breadcrumb mb-4">
 | 
			
		||||
        <li class="breadcrumb-item"><a href="/">Home</a></li>
 | 
			
		||||
        <li class="breadcrumb-item"><a href="/assets">Digital Assets</a></li>
 | 
			
		||||
        <li class="breadcrumb-item active">Create New Asset</li>
 | 
			
		||||
    </ol>
 | 
			
		||||
    
 | 
			
		||||
    <div class="card mb-4">
 | 
			
		||||
        <div class="card-header">
 | 
			
		||||
            <i class="fas fa-plus-circle me-1"></i>
 | 
			
		||||
            Asset Details
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
            <form id="createAssetForm" method="post" action="/assets/create">
 | 
			
		||||
                <!-- Basic Information -->
 | 
			
		||||
                <div class="mb-4">
 | 
			
		||||
                    <h5>Basic Information</h5>
 | 
			
		||||
                    <div class="row">
 | 
			
		||||
                        <div class="col-md-6 mb-3">
 | 
			
		||||
                            <label for="name" class="form-label">Name</label>
 | 
			
		||||
                            <input type="text" class="form-control" id="name" name="name" required>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-md-6 mb-3">
 | 
			
		||||
                            <label for="asset_type" class="form-label">Asset Type</label>
 | 
			
		||||
                            <select class="form-select" id="asset_type" name="asset_type" required>
 | 
			
		||||
                                {% for type_value, type_label in asset_types %}
 | 
			
		||||
                                <option value="{{ type_value }}">{{ type_label }}</option>
 | 
			
		||||
                                {% endfor %}
 | 
			
		||||
                            </select>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="description" class="form-label">Description</label>
 | 
			
		||||
                        <textarea class="form-control" id="description" name="description" rows="3" required></textarea>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="row">
 | 
			
		||||
                        <div class="col-md-6 mb-3">
 | 
			
		||||
                            <label for="image_url" class="form-label">Image URL (optional)</label>
 | 
			
		||||
                            <input type="url" class="form-control" id="image_url" name="image_url">
 | 
			
		||||
                            <div class="form-text">URL to an image representing this asset</div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-md-6 mb-3">
 | 
			
		||||
                            <label for="external_url" class="form-label">External URL (optional)</label>
 | 
			
		||||
                            <input type="url" class="form-control" id="external_url" name="external_url">
 | 
			
		||||
                            <div class="form-text">URL to an external resource for this asset</div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <!-- Blockchain Information -->
 | 
			
		||||
                <div class="mb-4">
 | 
			
		||||
                    <h5>Blockchain Information (optional)</h5>
 | 
			
		||||
                    <div class="form-check mb-3">
 | 
			
		||||
                        <input class="form-check-input" type="checkbox" id="has_blockchain_info" name="has_blockchain_info">
 | 
			
		||||
                        <label class="form-check-label" for="has_blockchain_info">
 | 
			
		||||
                            This asset has blockchain information
 | 
			
		||||
                        </label>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div id="blockchainInfoSection" style="display: none;">
 | 
			
		||||
                        <div class="row">
 | 
			
		||||
                            <div class="col-md-6 mb-3">
 | 
			
		||||
                                <label for="blockchain" class="form-label">Blockchain</label>
 | 
			
		||||
                                <input type="text" class="form-control" id="blockchain" name="blockchain">
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="col-md-6 mb-3">
 | 
			
		||||
                                <label for="token_id" class="form-label">Token ID</label>
 | 
			
		||||
                                <input type="text" class="form-control" id="token_id" name="token_id">
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="row">
 | 
			
		||||
                            <div class="col-md-6 mb-3">
 | 
			
		||||
                                <label for="contract_address" class="form-label">Contract Address</label>
 | 
			
		||||
                                <input type="text" class="form-control" id="contract_address" name="contract_address">
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="col-md-6 mb-3">
 | 
			
		||||
                                <label for="owner_address" class="form-label">Owner Address</label>
 | 
			
		||||
                                <input type="text" class="form-control" id="owner_address" name="owner_address">
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="row">
 | 
			
		||||
                            <div class="col-md-6 mb-3">
 | 
			
		||||
                                <label for="transaction_hash" class="form-label">Transaction Hash (optional)</label>
 | 
			
		||||
                                <input type="text" class="form-control" id="transaction_hash" name="transaction_hash">
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="col-md-6 mb-3">
 | 
			
		||||
                                <label for="block_number" class="form-label">Block Number (optional)</label>
 | 
			
		||||
                                <input type="number" class="form-control" id="block_number" name="block_number">
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <!-- Initial Valuation -->
 | 
			
		||||
                <div class="mb-4">
 | 
			
		||||
                    <h5>Initial Valuation (optional)</h5>
 | 
			
		||||
                    <div class="form-check mb-3">
 | 
			
		||||
                        <input class="form-check-input" type="checkbox" id="has_valuation" name="has_valuation">
 | 
			
		||||
                        <label class="form-check-label" for="has_valuation">
 | 
			
		||||
                            Add an initial valuation for this asset
 | 
			
		||||
                        </label>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div id="valuationSection" style="display: none;">
 | 
			
		||||
                        <div class="row">
 | 
			
		||||
                            <div class="col-md-4 mb-3">
 | 
			
		||||
                                <label for="value" class="form-label">Value</label>
 | 
			
		||||
                                <input type="number" class="form-control" id="value" name="value" step="0.01">
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="col-md-4 mb-3">
 | 
			
		||||
                                <label for="currency" class="form-label">Currency</label>
 | 
			
		||||
                                <input type="text" class="form-control" id="currency" name="currency" value="USD">
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="col-md-4 mb-3">
 | 
			
		||||
                                <label for="source" class="form-label">Source</label>
 | 
			
		||||
                                <input type="text" class="form-control" id="source" name="source">
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="valuation_notes" class="form-label">Notes</label>
 | 
			
		||||
                            <textarea class="form-control" id="valuation_notes" name="valuation_notes" rows="2"></textarea>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <!-- Metadata -->
 | 
			
		||||
                <div class="mb-4">
 | 
			
		||||
                    <h5>Additional Metadata (optional)</h5>
 | 
			
		||||
                    <div class="form-check mb-3">
 | 
			
		||||
                        <input class="form-check-input" type="checkbox" id="has_metadata" name="has_metadata">
 | 
			
		||||
                        <label class="form-check-label" for="has_metadata">
 | 
			
		||||
                            Add additional metadata for this asset
 | 
			
		||||
                        </label>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div id="metadataSection" style="display: none;">
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="metadata" class="form-label">Metadata (JSON format)</label>
 | 
			
		||||
                            <textarea class="form-control" id="metadata" name="metadata" rows="5"></textarea>
 | 
			
		||||
                            <div class="form-text">Enter additional metadata in JSON format</div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div class="d-grid gap-2 d-md-flex justify-content-md-end">
 | 
			
		||||
                    <a href="/assets" class="btn btn-secondary me-md-2">Cancel</a>
 | 
			
		||||
                    <button type="submit" class="btn btn-primary">Create Asset</button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </form>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block scripts %}
 | 
			
		||||
<script>
 | 
			
		||||
    document.addEventListener('DOMContentLoaded', function() {
 | 
			
		||||
        // Toggle blockchain info section
 | 
			
		||||
        const hasBlockchainInfo = document.getElementById('has_blockchain_info');
 | 
			
		||||
        const blockchainInfoSection = document.getElementById('blockchainInfoSection');
 | 
			
		||||
        
 | 
			
		||||
        hasBlockchainInfo.addEventListener('change', function() {
 | 
			
		||||
            blockchainInfoSection.style.display = this.checked ? 'block' : 'none';
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        // Toggle valuation section
 | 
			
		||||
        const hasValuation = document.getElementById('has_valuation');
 | 
			
		||||
        const valuationSection = document.getElementById('valuationSection');
 | 
			
		||||
        
 | 
			
		||||
        hasValuation.addEventListener('change', function() {
 | 
			
		||||
            valuationSection.style.display = this.checked ? 'block' : 'none';
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        // Toggle metadata section
 | 
			
		||||
        const hasMetadata = document.getElementById('has_metadata');
 | 
			
		||||
        const metadataSection = document.getElementById('metadataSection');
 | 
			
		||||
        
 | 
			
		||||
        hasMetadata.addEventListener('change', function() {
 | 
			
		||||
            metadataSection.style.display = this.checked ? 'block' : 'none';
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        // Form validation
 | 
			
		||||
        const form = document.getElementById('createAssetForm');
 | 
			
		||||
        form.addEventListener('submit', function(event) {
 | 
			
		||||
            let isValid = true;
 | 
			
		||||
            
 | 
			
		||||
            // Validate required fields
 | 
			
		||||
            const name = document.getElementById('name').value.trim();
 | 
			
		||||
            const description = document.getElementById('description').value.trim();
 | 
			
		||||
            
 | 
			
		||||
            if (!name) {
 | 
			
		||||
                isValid = false;
 | 
			
		||||
                document.getElementById('name').classList.add('is-invalid');
 | 
			
		||||
            } else {
 | 
			
		||||
                document.getElementById('name').classList.remove('is-invalid');
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (!description) {
 | 
			
		||||
                isValid = false;
 | 
			
		||||
                document.getElementById('description').classList.add('is-invalid');
 | 
			
		||||
            } else {
 | 
			
		||||
                document.getElementById('description').classList.remove('is-invalid');
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Validate blockchain info if checked
 | 
			
		||||
            if (hasBlockchainInfo.checked) {
 | 
			
		||||
                const blockchain = document.getElementById('blockchain').value.trim();
 | 
			
		||||
                const tokenId = document.getElementById('token_id').value.trim();
 | 
			
		||||
                const contractAddress = document.getElementById('contract_address').value.trim();
 | 
			
		||||
                const ownerAddress = document.getElementById('owner_address').value.trim();
 | 
			
		||||
                
 | 
			
		||||
                if (!blockchain || !tokenId || !contractAddress || !ownerAddress) {
 | 
			
		||||
                    isValid = false;
 | 
			
		||||
                    if (!blockchain) document.getElementById('blockchain').classList.add('is-invalid');
 | 
			
		||||
                    if (!tokenId) document.getElementById('token_id').classList.add('is-invalid');
 | 
			
		||||
                    if (!contractAddress) document.getElementById('contract_address').classList.add('is-invalid');
 | 
			
		||||
                    if (!ownerAddress) document.getElementById('owner_address').classList.add('is-invalid');
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Validate valuation if checked
 | 
			
		||||
            if (hasValuation.checked) {
 | 
			
		||||
                const value = document.getElementById('value').value.trim();
 | 
			
		||||
                const currency = document.getElementById('currency').value.trim();
 | 
			
		||||
                const source = document.getElementById('source').value.trim();
 | 
			
		||||
                
 | 
			
		||||
                if (!value || !currency || !source) {
 | 
			
		||||
                    isValid = false;
 | 
			
		||||
                    if (!value) document.getElementById('value').classList.add('is-invalid');
 | 
			
		||||
                    if (!currency) document.getElementById('currency').classList.add('is-invalid');
 | 
			
		||||
                    if (!source) document.getElementById('source').classList.add('is-invalid');
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Validate metadata if checked
 | 
			
		||||
            if (hasMetadata.checked) {
 | 
			
		||||
                const metadata = document.getElementById('metadata').value.trim();
 | 
			
		||||
                
 | 
			
		||||
                if (metadata) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        JSON.parse(metadata);
 | 
			
		||||
                        document.getElementById('metadata').classList.remove('is-invalid');
 | 
			
		||||
                    } catch (e) {
 | 
			
		||||
                        isValid = false;
 | 
			
		||||
                        document.getElementById('metadata').classList.add('is-invalid');
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (!isValid) {
 | 
			
		||||
                event.preventDefault();
 | 
			
		||||
                alert('Please fix the errors in the form before submitting.');
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
    .form-check-input:checked {
 | 
			
		||||
        background-color: #0d6efd;
 | 
			
		||||
        border-color: #0d6efd;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .is-invalid {
 | 
			
		||||
        border-color: #dc3545;
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										556
									
								
								actix_mvc_app/src/views/assets/detail.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										556
									
								
								actix_mvc_app/src/views/assets/detail.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,556 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}Asset Details - {{ asset.name }}{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container-fluid px-4">
 | 
			
		||||
    <h1 class="mt-4">Asset Details</h1>
 | 
			
		||||
    <ol class="breadcrumb mb-4">
 | 
			
		||||
        <li class="breadcrumb-item"><a href="/">Home</a></li>
 | 
			
		||||
        <li class="breadcrumb-item"><a href="/assets">Digital Assets</a></li>
 | 
			
		||||
        <li class="breadcrumb-item active">{{ asset.name }}</li>
 | 
			
		||||
    </ol>
 | 
			
		||||
    
 | 
			
		||||
    <!-- Asset Overview -->
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <div class="col-xl-4">
 | 
			
		||||
            <div class="card mb-4">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <i class="fas fa-info-circle me-1"></i>
 | 
			
		||||
                    Asset Information
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="text-center mb-4">
 | 
			
		||||
                        {% if asset.image_url %}
 | 
			
		||||
                        <img src="{{ asset.image_url }}" alt="{{ asset.name }}" class="img-fluid asset-image mb-3">
 | 
			
		||||
                        {% else %}
 | 
			
		||||
                        <div class="asset-placeholder mb-3">
 | 
			
		||||
                            <i class="fas fa-cube fa-5x"></i>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                        <h3>{{ asset.name }}</h3>
 | 
			
		||||
                        <div>
 | 
			
		||||
                            {% if asset.status == "Active" %}
 | 
			
		||||
                                <span class="badge bg-success">{{ asset.status }}</span>
 | 
			
		||||
                            {% elif asset.status == "For Sale" %}
 | 
			
		||||
                                <span class="badge bg-warning">{{ asset.status }}</span>
 | 
			
		||||
                            {% elif asset.status == "Locked" %}
 | 
			
		||||
                                <span class="badge bg-secondary">{{ asset.status }}</span>
 | 
			
		||||
                            {% elif asset.status == "Transferred" %}
 | 
			
		||||
                                <span class="badge bg-info">{{ asset.status }}</span>
 | 
			
		||||
                            {% elif asset.status == "Archived" %}
 | 
			
		||||
                                <span class="badge bg-danger">{{ asset.status }}</span>
 | 
			
		||||
                            {% else %}
 | 
			
		||||
                                <span class="badge bg-primary">{{ asset.status }}</span>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                            <span class="badge bg-primary">{{ asset.asset_type }}</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <h5>Description</h5>
 | 
			
		||||
                        <p>{{ asset.description }}</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <h5>Current Valuation</h5>
 | 
			
		||||
                        {% if asset.current_valuation %}
 | 
			
		||||
                        <h3 class="text-primary">{{ asset.valuation_currency }}{{ asset.current_valuation }}</h3>
 | 
			
		||||
                        <small class="text-muted">Last updated: {{ asset.valuation_date }}</small>
 | 
			
		||||
                        {% else %}
 | 
			
		||||
                        <p>No valuation available</p>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    </div>
 | 
			
		||||
                    
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <h5>Owner Information</h5>
 | 
			
		||||
                        <p><strong>Owner:</strong> {{ asset.owner_name }}</p>
 | 
			
		||||
                        <p><strong>Owner ID:</strong> {{ asset.owner_id }}</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <h5>Dates</h5>
 | 
			
		||||
                        <p><strong>Created:</strong> {{ asset.created_at }}</p>
 | 
			
		||||
                        <p><strong>Last Updated:</strong> {{ asset.updated_at }}</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    
 | 
			
		||||
                    {% if asset.external_url %}
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <a href="{{ asset.external_url }}" target="_blank" class="btn btn-outline-primary">
 | 
			
		||||
                            <i class="fas fa-external-link-alt me-1"></i> View External Resource
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-footer">
 | 
			
		||||
                    <div class="d-grid gap-2">
 | 
			
		||||
                        <button class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#valuationModal">
 | 
			
		||||
                            <i class="fas fa-dollar-sign me-1"></i> Add Valuation
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <button class="btn btn-secondary" type="button" data-bs-toggle="modal" data-bs-target="#transactionModal">
 | 
			
		||||
                            <i class="fas fa-exchange-alt me-1"></i> Record Transaction
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <button class="btn btn-warning" type="button" data-bs-toggle="modal" data-bs-target="#statusModal">
 | 
			
		||||
                            <i class="fas fa-edit me-1"></i> Change Status
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <div class="col-xl-8">
 | 
			
		||||
            <!-- Blockchain Information -->
 | 
			
		||||
            {% if asset.blockchain_info %}
 | 
			
		||||
            <div class="card mb-4">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <i class="fas fa-link me-1"></i>
 | 
			
		||||
                    Blockchain Information
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="row">
 | 
			
		||||
                        <div class="col-md-6">
 | 
			
		||||
                            <p><strong>Blockchain:</strong> {{ asset.blockchain_info.blockchain }}</p>
 | 
			
		||||
                            <p><strong>Token ID:</strong> {{ asset.blockchain_info.token_id }}</p>
 | 
			
		||||
                            <p><strong>Contract Address:</strong> 
 | 
			
		||||
                                <code class="blockchain-address">{{ asset.blockchain_info.contract_address }}</code>
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-md-6">
 | 
			
		||||
                            <p><strong>Owner Address:</strong> 
 | 
			
		||||
                                <code class="blockchain-address">{{ asset.blockchain_info.owner_address }}</code>
 | 
			
		||||
                            </p>
 | 
			
		||||
                            {% if asset.blockchain_info.transaction_hash %}
 | 
			
		||||
                            <p><strong>Transaction Hash:</strong> 
 | 
			
		||||
                                <code class="blockchain-address">{{ asset.blockchain_info.transaction_hash }}</code>
 | 
			
		||||
                            </p>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                            {% if asset.blockchain_info.block_number %}
 | 
			
		||||
                            <p><strong>Block Number:</strong> {{ asset.blockchain_info.block_number }}</p>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                            {% if asset.blockchain_info.timestamp %}
 | 
			
		||||
                            <p><strong>Timestamp:</strong> {{ asset.blockchain_info.timestamp }}</p>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
            
 | 
			
		||||
            <!-- Valuation History Chart -->
 | 
			
		||||
            <div class="card mb-4">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <i class="fas fa-chart-line me-1"></i>
 | 
			
		||||
                    Valuation History
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% if valuation_history and valuation_history|length > 0 %}
 | 
			
		||||
                    <canvas id="valuationChart" width="100%" height="40"></canvas>
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                    <div class="alert alert-info">
 | 
			
		||||
                        No valuation history available for this asset.
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <!-- Valuation History Table -->
 | 
			
		||||
            <div class="card mb-4">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <i class="fas fa-history me-1"></i>
 | 
			
		||||
                    Valuation History
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% if asset.valuation_history and asset.valuation_history|length > 0 %}
 | 
			
		||||
                    <div class="table-responsive">
 | 
			
		||||
                        <table class="table table-striped">
 | 
			
		||||
                            <thead>
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <th>Date</th>
 | 
			
		||||
                                    <th>Value</th>
 | 
			
		||||
                                    <th>Currency</th>
 | 
			
		||||
                                    <th>Source</th>
 | 
			
		||||
                                    <th>Notes</th>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                            </thead>
 | 
			
		||||
                            <tbody>
 | 
			
		||||
                                {% for valuation in asset.valuation_history %}
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <td>{{ valuation.date }}</td>
 | 
			
		||||
                                    <td>{{ valuation.value }}</td>
 | 
			
		||||
                                    <td>{{ valuation.currency }}</td>
 | 
			
		||||
                                    <td>{{ valuation.source }}</td>
 | 
			
		||||
                                    <td>{% if valuation.notes %}{{ valuation.notes }}{% else %}{% endif %}</td>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                                {% endfor %}
 | 
			
		||||
                            </tbody>
 | 
			
		||||
                        </table>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                    <div class="alert alert-info">
 | 
			
		||||
                        No valuation history available for this asset.
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <!-- Transaction History -->
 | 
			
		||||
            <div class="card mb-4">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <i class="fas fa-exchange-alt me-1"></i>
 | 
			
		||||
                    Transaction History
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% if asset.transaction_history and asset.transaction_history|length > 0 %}
 | 
			
		||||
                    <div class="table-responsive">
 | 
			
		||||
                        <table class="table table-striped">
 | 
			
		||||
                            <thead>
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <th>Date</th>
 | 
			
		||||
                                    <th>Type</th>
 | 
			
		||||
                                    <th>From</th>
 | 
			
		||||
                                    <th>To</th>
 | 
			
		||||
                                    <th>Amount</th>
 | 
			
		||||
                                    <th>Transaction Hash</th>
 | 
			
		||||
                                    <th>Notes</th>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                            </thead>
 | 
			
		||||
                            <tbody>
 | 
			
		||||
                                {% for transaction in asset.transaction_history %}
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <td>{{ transaction.date }}</td>
 | 
			
		||||
                                    <td>{{ transaction.transaction_type }}</td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        {% if transaction.from_address %}
 | 
			
		||||
                                        <code class="blockchain-address-small">{{ transaction.from_address }}</code>
 | 
			
		||||
                                        {% else %}
 | 
			
		||||
                                        N/A
 | 
			
		||||
                                        {% endif %}
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        {% if transaction.to_address %}
 | 
			
		||||
                                        <code class="blockchain-address-small">{{ transaction.to_address }}</code>
 | 
			
		||||
                                        {% else %}
 | 
			
		||||
                                        N/A
 | 
			
		||||
                                        {% endif %}
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        {% if transaction.amount %}
 | 
			
		||||
                                        {{ transaction.currency }}{{ transaction.amount }}
 | 
			
		||||
                                        {% else %}
 | 
			
		||||
                                        N/A
 | 
			
		||||
                                        {% endif %}
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        {% if transaction.transaction_hash %}
 | 
			
		||||
                                        <code class="blockchain-address-small">{{ transaction.transaction_hash }}</code>
 | 
			
		||||
                                        {% else %}
 | 
			
		||||
                                        N/A
 | 
			
		||||
                                        {% endif %}
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>{% if transaction.notes %}{{ transaction.notes }}{% else %}{% endif %}</td>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                                {% endfor %}
 | 
			
		||||
                            </tbody>
 | 
			
		||||
                        </table>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                    <div class="alert alert-info">
 | 
			
		||||
                        No transaction history available for this asset.
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- Valuation Modal -->
 | 
			
		||||
<div class="modal fade" id="valuationModal" tabindex="-1" aria-labelledby="valuationModalLabel" aria-hidden="true">
 | 
			
		||||
    <div class="modal-dialog">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
            <div class="modal-header">
 | 
			
		||||
                <h5 class="modal-title" id="valuationModalLabel">Add Valuation</h5>
 | 
			
		||||
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-body">
 | 
			
		||||
                <form id="valuationForm" method="post" action="/assets/{{ asset.id }}/valuation">
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="value" class="form-label">Value</label>
 | 
			
		||||
                        <input type="number" class="form-control" id="value" name="value" step="0.01" required>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="currency" class="form-label">Currency</label>
 | 
			
		||||
                        <input type="text" class="form-control" id="currency" name="currency" value="USD" required>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="source" class="form-label">Source</label>
 | 
			
		||||
                        <input type="text" class="form-control" id="source" name="source" required>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="notes" class="form-label">Notes</label>
 | 
			
		||||
                        <textarea class="form-control" id="notes" name="notes" rows="3"></textarea>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </form>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
 | 
			
		||||
                <button type="button" class="btn btn-primary" id="saveValuationBtn">Save</button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- Transaction Modal -->
 | 
			
		||||
<div class="modal fade" id="transactionModal" tabindex="-1" aria-labelledby="transactionModalLabel" aria-hidden="true">
 | 
			
		||||
    <div class="modal-dialog">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
            <div class="modal-header">
 | 
			
		||||
                <h5 class="modal-title" id="transactionModalLabel">Record Transaction</h5>
 | 
			
		||||
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-body">
 | 
			
		||||
                <form id="transactionForm" method="post" action="/assets/{{ asset.id }}/transaction">
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="transaction_type" class="form-label">Transaction Type</label>
 | 
			
		||||
                        <select class="form-select" id="transaction_type" name="transaction_type" required>
 | 
			
		||||
                            <option value="Purchase">Purchase</option>
 | 
			
		||||
                            <option value="Sale">Sale</option>
 | 
			
		||||
                            <option value="Transfer">Transfer</option>
 | 
			
		||||
                            <option value="Mint">Mint</option>
 | 
			
		||||
                            <option value="Burn">Burn</option>
 | 
			
		||||
                            <option value="Licensing">Licensing</option>
 | 
			
		||||
                            <option value="Other">Other</option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="from_address" class="form-label">From Address</label>
 | 
			
		||||
                        <input type="text" class="form-control" id="from_address" name="from_address">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="to_address" class="form-label">To Address</label>
 | 
			
		||||
                        <input type="text" class="form-control" id="to_address" name="to_address">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="amount" class="form-label">Amount</label>
 | 
			
		||||
                        <input type="number" class="form-control" id="amount" name="amount" step="0.01">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="transaction_currency" class="form-label">Currency</label>
 | 
			
		||||
                        <input type="text" class="form-control" id="transaction_currency" name="currency" value="USD">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="transaction_hash" class="form-label">Transaction Hash</label>
 | 
			
		||||
                        <input type="text" class="form-control" id="transaction_hash" name="transaction_hash">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="transaction_notes" class="form-label">Notes</label>
 | 
			
		||||
                        <textarea class="form-control" id="transaction_notes" name="notes" rows="3"></textarea>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </form>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
 | 
			
		||||
                <button type="button" class="btn btn-primary" id="saveTransactionBtn">Save</button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- Status Modal -->
 | 
			
		||||
<div class="modal fade" id="statusModal" tabindex="-1" aria-labelledby="statusModalLabel" aria-hidden="true">
 | 
			
		||||
    <div class="modal-dialog">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
            <div class="modal-header">
 | 
			
		||||
                <h5 class="modal-title" id="statusModalLabel">Change Asset Status</h5>
 | 
			
		||||
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-body">
 | 
			
		||||
                <form id="statusForm" method="post" action="/assets/{{ asset.id }}/status/">
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="newStatus" class="form-label">New Status</label>
 | 
			
		||||
                        <select class="form-select" id="newStatus" name="status">
 | 
			
		||||
                            <option value="Active" {% if asset.status == "Active" %}selected{% endif %}>Active</option>
 | 
			
		||||
                            <option value="Locked" {% if asset.status == "Locked" %}selected{% endif %}>Locked</option>
 | 
			
		||||
                            <option value="ForSale" {% if asset.status == "For Sale" %}selected{% endif %}>For Sale</option>
 | 
			
		||||
                            <option value="Transferred" {% if asset.status == "Transferred" %}selected{% endif %}>Transferred</option>
 | 
			
		||||
                            <option value="Archived" {% if asset.status == "Archived" %}selected{% endif %}>Archived</option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </form>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
 | 
			
		||||
                <button type="button" class="btn btn-primary" id="saveStatusBtn">Save Changes</button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block scripts %}
 | 
			
		||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
 | 
			
		||||
<script>
 | 
			
		||||
    document.addEventListener('DOMContentLoaded', function() {
 | 
			
		||||
        // Valuation History Chart
 | 
			
		||||
        {% if valuation_history and valuation_history|length > 0 %}
 | 
			
		||||
        const ctx = document.getElementById('valuationChart');
 | 
			
		||||
        
 | 
			
		||||
        const dates = [
 | 
			
		||||
            {% for point in valuation_history %}
 | 
			
		||||
                "{{ point.date }}"{% if not loop.last %},{% endif %}
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        const values = [
 | 
			
		||||
            {% for point in valuation_history %}
 | 
			
		||||
                {{ point.value }}{% if not loop.last %},{% endif %}
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        new Chart(ctx, {
 | 
			
		||||
            type: 'line',
 | 
			
		||||
            data: {
 | 
			
		||||
                labels: dates,
 | 
			
		||||
                datasets: [{
 | 
			
		||||
                    label: 'Valuation ({{ valuation_history[0].currency }})',
 | 
			
		||||
                    data: values,
 | 
			
		||||
                    lineTension: 0.3,
 | 
			
		||||
                    backgroundColor: "rgba(78, 115, 223, 0.05)",
 | 
			
		||||
                    borderColor: "rgba(78, 115, 223, 1)",
 | 
			
		||||
                    pointRadius: 3,
 | 
			
		||||
                    pointBackgroundColor: "rgba(78, 115, 223, 1)",
 | 
			
		||||
                    pointBorderColor: "rgba(78, 115, 223, 1)",
 | 
			
		||||
                    pointHoverRadius: 5,
 | 
			
		||||
                    pointHoverBackgroundColor: "rgba(78, 115, 223, 1)",
 | 
			
		||||
                    pointHoverBorderColor: "rgba(78, 115, 223, 1)",
 | 
			
		||||
                    pointHitRadius: 10,
 | 
			
		||||
                    pointBorderWidth: 2,
 | 
			
		||||
                    fill: true
 | 
			
		||||
                }],
 | 
			
		||||
            },
 | 
			
		||||
            options: {
 | 
			
		||||
                maintainAspectRatio: false,
 | 
			
		||||
                scales: {
 | 
			
		||||
                    x: {
 | 
			
		||||
                        grid: {
 | 
			
		||||
                            display: false,
 | 
			
		||||
                            drawBorder: false
 | 
			
		||||
                        },
 | 
			
		||||
                        ticks: {
 | 
			
		||||
                            maxTicksLimit: 7
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    y: {
 | 
			
		||||
                        ticks: {
 | 
			
		||||
                            maxTicksLimit: 5,
 | 
			
		||||
                            padding: 10,
 | 
			
		||||
                            callback: function(value, index, values) {
 | 
			
		||||
                                return '{{ valuation_history[0].currency }}' + value;
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        grid: {
 | 
			
		||||
                            color: "rgb(234, 236, 244)",
 | 
			
		||||
                            zeroLineColor: "rgb(234, 236, 244)",
 | 
			
		||||
                            drawBorder: false,
 | 
			
		||||
                            borderDash: [2],
 | 
			
		||||
                            zeroLineBorderDash: [2]
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
                plugins: {
 | 
			
		||||
                    legend: {
 | 
			
		||||
                        display: false
 | 
			
		||||
                    },
 | 
			
		||||
                    tooltip: {
 | 
			
		||||
                        backgroundColor: "rgb(255,255,255)",
 | 
			
		||||
                        bodyFontColor: "#858796",
 | 
			
		||||
                        titleMarginBottom: 10,
 | 
			
		||||
                        titleFontColor: '#6e707e',
 | 
			
		||||
                        titleFontSize: 14,
 | 
			
		||||
                        borderColor: '#dddfeb',
 | 
			
		||||
                        borderWidth: 1,
 | 
			
		||||
                        xPadding: 15,
 | 
			
		||||
                        yPadding: 15,
 | 
			
		||||
                        displayColors: false,
 | 
			
		||||
                        intersect: false,
 | 
			
		||||
                        mode: 'index',
 | 
			
		||||
                        caretPadding: 10,
 | 
			
		||||
                        callbacks: {
 | 
			
		||||
                            label: function(context) {
 | 
			
		||||
                                var label = context.dataset.label || '';
 | 
			
		||||
                                if (label) {
 | 
			
		||||
                                    label += ': ';
 | 
			
		||||
                                }
 | 
			
		||||
                                label += '{{ valuation_history[0].currency }}' + context.parsed.y;
 | 
			
		||||
                                return label;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        
 | 
			
		||||
        // Form submission handlers
 | 
			
		||||
        const saveValuationBtn = document.getElementById('saveValuationBtn');
 | 
			
		||||
        if (saveValuationBtn) {
 | 
			
		||||
            saveValuationBtn.addEventListener('click', function() {
 | 
			
		||||
                document.getElementById('valuationForm').submit();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        const saveTransactionBtn = document.getElementById('saveTransactionBtn');
 | 
			
		||||
        if (saveTransactionBtn) {
 | 
			
		||||
            saveTransactionBtn.addEventListener('click', function() {
 | 
			
		||||
                document.getElementById('transactionForm').submit();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        const saveStatusBtn = document.getElementById('saveStatusBtn');
 | 
			
		||||
        if (saveStatusBtn) {
 | 
			
		||||
            saveStatusBtn.addEventListener('click', function() {
 | 
			
		||||
                const form = document.getElementById('statusForm');
 | 
			
		||||
                const newStatus = document.getElementById('newStatus').value;
 | 
			
		||||
                form.action = form.action + newStatus;
 | 
			
		||||
                form.submit();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
    .asset-image {
 | 
			
		||||
        max-height: 200px;
 | 
			
		||||
        border-radius: 8px;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .asset-placeholder {
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        height: 200px;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        justify-content: center;
 | 
			
		||||
        background-color: #f8f9fa;
 | 
			
		||||
        border-radius: 8px;
 | 
			
		||||
        color: #6c757d;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .blockchain-address {
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        max-width: 100%;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        text-overflow: ellipsis;
 | 
			
		||||
        white-space: nowrap;
 | 
			
		||||
        font-size: 0.85rem;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .blockchain-address-small {
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        max-width: 100px;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        text-overflow: ellipsis;
 | 
			
		||||
        white-space: nowrap;
 | 
			
		||||
        font-size: 0.75rem;
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										140
									
								
								actix_mvc_app/src/views/assets/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								actix_mvc_app/src/views/assets/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}Digital Assets Dashboard{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container-fluid px-4">
 | 
			
		||||
    <h1 class="mt-4">Digital Assets Dashboard</h1>
 | 
			
		||||
    <ol class="breadcrumb mb-4">
 | 
			
		||||
        <li class="breadcrumb-item"><a href="/">Home</a></li>
 | 
			
		||||
        <li class="breadcrumb-item active">Digital Assets</li>
 | 
			
		||||
    </ol>
 | 
			
		||||
    
 | 
			
		||||
    <!-- Stats Cards -->
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <div class="col-xl-3 col-md-6">
 | 
			
		||||
            <div class="card bg-primary text-white mb-4">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h2 class="display-4">{{ stats.total_assets }}</h2>
 | 
			
		||||
                    <p class="mb-0">Total Assets</p>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-footer d-flex align-items-center justify-content-between">
 | 
			
		||||
                    <a class="small text-white stretched-link" href="/assets/list">View All Assets</a>
 | 
			
		||||
                    <div class="small text-white"><i class="fas fa-angle-right"></i></div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-xl-3 col-md-6">
 | 
			
		||||
            <div class="card bg-success text-white mb-4">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h2 class="display-4">${{ stats.total_value }}</h2>
 | 
			
		||||
                    <p class="mb-0">Total Valuation</p>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-footer d-flex align-items-center justify-content-between">
 | 
			
		||||
                    <a class="small text-white stretched-link" href="/assets/list">View Details</a>
 | 
			
		||||
                    <div class="small text-white"><i class="fas fa-angle-right"></i></div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-xl-3 col-md-6">
 | 
			
		||||
            <div class="card bg-warning text-white mb-4">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h2 class="display-4">{{ stats.assets_by_status.Active }}</h2>
 | 
			
		||||
                    <p class="mb-0">Active Assets</p>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-footer d-flex align-items-center justify-content-between">
 | 
			
		||||
                    <a class="small text-white stretched-link" href="/assets/list">View Active Assets</a>
 | 
			
		||||
                    <div class="small text-white"><i class="fas fa-angle-right"></i></div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-xl-3 col-md-6">
 | 
			
		||||
            <div class="card bg-danger text-white mb-4">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h2 class="display-4">0</h2>
 | 
			
		||||
                    <p class="mb-0">Pending Transactions</p>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-footer d-flex align-items-center justify-content-between">
 | 
			
		||||
                    <a class="small text-white stretched-link" href="/assets/list">View Transactions</a>
 | 
			
		||||
                    <div class="small text-white"><i class="fas fa-angle-right"></i></div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    <!-- Asset Types Distribution -->
 | 
			
		||||
    <div class="row mt-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="card mb-4">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <i class="bi bi-pie-chart me-1"></i>
 | 
			
		||||
                    Asset Types Distribution
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="table-responsive">
 | 
			
		||||
                        <table class="table table-striped table-hover">
 | 
			
		||||
                            <thead>
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <th>Asset Type</th>
 | 
			
		||||
                                    <th>Count</th>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                            </thead>
 | 
			
		||||
                            <tbody>
 | 
			
		||||
                                {% for asset_type in assets_by_type %}
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <td>{{ asset_type.type }}</td>
 | 
			
		||||
                                    <td>{{ asset_type.count }}</td>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                                {% endfor %}
 | 
			
		||||
                            </tbody>
 | 
			
		||||
                        </table>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    <!-- Recent Assets Table -->
 | 
			
		||||
    <div class="row mt-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="card mb-4">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <i class="bi bi-table me-1"></i>
 | 
			
		||||
                    Recent Assets
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="table-responsive">
 | 
			
		||||
                        <table class="table table-striped table-hover">
 | 
			
		||||
                            <thead>
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <th>Name</th>
 | 
			
		||||
                                    <th>Type</th>
 | 
			
		||||
                                    <th>Status</th>
 | 
			
		||||
                                    <th>Actions</th>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                            </thead>
 | 
			
		||||
                            <tbody>
 | 
			
		||||
                                {% for asset in recent_assets %}
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <td>{{ asset.name }}</td>
 | 
			
		||||
                                    <td>{{ asset.asset_type }}</td>
 | 
			
		||||
                                    <td>{{ asset.status }}</td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <a href="/assets/{{ asset.id }}" class="btn btn-sm btn-primary">
 | 
			
		||||
                                            <i class="bi bi-eye"></i> View
 | 
			
		||||
                                        </a>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                                {% endfor %}
 | 
			
		||||
                            </tbody>
 | 
			
		||||
                        </table>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-footer">
 | 
			
		||||
                    <a href="/assets/list" class="btn btn-primary">View All Assets</a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										286
									
								
								actix_mvc_app/src/views/assets/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										286
									
								
								actix_mvc_app/src/views/assets/list.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,286 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}Digital Assets List{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container-fluid px-4">
 | 
			
		||||
    <h1 class="mt-4">Digital Assets</h1>
 | 
			
		||||
    <ol class="breadcrumb mb-4">
 | 
			
		||||
        <li class="breadcrumb-item"><a href="/">Home</a></li>
 | 
			
		||||
        <li class="breadcrumb-item"><a href="/assets">Digital Assets</a></li>
 | 
			
		||||
        <li class="breadcrumb-item active">All Assets</li>
 | 
			
		||||
    </ol>
 | 
			
		||||
    
 | 
			
		||||
    <!-- Filters -->
 | 
			
		||||
    <div class="card mb-4">
 | 
			
		||||
        <div class="card-header">
 | 
			
		||||
            <i class="fas fa-filter me-1"></i>
 | 
			
		||||
            Filter Assets
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                <div class="col-md-3 mb-3">
 | 
			
		||||
                    <label for="assetTypeFilter" class="form-label">Asset Type</label>
 | 
			
		||||
                    <select class="form-select" id="assetTypeFilter">
 | 
			
		||||
                        <option value="all">All Types</option>
 | 
			
		||||
                        <option value="NFT">NFT</option>
 | 
			
		||||
                        <option value="Token">Token</option>
 | 
			
		||||
                        <option value="RealEstate">Real Estate</option>
 | 
			
		||||
                        <option value="Commodity">Commodity</option>
 | 
			
		||||
                        <option value="Share">Share</option>
 | 
			
		||||
                        <option value="Bond">Bond</option>
 | 
			
		||||
                        <option value="IntellectualProperty">Intellectual Property</option>
 | 
			
		||||
                        <option value="Other">Other</option>
 | 
			
		||||
                    </select>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="col-md-3 mb-3">
 | 
			
		||||
                    <label for="statusFilter" class="form-label">Status</label>
 | 
			
		||||
                    <select class="form-select" id="statusFilter">
 | 
			
		||||
                        <option value="all">All Statuses</option>
 | 
			
		||||
                        <option value="Active">Active</option>
 | 
			
		||||
                        <option value="Locked">Locked</option>
 | 
			
		||||
                        <option value="ForSale">For Sale</option>
 | 
			
		||||
                        <option value="Transferred">Transferred</option>
 | 
			
		||||
                        <option value="Archived">Archived</option>
 | 
			
		||||
                    </select>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="col-md-3 mb-3">
 | 
			
		||||
                    <label for="valuationFilter" class="form-label">Valuation</label>
 | 
			
		||||
                    <select class="form-select" id="valuationFilter">
 | 
			
		||||
                        <option value="all">All Valuations</option>
 | 
			
		||||
                        <option value="under1000">Under $1,000</option>
 | 
			
		||||
                        <option value="1000to10000">$1,000 - $10,000</option>
 | 
			
		||||
                        <option value="10000to100000">$10,000 - $100,000</option>
 | 
			
		||||
                        <option value="over100000">Over $100,000</option>
 | 
			
		||||
                    </select>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="col-md-3 mb-3">
 | 
			
		||||
                    <label for="searchInput" class="form-label">Search</label>
 | 
			
		||||
                    <input type="text" class="form-control" id="searchInput" placeholder="Search by name or description">
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                <div class="col-12">
 | 
			
		||||
                    <button id="applyFilters" class="btn btn-primary">Apply Filters</button>
 | 
			
		||||
                    <button id="resetFilters" class="btn btn-secondary">Reset</button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    <!-- Assets Table -->
 | 
			
		||||
    <div class="card mb-4">
 | 
			
		||||
        <div class="card-header">
 | 
			
		||||
            <div class="d-flex justify-content-between align-items-center">
 | 
			
		||||
                <div>
 | 
			
		||||
                    <i class="fas fa-table me-1"></i>
 | 
			
		||||
                    All Digital Assets
 | 
			
		||||
                </div>
 | 
			
		||||
                <div>
 | 
			
		||||
                    <a href="/assets/create" class="btn btn-primary btn-sm">
 | 
			
		||||
                        <i class="fas fa-plus"></i> Create New Asset
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
            <div class="table-responsive">
 | 
			
		||||
                <table class="table table-striped table-hover" id="assetsTable">
 | 
			
		||||
                    <thead>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <th>Name</th>
 | 
			
		||||
                            <th>Type</th>
 | 
			
		||||
                            <th>Status</th>
 | 
			
		||||
                            <th>Owner</th>
 | 
			
		||||
                            <th>Valuation</th>
 | 
			
		||||
                            <th>Created</th>
 | 
			
		||||
                            <th>Actions</th>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    </thead>
 | 
			
		||||
                    <tbody>
 | 
			
		||||
                        {% for asset in assets %}
 | 
			
		||||
                        <tr class="asset-row" 
 | 
			
		||||
                            data-type="{{ asset.asset_type }}" 
 | 
			
		||||
                            data-status="{{ asset.status }}" 
 | 
			
		||||
                            data-valuation="{% if asset.current_valuation %}{{ asset.current_valuation }}{% else %}0{% endif %}">
 | 
			
		||||
                            <td>
 | 
			
		||||
                                {% if asset.image_url %}
 | 
			
		||||
                                <img src="{{ asset.image_url }}" alt="{{ asset.name }}" class="asset-thumbnail me-2">
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                                {{ asset.name }}
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td>{{ asset.asset_type }}</td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                {% if asset.status == "Active" %}
 | 
			
		||||
                                    <span class="badge bg-success">{{ asset.status }}</span>
 | 
			
		||||
                                {% elif asset.status == "For Sale" %}
 | 
			
		||||
                                    <span class="badge bg-warning">{{ asset.status }}</span>
 | 
			
		||||
                                {% elif asset.status == "Locked" %}
 | 
			
		||||
                                    <span class="badge bg-secondary">{{ asset.status }}</span>
 | 
			
		||||
                                {% elif asset.status == "Transferred" %}
 | 
			
		||||
                                    <span class="badge bg-info">{{ asset.status }}</span>
 | 
			
		||||
                                {% elif asset.status == "Archived" %}
 | 
			
		||||
                                    <span class="badge bg-danger">{{ asset.status }}</span>
 | 
			
		||||
                                {% else %}
 | 
			
		||||
                                    <span class="badge bg-primary">{{ asset.status }}</span>
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td>{{ asset.owner_name }}</td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                {% if asset.current_valuation %}
 | 
			
		||||
                                    {{ asset.valuation_currency }}{{ asset.current_valuation }}
 | 
			
		||||
                                {% else %}
 | 
			
		||||
                                    N/A
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td>{{ asset.created_at }}</td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <div class="btn-group" role="group">
 | 
			
		||||
                                    <a href="/assets/{{ asset.id }}" class="btn btn-sm btn-primary">
 | 
			
		||||
                                        <i class="fas fa-eye"></i>
 | 
			
		||||
                                    </a>
 | 
			
		||||
                                    {% if asset.status == "Active" %}
 | 
			
		||||
                                    <button type="button" class="btn btn-sm btn-warning" data-bs-toggle="modal" data-bs-target="#statusModal" data-asset-id="{{ asset.id }}">
 | 
			
		||||
                                        <i class="fas fa-exchange-alt"></i>
 | 
			
		||||
                                    </button>
 | 
			
		||||
                                    {% endif %}
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
                    </tbody>
 | 
			
		||||
                </table>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- Status Change Modal -->
 | 
			
		||||
<div class="modal fade" id="statusModal" tabindex="-1" aria-labelledby="statusModalLabel" aria-hidden="true">
 | 
			
		||||
    <div class="modal-dialog">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
            <div class="modal-header">
 | 
			
		||||
                <h5 class="modal-title" id="statusModalLabel">Change Asset Status</h5>
 | 
			
		||||
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-body">
 | 
			
		||||
                <form id="statusForm" method="post" action="">
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="newStatus" class="form-label">New Status</label>
 | 
			
		||||
                        <select class="form-select" id="newStatus" name="status">
 | 
			
		||||
                            <option value="Active">Active</option>
 | 
			
		||||
                            <option value="Locked">Locked</option>
 | 
			
		||||
                            <option value="ForSale">For Sale</option>
 | 
			
		||||
                            <option value="Transferred">Transferred</option>
 | 
			
		||||
                            <option value="Archived">Archived</option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </form>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
 | 
			
		||||
                <button type="button" class="btn btn-primary" id="saveStatusBtn">Save Changes</button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block scripts %}
 | 
			
		||||
<script>
 | 
			
		||||
    document.addEventListener('DOMContentLoaded', function() {
 | 
			
		||||
        // Status modal functionality
 | 
			
		||||
        const statusModal = document.getElementById('statusModal');
 | 
			
		||||
        if (statusModal) {
 | 
			
		||||
            statusModal.addEventListener('show.bs.modal', function(event) {
 | 
			
		||||
                const button = event.relatedTarget;
 | 
			
		||||
                const assetId = button.getAttribute('data-asset-id');
 | 
			
		||||
                const form = document.getElementById('statusForm');
 | 
			
		||||
                form.action = `/assets/${assetId}/status/`;
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            const saveStatusBtn = document.getElementById('saveStatusBtn');
 | 
			
		||||
            saveStatusBtn.addEventListener('click', function() {
 | 
			
		||||
                const form = document.getElementById('statusForm');
 | 
			
		||||
                const newStatus = document.getElementById('newStatus').value;
 | 
			
		||||
                form.action = form.action + newStatus;
 | 
			
		||||
                form.submit();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Filtering functionality
 | 
			
		||||
        const applyFilters = document.getElementById('applyFilters');
 | 
			
		||||
        if (applyFilters) {
 | 
			
		||||
            applyFilters.addEventListener('click', function() {
 | 
			
		||||
                filterAssets();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        const resetFilters = document.getElementById('resetFilters');
 | 
			
		||||
        if (resetFilters) {
 | 
			
		||||
            resetFilters.addEventListener('click', function() {
 | 
			
		||||
                document.getElementById('assetTypeFilter').value = 'all';
 | 
			
		||||
                document.getElementById('statusFilter').value = 'all';
 | 
			
		||||
                document.getElementById('valuationFilter').value = 'all';
 | 
			
		||||
                document.getElementById('searchInput').value = '';
 | 
			
		||||
                filterAssets();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        const searchInput = document.getElementById('searchInput');
 | 
			
		||||
        if (searchInput) {
 | 
			
		||||
            searchInput.addEventListener('keyup', function(event) {
 | 
			
		||||
                if (event.key === 'Enter') {
 | 
			
		||||
                    filterAssets();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        function filterAssets() {
 | 
			
		||||
            const typeFilter = document.getElementById('assetTypeFilter').value;
 | 
			
		||||
            const statusFilter = document.getElementById('statusFilter').value;
 | 
			
		||||
            const valuationFilter = document.getElementById('valuationFilter').value;
 | 
			
		||||
            const searchText = document.getElementById('searchInput').value.toLowerCase();
 | 
			
		||||
            
 | 
			
		||||
            const rows = document.querySelectorAll('#assetsTable tbody tr');
 | 
			
		||||
            
 | 
			
		||||
            rows.forEach(row => {
 | 
			
		||||
                const type = row.getAttribute('data-type');
 | 
			
		||||
                const status = row.getAttribute('data-status');
 | 
			
		||||
                const valuation = parseFloat(row.getAttribute('data-valuation'));
 | 
			
		||||
                const name = row.querySelector('td:first-child').textContent.toLowerCase();
 | 
			
		||||
                
 | 
			
		||||
                let typeMatch = typeFilter === 'all' || type === typeFilter;
 | 
			
		||||
                let statusMatch = statusFilter === 'all' || status === statusFilter;
 | 
			
		||||
                let searchMatch = searchText === '' || name.includes(searchText);
 | 
			
		||||
                
 | 
			
		||||
                let valuationMatch = true;
 | 
			
		||||
                if (valuationFilter === 'under1000') {
 | 
			
		||||
                    valuationMatch = valuation < 1000;
 | 
			
		||||
                } else if (valuationFilter === '1000to10000') {
 | 
			
		||||
                    valuationMatch = valuation >= 1000 && valuation < 10000;
 | 
			
		||||
                } else if (valuationFilter === '10000to100000') {
 | 
			
		||||
                    valuationMatch = valuation >= 10000 && valuation < 100000;
 | 
			
		||||
                } else if (valuationFilter === 'over100000') {
 | 
			
		||||
                    valuationMatch = valuation >= 100000;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                if (typeMatch && statusMatch && valuationMatch && searchMatch) {
 | 
			
		||||
                    row.style.display = '';
 | 
			
		||||
                } else {
 | 
			
		||||
                    row.style.display = 'none';
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
    .asset-thumbnail {
 | 
			
		||||
        width: 30px;
 | 
			
		||||
        height: 30px;
 | 
			
		||||
        object-fit: cover;
 | 
			
		||||
        border-radius: 4px;
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										373
									
								
								actix_mvc_app/src/views/assets/my_assets.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										373
									
								
								actix_mvc_app/src/views/assets/my_assets.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,373 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}My Digital Assets{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container-fluid px-4">
 | 
			
		||||
    <h1 class="mt-4">My Digital Assets</h1>
 | 
			
		||||
    <ol class="breadcrumb mb-4">
 | 
			
		||||
        <li class="breadcrumb-item"><a href="/">Home</a></li>
 | 
			
		||||
        <li class="breadcrumb-item"><a href="/assets">Digital Assets</a></li>
 | 
			
		||||
        <li class="breadcrumb-item active">My Assets</li>
 | 
			
		||||
    </ol>
 | 
			
		||||
    
 | 
			
		||||
    <!-- Summary Cards -->
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <div class="col-xl-3 col-md-6">
 | 
			
		||||
            <div class="card bg-primary text-white mb-4">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h2 class="display-4">{{ assets | length }}</h2>
 | 
			
		||||
                    <p class="mb-0">Total Assets</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-xl-3 col-md-6">
 | 
			
		||||
            <div class="card bg-success text-white mb-4">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% set active_count = 0 %}
 | 
			
		||||
                    {% for asset in assets %}
 | 
			
		||||
                        {% if asset.status == "Active" %}
 | 
			
		||||
                            {% set active_count = active_count + 1 %}
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    {% endfor %}
 | 
			
		||||
                    <h2 class="display-4">{{ active_count }}</h2>
 | 
			
		||||
                    <p class="mb-0">Active Assets</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-xl-3 col-md-6">
 | 
			
		||||
            <div class="card bg-warning text-white mb-4">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% set for_sale_count = 0 %}
 | 
			
		||||
                    {% for asset in assets %}
 | 
			
		||||
                        {% if asset.status == "For Sale" %}
 | 
			
		||||
                            {% set for_sale_count = for_sale_count + 1 %}
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    {% endfor %}
 | 
			
		||||
                    <h2 class="display-4">{{ for_sale_count }}</h2>
 | 
			
		||||
                    <p class="mb-0">For Sale</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-xl-3 col-md-6">
 | 
			
		||||
            <div class="card bg-info text-white mb-4">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% set total_value = 0 %}
 | 
			
		||||
                    {% for asset in assets %}
 | 
			
		||||
                        {% if asset.current_valuation %}
 | 
			
		||||
                            {% set total_value = total_value + asset.current_valuation %}
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    {% endfor %}
 | 
			
		||||
                    <h2 class="display-4">${% if total_value %}{{ total_value }}{% else %}0.00{% endif %}</h2>
 | 
			
		||||
                    <p class="mb-0">Total Value</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    <!-- Assets Table -->
 | 
			
		||||
    <div class="card mb-4">
 | 
			
		||||
        <div class="card-header">
 | 
			
		||||
            <div class="d-flex justify-content-between align-items-center">
 | 
			
		||||
                <div>
 | 
			
		||||
                    <i class="fas fa-table me-1"></i>
 | 
			
		||||
                    My Digital Assets
 | 
			
		||||
                </div>
 | 
			
		||||
                <div>
 | 
			
		||||
                    <a href="/assets/create" class="btn btn-primary btn-sm">
 | 
			
		||||
                        <i class="fas fa-plus"></i> Create New Asset
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
            {% if assets and assets|length > 0 %}
 | 
			
		||||
            <div class="table-responsive">
 | 
			
		||||
                <table class="table table-striped table-hover" id="myAssetsTable">
 | 
			
		||||
                    <thead>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <th>Name</th>
 | 
			
		||||
                            <th>Type</th>
 | 
			
		||||
                            <th>Status</th>
 | 
			
		||||
                            <th>Valuation</th>
 | 
			
		||||
                            <th>Created</th>
 | 
			
		||||
                            <th>Actions</th>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    </thead>
 | 
			
		||||
                    <tbody>
 | 
			
		||||
                        {% for asset in assets %}
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                {% if asset.image_url %}
 | 
			
		||||
                                <img src="{{ asset.image_url }}" alt="{{ asset.name }}" class="asset-thumbnail me-2">
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                                {{ asset.name }}
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td>{{ asset.asset_type }}</td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                {% if asset.status == "Active" %}
 | 
			
		||||
                                    <span class="badge bg-success">{{ asset.status }}</span>
 | 
			
		||||
                                {% elif asset.status == "For Sale" %}
 | 
			
		||||
                                    <span class="badge bg-warning">{{ asset.status }}</span>
 | 
			
		||||
                                {% elif asset.status == "Locked" %}
 | 
			
		||||
                                    <span class="badge bg-secondary">{{ asset.status }}</span>
 | 
			
		||||
                                {% elif asset.status == "Transferred" %}
 | 
			
		||||
                                    <span class="badge bg-info">{{ asset.status }}</span>
 | 
			
		||||
                                {% elif asset.status == "Archived" %}
 | 
			
		||||
                                    <span class="badge bg-danger">{{ asset.status }}</span>
 | 
			
		||||
                                {% else %}
 | 
			
		||||
                                    <span class="badge bg-primary">{{ asset.status }}</span>
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                {% if asset.current_valuation %}
 | 
			
		||||
                                    {{ asset.valuation_currency }}{{ asset.current_valuation }}
 | 
			
		||||
                                {% else %}
 | 
			
		||||
                                    N/A
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td>{{ asset.created_at }}</td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <div class="btn-group" role="group">
 | 
			
		||||
                                    <a href="/assets/{{ asset.id }}" class="btn btn-sm btn-primary">
 | 
			
		||||
                                        <i class="fas fa-eye"></i>
 | 
			
		||||
                                    </a>
 | 
			
		||||
                                    <button type="button" class="btn btn-sm btn-warning" data-bs-toggle="modal" data-bs-target="#statusModal" data-asset-id="{{ asset.id }}">
 | 
			
		||||
                                        <i class="fas fa-exchange-alt"></i>
 | 
			
		||||
                                    </button>
 | 
			
		||||
                                    <button type="button" class="btn btn-sm btn-success" data-bs-toggle="modal" data-bs-target="#valuationModal" data-asset-id="{{ asset.id }}">
 | 
			
		||||
                                        <i class="fas fa-dollar-sign"></i>
 | 
			
		||||
                                    </button>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
                    </tbody>
 | 
			
		||||
                </table>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% else %}
 | 
			
		||||
            <div class="alert alert-info">
 | 
			
		||||
                <p>You don't have any digital assets yet.</p>
 | 
			
		||||
                <a href="/assets/create" class="btn btn-primary">Create Your First Asset</a>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    <!-- Asset Types Distribution -->
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <div class="col-xl-6">
 | 
			
		||||
            <div class="card mb-4">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <i class="fas fa-chart-pie me-1"></i>
 | 
			
		||||
                    Asset Types Distribution
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <canvas id="assetTypesChart" width="100%" height="40"></canvas>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-xl-6">
 | 
			
		||||
            <div class="card mb-4">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <i class="fas fa-chart-bar me-1"></i>
 | 
			
		||||
                    Asset Value Distribution
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <canvas id="assetValueChart" width="100%" height="40"></canvas>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- Status Change Modal -->
 | 
			
		||||
<div class="modal fade" id="statusModal" tabindex="-1" aria-labelledby="statusModalLabel" aria-hidden="true">
 | 
			
		||||
    <div class="modal-dialog">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
            <div class="modal-header">
 | 
			
		||||
                <h5 class="modal-title" id="statusModalLabel">Change Asset Status</h5>
 | 
			
		||||
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-body">
 | 
			
		||||
                <form id="statusForm" method="post" action="">
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="newStatus" class="form-label">New Status</label>
 | 
			
		||||
                        <select class="form-select" id="newStatus" name="status">
 | 
			
		||||
                            <option value="Active">Active</option>
 | 
			
		||||
                            <option value="Locked">Locked</option>
 | 
			
		||||
                            <option value="ForSale">For Sale</option>
 | 
			
		||||
                            <option value="Transferred">Transferred</option>
 | 
			
		||||
                            <option value="Archived">Archived</option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </form>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
 | 
			
		||||
                <button type="button" class="btn btn-primary" id="saveStatusBtn">Save Changes</button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- Valuation Modal -->
 | 
			
		||||
<div class="modal fade" id="valuationModal" tabindex="-1" aria-labelledby="valuationModalLabel" aria-hidden="true">
 | 
			
		||||
    <div class="modal-dialog">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
            <div class="modal-header">
 | 
			
		||||
                <h5 class="modal-title" id="valuationModalLabel">Add Valuation</h5>
 | 
			
		||||
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-body">
 | 
			
		||||
                <form id="valuationForm" method="post" action="">
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="value" class="form-label">Value</label>
 | 
			
		||||
                        <input type="number" class="form-control" id="value" name="value" step="0.01" required>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="currency" class="form-label">Currency</label>
 | 
			
		||||
                        <input type="text" class="form-control" id="currency" name="currency" value="USD" required>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="source" class="form-label">Source</label>
 | 
			
		||||
                        <input type="text" class="form-control" id="source" name="source" required>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <label for="notes" class="form-label">Notes</label>
 | 
			
		||||
                        <textarea class="form-control" id="notes" name="notes" rows="3"></textarea>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </form>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
 | 
			
		||||
                <button type="button" class="btn btn-primary" id="saveValuationBtn">Save</button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block scripts %}
 | 
			
		||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
 | 
			
		||||
<script>
 | 
			
		||||
    document.addEventListener('DOMContentLoaded', function() {
 | 
			
		||||
        // Status modal functionality
 | 
			
		||||
        const statusModal = document.getElementById('statusModal');
 | 
			
		||||
        if (statusModal) {
 | 
			
		||||
            statusModal.addEventListener('show.bs.modal', function(event) {
 | 
			
		||||
                const button = event.relatedTarget;
 | 
			
		||||
                const assetId = button.getAttribute('data-asset-id');
 | 
			
		||||
                const form = document.getElementById('statusForm');
 | 
			
		||||
                form.action = `/assets/${assetId}/status/`;
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            const saveStatusBtn = document.getElementById('saveStatusBtn');
 | 
			
		||||
            saveStatusBtn.addEventListener('click', function() {
 | 
			
		||||
                const form = document.getElementById('statusForm');
 | 
			
		||||
                const newStatus = document.getElementById('newStatus').value;
 | 
			
		||||
                form.action = form.action + newStatus;
 | 
			
		||||
                form.submit();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Valuation modal functionality
 | 
			
		||||
        const valuationModal = document.getElementById('valuationModal');
 | 
			
		||||
        if (valuationModal) {
 | 
			
		||||
            valuationModal.addEventListener('show.bs.modal', function(event) {
 | 
			
		||||
                const button = event.relatedTarget;
 | 
			
		||||
                const assetId = button.getAttribute('data-asset-id');
 | 
			
		||||
                const form = document.getElementById('valuationForm');
 | 
			
		||||
                form.action = `/assets/${assetId}/valuation`;
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            const saveValuationBtn = document.getElementById('saveValuationBtn');
 | 
			
		||||
            saveValuationBtn.addEventListener('click', function() {
 | 
			
		||||
                document.getElementById('valuationForm').submit();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Asset Types Chart
 | 
			
		||||
        const assetTypesCtx = document.getElementById('assetTypesChart');
 | 
			
		||||
        if (assetTypesCtx) {
 | 
			
		||||
            // Count assets by type
 | 
			
		||||
            const assetTypes = {};
 | 
			
		||||
            {% for asset in assets %}
 | 
			
		||||
                if (!assetTypes['{{ asset.asset_type }}']) {
 | 
			
		||||
                    assetTypes['{{ asset.asset_type }}'] = 0;
 | 
			
		||||
                }
 | 
			
		||||
                assetTypes['{{ asset.asset_type }}']++;
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
            
 | 
			
		||||
            const typeLabels = Object.keys(assetTypes);
 | 
			
		||||
            const typeCounts = Object.values(assetTypes);
 | 
			
		||||
            
 | 
			
		||||
            new Chart(assetTypesCtx, {
 | 
			
		||||
                type: 'pie',
 | 
			
		||||
                data: {
 | 
			
		||||
                    labels: typeLabels,
 | 
			
		||||
                    datasets: [{
 | 
			
		||||
                        data: typeCounts,
 | 
			
		||||
                        backgroundColor: [
 | 
			
		||||
                            '#4e73df', '#1cc88a', '#36b9cc', '#f6c23e', 
 | 
			
		||||
                            '#e74a3b', '#858796', '#5a5c69', '#2c9faf'
 | 
			
		||||
                        ],
 | 
			
		||||
                    }],
 | 
			
		||||
                },
 | 
			
		||||
                options: {
 | 
			
		||||
                    maintainAspectRatio: false,
 | 
			
		||||
                    plugins: {
 | 
			
		||||
                        legend: {
 | 
			
		||||
                            position: 'right',
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Asset Value Chart
 | 
			
		||||
        const assetValueCtx = document.getElementById('assetValueChart');
 | 
			
		||||
        if (assetValueCtx) {
 | 
			
		||||
            // Prepare data for assets with valuation
 | 
			
		||||
            const assetNames = [];
 | 
			
		||||
            const assetValues = [];
 | 
			
		||||
            
 | 
			
		||||
            {% for asset in assets %}
 | 
			
		||||
                {% if asset.current_valuation %}
 | 
			
		||||
                    assetNames.push('{{ asset.name }}');
 | 
			
		||||
                    assetValues.push({{ asset.current_valuation }});
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
            
 | 
			
		||||
            new Chart(assetValueCtx, {
 | 
			
		||||
                type: 'bar',
 | 
			
		||||
                data: {
 | 
			
		||||
                    labels: assetNames,
 | 
			
		||||
                    datasets: [{
 | 
			
		||||
                        label: 'Asset Value ($)',
 | 
			
		||||
                        data: assetValues,
 | 
			
		||||
                        backgroundColor: '#4e73df',
 | 
			
		||||
                    }],
 | 
			
		||||
                },
 | 
			
		||||
                options: {
 | 
			
		||||
                    maintainAspectRatio: false,
 | 
			
		||||
                    scales: {
 | 
			
		||||
                        y: {
 | 
			
		||||
                            beginAtZero: true
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
    .asset-thumbnail {
 | 
			
		||||
        width: 30px;
 | 
			
		||||
        height: 30px;
 | 
			
		||||
        object-fit: cover;
 | 
			
		||||
        border-radius: 4px;
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -76,6 +76,7 @@
 | 
			
		||||
                        <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
 | 
			
		||||
                            <li><a class="dropdown-item" href="/tickets/new">New Ticket</a></li>
 | 
			
		||||
                            <li><a class="dropdown-item" href="/my-tickets">My Tickets</a></li>
 | 
			
		||||
                            <li><a class="dropdown-item" href="/assets/my">My Assets</a></li>
 | 
			
		||||
                            <li><a class="dropdown-item" href="/governance/my-votes">My Votes</a></li>
 | 
			
		||||
                            {% if user.role == "Admin" %}
 | 
			
		||||
                            <li><a class="dropdown-item" href="/admin">Admin Panel</a></li>
 | 
			
		||||
@@ -127,6 +128,11 @@
 | 
			
		||||
                        <i class="bi bi-file-earmark-text me-2"></i> Contracts
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="nav-item">
 | 
			
		||||
                    <a class="nav-link d-flex align-items-center ps-3 py-2 {% if active_page == 'assets' %}active fw-bold border-start border-4 border-primary bg-light{% endif %}" href="/assets">
 | 
			
		||||
                        <i class="bi bi-coin me-2"></i> Digital Assets
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="nav-item">
 | 
			
		||||
                    <a class="nav-link d-flex align-items-center ps-3 py-2 {% if active_page == 'editor' %}active fw-bold border-start border-4 border-primary bg-light{% endif %}" href="/editor">
 | 
			
		||||
                        <i class="bi bi-markdown me-2"></i> Markdown Editor
 | 
			
		||||
@@ -169,7 +175,7 @@
 | 
			
		||||
                <span>Convenience, Safety and Privacy</span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div>
 | 
			
		||||
                <span>© {{ now(year=true) }} Zanzibar Autonomous Zone. All rights reserved.</span>
 | 
			
		||||
                <span>© 2024 Zanzibar Autonomous Zone. All rights reserved.</span>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </footer>
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
                
 | 
			
		||||
                <div class="alert alert-danger">
 | 
			
		||||
                    <p class="mb-2"><strong>Error Message:</strong></p>
 | 
			
		||||
                    <pre class="p-3 bg-light border rounded"><code>{{ error | default(value="Unknown error") }}</code></pre>
 | 
			
		||||
                    <pre class="p-3 bg-light border rounded"><code>{% if error %}{{ error }}{% else %}Unknown error{% endif %}</code></pre>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                {% if error_details is defined and error_details %}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								actix_mvc_app/src/views/test.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								actix_mvc_app/src/views/test.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
    <title>Test Page</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <h1>Test Page</h1>
 | 
			
		||||
    <p>This is a simple test page to verify template rendering.</p>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										10
									
								
								actix_mvc_app/src/views/test_base.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								actix_mvc_app/src/views/test_base.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}Test Base Template{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container-fluid px-4">
 | 
			
		||||
    <h1 class="mt-4">Test Base Template</h1>
 | 
			
		||||
    <p>This is a simplified template for testing that extends base.html.</p>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
		Reference in New Issue
	
	Block a user