diff --git a/.gitignore b/.gitignore
index 496ee2ca6..0baaf0cc9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,25 @@
-.DS_Store
\ No newline at end of file
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+react-build.tar.gz
+package-lock.json
diff --git a/.prettierrc b/.prettierrc
deleted file mode 100644
index 3f8d2998f..000000000
--- a/.prettierrc
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "bracketSpacing": true,
- "printWidth": 80,
- "singleQuote": true,
- "tabWidth": 2,
- "trailingComma": "all",
- "useTabs": false
-}
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 33717dc11..000000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "workbench.colorCustomizations": {
- "activityBar.activeBackground": "#dd894d",
- "activityBar.activeBorder": "#aff0ca",
- "activityBar.background": "#dd894d",
- "activityBar.foreground": "#15202b",
- "activityBar.inactiveForeground": "#15202b99",
- "activityBarBadge.background": "#aff0ca",
- "activityBarBadge.foreground": "#15202b",
- "editorGroup.border": "#dd894d",
- "panel.border": "#dd894d",
- "sideBar.border": "#dd894d",
- "statusBar.background": "#cf6d28",
- "statusBar.border": "#cf6d28",
- "statusBar.foreground": "#15202b",
- "statusBarItem.hoverBackground": "#a45620",
- "titleBar.activeBackground": "#cf6d28",
- "titleBar.activeForeground": "#15202b",
- "titleBar.border": "#cf6d28",
- "titleBar.inactiveBackground": "#cf6d2899",
- "titleBar.inactiveForeground": "#15202b99"
- },
- "peacock.color": "#cf6d28"
-}
\ No newline at end of file
diff --git a/README.md b/README.md
index b84b3924e..b87cb0044 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,46 @@
-# Node Hello World
+# Getting Started with Create React App
-Simple node.js app that servers "hello world"
+This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
-Great for testing simple deployments to the cloud
+## Available Scripts
-## Run It
+In the project directory, you can run:
-`npm start`
+### `npm start`
+
+Runs the app in the development mode.\
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
+
+The page will reload if you make edits.\
+You will also see any lint errors in the console.
+
+### `npm test`
+
+Launches the test runner in the interactive watch mode.\
+See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
+
+### `npm run build`
+
+Builds the app for production to the `build` folder.\
+It correctly bundles React in production mode and optimizes the build for the best performance.
+
+The build is minified and the filenames include the hashes.\
+Your app is ready to be deployed!
+
+See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
+
+### `npm run eject`
+
+**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
+
+If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
+
+Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
+
+You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
+
+## Learn More
+
+You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
+
+To learn React, check out the [React documentation](https://reactjs.org/).
diff --git a/index.js b/index.js
deleted file mode 100644
index 54e5fef1f..000000000
--- a/index.js
+++ /dev/null
@@ -1,12 +0,0 @@
-const http = require('http');
-const port = process.env.PORT || 3000;
-
-const server = http.createServer((req, res) => {
- res.statusCode = 200;
- const msg = 'Hello Node!\n'
- res.end(msg);
-});
-
-server.listen(port, () => {
- console.log(`Server running on http://localhost:${port}/`);
-});
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index 61b5dcb84..000000000
--- a/package-lock.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "papa-node-hello-000",
- "version": "1.0.0",
- "lockfileVersion": 1
-}
diff --git a/package.json b/package.json
index b0d12dfc6..e15804ef5 100644
--- a/package.json
+++ b/package.json
@@ -1,20 +1,72 @@
{
- "name": "node-hello",
- "version": "1.0.0",
- "description": "",
- "main": "index.js",
+ "name": "poc-criticalasset",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "@apollo/client": "^4.0.9",
+ "@chakra-ui/icons": "^2.2.4",
+ "@chakra-ui/react": "^2.10.9",
+ "@emotion/react": "^11.14.0",
+ "@emotion/styled": "^11.14.1",
+ "@hello-pangea/dnd": "^18.0.1",
+ "@hookform/resolvers": "^5.2.2",
+ "@mui/icons-material": "^5.18.0",
+ "@mui/material": "^5.18.0",
+ "@mui/x-data-grid": "^6.17.0",
+ "@mui/x-date-pickers": "^5.0.19",
+ "@testing-library/dom": "^10.4.1",
+ "@testing-library/jest-dom": "^6.7.0",
+ "@testing-library/react": "^16.3.0",
+ "@testing-library/user-event": "^13.5.0",
+ "@types/jest": "^27.5.2",
+ "@types/node": "^16.18.126",
+ "@types/react": "^19.1.10",
+ "@types/react-dom": "^19.1.7",
+ "axios": "^1.11.0",
+ "bootstrap": "^5.3.8",
+ "dayjs": "^1.11.13",
+ "flowise-embed-react": "^3.0.5",
+ "graphql": "^16.12.0",
+ "lucide-react": "^0.542.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-hook-form": "^7.62.0",
+ "react-icons": "^5.5.0",
+ "react-router-dom": "^6.26.1",
+ "react-scripts": "5.0.1",
+ "recharts": "^2.15.4",
+ "@types/react-color": "^3.0.13",
+ "react-color": "^2.19.3",
+ "tinycolor2": "^1.6.0",
+ "web-vitals": "^2.1.4",
+ "yup": "^1.7.0"
+ },
"scripts": {
- "start": "node index.js"
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject"
},
- "repository": {
- "type": "git",
- "url": "git+https://github.com/johnpapa/node-hello.git"
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
},
- "keywords": [],
- "author": "",
- "license": "ISC",
- "bugs": {
- "url": "https://github.com/johnpapa/node-hello/issues"
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
},
- "homepage": "https://github.com/johnpapa/node-hello#readme"
+ "devDependencies": {
+ "@types/recharts": "^1.8.29",
+ "typescript": "^4.9.5"
+ }
}
diff --git a/public/executive_team_json_payloads.json b/public/executive_team_json_payloads.json
new file mode 100644
index 000000000..91d456c4a
--- /dev/null
+++ b/public/executive_team_json_payloads.json
@@ -0,0 +1,643 @@
+{
+ "executive_dashboard": {
+ "user_context": {
+ "user_id": "USR-exec-001",
+ "tenant_id": "tenant_001",
+ "portfolio_scope": "ALL_PROPERTIES",
+ "timestamp": "2025-08-11T07:00:00Z",
+ "timezone": "America/New_York",
+ "reporting_period": "2025-08"
+ },
+
+ "panel_1_financial_health": {
+ "panel_id": "portfolio_financial_health_risk",
+ "title": "Portfolio Financial Health & Risk Exposure",
+ "priority": 1,
+ "refresh_interval_seconds": 3600,
+ "data": {
+ "real_time_performance": {
+ "portfolio_noi": {
+ "current": 2400000.00,
+ "budget": 2210000.00,
+ "variance_pct": 8.6,
+ "trend": "POSITIVE"
+ },
+ "total_revenue": {
+ "current": 3800000.00,
+ "budget": 3650000.00,
+ "variance_pct": 4.1,
+ "trend": "POSITIVE"
+ },
+ "operating_expenses": {
+ "current": 1400000.00,
+ "budget": 1440000.00,
+ "variance_pct": -2.8,
+ "trend": "FAVORABLE"
+ },
+ "roi_percentage": 12.3,
+ "occupancy_rate_pct": 94.2,
+ "occupancy_change_pct": 2.1,
+ "rent_per_sqft": 45.50,
+ "market_performance_vs_benchmark_pct": 8.5
+ },
+ "budget_variance_analysis": {
+ "maintenance_costs": {
+ "actual": 485000.00,
+ "budget": 430000.00,
+ "variance_pct": 12.8,
+ "variance_amount": 55000.00,
+ "status": "OVER_BUDGET",
+ "explanation": "Emergency HVAC repairs and elevator maintenance"
+ },
+ "energy_costs": {
+ "actual": 325000.00,
+ "budget": 298000.00,
+ "variance_pct": 9.1,
+ "variance_amount": 27000.00,
+ "status": "OVER_BUDGET",
+ "explanation": "Aging HVAC systems reducing efficiency"
+ },
+ "capex_utilization": {
+ "utilized": 1650000.00,
+ "allocated": 2540000.00,
+ "utilization_pct": 65.0,
+ "remaining": 890000.00,
+ "status": "UNDER_UTILIZED"
+ },
+ "vendor_costs": {
+ "actual": 275000.00,
+ "budget": 260000.00,
+ "variance_pct": 5.8,
+ "variance_amount": 15000.00,
+ "status": "SLIGHTLY_OVER"
+ }
+ },
+ "risk_assessment": {
+ "portfolio_risk_score": 25,
+ "risk_category": "LOW",
+ "compliance_percentage": 94.0,
+ "active_violations": 0,
+ "audit_findings": 2,
+ "insurance_claims_ytd": 1,
+ "debt_service_coverage_ratio": 1.8,
+ "loan_to_value_ratio": 65.5,
+ "tenant_payment_risk_score": 15,
+ "market_risk_score": 35,
+ "lease_expiry_risk_12m_pct": 23.5
+ },
+ "investment_performance": {
+ "portfolio_value": 47200000.00,
+ "value_appreciation_ytd_pct": 3.8,
+ "cash_flow_monthly": 420000.00,
+ "cash_reserves": 2100000.00,
+ "days_cash_on_hand": 180,
+ "liquidity_ratio": 2.5,
+ "market_position": "TOP_QUARTILE",
+ "esg_score": 78,
+ "sustainability_investment": 245000.00
+ },
+ "ai_financial_insights": {
+ "cost_optimization_opportunities": [
+ {
+ "opportunity": "LED lighting upgrade across portfolio",
+ "estimated_savings_annual": 156000.00,
+ "implementation_cost": 320000.00,
+ "roi_years": 2.1,
+ "confidence": 92
+ },
+ {
+ "opportunity": "HVAC system modernization",
+ "estimated_savings_annual": 285000.00,
+ "implementation_cost": 850000.00,
+ "roi_years": 3.0,
+ "confidence": 88
+ }
+ ],
+ "risk_predictions": [
+ {
+ "risk_type": "Maintenance Cost Escalation",
+ "probability_pct": 67,
+ "impact_estimate": 125000.00,
+ "mitigation": "Accelerate preventive maintenance program"
+ },
+ {
+ "risk_type": "Energy Cost Volatility",
+ "probability_pct": 45,
+ "impact_estimate": 85000.00,
+ "mitigation": "Lock in energy contracts, improve efficiency"
+ }
+ ],
+ "market_opportunities": [
+ {
+ "opportunity": "Rent optimization in Building A",
+ "revenue_potential_annual": 180000.00,
+ "market_rent_gap_pct": -5.2,
+ "implementation_timeline": "Q4 2025"
+ }
+ ]
+ }
+ }
+ },
+
+ "panel_2_strategic_kpis": {
+ "panel_id": "strategic_kpi_performance_market",
+ "title": "Strategic KPI Performance & Market Position",
+ "priority": 2,
+ "refresh_interval_seconds": 86400,
+ "data": {
+ "operational_efficiency": {
+ "portfolio_wide_metrics": {
+ "operating_expense_ratio": 36.8,
+ "industry_benchmark": 42.1,
+ "performance_vs_benchmark_pct": -12.6,
+ "rank_vs_peers": "TOP_DECILE"
+ },
+ "maintenance_efficiency": {
+ "cost_per_sqft": 4.85,
+ "industry_average": 5.40,
+ "efficiency_score": 90,
+ "preventive_vs_reactive_ratio": 3.2
+ },
+ "energy_performance": {
+ "energy_cost_per_sqft": 3.25,
+ "portfolio_energy_efficiency": 87.5,
+ "carbon_footprint_reduction_ytd_pct": 8.3,
+ "energy_star_score_avg": 82
+ }
+ },
+ "tenant_satisfaction": {
+ "overall_satisfaction_score": 4.2,
+ "response_rate_pct": 78.5,
+ "net_promoter_score": 45,
+ "retention_rate_pct": 89.2,
+ "satisfaction_trend": "IMPROVING",
+ "key_satisfaction_drivers": [
+ {"factor": "Maintenance Response Time", "score": 4.5},
+ {"factor": "Building Cleanliness", "score": 4.3},
+ {"factor": "HVAC Comfort", "score": 3.8},
+ {"factor": "Security", "score": 4.4}
+ ]
+ },
+ "market_positioning": {
+ "market_share_pct": 12.8,
+ "competitive_ranking": 3,
+ "rent_premium_vs_market_pct": 8.5,
+ "occupancy_vs_market": {
+ "portfolio_occupancy": 94.2,
+ "market_average": 87.6,
+ "advantage_pct": 6.6
+ },
+ "asset_quality_score": 88,
+ "brand_recognition_score": 72
+ },
+ "growth_metrics": {
+ "revenue_growth_ytd_pct": 6.8,
+ "noi_growth_ytd_pct": 8.6,
+ "same_store_growth_pct": 4.2,
+ "new_lease_spreads_pct": 12.3,
+ "renewal_spreads_pct": 4.7,
+ "development_pipeline_value": 15600000.00
+ },
+ "board_presentation_metrics": {
+ "key_highlights": [
+ "Portfolio NOI exceeds budget by 8.6% YTD",
+ "Occupancy rate 6.6% above market average",
+ "Operating efficiency in top decile vs peers",
+ "Tenant satisfaction improved to 4.2/5.0"
+ ],
+ "areas_of_focus": [
+ "HVAC system modernization program",
+ "Lease renewal negotiations for 2026",
+ "ESG initiative expansion",
+ "Market expansion opportunities"
+ ],
+ "upcoming_milestones": [
+ {"milestone": "Q3 Board Meeting", "date": "2025-09-15"},
+ {"milestone": "Annual Investor Conference", "date": "2025-10-22"},
+ {"milestone": "Sustainability Report Publication", "date": "2025-11-01"}
+ ]
+ }
+ }
+ },
+
+ "panel_3_ai_insights": {
+ "panel_id": "ai_strategic_insights_predictive",
+ "title": "AI-Driven Strategic Insights & Predictive Analytics",
+ "priority": 3,
+ "refresh_interval_seconds": 86400,
+ "data": {
+ "investment_optimization": {
+ "portfolio_optimization_score": 84,
+ "ai_recommendations": [
+ {
+ "recommendation_id": "AI-REC-001",
+ "type": "CAPITAL_ALLOCATION",
+ "title": "Accelerate HVAC Modernization Program",
+ "description": "AI analysis indicates 40% of portfolio HVAC systems underperforming, causing 15% energy inefficiency",
+ "financial_impact": {
+ "investment_required": 2400000.00,
+ "annual_savings": 780000.00,
+ "payback_period_years": 3.1,
+ "npv_10_year": 3250000.00,
+ "irr_pct": 28.5
+ },
+ "urgency": "HIGH",
+ "confidence_score": 91,
+ "implementation_timeline": "Q4 2025 - Q2 2026"
+ },
+ {
+ "recommendation_id": "AI-REC-002",
+ "type": "ASSET_OPTIMIZATION",
+ "title": "Building A Repositioning Strategy",
+ "description": "Market analysis suggests 25% rent premium achievable with amenity upgrades",
+ "financial_impact": {
+ "investment_required": 1200000.00,
+ "annual_revenue_increase": 450000.00,
+ "payback_period_years": 2.7,
+ "asset_value_increase": 3600000.00
+ },
+ "urgency": "MEDIUM",
+ "confidence_score": 87,
+ "implementation_timeline": "Q1 2026"
+ }
+ ],
+ "predictive_maintenance_roi": {
+ "total_savings_potential": 1250000.00,
+ "prevention_vs_reactive_cost_ratio": 4.2,
+ "assets_flagged_for_intervention": 12,
+ "emergency_repairs_prevented_ytd": 8
+ }
+ },
+ "market_intelligence": {
+ "market_trend_analysis": {
+ "rental_rate_forecast_12m_pct": 4.2,
+ "occupancy_forecast_12m_pct": 91.5,
+ "cap_rate_trend": "COMPRESSING",
+ "new_supply_impact": "MODERATE",
+ "demand_drivers": ["Tech sector growth", "Return-to-office trend", "Infrastructure investment"]
+ },
+ "competitive_intelligence": {
+ "new_competitors": 2,
+ "market_share_at_risk_pct": 3.2,
+ "competitive_advantages": [
+ "Superior maintenance response times",
+ "Energy efficiency leadership",
+ "Prime location portfolio"
+ ],
+ "threats": [
+ "New Class A development nearby",
+ "Aggressive competitor pricing"
+ ]
+ },
+ "expansion_opportunities": [
+ {
+ "market": "Austin Tech Corridor",
+ "opportunity_score": 88,
+ "investment_required": 25000000.00,
+ "projected_roi_pct": 16.8,
+ "risk_level": "MEDIUM"
+ },
+ {
+ "market": "Miami Financial District",
+ "opportunity_score": 76,
+ "investment_required": 18500000.00,
+ "projected_roi_pct": 14.2,
+ "risk_level": "MEDIUM_HIGH"
+ }
+ ]
+ },
+ "risk_analytics": {
+ "portfolio_risk_modeling": {
+ "value_at_risk_95_confidence": 2850000.00,
+ "stress_test_scenarios": [
+ {
+ "scenario": "Economic Recession",
+ "probability_pct": 25,
+ "noi_impact_pct": -15.2,
+ "occupancy_impact_pct": -8.5,
+ "mitigation_strategies": ["Flexible lease terms", "Cost reduction program"]
+ },
+ {
+ "scenario": "Interest Rate Shock",
+ "probability_pct": 35,
+ "noi_impact_pct": -5.8,
+ "refinancing_impact": 285000.00,
+ "mitigation_strategies": ["Fixed rate conversions", "Debt reduction"]
+ },
+ {
+ "scenario": "Major Tenant Default",
+ "probability_pct": 15,
+ "noi_impact_pct": -12.3,
+ "revenue_at_risk": 1250000.00,
+ "mitigation_strategies": ["Tenant diversification", "Credit enhancement"]
+ }
+ ]
+ },
+ "early_warning_indicators": [
+ {
+ "indicator": "Tenant Payment Delays",
+ "current_value": 2.3,
+ "threshold": 5.0,
+ "status": "NORMAL",
+ "trend": "STABLE"
+ },
+ {
+ "indicator": "Market Rent Divergence",
+ "current_value": -5.2,
+ "threshold": -10.0,
+ "status": "MONITOR",
+ "trend": "IMPROVING"
+ },
+ {
+ "indicator": "Maintenance Cost Escalation",
+ "current_value": 12.8,
+ "threshold": 15.0,
+ "status": "CAUTION",
+ "trend": "INCREASING"
+ }
+ ]
+ },
+ "ai_executive_summary": {
+ "daily_executive_brief": "Portfolio performing 8.6% above NOI budget. AI recommends accelerating HVAC modernization for $780K annual savings. Market conditions favorable for rent optimization in Q4. Monitor maintenance cost escalation approaching threshold.",
+ "strategic_priorities": [
+ {
+ "priority": "Implement AI-recommended HVAC modernization",
+ "timeline": "Q4 2025",
+ "expected_impact": "$780K annual savings",
+ "confidence": 91
+ },
+ {
+ "priority": "Execute Building A repositioning strategy",
+ "timeline": "Q1 2026",
+ "expected_impact": "$450K annual revenue increase",
+ "confidence": 87
+ },
+ {
+ "priority": "Evaluate Austin market expansion",
+ "timeline": "Q2 2026",
+ "expected_impact": "16.8% projected ROI",
+ "confidence": 78
+ }
+ ],
+ "board_talking_points": [
+ "Portfolio NOI growth 8.6% demonstrates operational excellence",
+ "AI-driven maintenance preventing $1.25M in emergency repairs",
+ "Top quartile performance vs peer group validates strategy",
+ "Market expansion opportunities identified in Austin and Miami"
+ ]
+ }
+ }
+ },
+
+ "panel_4_operational_efficiency": {
+ "panel_id": "operational_efficiency_resource_optimization",
+ "title": "Operational Efficiency & Resource Optimization",
+ "priority": 4,
+ "refresh_interval_seconds": 86400,
+ "data": {
+ "portfolio_performance": {
+ "total_properties": 8,
+ "total_sqft": 2850000,
+ "total_assets_managed": 342,
+ "properties_at_target_performance": 6,
+ "properties_underperforming": 2,
+ "avg_asset_health_score": 84.2,
+ "operational_efficiency_score": 91
+ },
+ "resource_utilization": {
+ "maintenance_team_efficiency": {
+ "total_technicians": 24,
+ "avg_utilization_pct": 78.5,
+ "productivity_score": 87,
+ "response_time_avg_hours": 2.3,
+ "sla_compliance_pct": 94.5
+ },
+ "vendor_performance": {
+ "total_vendors": 15,
+ "avg_performance_rating": 4.2,
+ "cost_efficiency_score": 82,
+ "vendors_exceeding_expectations": 9,
+ "vendors_requiring_improvement": 2,
+ "contract_renewals_upcoming": 4
+ },
+ "technology_adoption": {
+ "iot_sensors_deployed": 847,
+ "automation_coverage_pct": 68,
+ "ai_utilization_score": 76,
+ "digital_transformation_progress": 83
+ }
+ },
+ "cost_optimization": {
+ "energy_management": {
+ "total_energy_consumption_kwh": 2450000,
+ "energy_efficiency_improvement_ytd_pct": 8.3,
+ "energy_cost_reduction_ytd": 125000.00,
+ "renewable_energy_pct": 35,
+ "carbon_footprint_reduction_pct": 12.1
+ },
+ "maintenance_optimization": {
+ "preventive_maintenance_ratio": 76,
+ "emergency_repairs_reduction_pct": 23,
+ "maintenance_cost_per_sqft": 4.85,
+ "asset_uptime_pct": 98.2,
+ "warranty_claim_recovery": 45000.00
+ },
+ "procurement_efficiency": {
+ "vendor_consolidation_savings": 85000.00,
+ "bulk_purchasing_savings": 32000.00,
+ "contract_optimization_savings": 67000.00,
+ "total_procurement_savings_ytd": 184000.00
+ }
+ },
+ "performance_benchmarking": {
+ "industry_comparisons": {
+ "operating_expense_ratio": {
+ "portfolio": 36.8,
+ "industry_median": 42.1,
+ "percentile_rank": 85
+ },
+ "tenant_satisfaction": {
+ "portfolio": 4.2,
+ "industry_median": 3.7,
+ "percentile_rank": 78
+ },
+ "energy_efficiency": {
+ "portfolio": 87.5,
+ "industry_median": 79.2,
+ "percentile_rank": 82
+ },
+ "maintenance_response_time": {
+ "portfolio_hours": 2.3,
+ "industry_median_hours": 4.1,
+ "percentile_rank": 89
+ }
+ },
+ "improvement_opportunities": [
+ {
+ "area": "Digital Automation",
+ "current_score": 68,
+ "target_score": 85,
+ "investment_required": 450000.00,
+ "annual_savings_potential": 180000.00
+ },
+ {
+ "area": "Predictive Analytics",
+ "current_score": 76,
+ "target_score": 90,
+ "investment_required": 320000.00,
+ "annual_savings_potential": 240000.00
+ }
+ ]
+ },
+ "operational_insights": {
+ "efficiency_trends": [
+ "Maintenance team productivity increased 12% YTD",
+ "Emergency repairs down 23% due to predictive maintenance",
+ "Energy efficiency improvements saving $125K annually",
+ "Vendor performance ratings improved across 80% of contracts"
+ ],
+ "optimization_recommendations": [
+ {
+ "recommendation": "Expand IoT sensor deployment to remaining 15% of assets",
+ "impact": "Improve predictive maintenance coverage",
+ "investment": 125000.00,
+ "timeline": "Q4 2025"
+ },
+ {
+ "recommendation": "Implement automated work order routing",
+ "impact": "Reduce response times by 30%",
+ "investment": 85000.00,
+ "timeline": "Q1 2026"
+ }
+ ]
+ }
+ }
+ },
+
+ "panel_5_sustainability_esg": {
+ "panel_id": "sustainability_esg_performance",
+ "title": "Sustainability & ESG Performance Metrics",
+ "priority": 5,
+ "refresh_interval_seconds": 86400,
+ "data": {
+ "environmental_performance": {
+ "energy_metrics": {
+ "total_energy_consumption_kwh": 2450000,
+ "renewable_energy_pct": 35,
+ "energy_intensity_kwh_per_sqft": 86.0,
+ "energy_reduction_ytd_pct": 8.3,
+ "carbon_emissions_metric_tons": 1245,
+ "carbon_intensity_reduction_pct": 12.1,
+ "energy_star_portfolio_score": 82
+ },
+ "water_management": {
+ "total_water_consumption_gallons": 8450000,
+ "water_efficiency_improvement_pct": 6.2,
+ "water_recycling_pct": 15,
+ "leak_detection_saves_gallons": 125000
+ },
+ "waste_management": {
+ "waste_diversion_rate_pct": 78,
+ "recycling_rate_pct": 65,
+ "composting_rate_pct": 13,
+ "landfill_waste_reduction_pct": 22
+ }
+ },
+ "social_responsibility": {
+ "tenant_wellness": {
+ "air_quality_score": 89,
+ "wellness_amenities_count": 12,
+ "tenant_health_program_participation_pct": 43,
+ "wellness_certification_properties": 3
+ },
+ "community_impact": {
+ "local_vendor_pct": 68,
+ "community_investment_annual": 125000.00,
+ "volunteer_hours_ytd": 450,
+ "educational_partnerships": 4
+ },
+ "diversity_inclusion": {
+ "minority_vendor_pct": 32,
+ "women_owned_vendor_pct": 28,
+ "diversity_training_completion_pct": 95,
+ "supplier_diversity_score": 78
+ }
+ },
+ "governance_compliance": {
+ "board_governance": {
+ "board_diversity_pct": 40,
+ "independent_directors_pct": 75,
+ "esg_expertise_on_board": true,
+ "annual_esg_reporting": true
+ },
+ "compliance_metrics": {
+ "regulatory_compliance_score": 96,
+ "audit_findings_resolved_pct": 100,
+ "certifications_maintained": 8,
+ "training_compliance_pct": 98
+ },
+ "transparency": {
+ "sustainability_report_published": true,
+ "esg_data_third_party_verified": true,
+ "stakeholder_engagement_sessions": 6,
+ "public_commitments_on_track": 4
+ }
+ },
+ "esg_ratings_benchmarks": {
+ "external_ratings": {
+ "gresb_score": 78,
+ "gresb_peer_ranking": "GREEN_STAR",
+ "msci_esg_rating": "A",
+ "sustainalytics_score": 22.1,
+ "esg_risk_rating": "MEDIUM"
+ },
+ "certification_status": {
+ "leed_certified_properties": 5,
+ "energy_star_certified_properties": 6,
+ "boma_best_certified_properties": 3,
+ "well_certified_properties": 2
+ },
+ "investor_alignment": {
+ "esg_focused_investors_pct": 45,
+ "green_financing_utilized": 850000.00,
+ "sustainability_linked_loans": 2,
+ "green_bond_eligible_assets_pct": 60
+ }
+ },
+ "sustainability_roadmap": {
+ "net_zero_target": "2035",
+ "carbon_reduction_target_2030_pct": 50,
+ "renewable_energy_target_2028_pct": 75,
+ "key_initiatives": [
+ {
+ "initiative": "Solar Panel Installation Program",
+ "timeline": "2025-2027",
+ "investment": 2400000.00,
+ "carbon_reduction_tons": 890,
+ "roi_years": 8.5
+ },
+ {
+ "initiative": "Building Automation Upgrade",
+ "timeline": "2025-2026",
+ "investment": 1200000.00,
+ "energy_savings_pct": 15,
+ "roi_years": 6.2
+ },
+ {
+ "initiative": "Electric Vehicle Infrastructure",
+ "timeline": "2026",
+ "investment": 450000.00,
+ "tenant_satisfaction_impact": "HIGH"
+ }
+ ],
+ "progress_tracking": {
+ "initiatives_on_track": 8,
+ "initiatives_ahead_of_schedule": 2,
+ "initiatives_requiring_attention": 1,
+ "overall_progress_pct": 78
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 000000000..969767068
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/favicon1.ico b/public/favicon1.ico
new file mode 100644
index 000000000..969767068
Binary files /dev/null and b/public/favicon1.ico differ
diff --git a/public/images/alert.svg b/public/images/alert.svg
new file mode 100644
index 000000000..600c402f8
--- /dev/null
+++ b/public/images/alert.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/images/asset.svg b/public/images/asset.svg
new file mode 100644
index 000000000..733955f5b
--- /dev/null
+++ b/public/images/asset.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/images/bot.svg b/public/images/bot.svg
new file mode 100644
index 000000000..e8c749dd8
--- /dev/null
+++ b/public/images/bot.svg
@@ -0,0 +1,15 @@
+
+
diff --git a/public/images/chat-bot-1.svg b/public/images/chat-bot-1.svg
new file mode 100644
index 000000000..50d870a8f
--- /dev/null
+++ b/public/images/chat-bot-1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/images/chat-bot.svg b/public/images/chat-bot.svg
new file mode 100644
index 000000000..5e5e20806
--- /dev/null
+++ b/public/images/chat-bot.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/images/emptyData.svg b/public/images/emptyData.svg
new file mode 100644
index 000000000..c8ecd9ff2
--- /dev/null
+++ b/public/images/emptyData.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/images/error.svg b/public/images/error.svg
new file mode 100644
index 000000000..600c402f8
--- /dev/null
+++ b/public/images/error.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/images/floor.svg b/public/images/floor.svg
new file mode 100644
index 000000000..4e1b9a59a
--- /dev/null
+++ b/public/images/floor.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/public/images/gear.svg b/public/images/gear.svg
new file mode 100644
index 000000000..6fd849af3
--- /dev/null
+++ b/public/images/gear.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/images/google.svg b/public/images/google.svg
new file mode 100644
index 000000000..c5307b0a5
--- /dev/null
+++ b/public/images/google.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/images/location.svg b/public/images/location.svg
new file mode 100644
index 000000000..fe890130c
--- /dev/null
+++ b/public/images/location.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/images/logo-white.png b/public/images/logo-white.png
new file mode 100644
index 000000000..f6bb4bdf0
Binary files /dev/null and b/public/images/logo-white.png differ
diff --git a/public/images/robot.svg b/public/images/robot.svg
new file mode 100644
index 000000000..d8937058b
--- /dev/null
+++ b/public/images/robot.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/images/room.svg b/public/images/room.svg
new file mode 100644
index 000000000..83efddc4b
--- /dev/null
+++ b/public/images/room.svg
@@ -0,0 +1,26 @@
+
+
+
diff --git a/public/images/trend.svg b/public/images/trend.svg
new file mode 100644
index 000000000..67c6f9913
--- /dev/null
+++ b/public/images/trend.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/images/user.svg b/public/images/user.svg
new file mode 100644
index 000000000..c46ff8b46
--- /dev/null
+++ b/public/images/user.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/public/images/whitelogo.svg b/public/images/whitelogo.svg
new file mode 100644
index 000000000..0a2f25fb5
--- /dev/null
+++ b/public/images/whitelogo.svg
@@ -0,0 +1,31 @@
+
diff --git a/public/images/zone.svg b/public/images/zone.svg
new file mode 100644
index 000000000..4ede24365
--- /dev/null
+++ b/public/images/zone.svg
@@ -0,0 +1,2 @@
+
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 000000000..c3dd7c707
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Critical Asset
+
+
+
+
+
+
+
diff --git a/public/manifest.json b/public/manifest.json
new file mode 100644
index 000000000..080d6c77a
--- /dev/null
+++ b/public/manifest.json
@@ -0,0 +1,25 @@
+{
+ "short_name": "React App",
+ "name": "Create React App Sample",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "64x64 32x32 24x24 16x16",
+ "type": "image/x-icon"
+ },
+ {
+ "src": "logo192.png",
+ "type": "image/png",
+ "sizes": "192x192"
+ },
+ {
+ "src": "logo512.png",
+ "type": "image/png",
+ "sizes": "512x512"
+ }
+ ],
+ "start_url": ".",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
diff --git a/public/property_manager_json_payloads.json b/public/property_manager_json_payloads.json
new file mode 100644
index 000000000..8aebf1db1
--- /dev/null
+++ b/public/property_manager_json_payloads.json
@@ -0,0 +1,366 @@
+{
+ "property_manager_dashboard": {
+ "user_context": {
+ "user_id": "USR-pm-001",
+ "tenant_id": "tenant_001",
+ "property_ids": ["PROP-DOWNTOWN-01", "PROP-RIVERSIDE-02"],
+ "timestamp": "2025-08-11T07:00:00Z",
+ "timezone": "America/New_York"
+ },
+
+ "panel_1_critical_alerts": {
+ "panel_id": "critical_alerts_emergency_status",
+ "title": "Critical Alerts & Emergency Status",
+ "priority": 1,
+ "refresh_interval_seconds": 30,
+ "data": {
+ "summary": {
+ "total_active_alerts": 3,
+ "emergency_count": 1,
+ "critical_count": 1,
+ "security_count": 1,
+ "system_failure_count": 0
+ },
+ "alerts": [
+ {
+ "alert_id": "ALT-2025-08-11-001",
+ "property_id": "PROP-DOWNTOWN-01",
+ "property_name": "Downtown Office Complex",
+ "timestamp": "2025-08-11T06:45:23Z",
+ "alert_type": "EMERGENCY",
+ "severity_level": 1,
+ "title": "HVAC System Complete Failure - Building A",
+ "description": "Main HVAC unit offline, temperature rising rapidly, 200+ tenants affected",
+ "affected_areas": ["Floor 1-5", "Conference Rooms", "Main Lobby"],
+ "estimated_impact": "High - 200 tenants affected, potential revenue loss $15K/day",
+ "status": "IN_PROGRESS",
+ "assigned_to": "John Smith",
+ "assigned_to_phone": "+1-555-1001",
+ "eta_resolution": "2025-08-11T10:00:00Z",
+ "escalation_level": 2,
+ "action_required": "Emergency HVAC vendor contacted, backup systems activated, tenant notifications sent",
+ "data_source": "IoT_SENSORS",
+ "contact_info": {
+ "vendor": "HVAC Solutions Inc",
+ "phone": "+1-555-0123",
+ "technician": "Bob Wilson",
+ "eta": "09:30"
+ },
+ "time_since_alert": "75 minutes",
+ "urgency_indicator": "IMMEDIATE"
+ },
+ {
+ "alert_id": "ALT-2025-08-11-002",
+ "property_id": "PROP-DOWNTOWN-01",
+ "timestamp": "2025-08-11T07:15:45Z",
+ "alert_type": "SECURITY",
+ "severity_level": 3,
+ "title": "Security Camera Offline - Floor 3",
+ "description": "Main security camera in lobby area offline for 45 minutes",
+ "affected_areas": ["Floor 3 Main Lobby"],
+ "estimated_impact": "Medium - Security monitoring gap, no immediate tenant impact",
+ "status": "ACKNOWLEDGED",
+ "assigned_to": "Security Team",
+ "eta_resolution": "2025-08-11T14:00:00Z",
+ "escalation_level": 1,
+ "action_required": "Security vendor notified, temporary coverage arranged",
+ "data_source": "SECURITY_SYSTEM",
+ "contact_info": {
+ "vendor": "SecureWatch Pro",
+ "phone": "+1-555-0789"
+ },
+ "time_since_alert": "45 minutes",
+ "urgency_indicator": "MONITOR"
+ }
+ ],
+ "emergency_readiness": {
+ "backup_systems_status": "ONLINE",
+ "emergency_contacts_available": true,
+ "vendor_response_confirmed": "2_OF_3_CONFIRMED",
+ "evacuation_procedures_ready": true
+ }
+ }
+ },
+
+ "panel_2_work_orders": {
+ "panel_id": "work_order_pipeline_resources",
+ "title": "Today's Work Order Pipeline & Resource Allocation",
+ "priority": 2,
+ "refresh_interval_seconds": 300,
+ "data": {
+ "summary": {
+ "total_work_orders_today": 8,
+ "overdue_count": 1,
+ "in_progress_count": 3,
+ "scheduled_count": 4,
+ "technician_utilization_pct": 75,
+ "budget_utilized_today": 3200.00,
+ "budget_planned_today": 3850.00
+ },
+ "work_orders": [
+ {
+ "work_order_id": "WO-2025-08-11-0001",
+ "property_id": "PROP-DOWNTOWN-01",
+ "title": "Emergency Elevator Repair - Building A Main",
+ "priority": "EMERGENCY",
+ "category": "EMERGENCY",
+ "status": "OVERDUE",
+ "scheduled_date": "2025-08-10",
+ "scheduled_time": "14:00:00",
+ "estimated_duration": 240,
+ "assigned_technician": "Mike Chen (TECH-001)",
+ "technician_skills": ["ELEVATORS", "Mechanical", "Safety_Certified"],
+ "technician_status": "AVAILABLE",
+ "team_size": 2,
+ "location_detail": "Building A, Main Elevator Bank, Unit 1",
+ "parts_required": [
+ {"part": "Elevator Motor Controller", "qty": 1, "status": "IN_STOCK"},
+ {"part": "Safety Cable", "qty": 2, "status": "ORDERED"}
+ ],
+ "tools_required": ["Specialized Elevator Tools", "Safety Harness", "Multimeter"],
+ "vendor_involved": "Elevator Services Pro",
+ "vendor_eta": "2025-08-11T09:30:00Z",
+ "tenant_impact": "HIGH",
+ "access_requirements": "Building manager escort required, tenant notifications sent",
+ "completion_percentage": 0,
+ "notes": "Waiting for specialized parts delivery, vendor confirmed 9:30 AM arrival",
+ "due_date": "2025-08-10T18:00:00Z",
+ "cost_estimate": 1200.00,
+ "actual_cost": 0.00,
+ "sla_status": "BREACHED",
+ "overdue_hours": 15
+ },
+ {
+ "work_order_id": "WO-2025-08-11-0002",
+ "property_id": "PROP-DOWNTOWN-01",
+ "title": "HVAC Filter Replacement - Floor 3",
+ "priority": "MEDIUM",
+ "category": "PREVENTIVE",
+ "status": "IN_PROGRESS",
+ "scheduled_date": "2025-08-11",
+ "scheduled_time": "09:00:00",
+ "estimated_duration": 120,
+ "assigned_technician": "John Smith (TECH-002), Sarah Lee (TECH-003)",
+ "technician_skills": ["HVAC", "Preventive_Maintenance"],
+ "team_size": 2,
+ "location_detail": "Floor 3, Room 315, Main HVAC Unit",
+ "parts_required": [
+ {"part": "HVAC Filter - High Efficiency", "qty": 4, "status": "IN_STOCK"}
+ ],
+ "tools_required": ["Screwdriver Set", "Cleaning Supplies"],
+ "vendor_involved": null,
+ "tenant_impact": "MINIMAL",
+ "access_requirements": "Standard access, brief noise during installation",
+ "completion_percentage": 75,
+ "notes": "75% complete, final filter installation in progress",
+ "due_date": "2025-08-11T11:00:00Z",
+ "cost_estimate": 450.00,
+ "actual_cost": 425.00,
+ "eta_completion": "2025-08-11T10:45:00Z"
+ },
+ {
+ "work_order_id": "WO-2025-08-11-0003",
+ "property_id": "PROP-DOWNTOWN-01",
+ "title": "Plumbing Inspection - Floors 1-2",
+ "priority": "LOW",
+ "category": "INSPECTION",
+ "status": "PENDING_PARTS",
+ "scheduled_date": "2025-08-11",
+ "scheduled_time": "14:00:00",
+ "estimated_duration": 180,
+ "assigned_technician": null,
+ "technician_skills": ["Plumbing", "Inspection_Certified"],
+ "team_size": 1,
+ "location_detail": "Floors 1-2, All Restroom Facilities",
+ "parts_required": [],
+ "tools_required": ["Inspection Camera", "Pressure Gauge"],
+ "vendor_involved": null,
+ "tenant_impact": "NONE",
+ "access_requirements": "Coordinate with tenants for restroom access",
+ "completion_percentage": 0,
+ "notes": "CONFLICT: All qualified technicians assigned to higher priority work",
+ "due_date": "2025-08-11T17:00:00Z",
+ "cost_estimate": 200.00,
+ "actual_cost": 0.00,
+ "conflict_reason": "NO_TECHNICIAN_AVAILABLE",
+ "suggested_reschedule": "2025-08-12T09:00:00Z"
+ }
+ ],
+ "resource_utilization": {
+ "technicians": {
+ "total_available": 8,
+ "currently_assigned": 6,
+ "utilization_percentage": 75,
+ "on_break": 1,
+ "off_duty": 1
+ },
+ "parts_inventory": {
+ "total_items_tracked": 150,
+ "items_in_stock": 142,
+ "critical_low_items": 2,
+ "items_on_order": 6
+ },
+ "vendor_coordination": {
+ "appointments_scheduled": 3,
+ "vendors_confirmed": 2,
+ "vendors_delayed": 1,
+ "emergency_vendors_on_call": 5
+ },
+ "budget_tracking": {
+ "daily_budget_planned": 3850.00,
+ "actual_spent": 2425.00,
+ "remaining_budget": 1425.00,
+ "utilization_percentage": 62.98
+ }
+ }
+ }
+ },
+
+ "panel_3_asset_performance": {
+ "panel_id": "asset_performance_predictive_maintenance",
+ "title": "Asset Performance & Predictive Maintenance Intelligence",
+ "priority": 3,
+ "refresh_interval_seconds": 3600,
+ "data": {
+ "summary": {
+ "total_assets_monitored": 47,
+ "healthy_assets": 38,
+ "warning_assets": 6,
+ "critical_assets": 3,
+ "avg_portfolio_health": 84.2,
+ "ai_predictions_generated": 12,
+ "potential_cost_savings": 23400.00,
+ "energy_efficiency_avg": 87.5
+ },
+ "assets": [
+ {
+ "asset_id": "AST-HVAC-B1-001",
+ "asset_name": "Main HVAC Unit - Building A",
+ "property_id": "PROP-DOWNTOWN-01",
+ "asset_type": "HVAC",
+ "location_detail": "Building A, Roof Level, Unit 1",
+ "current_status": "CRITICAL",
+ "health_score": 45,
+ "performance_trend": "DECLINING",
+ "efficiency_rating": 67.5,
+ "baseline_efficiency": 92.0,
+ "efficiency_variance": -24.5,
+ "energy_consumption_current": 285.7,
+ "energy_consumption_baseline": 220.3,
+ "energy_variance_pct": 29.7,
+ "temperature_reading": 78.5,
+ "pressure_reading": 12.8,
+ "vibration_level": 3.2,
+ "runtime_hours_today": 20.5,
+ "last_maintenance_date": "2025-07-15",
+ "next_scheduled_maintenance": "2025-09-15",
+ "predicted_failure_date": "2025-08-25",
+ "failure_probability": 78.5,
+ "predicted_failure_type": "Motor Bearing Wear",
+ "maintenance_priority": "IMMEDIATE",
+ "cost_impact_prediction": 8500.00,
+ "preventive_cost_estimate": 1200.00,
+ "roi_preventive_action": 7.08,
+ "anomaly_detected": true,
+ "anomaly_description": "Temperature 15°F above normal, vibration levels elevated for 3 consecutive days",
+ "anomaly_severity": "CRITICAL",
+ "recommended_action": "Schedule emergency motor bearing replacement within 48 hours",
+ "action_urgency": "IMMEDIATE",
+ "vendor_recommendation": "HVAC Solutions Inc",
+ "parts_required_prediction": [
+ {"part": "Motor Bearing Assembly", "confidence": 90},
+ {"part": "Cooling Fan", "confidence": 65}
+ ],
+ "ai_confidence_score": 92,
+ "warranty_status": "ACTIVE",
+ "compliance_status": "COMPLIANT",
+ "days_until_failure": 14
+ },
+ {
+ "asset_id": "AST-ELEV-001",
+ "asset_name": "Main Elevator - Building A",
+ "property_id": "PROP-DOWNTOWN-01",
+ "asset_type": "ELEVATORS",
+ "location_detail": "Building A, Main Elevator Bank",
+ "current_status": "WARNING",
+ "health_score": 72,
+ "performance_trend": "DECLINING",
+ "efficiency_rating": 89.0,
+ "baseline_efficiency": 95.0,
+ "energy_consumption_current": 145.2,
+ "energy_consumption_baseline": 130.0,
+ "energy_variance_pct": 11.7,
+ "runtime_hours_today": 16.0,
+ "last_maintenance_date": "2025-06-01",
+ "next_scheduled_maintenance": "2025-09-01",
+ "predicted_failure_date": "2025-11-15",
+ "failure_probability": 35.2,
+ "predicted_failure_type": "Cable Tension System",
+ "maintenance_priority": "MEDIUM",
+ "cost_impact_prediction": 12000.00,
+ "preventive_cost_estimate": 800.00,
+ "roi_preventive_action": 15.0,
+ "anomaly_detected": true,
+ "anomaly_description": "Energy consumption 12% above baseline, slight increase in travel time",
+ "anomaly_severity": "MODERATE",
+ "recommended_action": "Schedule comprehensive inspection within 4 weeks",
+ "action_urgency": "THIS_MONTH",
+ "vendor_recommendation": "Elevator Services Pro",
+ "ai_confidence_score": 78,
+ "warranty_status": "ACTIVE",
+ "compliance_status": "DUE_INSPECTION",
+ "days_until_failure": 96
+ },
+ {
+ "asset_id": "AST-HVAC-B2-002",
+ "asset_name": "Secondary HVAC - Building A",
+ "property_id": "PROP-DOWNTOWN-01",
+ "asset_type": "HVAC",
+ "location_detail": "Building A, Floor 10, Unit 2",
+ "current_status": "HEALTHY",
+ "health_score": 94,
+ "performance_trend": "IMPROVING",
+ "efficiency_rating": 96.0,
+ "baseline_efficiency": 92.0,
+ "energy_consumption_current": 195.8,
+ "energy_consumption_baseline": 210.0,
+ "energy_variance_pct": -6.8,
+ "temperature_reading": 68.2,
+ "runtime_hours_today": 18.0,
+ "last_maintenance_date": "2025-07-01",
+ "next_scheduled_maintenance": "2025-12-01",
+ "predicted_failure_date": "2026-06-15",
+ "failure_probability": 8.5,
+ "maintenance_priority": "LOW",
+ "cost_impact_prediction": 4500.00,
+ "preventive_cost_estimate": 400.00,
+ "roi_preventive_action": 11.25,
+ "anomaly_detected": false,
+ "recommended_action": "Continue standard monitoring, no intervention needed",
+ "action_urgency": "NEXT_QUARTER",
+ "ai_confidence_score": 95,
+ "warranty_status": "ACTIVE",
+ "compliance_status": "COMPLIANT",
+ "days_until_failure": 308
+ }
+ ],
+ "portfolio_intelligence": {
+ "total_assets": 47,
+ "ai_predictions": 12,
+ "cost_savings_potential": 23400.00,
+ "energy_efficiency_trend": "IMPROVING",
+ "compliance_percentage": 94.0,
+ "inspections_due_30_days": 3,
+ "predictive_maintenance_opportunities": 8,
+ "emergency_repairs_prevented": 2
+ },
+ "ai_insights": {
+ "top_recommendation": "Prioritize HVAC-B1-001 motor bearing replacement to prevent $8,500 emergency repair",
+ "cost_optimization": "Implementing all AI recommendations could save $23,400 in emergency repairs",
+ "efficiency_trend": "Portfolio energy efficiency improved 2.3% vs last month",
+ "risk_assessment": "3 assets require immediate attention, 6 assets trending toward issues"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 000000000..e9e57dc4d
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:
diff --git a/src/App.css b/src/App.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/App.test.tsx b/src/App.test.tsx
new file mode 100644
index 000000000..2a68616d9
--- /dev/null
+++ b/src/App.test.tsx
@@ -0,0 +1,9 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import App from './App';
+
+test('renders learn react link', () => {
+ render();
+ const linkElement = screen.getByText(/learn react/i);
+ expect(linkElement).toBeInTheDocument();
+});
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 000000000..25c182e19
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,389 @@
+import { useMemo } from "react";
+import { ChakraProvider } from "@chakra-ui/react";
+import {
+ StyledEngineProvider,
+ ThemeProvider as MuiThemeProvider,
+} from "@mui/material";
+import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
+import { ApolloProvider } from "@apollo/client/react";
+import apolloClient from "./graphql/apolloClient";
+import Login from "./pages/Login";
+import Layout from "./components/Layout";
+import { AuthProvider } from "./context/AuthContext";
+import Signup from "./pages/Signup";
+import ForgetPassword from "./pages/Forget-Password";
+import ResetPassword from "./pages/Reset-password";
+import AcceptInvitation from "./pages/accept-invitation/AcceptInvitation";
+import { ProtectedRoute } from "./components/ProtectedRoute";
+import { RoleProtectedRoute } from "./components/RoleProtectedRoute";
+import Dashboard from "./pages/dashboard/Dashboard";
+import MainDashboard from "./pages/dashboard/MainDashboard";
+import Onboard from "./pages/onBoard/Onboard";
+import TicketingBoard from "./pages/ticketing";
+import { getRoleTheme } from "./themes/theme";
+import { LoaderProvider } from "./context/LoaderContext";
+import createCache from "@emotion/cache";
+import { CacheProvider } from "@emotion/react";
+import PageWithTitle from "./components/PageWithTitle";
+import muiTheme from "./themes/muiTheme";
+import Gallery from "./pages/gallery";
+import SubscriptionOverview from "./pages/subscription/SubscriptionOverview";
+import Team from "./pages/team/Team";
+import Partners from "./pages/partners/Partners";
+import Company from "./pages/company/Company";
+import { PublicRoute } from "./components/PublicRoute";
+import AiAddons from "./pages/ai-addons/AiAddons";
+import MasterData from "./pages/master-data/MasterData";
+import AssetCategory from "./pages/masterData/assetCategory/AssetCategory";
+import AssetType from "./pages/masterData/assetType/AssetType";
+import AssetFields from "./pages/masterData/assetfields/AssetFields";
+import AssetPartFields from "./pages/masterData/assetpartfields/AssetPartFields";
+import AssetParts from "./pages/masterData/assetparts/AssetParts";
+import AssignmentType from "./pages/masterData/assignmentType/AssignmentType";
+import Manufacturer from "./pages/masterData/manufacturer/Manufacturer";
+import ServiceType from "./pages/masterData/serviceType/ServiceType";
+import ServiceCategory from "./pages/masterData/serviecCategory/ServiceCategory";
+import Vendor from "./pages/masterData/vendor/Vendor";
+import WorkOrderType from "./pages/masterData/workOrderType/WorkOrderType";
+import WorkOrderStage from "./pages/masterData/workorderStage/WorkOrderStage";
+import Plans from "./pages/plans/Plans";
+// import PlanForm from "./pages/plans/PlanForm";
+import SettingsPage from "./pages/settings/Settings";
+const muiCache = createCache({ key: "mui", prepend: true });
+const chakraCache = createCache({ key: "chakra" });
+
+function App() {
+ const chakraTheme = getRoleTheme("user"); // or 'user', 'manager', etc. based on your role logic
+ const memoizedMuiTheme = useMemo(() => muiTheme, []);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+ }
+ />
+
+
+
+ }
+ >
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+ {/*
+
+
+
+
+ }
+ /> */}
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+ {/* master data routes */}
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+ {/* } /> */}
+
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/src/components/AssetPerformance.tsx b/src/components/AssetPerformance.tsx
new file mode 100644
index 000000000..f593e71a0
--- /dev/null
+++ b/src/components/AssetPerformance.tsx
@@ -0,0 +1,375 @@
+import { InfoOutlineIcon } from "@chakra-ui/icons";
+import {
+ Box,
+ Text,
+ SimpleGrid,
+ Accordion,
+ AccordionItem,
+ AccordionButton,
+ AccordionPanel,
+ AccordionIcon,
+ Badge,
+ Progress,
+ Divider,
+ CardHeader,
+ Card,
+ CardBody,
+ Flex,
+ Heading,
+ Image,
+ IconButton,
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuList
+} from "@chakra-ui/react";
+import { useState } from "react";
+
+type AssetSummary = {
+ total_assets_monitored: number;
+ healthy_assets: number;
+ warning_assets: number;
+ critical_assets: number;
+ avg_portfolio_health: number;
+ potential_cost_savings: number;
+};
+
+type Asset = {
+ asset_id: string;
+ asset_name: string;
+ current_status: "CRITICAL" | "WARNING" | "HEALTHY" | string;
+ health_score: number;
+ efficiency_rating: number;
+ performance_trend: "DECLINING" | "IMPROVING" | "STABLE" | string;
+ failure_probability: number;
+ predicted_failure_date: string;
+ predicted_failure_type?: string; // ✅ optional now
+ anomaly_detected?: boolean;
+ anomaly_description?: string;
+ recommended_action?: string;
+
+ // Allow extra fields from backend without errors
+ [key: string]: any;
+};
+
+type PortfolioIntelligence = {
+ compliance_percentage: number;
+ inspections_due_30_days: number;
+ predictive_maintenance_opportunities: number;
+};
+
+type AIInsights = {
+ top_recommendation: string;
+ cost_optimization: string;
+ efficiency_trend: string;
+ risk_assessment: string;
+};
+
+type AssetPanel = {
+ panel_id: string;
+ title: string;
+ priority: number;
+ refresh_interval_seconds: number;
+ data: {
+ summary: AssetSummary;
+ assets: Asset[];
+ portfolio_intelligence: PortfolioIntelligence;
+ ai_insights: AIInsights;
+ };
+};
+
+type AssetPerformanceProps = {
+ panel: AssetPanel;
+};
+
+const AssetPerformance = ({ panel }: AssetPerformanceProps) => {
+ const { summary, assets, portfolio_intelligence, ai_insights } = panel.data;
+ const [filterAssetData, setFilterAssetData] = useState(panel.data.assets || []);
+ const filterAsset = (status?: string) => {
+ if(status == 'ALL'){
+ setFilterAssetData(assets)
+ }else{
+ const filters = assets?.filter((data:any) => data.current_status == status)
+ setFilterAssetData(filters)
+ }
+}
+
+ return (
+ <>
+
+
+
+
+
+ analytics
+
+
+ {panel.title}
+
+
+
+
+
+
+
+ AI Predictions Generated
+ 12
+
+
+ Avg Portfolio Health
+ 84.2
+
+
+ Energy Efficiency Avg
+ 87.5
+
+
+ Potential Cost Savings
+ 23400
+
+ {/*
+ Total Assets Monitored
+ 47
+ */}
+
+
+
+ {filterAssetData?.map((asset:any) => (
+
+
+
+
+
+ {asset.asset_name}
+ {asset.current_status}
+
+
+
+
+
+
+
+
+
+
+
+ Asset ID
+
+ {asset.asset_id}
+
+
+
+ Health Score
+
+ {/* */}
+ {asset.health_score}
+
+
+
+ Efficiency Rating
+
+ {asset.efficiency_rating}%
+
+
+
+ Performance Trend
+
+
+ {asset.performance_trend}
+
+
+
+
+ Failure Probability
+
+ {/* */}
+ {asset.failure_probability}%
+
+
+
+ Predicted Failure
+
+
+
+ {asset.predicted_failure_type}
+
+
+
+
+ Predicted Failure Date
+
+
+ {asset.predicted_failure_date}
+
+
+
+
+
+ {/* Anomaly */}
+ {asset.anomaly_detected && (
+
+
+
+ Anomaly Detected:
+
+ {asset.anomaly_description}
+
+ Recommended: {asset.recommended_action}
+
+
+ )}
+
+
+ ))}
+
+
+ {filterAssetData?.length == 0 && (
+ No alerts found.
+ )}
+
+
+
+ >
+ );
+};
+
+export default AssetPerformance;
\ No newline at end of file
diff --git a/src/components/Cards/ActionItemsCard.tsx b/src/components/Cards/ActionItemsCard.tsx
new file mode 100644
index 000000000..8834d88be
--- /dev/null
+++ b/src/components/Cards/ActionItemsCard.tsx
@@ -0,0 +1,68 @@
+import { Box, Flex, Text, Badge, VStack, HStack, Button, Circle } from "@chakra-ui/react";
+
+interface ActionItem {
+ id: number;
+ title: string;
+ description: string;
+ color?: string; // optional, default color if not provided
+}
+
+interface ActionItemsCardProps {
+ headerTitle?: string;
+ urgentCount?: number;
+ items: ActionItem[];
+}
+
+const ActionItemsCard: React.FC = ({
+ headerTitle = "⚠️ Action Items",
+ urgentCount = 0,
+ items,
+}) => {
+ return (
+
+ {/* Header */}
+
+ {headerTitle}
+ {urgentCount > 0 && (
+
+ {urgentCount} URGENT
+
+ )}
+
+
+ {/* Items */}
+
+ {items.map((item, index) => (
+
+
+ {index + 1}
+
+
+ {item.title}
+
+ {item.description}
+
+
+
+ ))}
+
+
+ {/* Footer */}
+
+
+
+
+
+ );
+};
+
+export default ActionItemsCard;
diff --git a/src/components/Cards/ContractorActivityCard.tsx b/src/components/Cards/ContractorActivityCard.tsx
new file mode 100644
index 000000000..fa36d11f3
--- /dev/null
+++ b/src/components/Cards/ContractorActivityCard.tsx
@@ -0,0 +1,70 @@
+import { Box, Flex, Text, Badge, VStack, Button, HStack } from "@chakra-ui/react";
+
+interface Contractor {
+ id: number;
+ title: string;
+ description: string;
+ status: string;
+ statusColor: string; // Chakra colorScheme
+}
+
+interface ContractorActivityCardProps {
+ headerTitle?: string;
+ headerBadge?: { label: string; colorScheme: string };
+ contractors: Contractor[];
+ footerButtons?: { label: string; variant?: string; colorScheme?: string }[];
+}
+
+const ContractorActivityCard: React.FC = ({
+ headerTitle = "🔧 Today’s Contractor Activity",
+ headerBadge = { label: "ON SCHEDULE", colorScheme: "green" },
+ contractors,
+ footerButtons = [
+ { label: "Track Progress", colorScheme: "blue" },
+ { label: "All Contractors", variant: "ghost" },
+ ],
+}) => {
+ return (
+
+ {/* Header */}
+
+ {headerTitle}
+
+ {headerBadge.label}
+
+
+
+ {/* Contractor List */}
+
+ {contractors.map((contractor) => (
+
+
+ {contractor.title}
+
+ {contractor.description}
+
+
+ {contractor.status}
+
+ ))}
+
+
+ {/* Footer */}
+
+ {footerButtons.map((btn, i) => (
+
+ ))}
+
+
+ );
+};
+
+export default ContractorActivityCard;
diff --git a/src/components/Cards/FinancialPulseCard.tsx b/src/components/Cards/FinancialPulseCard.tsx
new file mode 100644
index 000000000..8609ddebd
--- /dev/null
+++ b/src/components/Cards/FinancialPulseCard.tsx
@@ -0,0 +1,72 @@
+import { Box, Flex, Text, Badge, Button, VStack } from "@chakra-ui/react";
+
+interface FinancialItem {
+ id: number;
+ label: string;
+ value: string;
+ color: string; // Chakra text color
+}
+
+interface FinancialPulseCardProps {
+ headerTitle?: string;
+ headerBadge?: { label: string; bg: string; color: string };
+ financialData: FinancialItem[];
+ footerButtons?: { label: string; variant?: string; colorScheme?: string }[];
+}
+
+const FinancialPulseCard: React.FC = ({
+ headerTitle = "💰 Financial Pulse",
+ headerBadge = { label: "OVER BUDGET", bg: "yellow.400", color: "black" },
+ financialData,
+ footerButtons = [
+ { label: "Review Overages", colorScheme: "blue" },
+ { label: "Full Report", variant: "ghost" },
+ ],
+}) => {
+ return (
+
+ {/* Header */}
+
+ {headerTitle}
+
+ {headerBadge.label}
+
+
+
+ {/* Financial Data */}
+
+ {financialData.map((item) => (
+
+ {item.label}
+
+ {item.value}
+
+
+ ))}
+
+
+ {/* Footer */}
+
+ {footerButtons.map((btn, i) => (
+
+ ))}
+
+
+ );
+};
+
+export default FinancialPulseCard;
diff --git a/src/components/Cards/KeyPerformanceCard.tsx b/src/components/Cards/KeyPerformanceCard.tsx
new file mode 100644
index 000000000..c6bd3e128
--- /dev/null
+++ b/src/components/Cards/KeyPerformanceCard.tsx
@@ -0,0 +1,47 @@
+import { Box, Flex, Text, Badge, VStack } from "@chakra-ui/react";
+
+interface KPIItem {
+ id: number;
+ label: string;
+ value: string;
+ color: string; // Chakra color for value
+}
+
+interface KeyPerformanceCardProps {
+ headerTitle?: string;
+ headerBadge?: { label: string; colorScheme: string };
+ kpis: KPIItem[];
+}
+
+const KeyPerformanceCard: React.FC = ({
+ headerTitle = "📈 Key Performance",
+ headerBadge = { label: "TRENDING UP", colorScheme: "green" },
+ kpis,
+}) => {
+ return (
+
+ {/* Header */}
+
+ {headerTitle}
+
+ {headerBadge.label}
+
+
+
+ {/* KPIs */}
+
+ {kpis.map((kpi) => (
+
+ {kpi.label}
+
+ {kpi.value}
+
+
+ ))}
+
+
+ );
+};
+
+export default KeyPerformanceCard;
+
diff --git a/src/components/Cards/TenantImpactCard.tsx b/src/components/Cards/TenantImpactCard.tsx
new file mode 100644
index 000000000..12babb6ef
--- /dev/null
+++ b/src/components/Cards/TenantImpactCard.tsx
@@ -0,0 +1,70 @@
+import { Box, Flex, Text, Badge, Button, VStack, HStack } from "@chakra-ui/react";
+
+interface TenantImpact {
+ id: number;
+ name: string;
+ description: string;
+ impactLevel: string;
+ impactColor: string; // Chakra Badge colorScheme
+}
+
+interface TenantImpactCardProps {
+ headerTitle?: string;
+ headerBadge?: { label: string; colorScheme: string };
+ tenants: TenantImpact[];
+ footerButtons?: { label: string; variant?: string; colorScheme?: string }[];
+}
+
+const TenantImpactCard: React.FC = ({
+ headerTitle = "Tenant Impact Today",
+ headerBadge = { label: "5 AFFECTED", colorScheme: "orange" },
+ tenants,
+ footerButtons = [
+ { label: "Send Notifications", colorScheme: "blue" },
+ { label: "View All", variant: "ghost" },
+ ],
+}) => {
+ return (
+
+ {/* Header */}
+
+ {headerTitle}
+
+ {headerBadge.label}
+
+
+
+ {/* Tenant List */}
+
+ {tenants.map((tenant) => (
+
+
+ {tenant.name}
+
+ {tenant.description}
+
+
+ {tenant.impactLevel}
+
+ ))}
+
+
+ {/* Footer */}
+
+ {footerButtons.map((btn, i) => (
+
+ ))}
+
+
+ );
+};
+
+export default TenantImpactCard;
diff --git a/src/components/CenterModal.tsx b/src/components/CenterModal.tsx
new file mode 100644
index 000000000..c1d1b93e7
--- /dev/null
+++ b/src/components/CenterModal.tsx
@@ -0,0 +1,27 @@
+import { Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalCloseButton } from "@chakra-ui/react";
+
+type CenterModalProps = {
+ isOpen: boolean;
+ onClose: () => void;
+ title?: string;
+ children: React.ReactNode;
+};
+
+const CenterModal: React.FC = ({ isOpen, onClose, title, children }) => {
+ return (
+
+
+
+ {title}
+
+ {children}
+
+
+ );
+};
+
+export default CenterModal;
\ No newline at end of file
diff --git a/src/components/Chatbot.tsx b/src/components/Chatbot.tsx
new file mode 100644
index 000000000..63f8b2659
--- /dev/null
+++ b/src/components/Chatbot.tsx
@@ -0,0 +1,223 @@
+import { useState } from "react";
+import {
+ Box,
+ IconButton,
+ Stack,
+ TextField,
+ Typography,
+ useMediaQuery,
+ useTheme,
+} from "@mui/material";
+import { Send } from "lucide-react";
+
+type ChatbotProps = {
+ onClose?: () => void;
+ onMobileClose?: () => void;
+};
+
+type Message = {
+ id: number;
+ role: "user" | "assistant";
+ text: string;
+};
+
+const Chatbot: React.FC = ({ onClose, onMobileClose }) => {
+ const [messages, setMessages] = useState([
+ { id: 1, role: "assistant", text: "Hello! How can I help you today?" },
+ { id: 2, role: "user", text: "Show me today's work orders." },
+ { id: 3, role: "assistant", text: "Here are your assigned work orders." },
+ ]);
+ const [input, setInput] = useState("");
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down("md"));
+
+ const handleSend = () => {
+ if (!input.trim()) return;
+ setMessages([...messages, { id: Date.now(), role: "user", text: input }]);
+ setTimeout(() => {
+ setMessages((prev) => [
+ ...prev,
+ { id: Date.now(), role: "assistant", text: "This is an AI response" },
+ ]);
+ }, 600);
+ setInput("");
+ };
+
+ const handleClose = () => {
+ if (onClose) {
+ onClose();
+ }
+ if (isMobile && onMobileClose) {
+ onMobileClose();
+ }
+ };
+
+ return (
+
+ {/* Header */}
+
+
+ Chatbot
+
+
+
+ close
+
+
+
+
+ {/* Messages */}
+
+ {messages.map((msg) => (
+
+
+
+ {msg.role === "assistant" ? "smart_toy" : "person"}
+
+
+
+ {msg.text}
+
+
+ ))}
+
+
+ {/* Input */}
+ {
+ e.preventDefault();
+ handleSend();
+ }}
+ sx={{
+ px: 3,
+ py: 2,
+ borderTop: "1px solid #333",
+ display: "flex",
+ gap: 2,
+ alignItems: "center",
+ }}
+ >
+ setInput(e.target.value)}
+ fullWidth
+ size="small"
+ sx={{
+ "& .MuiOutlinedInput-root": {
+ bgcolor: "#1f2937",
+ color: "#fff",
+ "& fieldset": {
+ border: "none",
+ },
+ "&:hover fieldset": {
+ border: "none",
+ },
+ "&.Mui-focused fieldset": {
+ border: "none",
+ },
+ "& input::placeholder": {
+ color: "#9ca3af",
+ opacity: 1,
+ },
+ },
+ }}
+ />
+
+
+
+
+
+ );
+};
+
+export default Chatbot;
+
+
diff --git a/src/components/CrisisCheck.tsx b/src/components/CrisisCheck.tsx
new file mode 100644
index 000000000..0482a7063
--- /dev/null
+++ b/src/components/CrisisCheck.tsx
@@ -0,0 +1,288 @@
+import React, { useState } from "react";
+import {
+ Box,
+ Flex,
+ Text,
+ SimpleGrid,
+ Tabs,
+ Tab,
+ TabList,
+ TabPanel,
+ TabPanels,
+ Card,
+ CardBody,
+ CardHeader,
+ Heading,
+ IconButton,
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuList,
+ Image
+} from "@chakra-ui/react";
+import { AddIcon, EditIcon, ExternalLinkIcon, HamburgerIcon, InfoIcon, RepeatIcon, WarningIcon, WarningTwoIcon } from "@chakra-ui/icons";
+import CenterModal from "./CenterModal";
+
+
+type CrisisSummary = {
+ total_active_alerts: number;
+ emergency_count: number;
+ critical_count: number;
+ security_count: number;
+ system_failure_count: number;
+};
+
+type CrisisPanel = {
+ panel_id: string;
+ title: string;
+ priority: number;
+ refresh_interval_seconds: number;
+ data: {
+ summary: CrisisSummary;
+ alerts: any[];
+ emergency_readiness: any;
+ };
+};
+
+type CrisisCheckProps = {
+ panel: CrisisPanel;
+};
+
+const CrisisCheck: React.FC = ({ panel }) => {
+ const { summary, alerts } = panel.data;
+ const [selectedItem, setSelectedItem] = useState(null);
+ const [isOpen, setIsOpen] = useState(false);
+ const [filterAlertsData, setFilterAlertsData] = useState(panel.data.alerts || []);
+
+ const getIcon = (type: string) => {
+ switch (type) {
+ case "EMERGENCY":
+ return ;
+ case "SECURITY":
+ return ;
+ case "CRITICAL":
+ return ;
+ default:
+ return null;
+ }
+ };
+
+ const getBg = (type: string) => {
+ switch (type) {
+ case "EMERGENCY":
+ return "red.50";
+ case "CRITICAL":
+ return "blue.50";
+ case "SECURITY":
+ return "orange.50";
+ case "SYSTEM_FAILURE":
+ return "yellow.50";
+ default:
+ return "white";
+ }
+ };
+
+ const getBorders = (type: string) => {
+ switch (type) {
+ case "EMERGENCY":
+ return "red.400";
+ case "CRITICAL":
+ return "blue.400";
+ case "SECURITY":
+ return "orange.400";
+ case "SYSTEM_FAILURE":
+ return "yellow.400";
+ default:
+ return "gray.200";
+ }
+ };
+ console.log(alerts)
+ // const filterAlerts = (type?: string) =>
+
+ // type ? alerts.filter((a: any) => a.alert_type === type) : alerts;
+
+ const filterAlerts = (type?: string) => {
+ const filterAlerts = (type?: string) =>type ? alerts.filter((a: any) => a.alert_type === type) : alerts;
+ setFilterAlertsData(filterAlerts(type));
+ }
+
+ const renderAlerts = (data: any) => {
+
+ // const mapped = data;
+
+ return data?.length ? (
+
+ {data?.map((alert: any, index: number) => (
+ {
+ setIsOpen(true);
+ setSelectedItem(data[index]);
+ }}
+ >
+ {getIcon(alert.alert_type)}
+
+
+ {alert.title}
+
+
+ {alert.description}
+
+
+ {alert.time}
+
+ ))}
+
+ ) : (
+
+ No alerts found.
+
+ );
+ };
+
+ return (
+ <>
+
+
+
+
+
+ notifications_active
+
+ {panel.title}
+
+
+
+
+
+ {renderAlerts(filterAlertsData)}
+
+
+
+
+ {/* Modal for Alert Details */}
+ setIsOpen(false)}
+ title={selectedItem?.title || "Alert Details"}
+ >
+
+
+
+ Description
+ {selectedItem?.description}
+
+
+ Time
+ {selectedItem?.time || 'N/A'}
+
+
+ Property
+ {selectedItem?.property_name || selectedItem?.property_id}
+
+
+ Affected Areas
+ {selectedItem?.affected_areas?.join(", ")}
+
+
+
+ Estimated Impact
+ {selectedItem?.estimated_impact}
+
+
+ Assigned To
+ {selectedItem?.assigned_to}
+
+
+ ETA Resolution
+ {selectedItem?.eta_resolution}
+
+
+ Action Required
+ {selectedItem?.action_required}
+
+ {selectedItem?.contact_info &&(
+
+ Vendor Contact
+ {selectedItem.contact_info.vendor} -{" "}
+ {selectedItem.contact_info.phone}{" "}
+ {selectedItem.contact_info.technician
+ ? `(Technician: ${selectedItem.contact_info.technician})`
+ : ""}
+
+ )}
+
+ Urgency Indicator
+ {selectedItem?.urgency_indicator}
+
+
+
+
+
+ >
+ );
+};
+
+export default CrisisCheck;
diff --git a/src/components/DashboardCard.tsx b/src/components/DashboardCard.tsx
new file mode 100644
index 000000000..7abb7a039
--- /dev/null
+++ b/src/components/DashboardCard.tsx
@@ -0,0 +1,53 @@
+import React from "react";
+import { Box, Text, Button, Stack } from "@chakra-ui/react";
+
+interface DashboardCardProps {
+ title: string;
+ children: React.ReactNode;
+ footer?: React.ReactNode;
+ status?: string; // optional: for things like HIGH, MEDIUM, LOW
+}
+
+const DashboardCard: React.FC = ({ title, children, footer, status }) => {
+ return (
+
+ {/* Header */}
+
+ {title}
+ {status && (
+
+ {status}
+
+ )}
+
+
+ {/* Content */}
+
+ {children}
+
+
+ {/* Footer */}
+ {footer && {footer}}
+
+ );
+};
+
+export default DashboardCard;
diff --git a/src/components/GreetingBar.tsx b/src/components/GreetingBar.tsx
new file mode 100644
index 000000000..abc19be6a
--- /dev/null
+++ b/src/components/GreetingBar.tsx
@@ -0,0 +1,73 @@
+import React, { useEffect, useState } from "react";
+import { Box, Flex, Heading, Text } from "@chakra-ui/react";
+import { useAuth } from "../context/AuthContext";
+
+interface HeaderGreetingProps {
+ name: string;
+ buildingInfo: string; // Example: "Metro Plaza Complex • 3 Buildings • 450,000 sq ft"
+ weather: string; // Optional: for displaying current weather
+}
+
+const HeaderGreeting: React.FC = ({ name, buildingInfo, weather}) => {
+ const [currentTime, setCurrentTime] = useState(new Date());
+ const { user } = useAuth();
+
+ // Update time every second
+ useEffect(() => {
+ const timer = setInterval(() => {
+ setCurrentTime(new Date());
+ }, 1000);
+
+ return () => clearInterval(timer);
+ }, []);
+
+ // Function to get greeting based on current hour
+ const getGreeting = () => {
+ const hour = currentTime.getHours();
+ if (hour < 12) return "Good Morning";
+ if (hour < 18) return "Good Afternoon";
+ return "Good Evening";
+ };
+
+ // Format time like "7:32 AM"
+ const formatTime = (date: Date) =>
+ date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
+
+ // Format date like "Tuesday, Dec 17, 2024"
+ const formatDate = (date: Date) =>
+ date.toLocaleDateString([], { weekday: "long", month: "short", day: "numeric", year: "numeric" });
+
+ return (
+
+ {/* Left side */}
+
+ {getGreeting()}, {user?.displayname}
+ {user?.properties?.roles[0].displayname == 'propertymanager' ? 'Property Manager' : 'Excutive manager'}
+ {/* {formatDate(currentTime)} */}
+
+
+ {/* Right side */}
+
+
+ {formatTime(currentTime)}
+
+ {formatDate(currentTime)}{","} {weather || formatDate(currentTime)}
+ {buildingInfo}
+ {/* {weather || formatDate(currentTime)} */}
+
+
+
+ );
+};
+
+export default HeaderGreeting;
+
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
new file mode 100644
index 000000000..be591f2b9
--- /dev/null
+++ b/src/components/Layout.tsx
@@ -0,0 +1,229 @@
+// src/components/Layout.tsx
+import Box from "@mui/material/Box";
+import IconButton from "@mui/material/IconButton";
+import Typography from "@mui/material/Typography";
+import useMediaQuery from "@mui/material/useMediaQuery";
+import { useTheme } from "@mui/material/styles";
+import { Outlet, useOutletContext } from "react-router-dom";
+import SideNav from "./SideNav";
+import { useState, useEffect, useMemo, useRef } from "react";
+import {PanelLeft } from "lucide-react";
+import { useAuth } from "../context/AuthContext";
+import { useQuery } from "@apollo/client/react";
+import { COMPANIES_QUERY } from "../graphql/queries";
+import { BubbleChat } from 'flowise-embed-react'
+
+type LayoutOutletContext = {
+ setHeaderTitle: (title: string) => void;
+ onMobileClose?: () => void;
+};
+
+const Layout = () => {
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down("lg"));
+ // On mobile, start with panels closed. On desktop, start with sidebar open
+ const [sidenavOpen, setSidenavOpen] = useState(true);
+ const [headerTitle, setHeaderTitle] = useState("My Application");
+ const { setTenantList } = useAuth();
+ const userJwtToken = localStorage.getItem("accessToken");
+ const bubbleTheme = {
+ button: {
+ backgroundColor: '#2563eb',
+ right: 24,
+ bottom: 24,
+ size: 48,
+ iconColor: '#ffffff',
+ dragAndDrop: true,
+ autoWindowOpen: { autoOpen: false }
+ },
+
+ tooltip: {
+ showTooltip: false,
+ tooltipMessage: 'Need help? Chat with us!',
+ tooltipBackgroundColor: '#1f2937',
+ tooltipTextColor: '#ffffff',
+ tooltipFontSize: 14,
+ },
+
+ disclaimer: {
+ title: 'Welcome to Critical Asset Assistant',
+ message: 'Your intelligent companion for asset management.',
+ textColor: '#f9fafb',
+ buttonColor: '#1d4ed8',
+ buttonText: 'Get Started',
+ buttonTextColor: '#ffffff',
+ blurredBackgroundColor: 'rgba(15,23,42,0.85)',
+ backgroundColor: '#1e293b',
+ },
+ chatWindow: {
+ showTitle: true,
+ title: 'Critical Asset Assistant',
+
+ welcomeMessage: 'Hello! How can I help you today?',
+ backgroundColor: '#ffffff',
+
+ starterPrompts: [
+ 'Show me my assets',
+ 'What are my locations?',
+ 'List asset categories',
+ 'Get asset summary'
+ ],
+ clearChatOnReload: false,
+ botMessage: {
+ backgroundColor: '#f1f5f9',
+ textColor: '#0f172a',
+ showAvatar: true,
+ avatarSrc: '/icons/bot.svg',
+ },
+
+ userMessage: {
+ backgroundColor: '#1d4ed8',
+ textColor: '#ffffff',
+ showAvatar: true,
+ avatarSrc: '/icons/user.svg',
+ },
+
+ textInput: {
+ placeholder: 'Type your message here...',
+ backgroundColor: '#ffffff',
+ textColor: '#0f172a',
+ sendButtonColor: '#1d4ed8',
+ autoFocus: true,
+ maxChars: 500,
+ },
+
+ footer: {
+ textColor: 'transparent',
+ text: '',
+ company: '',
+ },
+ },
+ };
+
+ // Update state when breakpoint changes
+ useEffect(() => {
+ if (isMobile) {
+ // On mobile, keep sidebar visible but panels closed
+ setSidenavOpen(false);
+ } else {
+ setSidenavOpen(true);
+ }
+ }, [isMobile]);
+
+ // Fetch companies using GraphQL
+ const { data, error, refetch } = useQuery(COMPANIES_QUERY, {
+ fetchPolicy: 'cache-and-network', // Always fetch from network, but use cache if available
+ });
+
+ // Memoize the tenants to prevent unnecessary re-renders
+ const tenants = useMemo(() => {
+ if (data && typeof data === 'object' && 'companies' in data && Array.isArray(data.companies)) {
+ // Map companies to Tenant format
+ return (data as { companies: any[] }).companies.map((company: any) => ({
+ id: company.id,
+ displayname: company.name || company.displayName || '',
+ slug: company.id, // Using id as slug, adjust if you have a slug field
+ ...company, // Include all company data
+ }));
+ }
+ return [];
+ }, [data]);
+
+ // Track previous tenants to avoid unnecessary updates
+ const prevTenantsRef = useRef(null);
+
+ useEffect(() => {
+ // Create a stable string representation to compare
+ const tenantsKey = JSON.stringify(tenants.map(t => t.id).sort());
+
+ // Only update if the tenant IDs have actually changed
+ if (prevTenantsRef.current !== tenantsKey) {
+ prevTenantsRef.current = tenantsKey;
+ setTenantList(tenants);
+ }
+ }, [tenants, setTenantList]);
+
+ useEffect(() => {
+ if (error) {
+ console.error("Failed to fetch company list:", error);
+ }
+ }, [error]);
+
+ const handleMobileClose = () => {
+ if (isMobile) {
+ setSidenavOpen(false);
+ }
+ };
+
+ const handleToggleSidebar = () => {
+ setSidenavOpen(!sidenavOpen);
+ };
+ return (
+
+
+
+
+
+
+
+
+
+ {headerTitle}
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Layout;
+
+export const useLayoutContext = () => useOutletContext();
diff --git a/src/components/MainMenu.tsx b/src/components/MainMenu.tsx
new file mode 100644
index 000000000..6d00d3626
--- /dev/null
+++ b/src/components/MainMenu.tsx
@@ -0,0 +1,635 @@
+import React, { Fragment, useEffect, useMemo, useState } from "react";
+import {
+ Box,
+ Button,
+ List,
+ ListItemButton,
+ ListItemIcon,
+ ListItemText,
+ Collapse,
+ Icon,
+ Avatar,
+ Menu,
+ MenuItem,
+ ListSubheader,
+ Typography,
+ useMediaQuery,
+} from "@mui/material";
+import { Link as RouterLink, useLocation } from "react-router-dom";
+import { useAuth, type Tenant } from "../context/AuthContext";
+import { ExpandLess, ExpandMore } from "@mui/icons-material";
+import { Building2, Search } from "lucide-react";
+import TextField from "@mui/material/TextField";
+import InputAdornment from "@mui/material/InputAdornment";
+
+type MainMenuProps = {
+ onMobileClose?: () => void;
+};
+
+const MainMenu: React.FC = ({ onMobileClose }) => {
+ const { user, tenantList } = useAuth();
+ const location = useLocation();
+ const [openMenus, setOpenMenus] = useState>({});
+ const isMobile = useMediaQuery("(max-width: 768px)");
+ const inactiveColor = "rgba(255, 255, 255, 0.72)";
+ const [selectedTenantId, setSelectedTenantId] = useState(
+ tenantList && tenantList.length > 0 ? tenantList[0]?.id ?? null : null
+ );
+ const [tenantMenuAnchor, setTenantMenuAnchor] =
+ useState(null);
+ const [searchTerm, setSearchTerm] = useState("");
+
+ // Update selected tenant when tenantList changes
+ useEffect(() => {
+ if (tenantList && tenantList.length > 0 && !selectedTenantId) {
+ setSelectedTenantId(tenantList[0]?.id ?? null);
+ }
+ }, [tenantList, selectedTenantId]);
+
+ const selectedTenant = tenantList?.find(
+ (tenant) => tenant.id === selectedTenantId
+ );
+
+ const handleTenantMenuOpen = (event: React.MouseEvent) => {
+ setTenantMenuAnchor(event.currentTarget);
+ };
+
+ const handleTenantMenuClose = () => {
+ setTenantMenuAnchor(null);
+ setSearchTerm(""); // Clear search when menu closes
+ };
+
+ const handleTenantSelect = (tenant: Tenant) => {
+ setSelectedTenantId(tenant.id);
+ handleTenantMenuClose();
+
+ const authToken = localStorage.getItem("accessToken");
+ const id = tenant.id || "";
+ // const name = (tenant as any).name || tenant.displayname || "";
+ const email = (tenant as any).email || "";
+ const phone = (tenant as any).phone || "";
+ const companyName = tenant.name || tenant.displayname || "";
+
+ if (authToken) {
+ // Navigate to external application with authToken, company name, email, and phone as query parameters
+ const url = new URL("http://company.criticalasset.com/login");
+ url.searchParams.set("id", id);
+ url.searchParams.set("authToken", authToken);
+ if (companyName) url.searchParams.set("companyName", companyName);
+ if (email) url.searchParams.set("email", email);
+ if (phone) url.searchParams.set("phone", phone);
+ window.open(url.toString(), "_blank");
+ }
+ };
+
+ const getInitials = (name?: string | null) => {
+ if (!name) return "?";
+ const parts = name.trim().split(/\s+/);
+ const initials = parts
+ .map((part) => part.charAt(0).toUpperCase())
+ .join("")
+ .slice(0, 1);
+ return initials || "?";
+ };
+
+ // Filter tenants based on search term
+ const filteredTenants = useMemo(() => {
+ if (!searchTerm) return tenantList || [];
+ const term = searchTerm.toLowerCase();
+ return (tenantList || []).filter((tenant: any) => {
+ const name = (tenant.name || tenant.displayname || "").toLowerCase();
+ const email = (tenant.email || "").toLowerCase();
+ return name.includes(term) || email.includes(term);
+ });
+ }, [tenantList, searchTerm]);
+
+ const handleMenuToggle = (menuKey: string, isActive: boolean) => {
+ setOpenMenus((prev) => {
+ // Determine current open state
+ const currentlyOpen = prev[menuKey] !== undefined
+ ? prev[menuKey]
+ : isActive;
+
+ // Toggle the current state
+ return {
+ ...prev,
+ [menuKey]: !currentlyOpen,
+ };
+ });
+ };
+
+ const isMenuOpen = (menuKey: string, isActive: boolean) => {
+ // If user has explicitly set a state, use that (allows manual override)
+ if (openMenus[menuKey] !== undefined) {
+ return openMenus[menuKey];
+ }
+ // Otherwise, auto-open if active
+ return isActive;
+ };
+
+ // Define all navigation items with their allowed roles
+ const allNavItems: any = [
+ // { path: "/onboard", text: "Assets", Icon: "apartment", allowedRoles: ["SUPER_ADMIN", "partner_admin"] },
+ {
+ path: "/dashboard",
+ text: "Dashboard",
+ Icon: "dashboard",
+ allowedRoles: ["SUPER_ADMIN", "partner_admin", "PARTNER_ADMIN", "super_admin"] // Add more roles as needed
+ },
+ {
+ path: "/ticketing",
+ text: "Ticketing",
+ Icon: "view_kanban",
+ allowedRoles: ["SUPER_ADMIN", "partner_admin", "PARTNER_ADMIN", "super_admin"] // Add more roles as needed
+ },
+ {
+ path: "/subscription",
+ text: "Plan & Billing",
+ Icon: "credit_card",
+ allowedRoles: ["SUPER_ADMIN", "super_admin", "partner_admin", "PARTNER_ADMIN"]
+ },
+ {
+ path: "/plans",
+ text: "Subscription Plans",
+ Icon: "card_membership",
+ allowedRoles: ["SUPER_ADMIN", "super_admin"]
+ },
+ {
+ path: "/ai-addons",
+ text: "AI Addons",
+ Icon: "smart_toy",
+ allowedRoles: ["SUPER_ADMIN", "super_admin", "partner_admin", "PARTNER_ADMIN"]
+ },
+ {
+ path: "/team",
+ text: "Team",
+ Icon: "groups",
+ allowedRoles: ["SUPER_ADMIN", "super_admin", "partner_admin", "PARTNER_ADMIN"]
+ },
+ // {
+ // path: "/gallery",
+ // text: "Gallery",
+ // Icon: "gallery_thumbnail",
+ // allowedRoles: ["SUPER_ADMIN","super_admin", "partner_admin"]
+ // },
+ {
+ path: "/partners",
+ text: "Partners",
+ Icon: "people",
+ allowedRoles: ["SUPER_ADMIN", "super_admin"]
+ },
+ // {
+ // path: "/master-data",
+ // text: "Master Data",
+ // Icon: "database",
+ // allowedRoles: ["SUPER_ADMIN", "super_admin", "partner_admin", "PARTNER_ADMIN"]
+ // },
+ {
+ path: "/company",
+ text: "Company",
+ Icon: "business",
+ allowedRoles: ["SUPER_ADMIN", "super_admin", "partner_admin", "PARTNER_ADMIN"]
+ },
+ {
+ text: "Master Data",
+ isParent: true,
+ Icon: "data_table",
+ children: [
+ { path: "/manufacturer", text: "Manufacturer", Icon: "factory" },
+ // { path: "/vendor", text: "Vendor", Icon: "storefront" },
+ { path: "/assetcategory", text: "Asset Category", Icon: "category" },
+ { path: "/assettype", text: "Asset Type", Icon: "warehouse" },
+ { path: "/assetparts", text: "Asset Parts", Icon: "build" },
+ { path: "/assetpartfields", text: "Asset Part Fields", Icon: "list_alt" },
+ { path: "/assetfields", text: "Asset Fields", Icon: "view_list" },
+ // { path: "/assignmenttype", text: "Assignment Type", Icon: "assignment" },
+ // { path: "/servicecategory", text: "Service Category", Icon: "construction" },
+ { path: "/servicetype", text: "Service Type", Icon: "service_toolbox" },
+ // { path: "/workordertype", text: "Workorder Type", Icon: "business_center" },
+ // { path: "/workorderstage", text: "Workorder Stages", Icon: "timeline" },
+ ],
+ },
+ ];
+
+ // Filter navItems based on user role
+ const navItems = useMemo(() => {
+ const userRole = (user as any)?.role;
+
+ // If no role is set, return empty array or all items (adjust based on your needs)
+ if (!userRole) {
+ return [];
+ }
+
+ // Filter items based on allowed roles
+ return allNavItems.filter((item: any) => {
+ // If allowedRoles is not defined, show to all (backward compatibility)
+ if (!item.allowedRoles || item.allowedRoles.length === 0) {
+ return true;
+ }
+ // Check if user's role is in the allowed roles array
+ return item.allowedRoles.includes(userRole);
+ });
+ }, [user]);
+
+ // Reset menu state when menus become inactive, so they auto-open when active again
+ useEffect(() => {
+ setOpenMenus((prev) => {
+ const updated = { ...prev };
+ let hasChanges = false;
+
+ navItems.forEach((nav: any) => {
+ if (nav.children) {
+ const menuKey = nav.path || nav.text;
+ const isActive = nav.children.some((child: any) =>
+ location.pathname.startsWith(child.path)
+ );
+
+ // If menu is inactive and has a state set, clear it
+ if (!isActive && updated[menuKey] !== undefined) {
+ delete updated[menuKey];
+ hasChanges = true;
+ }
+ }
+ });
+
+ return hasChanges ? updated : prev;
+ });
+ }, [location.pathname]);
+
+ return (
+
+
+
+ ) : (
+
+ )
+ }
+ sx={{
+ justifyContent: "flex-start",
+ textTransform: "none",
+ bgcolor: "transparent",
+ color: "#fff",
+ // borderRadius: 2,
+ px: 0,
+ py: 0,
+ // minHeight: 72,
+ "&:hover": {
+ bgcolor: "transparent",
+ },
+ }}
+ >
+
+
+
+ {/* {selectedTenant ? (
+ getInitials((selectedTenant as any).name || selectedTenant.displayname)
+ ) : (
+
+ )} */}
+
+
+
+ {/* {(selectedTenant as any)?.name || selectedTenant?.displayname || "Select Company"} */}
+ Select Company
+
+ {/*
+ {(selectedTenant as any)?.email || user?.useremail || "--"}
+ */}
+
+
+
+
+
+
+ {navItems.map((nav: any) => {
+ const isActive = nav.path
+ ? location.pathname.startsWith(nav.path)
+ : false;
+
+ if (!nav.isParent) {
+ return (
+ {
+ if (isMobile && onMobileClose) {
+ onMobileClose();
+ }
+ }}
+ sx={{
+ my: 0.5,
+ px: 1.2,
+ py: 1,
+ borderLeft: "2px solid",
+ borderLeftColor: isActive ? "#fff" : "transparent",
+ bgcolor: isActive ? "#3b82f6" : "transparent",
+ color: isActive ? "#fff" : inactiveColor,
+ "&:hover": {
+ color: "#fff",
+ bgcolor: "#3b82f6",
+ borderLeftColor: "#fff",
+ },
+ }}
+ >
+
+
+ {nav.Icon}
+
+
+
+
+ );
+ }
+
+ const isParentActive =
+ nav.children?.some((child: any) =>
+ location.pathname.startsWith(child.path)
+ ) ?? false;
+
+ const menuKey = nav.path || nav.text;
+ const menuIsOpen = isMenuOpen(menuKey, isParentActive);
+
+ return (
+
+ handleMenuToggle(menuKey, isParentActive)}
+ sx={{
+ my: 0.5,
+ px: 1.2,
+ py: 1,
+ borderLeft: "2px solid",
+ borderLeftColor: isParentActive ? "#fff" : "transparent",
+ bgcolor: isParentActive ? "#3b82f6" : "transparent",
+ color: isParentActive ? "#fff" : inactiveColor,
+ "&:hover": {
+ color: "#fff",
+ bgcolor: "#3b82f6",
+ borderLeftColor: "#fff",
+ },
+ }}
+ >
+
+
+ {nav.Icon}
+
+
+
+ {menuIsOpen ? (
+
+ ) : (
+
+ )}
+
+
+
+ {nav.children.map((child: any) => {
+ const isChildActive = location.pathname.startsWith(
+ child.path
+ );
+
+ return (
+ {
+ if (isMobile && onMobileClose) {
+ onMobileClose();
+ }
+ }}
+ sx={{
+ pl: 4,
+ py: 1,
+ borderLeft: "2px solid",
+ borderLeftColor: isChildActive
+ ? "#fff"
+ : "transparent",
+ bgcolor: isChildActive ? "#3b82f6" : "transparent",
+ color: isChildActive ? "#fff" : inactiveColor,
+ "&:hover": {
+ color: "#fff",
+ bgcolor: "#3b82f6",
+ borderLeftColor: "#fff",
+ },
+ }}
+ >
+
+
+ {child.Icon}
+
+
+
+
+ );
+ })}
+
+
+
+ );
+ })}
+
+
+
+ {/*
+
+ */}
+
+ );
+};
+
+export default MainMenu;
+
diff --git a/src/components/MiniSidebar.tsx b/src/components/MiniSidebar.tsx
new file mode 100644
index 000000000..34262d4b7
--- /dev/null
+++ b/src/components/MiniSidebar.tsx
@@ -0,0 +1,184 @@
+import { useState, MouseEvent } from "react";
+import {
+ Avatar,
+ Box,
+ IconButton,
+ Menu,
+ MenuItem,
+ Tooltip,
+ Typography,
+} from "@mui/material";
+import { useAuth } from "../context/AuthContext";
+import LogoIcon from "./icons/logoIconDarkBg";
+
+import { Sparkles } from "lucide-react";
+import { Navigation } from "lucide-react";
+
+
+type MiniSidebarProps = {
+ activePanel: "menu" | "chat" | null;
+ isOpen: boolean;
+ onMenuClick: () => void;
+ onChatClick: () => void;
+ zIndex?: number;
+};
+
+const MiniSidebar: React.FC = ({
+ activePanel,
+ isOpen,
+ onMenuClick,
+ onChatClick,
+ zIndex = 1,
+}) => {
+ const { user, logout } = useAuth();
+ const [menuAnchorEl, setMenuAnchorEl] = useState(null);
+
+ const userDisplayName =
+ user?.userdisplayname || user?.name || user?.email || user?.user || "User";
+ const userInitial = userDisplayName?.trim()?.[0]?.toUpperCase() || "U";
+
+ const handleMenuOpen = (event: MouseEvent) => {
+ setMenuAnchorEl(event.currentTarget);
+ };
+
+ const handleMenuClose = () => {
+ setMenuAnchorEl(null);
+ };
+
+ const handleLogout = () => {
+ handleMenuClose();
+ logout();
+ };
+
+ const isMenuOpen = Boolean(menuAnchorEl);
+ const isMenuPanelActive = activePanel === "menu" && isOpen;
+ const isChatPanelActive = activePanel === "chat" && isOpen;
+
+ return (
+
+ {/* Top Section */}
+
+
+
+ {/* Menu Icon */}
+
+
+
+
+
+
+ {/* Chatbot Icon */}
+
+
+
+
+
+
+
+ {/* Bottom Section - User Icon */}
+
+
+
+
+
+ );
+};
+
+export default MiniSidebar;
+
diff --git a/src/components/ModalContent/AssetModal.tsx b/src/components/ModalContent/AssetModal.tsx
new file mode 100644
index 000000000..0ff0c276b
--- /dev/null
+++ b/src/components/ModalContent/AssetModal.tsx
@@ -0,0 +1,150 @@
+import React, { useEffect, useState } from "react";
+import Box from "@mui/material/Box";
+import Button from "@mui/material/Button";
+import MenuItem from "@mui/material/MenuItem";
+import Stack from "@mui/material/Stack";
+import TextField from "@mui/material/TextField";
+import { api } from "../api";
+// import { Box } from "lucide-react";
+type assetModalProps = {
+ data: any;
+ setData: React.Dispatch>;
+ submitData: (data: any, isEdit: boolean) => void;
+};
+const initialState = {
+ name: "",
+ // description: "",
+ assettypeid: "",
+ maintenancestatus: "",
+};
+const AssetModal: React.FC = ({data, setData, submitData }) => {
+ const [formData, setFormData] = useState(initialState);
+
+ const [errors, setErrors] = useState<{ [key: string]: string }>({});
+ const [assettype, setAssettype] = useState([]);
+useEffect(() => {
+ if (data && Object.keys(data).length > 0) {
+ const assetData = {
+ name: data?.name,
+ assettypeid: data?.assettypeid,
+ maintenancestatus: data?.maintenancestatus,
+ id: data?.id,
+ }
+ setFormData(assetData);
+ } else {
+ setFormData(initialState);
+ }
+ }, [data]);
+ useEffect(() => {
+ getAssetType();
+ }, []);
+ const getAssetType=async()=>{
+ try {
+ const res = await api.patch("data/rest", {
+ query: `ca_asset_type{}`,
+ });
+ if (res?.data) {
+ setAssettype(res.data.data.ca_asset_type || []);
+ }
+ } catch (error: any) {
+ console.error("Fetch error:", error.message);
+ } finally {
+ // setLoading(false);
+ }
+ }
+
+ const handleChange = (
+ e: React.ChangeEvent
+ ) => {
+ const { name, value } = e.target;
+ setFormData((prev: any) => ({ ...prev, [name]: value }));
+
+ // clear error when user types again
+ setErrors((prev) => ({ ...prev, [name]: "" }));
+ };
+
+ const validate = () => {
+ const newErrors: { [key: string]: string } = {};
+
+ Object.entries(formData).forEach(([key, value]: any) => {
+ if (!value.trim()) {
+ newErrors[key] = `${key} is required`;
+ }
+ });
+
+ return newErrors;
+ };
+
+ const handleSubmit = () => {
+ const validationErrors = validate();
+ if (Object.keys(validationErrors).length > 0) {
+ setErrors(validationErrors);
+ return;
+ }
+ const isEdit = !!formData.id;
+ submitData(formData, isEdit);
+ setFormData(initialState);
+ console.log("✅ Form submitted:", formData);
+ // here you can trigger API call
+ };
+
+ return (
+
+
+
+
+ {assettype?.map((asset: any) => (
+
+ ))}
+
+
+
+
+
+
+
+ );
+};
+
+export default AssetModal;
diff --git a/src/components/ModalContent/FloorModal.tsx b/src/components/ModalContent/FloorModal.tsx
new file mode 100644
index 000000000..01dd1b9f5
--- /dev/null
+++ b/src/components/ModalContent/FloorModal.tsx
@@ -0,0 +1,141 @@
+import React, { useEffect, useState } from "react";
+import Box from "@mui/material/Box";
+import Button from "@mui/material/Button";
+import Stack from "@mui/material/Stack";
+import TextField from "@mui/material/TextField";
+type floorModalProps = {
+ data: any;
+ setData: React.Dispatch>;
+ submitData: (data: any, isEdit: boolean) => void;
+};
+const initialState = {
+ name: "",
+ description: "",
+ // path: "",
+ // type: "",
+ // locationid: "",
+ // filesize: "",
+ // createdusing: "",
+};
+const FloorModal: React.FC = ({data, setData, submitData }) => {
+ const [formData, setFormData] = useState(initialState);
+ const [errors, setErrors] = useState<{ [key: string]: string }>({});
+
+useEffect(() => {
+ if (data && Object.keys(data).length > 0) {
+ const floorData = {
+ name: data.name,
+ description: data.description,
+ id: data.id,
+ }
+ setFormData({...initialState,...floorData});
+ } else {
+ setFormData(initialState);
+ }
+ }, [data]);
+
+
+ const handleChange = (
+ e: React.ChangeEvent
+ ) => {
+ const { name, value } = e.target;
+ setFormData((prev: any) => ({ ...prev, [name]: value }));
+
+ // clear error when user types again
+ setErrors((prev) => ({ ...prev, [name]: "" }));
+ };
+
+ const validate = () => {
+ const newErrors: { [key: string]: string } = {};
+
+ Object.entries(formData).forEach(([key, value]: any) => {
+ if (!value?.trim()) {
+ newErrors[key] = `${key} is required`;
+ }
+ });
+
+ // special check for filesize
+ if (formData.filesize && !/^\d+$/.test(formData.filesize)) {
+ newErrors.filesize = "Filesize must be numeric";
+ }
+
+ return newErrors;
+ };
+
+ // const handleSubmit = () => {
+ // const validationErrors = validate();
+ // if (Object.keys(validationErrors).length > 0) {
+ // setErrors(validationErrors);
+ // return;
+ // }
+ // const isEdit = !!formData.id;
+ // submitData(formData, isEdit);
+ // setFormData(initialState);
+ // console.log("✅ Form submitted:", formData);
+ // // here you can trigger API call
+ // };
+ const handleSubmit = async () => {
+
+ const validationErrors = validate();
+ if (Object.keys(validationErrors).length > 0) {
+ setErrors(validationErrors);
+ return;
+ }
+
+ const isEdit = !!formData.id;
+
+ try {
+
+ await submitData(formData, isEdit);
+ setFormData(initialState);
+ } catch (err) {
+ console.error("Error in submitData:", err);
+ }
+
+
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default FloorModal;
diff --git a/src/components/ModalContent/LocationModal.tsx b/src/components/ModalContent/LocationModal.tsx
new file mode 100644
index 000000000..3ed042f65
--- /dev/null
+++ b/src/components/ModalContent/LocationModal.tsx
@@ -0,0 +1,235 @@
+import React, { useEffect, useState } from "react";
+import Box from "@mui/material/Box";
+import Button from "@mui/material/Button";
+import MenuItem from "@mui/material/MenuItem";
+import Stack from "@mui/material/Stack";
+import TextField from "@mui/material/TextField";
+
+type locationModalProps = {
+ data: any;
+ setData: React.Dispatch>;
+ submitData: (data: any, isEdit: boolean) => void;
+};
+const initialState = {
+ name: "",
+ description: "",
+ type: "",
+ address1: "",
+ address2: "",
+ city: "",
+ state: "",
+ zip: "",
+ countryalpha2: "",
+};
+const LocationModal: React.FC = ({ data, setData, submitData }) => {
+
+ // const [formData, setFormData] = useState({
+ // // id: "",
+ // name: "",
+ // description: "",
+ // type: "",
+ // address1: "",
+ // address2: "",
+ // city: "",
+ // state: "",
+ // zip: "",
+ // countryalpha2: "",
+ // // parentid: "",
+ // });
+ const [formData, setFormData] = useState(initialState);
+ const [errors, setErrors] = useState<{ [key: string]: string }>({});
+ useEffect(() => {
+ if (data && Object.keys(data).length > 0) {
+ const locationData = {
+ name: data?.name,
+ description: data?.description,
+ type: data?.location_type,
+ address1: data?.address?.address1,
+ address2: data?.address?.address2,
+ city: data?.address?.city,
+ state: data?.address?.state,
+ zip: data?.address?.zip,
+ countryalpha2: data?.address?.countryalpha2,
+ id: data?.id,
+ }
+ setFormData(locationData);
+ } else {
+ setFormData(initialState);
+ }
+ }, [data]);
+ const handleChange = (
+ e: React.ChangeEvent
+ ) => {
+ const { name, value } = e.target;
+ setFormData((prev:any) => ({ ...prev, [name]: value }));
+
+ // clear error when user types again
+ setErrors((prev) => ({ ...prev, [name]: "" }));
+ };
+
+ const validate = () => {
+ const newErrors: { [key: string]: string } = {};
+
+ Object.entries(formData).forEach(([key, value]:any) => {
+ // if (key === "id" || key === "parentid") return;
+ if (!value?.trim()) {
+ newErrors[key] = `${key} is required`;
+ }
+ });
+
+ // special check for zip
+ if (formData.zip && !/^\d+$/.test(formData.zip)) {
+ newErrors.zip = "Zip must be numeric";
+ }
+
+ return newErrors;
+ };
+
+ const handleSubmit = () => {
+ const validationErrors = validate();
+ if (Object.keys(validationErrors).length > 0) {
+ setErrors(validationErrors);
+ return;
+ }
+ const isEdit = !!formData.id;
+ submitData(formData, isEdit);
+ setFormData(initialState);
+ // console.log("✅ Form submitted:", formData);
+ // here you can trigger API call
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default LocationModal;
+
diff --git a/src/components/ModalContent/RoomModal.tsx b/src/components/ModalContent/RoomModal.tsx
new file mode 100644
index 000000000..d89df5ee5
--- /dev/null
+++ b/src/components/ModalContent/RoomModal.tsx
@@ -0,0 +1,110 @@
+import React, { useEffect, useState } from "react";
+import Box from "@mui/material/Box";
+import Button from "@mui/material/Button";
+import Stack from "@mui/material/Stack";
+import TextField from "@mui/material/TextField";
+type roomModalProps = {
+ data: any;
+ setData: React.Dispatch>;
+ submitData: (data: any, isEdit: boolean) => void;
+};
+const initialState = {
+ // id: "",
+ name: "",
+ description: "",
+ // floorid: "",
+};
+const RoomModal: React.FC = ({ data, setData, submitData }) => {
+ const [formData, setFormData] = useState(initialState);
+
+ const [errors, setErrors] = useState<{ [key: string]: string }>({});
+ useEffect(() => {
+ if (data && Object.keys(data).length > 0) {
+ const roomData = {
+ name: data.name,
+ description: data.description,
+ id: data.id,
+ }
+ setFormData({...initialState,...roomData});
+ } else {
+ setFormData(initialState);
+ }
+ }, [data]);
+
+ const handleChange = (
+ e: React.ChangeEvent
+ ) => {
+ const { name, value } = e.target;
+ setFormData((prev: any) => ({ ...prev, [name]: value }));
+
+ // clear error when user types again
+ setErrors((prev) => ({ ...prev, [name]: "" }));
+ };
+
+ const validate = () => {
+ const newErrors: { [key: string]: string } = {};
+
+ Object.entries(formData).forEach(([key, value]: any) => {
+ if (!value.trim()) {
+ newErrors[key] = `${key} is required`;
+ }
+ });
+
+ return newErrors;
+ };
+
+ const handleSubmit = () => {
+ const validationErrors = validate();
+ if (Object.keys(validationErrors).length > 0) {
+ setErrors(validationErrors);
+ return;
+ }
+ const isEdit = !!formData.id;
+ submitData(formData, isEdit);
+ setFormData(initialState);
+ console.log("✅ Form submitted:", formData);
+ // here you can trigger API call
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default RoomModal;
diff --git a/src/components/ModalContent/ZoneModal.tsx b/src/components/ModalContent/ZoneModal.tsx
new file mode 100644
index 000000000..83c30a327
--- /dev/null
+++ b/src/components/ModalContent/ZoneModal.tsx
@@ -0,0 +1,127 @@
+import React, { useEffect, useState } from "react";
+import Box from "@mui/material/Box";
+import Button from "@mui/material/Button";
+import Stack from "@mui/material/Stack";
+import TextField from "@mui/material/TextField";
+type zoneModalProps = {
+ data: any;
+ setData: React.Dispatch>;
+ submitData: (data: any, isEdit: boolean) => void;
+};
+const initialState = {
+ // id: "",
+ name: "",
+ description: "",
+ // floorid: "",
+}
+const ZoneModal: React.FC = ({ data, setData, submitData }) => {
+ const [formData, setFormData] = useState(initialState);
+useEffect(() => {
+ if (data && Object.keys(data).length > 0) {
+ const locationData = {
+ name: data.name,
+ description: data.description,
+ id: data.id,
+ }
+ setFormData(locationData);
+ } else {
+ setFormData(initialState);
+ }
+ }, [data]);
+ const [errors, setErrors] = useState<{ [key: string]: string }>({});
+
+ const handleChange = (
+ e: React.ChangeEvent
+ ) => {
+ const { name, value } = e.target;
+ setFormData((prev: any) => ({ ...prev, [name]: value }));
+
+ // clear error when user types again
+ setErrors((prev) => ({ ...prev, [name]: "" }));
+ };
+
+ const validate = () => {
+ const newErrors: { [key: string]: string } = {};
+
+ Object.entries(formData).forEach(([key, value]: any) => {
+ if (!value.trim()) {
+ newErrors[key] = `${key} is required`;
+ }
+ });
+
+ return newErrors;
+ };
+
+ // const handleSubmit = () => {
+ // const validationErrors = validate();
+ // if (Object.keys(validationErrors).length > 0) {
+ // setErrors(validationErrors);
+ // return;
+ // }
+ // const isEdit = !!formData.id;
+ // submitData(formData, isEdit);
+ // console.log("✅ Form submitted:", formData);
+ // // here you can trigger API call
+ // };
+ const handleSubmit = async () => {
+ const validationErrors = validate();
+ if (Object.keys(validationErrors).length > 0) {
+ setErrors(validationErrors);
+ return;
+ }
+
+ const isEdit = !!formData.id;
+
+ try {
+ console.log("Submitting data...", formData);
+ await submitData(formData, isEdit); // ✅ wait for async function
+ console.log("✅ Data submitted successfully");
+ } catch (error) {
+ console.error("❌ Error submitting data:", error);
+ }
+
+ setFormData(initialState); // reset form after submission
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ZoneModal;
diff --git a/src/components/PageWithTitle.tsx b/src/components/PageWithTitle.tsx
new file mode 100644
index 000000000..037690786
--- /dev/null
+++ b/src/components/PageWithTitle.tsx
@@ -0,0 +1,19 @@
+import { ReactNode, useEffect } from "react";
+import { useLayoutContext } from "./Layout";
+
+interface PageWithTitleProps {
+ title: string;
+ children: ReactNode;
+}
+
+const PageWithTitle = ({ title, children }: PageWithTitleProps) => {
+ const { setHeaderTitle } = useLayoutContext();
+
+ useEffect(() => {
+ setHeaderTitle(title);
+ }, [setHeaderTitle, title]);
+
+ return <>{children}>;
+};
+
+export default PageWithTitle;
diff --git a/src/components/ProtectedRoute.tsx b/src/components/ProtectedRoute.tsx
new file mode 100644
index 000000000..1906e3f98
--- /dev/null
+++ b/src/components/ProtectedRoute.tsx
@@ -0,0 +1,18 @@
+import { Navigate, useLocation } from "react-router-dom";
+import { useAuth } from "../context/AuthContext";
+import { ReactElement } from "react";
+export const ProtectedRoute = ({ children }: { children: ReactElement }) => {
+ const { user } = useAuth();
+ const storedUser = localStorage.getItem("user");
+ const token = localStorage.getItem("accessToken");
+ if (!token) {
+ return (
+
+ );
+ }
+ return children;
+};
\ No newline at end of file
diff --git a/src/components/PublicRoute.tsx b/src/components/PublicRoute.tsx
new file mode 100644
index 000000000..84b60da4d
--- /dev/null
+++ b/src/components/PublicRoute.tsx
@@ -0,0 +1,19 @@
+ import { JSX } from "react";
+import { Navigate, useLocation } from "react-router-dom";
+
+export const PublicRoute = ({ children }: { children: JSX.Element }) => {
+
+ const token = localStorage.getItem("accessToken");
+ const location = useLocation();
+
+ if (token) {
+ const previous = location.state?.from;
+ return ;
+
+ }
+
+
+ return children;
+};
+
+
diff --git a/src/components/RightSideModal.tsx b/src/components/RightSideModal.tsx
new file mode 100644
index 000000000..2c793ef61
--- /dev/null
+++ b/src/components/RightSideModal.tsx
@@ -0,0 +1,99 @@
+import React from "react";
+import Dialog from "@mui/material/Dialog";
+import DialogContent from "@mui/material/DialogContent";
+import DialogTitle from "@mui/material/DialogTitle";
+import IconButton from "@mui/material/IconButton";
+import Slide from "@mui/material/Slide";
+import { TransitionProps } from "@mui/material/transitions";
+import CloseIcon from "@mui/icons-material/Close";
+import Box from "@mui/material/Box";
+
+type RightSideModalProps = {
+ isOpen: boolean;
+ onClose: () => void;
+ title?: string;
+ children: React.ReactNode;
+};
+
+const Transition = React.forwardRef(function Transition(
+ props: TransitionProps & {
+ children: React.ReactElement;
+ },
+ ref: React.Ref
+) {
+ return ;
+});
+
+const RightSideModal: React.FC = ({
+ isOpen,
+ onClose,
+ title,
+ children,
+}) => {
+ return (
+
+ );
+};
+
+export default RightSideModal;
\ No newline at end of file
diff --git a/src/components/RoleProtectedRoute.tsx b/src/components/RoleProtectedRoute.tsx
new file mode 100644
index 000000000..42041a35e
--- /dev/null
+++ b/src/components/RoleProtectedRoute.tsx
@@ -0,0 +1,34 @@
+import { Navigate } from "react-router-dom";
+import { useAuth } from "../context/AuthContext";
+import { ReactElement } from "react";
+
+type RoleProtectedRouteProps = {
+ children: ReactElement;
+ allowedRoles: string[];
+};
+
+export const RoleProtectedRoute = ({
+ children,
+ allowedRoles
+}: RoleProtectedRouteProps) => {
+ const user = JSON.parse(localStorage.getItem("userData") || "{}");
+ const token = localStorage.getItem("accessToken");
+
+ // First check if user is authenticated
+ if (!token) {
+ return ;
+ }
+
+ // Then check if user has the required role (case-insensitive comparison)
+ const userRole = (user as any)?.role?.toLowerCase();
+ const normalizedAllowedRoles = allowedRoles.map(role => role.toLowerCase());
+
+ if (!userRole || !normalizedAllowedRoles.includes(userRole)) {
+ // Redirect to a default route or show unauthorized
+ // You can customize this based on your needs
+ return ;
+ }
+
+ return children;
+};
+
diff --git a/src/components/SideNav.tsx b/src/components/SideNav.tsx
new file mode 100644
index 000000000..04ec705a8
--- /dev/null
+++ b/src/components/SideNav.tsx
@@ -0,0 +1,202 @@
+import { useState, useEffect, useRef } from "react";
+import { Box, useBreakpointValue } from "@chakra-ui/react";
+import MainMenu from "./MainMenu";
+import Chatbot from "./Chatbot";
+import MiniSidebar from "./MiniSidebar";
+
+type ParentProps = {
+ isOpen: boolean;
+ setIsOpen: React.Dispatch>;
+ onMobileClose?: () => void;
+};
+
+const SideNav: React.FC = ({ isOpen, setIsOpen, onMobileClose }) => {
+ // Initialize with menu open if sidebar is open (for initial login)
+ const [activePanel, setActivePanel] = useState<"menu" | "chat" | null>(isOpen ? "menu" : null);
+ const isMobile = useBreakpointValue({ base: true, lg: false });
+ const prevIsOpenRef = useRef(undefined);
+
+ // Sync activePanel with isOpen prop changes
+ useEffect(() => {
+ const prevIsOpen = prevIsOpenRef.current;
+
+ if (!isOpen) {
+ // When sidebar is closed externally, clear active panel
+ setActivePanel(null);
+ } else if (isOpen && (prevIsOpen === undefined || !prevIsOpen)) {
+ // When sidebar is opened (changed from false to true, or initial mount with isOpen=true)
+ // Open menu by default - this is especially important on mobile when the toggle button in Layout is clicked
+ setActivePanel("menu");
+ }
+
+ // Update ref after handling the state change
+ prevIsOpenRef.current = isOpen;
+ }, [isOpen, isMobile]);
+
+ // Handle panel toggle
+ const handlePanelToggle = (panel: "menu" | "chat") => {
+ if (isMobile) {
+ // On mobile, opening a panel should also open the drawer
+ setIsOpen(true);
+ setActivePanel(activePanel === panel ? null : panel);
+ } else {
+ // On desktop, toggle the panel and sidebar state
+ if (activePanel === panel) {
+ // If clicking the same panel, minimize
+ setActivePanel(null);
+ setIsOpen(false);
+ } else {
+ // Switch to the clicked panel
+ setActivePanel(panel);
+ setIsOpen(true);
+ }
+ }
+ };
+
+ const handleChatbotClose = () => {
+ setActivePanel(null);
+ setIsOpen(false);
+ };
+
+ // Mobile View - Mini sidebar always visible, panels overlay
+ if (isMobile) {
+ return (
+ <>
+ {/* Mini Sidebar - Always visible on mobile */}
+ handlePanelToggle("menu")}
+ onChatClick={() => handlePanelToggle("chat")}
+ zIndex={2}
+ />
+
+ {/* Menu Panel - Overlay on mobile */}
+ {activePanel === "menu" && isOpen && (
+ <>
+
+
+
+ {/* Overlay backdrop */}
+ {
+ setIsOpen(false);
+ if (onMobileClose) onMobileClose();
+ }}
+ />
+ >
+ )}
+
+ {/* Chatbot Panel - Overlay on mobile */}
+ {activePanel === "chat" && isOpen && (
+ <>
+
+
+
+ {/* Overlay backdrop */}
+ {
+ setIsOpen(false);
+ if (onMobileClose) onMobileClose();
+ }}
+ />
+ >
+ )}
+ >
+ );
+ }
+
+ // Desktop View - Panels push content to the right
+ return (
+ <>
+ {/* ---------- LEFT MINI SIDEBAR ---------- */}
+ handlePanelToggle("menu")}
+ onChatClick={() => handlePanelToggle("chat")}
+ />
+
+ {/* ---------- RIGHT MAIN PANEL ---------- */}
+ {activePanel === "menu" && isOpen && (
+
+
+
+ )}
+
+ {/* ---------- CHATBOT PANEL ---------- */}
+ {activePanel === "chat" && isOpen && (
+
+
+
+ )}
+ >
+ );
+};
+
+export default SideNav;
diff --git a/src/components/aiChatbox.tsx b/src/components/aiChatbox.tsx
new file mode 100644
index 000000000..bb2257389
--- /dev/null
+++ b/src/components/aiChatbox.tsx
@@ -0,0 +1,159 @@
+import { useState } from "react";
+import {
+ Box,
+ VStack,
+ HStack,
+ Text,
+ IconButton,
+ Input,
+ InputGroup,
+ InputRightElement,
+} from "@chakra-ui/react";
+import { CloseIcon } from "@chakra-ui/icons";
+
+const AIChatBox = ({ onClose }: { onClose: () => void }) => {
+ const [messages, setMessages] = useState<{ sender: "user" | "ai"; text: string }[]>([]);
+ const [input, setInput] = useState("");
+
+ const handleSend = () => {
+ if (!input.trim()) return;
+ setMessages([...messages, { sender: "user", text: input }]);
+
+ setTimeout(() => {
+ setMessages((prev) => [
+ ...prev,
+ { sender: "ai", text: "This is an AI response to: " + input },
+ ]);
+ }, 600);
+
+ setInput("");
+ };
+
+ return (
+
+ {/* Header */}
+
+
+ Copilot
+
+ }
+ aria-label="close"
+ variant="ghost"
+ color="white"
+ _hover={{ bg: "whiteAlpha.300" }}
+ onClick={onClose}
+ />
+
+
+ {/* Messages */}
+
+ {messages.length === 0 ? (
+
+ Start chatting...
+
+ ) : (
+ messages.map((msg, idx) => (
+
+ {/* Icon before AI message */}
+ {msg.sender === "ai" && (
+
+ smart_toy
+
+ )}
+
+ {/* Message Bubble */}
+
+ {msg.text}
+
+
+ {/* Icon before User message (placed after bubble but aligned right) */}
+ {msg.sender === "user" && (
+
+ person
+
+ )}
+
+ ))
+ )}
+
+
+ {/* Input */}
+
+
+ setInput(e.target.value)}
+ onKeyDown={(e) => e.key === "Enter" && handleSend()}
+ />
+
+
+ send
+
+ }
+ onClick={handleSend}
+ />
+
+
+
+
+ );
+};
+
+export default AIChatBox;
diff --git a/src/components/api.tsx b/src/components/api.tsx
new file mode 100644
index 000000000..d9e9f442e
--- /dev/null
+++ b/src/components/api.tsx
@@ -0,0 +1,39 @@
+import axios from "axios";
+
+// ----------------- /account instance -----------------
+const api = axios.create({
+ baseURL: "http://api-dev2.criticalasset.com/",
+});
+
+// ----------------- /bff instance -----------------
+const bffApi = axios.create({
+ baseURL: "/",
+});
+
+// Common request interceptor
+const requestInterceptor = (config: any) => {
+ const token = localStorage.getItem("accessToken");
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`;
+ }
+ return config;
+};
+
+// Common response interceptor
+const responseInterceptor = (response: any) => response;
+const responseErrorInterceptor = async (error: any) => {
+ if (error.response?.status === 401) {
+ localStorage.removeItem("accessToken");
+ localStorage.removeItem("userData");
+ window.location.href = "/login";
+ }
+ return Promise.reject(error);
+};
+
+// Apply interceptors to both instances
+[api, bffApi].forEach((instance) => {
+ instance.interceptors.request.use(requestInterceptor, (error) => Promise.reject(error));
+ instance.interceptors.response.use(responseInterceptor, responseErrorInterceptor);
+});
+
+export { api, bffApi };
diff --git a/src/components/assetIcons/AssetIcon.tsx b/src/components/assetIcons/AssetIcon.tsx
new file mode 100644
index 000000000..0c237e6b7
--- /dev/null
+++ b/src/components/assetIcons/AssetIcon.tsx
@@ -0,0 +1,104 @@
+import React from "react";
+import Box from "@mui/material/Box";
+import tinycolor2 from "tinycolor2";
+import AssetIcons from "../icons/assets/index";
+
+export type IconType = "Square" | "Circle";
+
+interface AssetIconProps {
+ iconName: string;
+ iconSize?: "xs" | "sm" | "md" | "lg";
+ iconColor?: string;
+ iconType: IconType;
+ sx?: any;
+}
+
+const getIconComponent = (icon: string): React.FC | undefined => {
+ return (AssetIcons as unknown as { [key: string]: React.FC })[icon];
+};
+
+const AssetIcon: React.FC = ({
+ iconName,
+ iconSize = "md",
+ iconColor = "#4A5568",
+ iconType,
+ sx,
+}) => {
+ const Asset = React.useMemo(() => getIconComponent(iconName), [iconName]);
+ const iconTextColor = React.useMemo(
+ () => (tinycolor2(iconColor).isDark() ? "white" : "black"),
+ [iconColor]
+ );
+
+ const boxSize =
+ iconSize === "md"
+ ? "4rem"
+ : iconSize === "sm"
+ ? "3rem"
+ : iconSize === "xs"
+ ? "2rem"
+ : "6rem";
+ const iconBoxSize =
+ iconSize === "md"
+ ? "3.2rem"
+ : iconSize === "sm"
+ ? "2.3rem"
+ : iconSize === "xs"
+ ? "1.5rem"
+ : "5rem";
+ let iconTextSize =
+ iconSize === "md"
+ ? 1.2
+ : iconSize === "sm"
+ ? 0.9
+ : iconSize === "xs"
+ ? 0.65
+ : 1.8;
+ if (iconName.length > 3) {
+ iconTextSize -= 0.2;
+ }
+
+ return (
+
+ {Asset ? (
+
+ ) : (
+
+ {iconName}
+
+ )}
+
+ );
+};
+
+export { getIconComponent };
+export default AssetIcon;
+
diff --git a/src/components/assetIcons/AssetIconField.tsx b/src/components/assetIcons/AssetIconField.tsx
new file mode 100644
index 000000000..ff9418cc0
--- /dev/null
+++ b/src/components/assetIcons/AssetIconField.tsx
@@ -0,0 +1,127 @@
+import React, { useState } from "react";
+import Box from "@mui/material/Box";
+import Button from "@mui/material/Button";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import FormHelperText from "@mui/material/FormHelperText";
+import AssetIcon, { IconType, getIconComponent } from "./AssetIcon";
+import ColorPicker from "./ColorPicker";
+import IconPicker from "./IconPicker";
+import TypePicker from "./TypePicker";
+
+interface AssetIconFieldProps {
+ iconName: string;
+ iconColor: string;
+ iconType: IconType;
+ onIconNameChange: (value: string) => void;
+ onIconColorChange: (value: string) => void;
+ onIconTypeChange: (value: IconType) => void;
+ parentColor?: string;
+ error?: string;
+ touched?: boolean;
+}
+
+const AssetIconField: React.FC = ({
+ iconName,
+ iconColor,
+ iconType,
+ onIconNameChange,
+ onIconColorChange,
+ onIconTypeChange,
+ parentColor,
+ error,
+ touched,
+}) => {
+ const [isIconPickerOpen, setIsIconPickerOpen] = useState(false);
+ const [isColorPickerOpen, setIsColorPickerOpen] = useState(false);
+
+ const finalColorValue = iconColor || parentColor || "#4A5568";
+
+ return (
+
+
+
+
+
+ {iconName ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+ {error && touched && (
+
+ {error}
+
+ )}
+
+ setIsColorPickerOpen(false)}
+ value={iconColor || finalColorValue}
+ onChange={onIconColorChange}
+ />
+
+ setIsIconPickerOpen(false)}
+ onSelect={onIconNameChange}
+ color={finalColorValue}
+ iconType={iconType}
+ />
+
+
+ );
+};
+
+export default AssetIconField;
+
diff --git a/src/components/assetIcons/ColorPicker.tsx b/src/components/assetIcons/ColorPicker.tsx
new file mode 100644
index 000000000..62cd9e540
--- /dev/null
+++ b/src/components/assetIcons/ColorPicker.tsx
@@ -0,0 +1,120 @@
+import React from "react";
+import Box from "@mui/material/Box";
+import Button from "@mui/material/Button";
+import Stack from "@mui/material/Stack";
+import { GithubPicker, MaterialPicker, SliderPicker } from "react-color";
+
+export const defaultAssetCategoryColors = [
+ "#C0C0C0",
+ "#808080",
+ "#2F4F4F",
+ "#708090",
+ "#000000",
+ "#FF0000",
+ "#8B0000",
+ "#FFA500",
+ "#FFD700",
+ "#F0E68C",
+ "#FFFF00",
+ "#4169E1",
+ "#1E90FF",
+ "#6495ED",
+ "#808000",
+ "#00FF00",
+ "#98FB98",
+ "#008000",
+ "#00FFFF",
+ "#008080",
+ "#0000FF",
+ "#000080",
+ "#FF00FF",
+ "#800080",
+];
+
+interface ColorPickerProps {
+ parentColor?: string;
+ isColorPickerOpen: boolean;
+ onColorPickerClose: () => void;
+ value: string;
+ onChange: (color: string) => void;
+ showAdvanced?: boolean;
+}
+
+const ColorPicker: React.FC = ({
+ parentColor,
+ isColorPickerOpen,
+ onColorPickerClose,
+ value,
+ onChange,
+ showAdvanced = false,
+}) => {
+ if (!isColorPickerOpen) return null;
+
+ return (
+
+ span": {
+ margin: "4px 6px",
+ },
+ },
+ }}
+ >
+ onChange(color.hex)}
+ />
+ {showAdvanced && (
+ <>
+
+ onChange(color.hex)}
+ />
+
+ onChange(color.hex)}
+ />
+ >
+ )}
+
+
+
+ {!!parentColor && !!value && value !== parentColor && (
+
+ )}
+
+
+ );
+};
+
+export default ColorPicker;
+
diff --git a/src/components/assetIcons/IconPicker.tsx b/src/components/assetIcons/IconPicker.tsx
new file mode 100644
index 000000000..646786e01
--- /dev/null
+++ b/src/components/assetIcons/IconPicker.tsx
@@ -0,0 +1,170 @@
+import React, { useState, useEffect } from "react";
+import Box from "@mui/material/Box";
+import Button from "@mui/material/Button";
+import Input from "@mui/material/Input";
+import Modal from "@mui/material/Modal";
+import Stack from "@mui/material/Stack";
+import Tabs from "@mui/material/Tabs";
+import Tab from "@mui/material/Tab";
+import Typography from "@mui/material/Typography";
+import AssetIcons from "../icons/assets/index";
+import AssetIcon, { IconType } from "./AssetIcon";
+
+interface IconPickerProps {
+ defaultCustomIconText: string;
+ value: string;
+ isIconPickerOpen: boolean;
+ onIconPickerClose: () => void;
+ onSelect: (icon: string) => void;
+ color: string;
+ iconType: IconType;
+}
+
+const IconPicker: React.FC = ({
+ defaultCustomIconText,
+ value,
+ isIconPickerOpen,
+ onIconPickerClose,
+ onSelect,
+ color,
+ iconType,
+}) => {
+ const [customIconText, setCustomIconText] = useState(defaultCustomIconText);
+ const [tabValue, setTabValue] = useState(0);
+
+ const setIconValue = React.useCallback(
+ (icon: string) => {
+ onSelect(icon);
+ setCustomIconText("");
+ onIconPickerClose();
+ },
+ [onSelect, onIconPickerClose]
+ );
+
+ useEffect(() => {
+ setCustomIconText(defaultCustomIconText);
+ setTabValue(defaultCustomIconText ? 1 : 0);
+ }, [defaultCustomIconText]);
+
+ const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => {
+ setTabValue(newValue);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ {tabValue === 0 && (
+
+ {Object.keys(AssetIcons || {}).length === 0 && (
+
+ No icons available
+
+ )}
+ {Object.keys(AssetIcons || {}).map((key) => (
+ setIconValue(key)}
+ sx={{
+ cursor: "pointer",
+ padding: "4px",
+ borderRadius: 1,
+ border: value === key ? "3px solid" : "3px solid transparent",
+ borderColor: value === key ? "secondary.main" : "transparent",
+ "&:hover": {
+ borderColor: "secondary.main",
+ backgroundColor: "action.hover",
+ },
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ }}
+ >
+
+
+ ))}
+
+ )}
+
+ {tabValue === 1 && (
+
+
+
+
+
+
+ Enter three letters for your icon
+
+
+ setCustomIconText(e.target.value)}
+ sx={{ textAlign: "center" }}
+ />
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default IconPicker;
+
diff --git a/src/components/assetIcons/TypePicker.tsx b/src/components/assetIcons/TypePicker.tsx
new file mode 100644
index 000000000..6387de15f
--- /dev/null
+++ b/src/components/assetIcons/TypePicker.tsx
@@ -0,0 +1,75 @@
+import React from "react";
+import Box from "@mui/material/Box";
+import Button from "@mui/material/Button";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { IconType } from "./AssetIcon";
+
+interface TypePickerProps {
+ color: string;
+ value: IconType;
+ onChange: (type: IconType) => void;
+}
+
+const TypePicker: React.FC = ({ color, value, onChange }) => {
+ return (
+
+ Icon Style
+
+
+
+ );
+};
+
+export default TypePicker;
+
diff --git a/src/components/assetIcons/index.ts b/src/components/assetIcons/index.ts
new file mode 100644
index 000000000..ac86a3855
--- /dev/null
+++ b/src/components/assetIcons/index.ts
@@ -0,0 +1,6 @@
+export { default as AssetIcon, getIconComponent, type IconType } from "./AssetIcon";
+export { default as AssetIconField } from "./AssetIconField";
+export { default as ColorPicker, defaultAssetCategoryColors } from "./ColorPicker";
+export { default as IconPicker } from "./IconPicker";
+export { default as TypePicker } from "./TypePicker";
+
diff --git a/src/components/icons/add.tsx b/src/components/icons/add.tsx
new file mode 100644
index 000000000..f2c1fd27e
--- /dev/null
+++ b/src/components/icons/add.tsx
@@ -0,0 +1,13 @@
+import { Icon, IconProps } from "@chakra-ui/react";
+import React from "react";
+
+const Add = (props: IconProps & any) => (
+
+
+
+);
+
+export default Add;
diff --git a/src/components/icons/addCircle.tsx b/src/components/icons/addCircle.tsx
new file mode 100644
index 000000000..6b851282e
--- /dev/null
+++ b/src/components/icons/addCircle.tsx
@@ -0,0 +1,11 @@
+import { Icon, IconProps } from "@chakra-ui/react";
+
+const AddCircle = (props: IconProps & any) => (
+
+
+
+
+
+);
+
+export default AddCircle;
diff --git a/src/components/icons/addMultiple.tsx b/src/components/icons/addMultiple.tsx
new file mode 100644
index 000000000..f0256c4d0
--- /dev/null
+++ b/src/components/icons/addMultiple.tsx
@@ -0,0 +1,10 @@
+import { Icon, IconProps } from "@chakra-ui/react";
+import React from "react";
+
+const AddMultiple = (props: IconProps & any) => (
+
+
+
+);
+
+export default AddMultiple;
diff --git a/src/components/icons/address.tsx b/src/components/icons/address.tsx
new file mode 100644
index 000000000..9b5802312
--- /dev/null
+++ b/src/components/icons/address.tsx
@@ -0,0 +1,25 @@
+import { Icon, IconProps } from "@chakra-ui/react";
+import React from "react";
+
+const address = (props: IconProps & any) => (
+
+
+
+
+
+);
+
+export default address;
diff --git a/src/components/icons/admin.tsx b/src/components/icons/admin.tsx
new file mode 100644
index 000000000..aec0c765a
--- /dev/null
+++ b/src/components/icons/admin.tsx
@@ -0,0 +1,16 @@
+import { Icon, IconProps } from "@chakra-ui/react";
+import React from "react";
+
+const Admin = (props: IconProps & any) => (
+
+
+
+);
+
+export default Admin;
diff --git a/src/components/icons/ai.tsx b/src/components/icons/ai.tsx
new file mode 100644
index 000000000..406e94963
--- /dev/null
+++ b/src/components/icons/ai.tsx
@@ -0,0 +1,13 @@
+import { Icon, IconProps } from "@chakra-ui/react";
+import React from "react";
+
+const AI = (props: IconProps & any) => (
+
+
+
+);
+
+export default AI;
diff --git a/src/components/icons/americanExpressCard.tsx b/src/components/icons/americanExpressCard.tsx
new file mode 100644
index 000000000..a1cca1eb5
--- /dev/null
+++ b/src/components/icons/americanExpressCard.tsx
@@ -0,0 +1,28 @@
+import { Icon, IconProps } from "@chakra-ui/react";
+import React from "react";
+
+const AmericanExpress = (props: IconProps & any) => (
+
+
+
+
+
+
+);
+
+export default AmericanExpress;
diff --git a/src/components/icons/asset.tsx b/src/components/icons/asset.tsx
new file mode 100644
index 000000000..331194851
--- /dev/null
+++ b/src/components/icons/asset.tsx
@@ -0,0 +1,11 @@
+import { Icon, IconProps } from "@chakra-ui/react";
+import React from "react";
+
+const Asset = (props: IconProps & any) => (
+
+
+
+
+);
+
+export default Asset;
diff --git a/src/components/icons/assetCategories.tsx b/src/components/icons/assetCategories.tsx
new file mode 100644
index 000000000..a55d1a5db
--- /dev/null
+++ b/src/components/icons/assetCategories.tsx
@@ -0,0 +1,15 @@
+import { Icon, IconProps } from "@chakra-ui/react";
+import React from "react";
+
+const AssetCategories = (props: IconProps & any) => (
+
+
+
+
+
+
+
+
+);
+
+export default AssetCategories;
diff --git a/src/components/icons/assets.tsx b/src/components/icons/assets.tsx
new file mode 100644
index 000000000..8ec712779
--- /dev/null
+++ b/src/components/icons/assets.tsx
@@ -0,0 +1,10 @@
+import { Icon, IconProps } from "@chakra-ui/react";
+import React from "react";
+
+const Asset = (props: IconProps & any) => (
+
+
+
+);
+
+export default Asset;
diff --git a/src/components/icons/assets/ahu1.tsx b/src/components/icons/assets/ahu1.tsx
new file mode 100644
index 000000000..fe79a2dd0
--- /dev/null
+++ b/src/components/icons/assets/ahu1.tsx
@@ -0,0 +1,24 @@
+import React from "react";
+
+interface Ahu1Props extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Ahu1: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Ahu1;
diff --git a/src/components/icons/assets/ahu2.tsx b/src/components/icons/assets/ahu2.tsx
new file mode 100644
index 000000000..f0989ece2
--- /dev/null
+++ b/src/components/icons/assets/ahu2.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+
+interface Ahu2Props extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Ahu2: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Ahu2;
diff --git a/src/components/icons/assets/airAdmittanceValve.tsx b/src/components/icons/assets/airAdmittanceValve.tsx
new file mode 100644
index 000000000..bcd778868
--- /dev/null
+++ b/src/components/icons/assets/airAdmittanceValve.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+
+interface AirAdmittanceValveProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const AirAdmittanceValve: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default AirAdmittanceValve;
diff --git a/src/components/icons/assets/airCompressor.tsx b/src/components/icons/assets/airCompressor.tsx
new file mode 100644
index 000000000..f15e4dd5e
--- /dev/null
+++ b/src/components/icons/assets/airCompressor.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface AirCompressorProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const AirCompressor: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default AirCompressor;
diff --git a/src/components/icons/assets/backflowPreventer.tsx b/src/components/icons/assets/backflowPreventer.tsx
new file mode 100644
index 000000000..e661c33a7
--- /dev/null
+++ b/src/components/icons/assets/backflowPreventer.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface BackflowPreventerProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const BackflowPreventer: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default BackflowPreventer;
diff --git a/src/components/icons/assets/battery.tsx b/src/components/icons/assets/battery.tsx
new file mode 100644
index 000000000..fea3aa0a3
--- /dev/null
+++ b/src/components/icons/assets/battery.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface BatteryProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Battery: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Battery;
diff --git a/src/components/icons/assets/batteryInverter.tsx b/src/components/icons/assets/batteryInverter.tsx
new file mode 100644
index 000000000..2f2f97e83
--- /dev/null
+++ b/src/components/icons/assets/batteryInverter.tsx
@@ -0,0 +1,36 @@
+import React from "react";
+
+interface BatteryInverterProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const BatteryInverter: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default BatteryInverter;
diff --git a/src/components/icons/assets/cabinet.tsx b/src/components/icons/assets/cabinet.tsx
new file mode 100644
index 000000000..78232b0f3
--- /dev/null
+++ b/src/components/icons/assets/cabinet.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface CabinetProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Cabinet: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Cabinet;
diff --git a/src/components/icons/assets/cableTray.tsx b/src/components/icons/assets/cableTray.tsx
new file mode 100644
index 000000000..ca4cb938b
--- /dev/null
+++ b/src/components/icons/assets/cableTray.tsx
@@ -0,0 +1,32 @@
+import React from "react";
+
+interface CableTrayProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const CableTray: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default CableTray;
diff --git a/src/components/icons/assets/carbonDioxideSensor.tsx b/src/components/icons/assets/carbonDioxideSensor.tsx
new file mode 100644
index 000000000..e32c1c7ca
--- /dev/null
+++ b/src/components/icons/assets/carbonDioxideSensor.tsx
@@ -0,0 +1,38 @@
+import React from "react";
+
+interface CarbonDioxideSensorProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const CarbonDioxideSensor: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default CarbonDioxideSensor;
diff --git a/src/components/icons/assets/carbonMonoxideDetector.tsx b/src/components/icons/assets/carbonMonoxideDetector.tsx
new file mode 100644
index 000000000..79bd1eec3
--- /dev/null
+++ b/src/components/icons/assets/carbonMonoxideDetector.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+
+interface CarbonMonoxideDetectorProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const CarbonMonoxideDetector: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default CarbonMonoxideDetector;
diff --git a/src/components/icons/assets/cardReader.tsx b/src/components/icons/assets/cardReader.tsx
new file mode 100644
index 000000000..a8f24af03
--- /dev/null
+++ b/src/components/icons/assets/cardReader.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface CardReaderProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const CardReader: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default CardReader;
diff --git a/src/components/icons/assets/ceilingTiles.tsx b/src/components/icons/assets/ceilingTiles.tsx
new file mode 100644
index 000000000..610c95eab
--- /dev/null
+++ b/src/components/icons/assets/ceilingTiles.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+
+interface CeilingTilesProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const CeilingTiles: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default CeilingTiles;
diff --git a/src/components/icons/assets/chillerAirCooled.tsx b/src/components/icons/assets/chillerAirCooled.tsx
new file mode 100644
index 000000000..35658daec
--- /dev/null
+++ b/src/components/icons/assets/chillerAirCooled.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+
+interface ChillerAirCooledProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const ChillerAirCooled: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default ChillerAirCooled;
diff --git a/src/components/icons/assets/chillerWaterCooled.tsx b/src/components/icons/assets/chillerWaterCooled.tsx
new file mode 100644
index 000000000..cd6c59186
--- /dev/null
+++ b/src/components/icons/assets/chillerWaterCooled.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+
+interface ChillerWaterCooledProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const ChillerWaterCooled: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default ChillerWaterCooled;
diff --git a/src/components/icons/assets/clock.tsx b/src/components/icons/assets/clock.tsx
new file mode 100644
index 000000000..e8a191ef1
--- /dev/null
+++ b/src/components/icons/assets/clock.tsx
@@ -0,0 +1,36 @@
+import React from "react";
+
+interface ClockProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Clock: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Clock;
diff --git a/src/components/icons/assets/computer.tsx b/src/components/icons/assets/computer.tsx
new file mode 100644
index 000000000..55d9d6f05
--- /dev/null
+++ b/src/components/icons/assets/computer.tsx
@@ -0,0 +1,38 @@
+import React from "react";
+
+interface ComputerProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Computer: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Computer;
diff --git a/src/components/icons/assets/controlDamper.tsx b/src/components/icons/assets/controlDamper.tsx
new file mode 100644
index 000000000..c2a7d0d13
--- /dev/null
+++ b/src/components/icons/assets/controlDamper.tsx
@@ -0,0 +1,52 @@
+import React from "react";
+
+interface ControlDamperProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const ControlDamper: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default ControlDamper;
diff --git a/src/components/icons/assets/coolingTower.tsx b/src/components/icons/assets/coolingTower.tsx
new file mode 100644
index 000000000..c398f7e49
--- /dev/null
+++ b/src/components/icons/assets/coolingTower.tsx
@@ -0,0 +1,38 @@
+import React from "react";
+
+interface CoolingTowerProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const CoolingTower: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default CoolingTower;
diff --git a/src/components/icons/assets/dataReceptacle.tsx b/src/components/icons/assets/dataReceptacle.tsx
new file mode 100644
index 000000000..7064581bd
--- /dev/null
+++ b/src/components/icons/assets/dataReceptacle.tsx
@@ -0,0 +1,32 @@
+import React from "react";
+
+interface DataReceptacleProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const DataReceptacle: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default DataReceptacle;
diff --git a/src/components/icons/assets/defibrillator.tsx b/src/components/icons/assets/defibrillator.tsx
new file mode 100644
index 000000000..d068b9092
--- /dev/null
+++ b/src/components/icons/assets/defibrillator.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface DefibrillatorProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Defibrillator: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Defibrillator;
diff --git a/src/components/icons/assets/defibrillator2.tsx b/src/components/icons/assets/defibrillator2.tsx
new file mode 100644
index 000000000..fc2368055
--- /dev/null
+++ b/src/components/icons/assets/defibrillator2.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface Defibrillator2Props extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Defibrillator2: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Defibrillator2;
diff --git a/src/components/icons/assets/dehumidifier.tsx b/src/components/icons/assets/dehumidifier.tsx
new file mode 100644
index 000000000..d2fd2f0f4
--- /dev/null
+++ b/src/components/icons/assets/dehumidifier.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+
+interface DehumidifierProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Dehumidifier: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Dehumidifier;
diff --git a/src/components/icons/assets/desktopComputer.tsx b/src/components/icons/assets/desktopComputer.tsx
new file mode 100644
index 000000000..c14ee8b39
--- /dev/null
+++ b/src/components/icons/assets/desktopComputer.tsx
@@ -0,0 +1,40 @@
+import React from "react";
+
+interface DesktopComputerProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const DesktopComputer: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default DesktopComputer;
diff --git a/src/components/icons/assets/dimmingSwitch.tsx b/src/components/icons/assets/dimmingSwitch.tsx
new file mode 100644
index 000000000..2c16958ab
--- /dev/null
+++ b/src/components/icons/assets/dimmingSwitch.tsx
@@ -0,0 +1,43 @@
+import React from "react";
+
+interface DimmingSwitchProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const DimmingSwitch: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default DimmingSwitch;
diff --git a/src/components/icons/assets/disconnectSwitch.tsx b/src/components/icons/assets/disconnectSwitch.tsx
new file mode 100644
index 000000000..d09741375
--- /dev/null
+++ b/src/components/icons/assets/disconnectSwitch.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+
+interface DisconnectSwitchProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const DisconnectSwitch: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default DisconnectSwitch;
diff --git a/src/components/icons/assets/disconnectSwitch2.tsx b/src/components/icons/assets/disconnectSwitch2.tsx
new file mode 100644
index 000000000..5fc0c277c
--- /dev/null
+++ b/src/components/icons/assets/disconnectSwitch2.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+
+interface DisconnectSwitch2Props extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const DisconnectSwitch2: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default DisconnectSwitch2;
diff --git a/src/components/icons/assets/disposal.tsx b/src/components/icons/assets/disposal.tsx
new file mode 100644
index 000000000..37078d556
--- /dev/null
+++ b/src/components/icons/assets/disposal.tsx
@@ -0,0 +1,61 @@
+import React from "react";
+
+interface DisposalProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Disposal: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Disposal;
diff --git a/src/components/icons/assets/door.tsx b/src/components/icons/assets/door.tsx
new file mode 100644
index 000000000..e91033fae
--- /dev/null
+++ b/src/components/icons/assets/door.tsx
@@ -0,0 +1,32 @@
+import React from "react";
+
+interface DoorProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Door: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Door;
diff --git a/src/components/icons/assets/doorContact.tsx b/src/components/icons/assets/doorContact.tsx
new file mode 100644
index 000000000..24245a694
--- /dev/null
+++ b/src/components/icons/assets/doorContact.tsx
@@ -0,0 +1,45 @@
+import React from "react";
+
+interface DoorContactProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const DoorContact: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default DoorContact;
diff --git a/src/components/icons/assets/drinkingFountain.tsx b/src/components/icons/assets/drinkingFountain.tsx
new file mode 100644
index 000000000..8ab7eb05e
--- /dev/null
+++ b/src/components/icons/assets/drinkingFountain.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface DrinkingFountainProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const DrinkingFountain: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default DrinkingFountain;
diff --git a/src/components/icons/assets/duckworkSheetmetal.tsx b/src/components/icons/assets/duckworkSheetmetal.tsx
new file mode 100644
index 000000000..4cd72a8cd
--- /dev/null
+++ b/src/components/icons/assets/duckworkSheetmetal.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface DuckworkSheetmetalProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const DuckworkSheetmetal: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default DuckworkSheetmetal;
diff --git a/src/components/icons/assets/ductworkInsulation.tsx b/src/components/icons/assets/ductworkInsulation.tsx
new file mode 100644
index 000000000..517501749
--- /dev/null
+++ b/src/components/icons/assets/ductworkInsulation.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface DuctworkInsulationProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const DuctworkInsulation: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default DuctworkInsulation;
diff --git a/src/components/icons/assets/electrical.tsx b/src/components/icons/assets/electrical.tsx
new file mode 100644
index 000000000..9266012c4
--- /dev/null
+++ b/src/components/icons/assets/electrical.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface ElectricalProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Electrical: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Electrical;
diff --git a/src/components/icons/assets/electricalPanel.tsx b/src/components/icons/assets/electricalPanel.tsx
new file mode 100644
index 000000000..23e9e2a21
--- /dev/null
+++ b/src/components/icons/assets/electricalPanel.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+
+interface ElectricalPanelProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const ElectricalPanel: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default ElectricalPanel;
diff --git a/src/components/icons/assets/elevator1.tsx b/src/components/icons/assets/elevator1.tsx
new file mode 100644
index 000000000..e8eaa58a0
--- /dev/null
+++ b/src/components/icons/assets/elevator1.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface Elevator1Props extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Elevator1: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Elevator1;
diff --git a/src/components/icons/assets/elevator2.tsx b/src/components/icons/assets/elevator2.tsx
new file mode 100644
index 000000000..92f9ea714
--- /dev/null
+++ b/src/components/icons/assets/elevator2.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface Elevator2Props extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Elevator2: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Elevator2;
diff --git a/src/components/icons/assets/eolResistor.tsx b/src/components/icons/assets/eolResistor.tsx
new file mode 100644
index 000000000..9bb05e256
--- /dev/null
+++ b/src/components/icons/assets/eolResistor.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface EolResistorProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const EolResistor: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default EolResistor;
diff --git a/src/components/icons/assets/eolResistor2.tsx b/src/components/icons/assets/eolResistor2.tsx
new file mode 100644
index 000000000..418664ca4
--- /dev/null
+++ b/src/components/icons/assets/eolResistor2.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface EolResistor2Props extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const EolResistor2: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default EolResistor2;
diff --git a/src/components/icons/assets/escalator.tsx b/src/components/icons/assets/escalator.tsx
new file mode 100644
index 000000000..cac73d710
--- /dev/null
+++ b/src/components/icons/assets/escalator.tsx
@@ -0,0 +1,38 @@
+import React from "react";
+
+interface EscalatorProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Escalator: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Escalator;
diff --git a/src/components/icons/assets/exhaustFan.tsx b/src/components/icons/assets/exhaustFan.tsx
new file mode 100644
index 000000000..a4db14f01
--- /dev/null
+++ b/src/components/icons/assets/exhaustFan.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface ExhaustFanProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const ExhaustFan: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default ExhaustFan;
diff --git a/src/components/icons/assets/expansionTank.tsx b/src/components/icons/assets/expansionTank.tsx
new file mode 100644
index 000000000..3707a6b2c
--- /dev/null
+++ b/src/components/icons/assets/expansionTank.tsx
@@ -0,0 +1,29 @@
+import React from "react";
+
+interface ExpansionTankProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const ExpansionTank: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default ExpansionTank;
diff --git a/src/components/icons/assets/eyeWash.tsx b/src/components/icons/assets/eyeWash.tsx
new file mode 100644
index 000000000..b477394aa
--- /dev/null
+++ b/src/components/icons/assets/eyeWash.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface EyeWashProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const EyeWash: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default EyeWash;
diff --git a/src/components/icons/assets/fanCoilUnit.tsx b/src/components/icons/assets/fanCoilUnit.tsx
new file mode 100644
index 000000000..f3abc3a57
--- /dev/null
+++ b/src/components/icons/assets/fanCoilUnit.tsx
@@ -0,0 +1,38 @@
+import React from "react";
+
+interface FanCoilUnitProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const FanCoilUnit: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default FanCoilUnit;
diff --git a/src/components/icons/assets/faucet.tsx b/src/components/icons/assets/faucet.tsx
new file mode 100644
index 000000000..fad61c4a3
--- /dev/null
+++ b/src/components/icons/assets/faucet.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface FaucetProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Faucet: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Faucet;
diff --git a/src/components/icons/assets/fence.tsx b/src/components/icons/assets/fence.tsx
new file mode 100644
index 000000000..94ce30f95
--- /dev/null
+++ b/src/components/icons/assets/fence.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface FenceProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Fence: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Fence;
diff --git a/src/components/icons/assets/fireAlarm.tsx b/src/components/icons/assets/fireAlarm.tsx
new file mode 100644
index 000000000..4da0f517a
--- /dev/null
+++ b/src/components/icons/assets/fireAlarm.tsx
@@ -0,0 +1,39 @@
+import React from "react";
+
+interface FireAlarmProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const FireAlarm: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default FireAlarm;
diff --git a/src/components/icons/assets/fireAlarmCommunicator.tsx b/src/components/icons/assets/fireAlarmCommunicator.tsx
new file mode 100644
index 000000000..0ebd0d00c
--- /dev/null
+++ b/src/components/icons/assets/fireAlarmCommunicator.tsx
@@ -0,0 +1,46 @@
+import React from "react";
+
+interface FireAlarmCommunicatorProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const FireAlarmCommunicator: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default FireAlarmCommunicator;
diff --git a/src/components/icons/assets/fireExtinguisher1.tsx b/src/components/icons/assets/fireExtinguisher1.tsx
new file mode 100644
index 000000000..1b49583f5
--- /dev/null
+++ b/src/components/icons/assets/fireExtinguisher1.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface FireExtinguisher1Props extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const FireExtinguisher1: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default FireExtinguisher1;
diff --git a/src/components/icons/assets/fireExtinguisher2.tsx b/src/components/icons/assets/fireExtinguisher2.tsx
new file mode 100644
index 000000000..47911ca5f
--- /dev/null
+++ b/src/components/icons/assets/fireExtinguisher2.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface FireExtinguisher2Props extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const FireExtinguisher2: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default FireExtinguisher2;
diff --git a/src/components/icons/assets/fireJBox.tsx b/src/components/icons/assets/fireJBox.tsx
new file mode 100644
index 000000000..544b76be0
--- /dev/null
+++ b/src/components/icons/assets/fireJBox.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+
+interface FireJBoxProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const FireJBox: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default FireJBox;
diff --git a/src/components/icons/assets/fireSafety.tsx b/src/components/icons/assets/fireSafety.tsx
new file mode 100644
index 000000000..30d3d1fea
--- /dev/null
+++ b/src/components/icons/assets/fireSafety.tsx
@@ -0,0 +1,27 @@
+import React from "react";
+
+interface FireSafetyProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const FireSafety: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default FireSafety;
diff --git a/src/components/icons/assets/floorDrain.tsx b/src/components/icons/assets/floorDrain.tsx
new file mode 100644
index 000000000..716f1aabf
--- /dev/null
+++ b/src/components/icons/assets/floorDrain.tsx
@@ -0,0 +1,40 @@
+import React from "react";
+
+interface FloorDrainProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const FloorDrain: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default FloorDrain;
diff --git a/src/components/icons/assets/floorSink.tsx b/src/components/icons/assets/floorSink.tsx
new file mode 100644
index 000000000..ec19aaa60
--- /dev/null
+++ b/src/components/icons/assets/floorSink.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface FloorSinkProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const FloorSink: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default FloorSink;
diff --git a/src/components/icons/assets/fuse.tsx b/src/components/icons/assets/fuse.tsx
new file mode 100644
index 000000000..909383b80
--- /dev/null
+++ b/src/components/icons/assets/fuse.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface FuseProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Fuse: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Fuse;
diff --git a/src/components/icons/assets/gasMeter.tsx b/src/components/icons/assets/gasMeter.tsx
new file mode 100644
index 000000000..f91fbce05
--- /dev/null
+++ b/src/components/icons/assets/gasMeter.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+
+interface GasMeterProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const GasMeter: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default GasMeter;
diff --git a/src/components/icons/assets/generator.tsx b/src/components/icons/assets/generator.tsx
new file mode 100644
index 000000000..3a6ede3c0
--- /dev/null
+++ b/src/components/icons/assets/generator.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface GeneratorProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Generator: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Generator;
diff --git a/src/components/icons/assets/greaseTrap.tsx b/src/components/icons/assets/greaseTrap.tsx
new file mode 100644
index 000000000..049d20ccb
--- /dev/null
+++ b/src/components/icons/assets/greaseTrap.tsx
@@ -0,0 +1,38 @@
+import React from "react";
+
+interface GreaseTrapProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const GreaseTrap: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default GreaseTrap;
diff --git a/src/components/icons/assets/groundingCabinet.tsx b/src/components/icons/assets/groundingCabinet.tsx
new file mode 100644
index 000000000..e0d448aec
--- /dev/null
+++ b/src/components/icons/assets/groundingCabinet.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface GroundingCabinetProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const GroundingCabinet: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default GroundingCabinet;
diff --git a/src/components/icons/assets/gutter.tsx b/src/components/icons/assets/gutter.tsx
new file mode 100644
index 000000000..62efae659
--- /dev/null
+++ b/src/components/icons/assets/gutter.tsx
@@ -0,0 +1,29 @@
+import React from "react";
+
+interface GutterProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Gutter: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Gutter;
diff --git a/src/components/icons/assets/heaterBaseboard.tsx b/src/components/icons/assets/heaterBaseboard.tsx
new file mode 100644
index 000000000..64c6cf48d
--- /dev/null
+++ b/src/components/icons/assets/heaterBaseboard.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface HeaterBaseboardProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const HeaterBaseboard: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default HeaterBaseboard;
diff --git a/src/components/icons/assets/heaterElectric.tsx b/src/components/icons/assets/heaterElectric.tsx
new file mode 100644
index 000000000..645e2207a
--- /dev/null
+++ b/src/components/icons/assets/heaterElectric.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface HeaterElectricProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const HeaterElectric: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default HeaterElectric;
diff --git a/src/components/icons/assets/heaterInfrared.tsx b/src/components/icons/assets/heaterInfrared.tsx
new file mode 100644
index 000000000..170cb4d6d
--- /dev/null
+++ b/src/components/icons/assets/heaterInfrared.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface HeaterInfraredProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const HeaterInfrared: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default HeaterInfrared;
diff --git a/src/components/icons/assets/hoseBibb.tsx b/src/components/icons/assets/hoseBibb.tsx
new file mode 100644
index 000000000..0d0c2b7e9
--- /dev/null
+++ b/src/components/icons/assets/hoseBibb.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface HoseBibbProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const HoseBibb: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default HoseBibb;
diff --git a/src/components/icons/assets/hubDrain.tsx b/src/components/icons/assets/hubDrain.tsx
new file mode 100644
index 000000000..4020348d3
--- /dev/null
+++ b/src/components/icons/assets/hubDrain.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface HubDrainProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const HubDrain: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default HubDrain;
diff --git a/src/components/icons/assets/humidifier.tsx b/src/components/icons/assets/humidifier.tsx
new file mode 100644
index 000000000..d54e5b7b9
--- /dev/null
+++ b/src/components/icons/assets/humidifier.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface HumidifierProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Humidifier: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Humidifier;
diff --git a/src/components/icons/assets/humiditySensor.tsx b/src/components/icons/assets/humiditySensor.tsx
new file mode 100644
index 000000000..e66c0a0d6
--- /dev/null
+++ b/src/components/icons/assets/humiditySensor.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+
+interface HumiditySensorProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const HumiditySensor: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default HumiditySensor;
diff --git a/src/components/icons/assets/hvac.tsx b/src/components/icons/assets/hvac.tsx
new file mode 100644
index 000000000..a206a325f
--- /dev/null
+++ b/src/components/icons/assets/hvac.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+
+interface HvacProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Hvac: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Hvac;
diff --git a/src/components/icons/assets/index.ts b/src/components/icons/assets/index.ts
new file mode 100644
index 000000000..6c07087c5
--- /dev/null
+++ b/src/components/icons/assets/index.ts
@@ -0,0 +1,317 @@
+import React from "react";
+import AHU1 from "./ahu1";
+import AHU2 from "./ahu2";
+import AirAdmittanceValve from "./airAdmittanceValve";
+import AirCompressor from "./airCompressor";
+import BackflowPreventer from "./backflowPreventer";
+import Battery from "./battery";
+import BatteryInverter from "./batteryInverter";
+import Cabinet from "./cabinet";
+import CableTray from "./cableTray";
+import CarbonDioxideSensor from "./carbonDioxideSensor";
+import CarbonMonoxideDetector from "./carbonMonoxideDetector";
+import CardReader from "./cardReader";
+import CeilingTiles from "./ceilingTiles";
+import ChillerAirCooled from "./chillerAirCooled";
+import ChillerWaterCooled from "./chillerWaterCooled";
+import Clock from "./clock";
+import Computer from "./computer";
+import ControlDamper from "./controlDamper";
+import CoolingTower from "./coolingTower";
+import DataReceptacle from "./dataReceptacle";
+import Defibrillator from "./defibrillator";
+import Defibrillator2 from "./defibrillator2";
+import Dehumidifier from "./dehumidifier";
+import DesktopComputer from "./desktopComputer";
+import DimmingSwitch from "./dimmingSwitch";
+import DisconnectSwitch from "./disconnectSwitch";
+import DisconnectSwitch2 from "./disconnectSwitch2";
+import Disposal from "./disposal";
+import Door from "./door";
+import DoorContact from "./doorContact";
+import DrinkingFountain from "./drinkingFountain";
+import DuctworkSheetmetal from "./duckworkSheetmetal";
+import DuctworkInsulation from "./ductworkInsulation";
+import Electrical from "./electrical";
+import ElectricalPanel from "./electricalPanel";
+import Elevator1 from "./elevator1";
+import Elevator2 from "./elevator2";
+import EOLResistor from "./eolResistor";
+import EolResistor2 from "./eolResistor2";
+import Escalator from "./escalator";
+import ExhaustFan from "./exhaustFan";
+import ExpansionTank from "./expansionTank";
+import EyeWash from "./eyeWash";
+import FanCoilUnit from "./fanCoilUnit";
+import Faucet from "./faucet";
+import Fence from "./fence";
+import FireAlarm from "./fireAlarm";
+import FireAlarmCommunicator from "./fireAlarmCommunicator";
+import FireExtinguisher1 from "./fireExtinguisher1";
+import FireExtinguisher2 from "./fireExtinguisher2";
+import FireJBox from "./fireJBox";
+import FireSafety from "./fireSafety";
+import FloorDrain from "./floorDrain";
+import FloorSink from "./floorSink";
+import Fuse from "./fuse";
+import GasMeter from "./gasMeter";
+import Generator from "./generator";
+import GreaseTrap from "./greaseTrap";
+import GroundingCabinet from "./groundingCabinet";
+import Gutter from "./gutter";
+import HeaderBaseboard from "./heaterBaseboard";
+import HeaterElectric from "./heaterElectric";
+import HeaterInfrared from "./heaterInfrared";
+import HoseBibb from "./hoseBibb";
+import HubDrain from "./hubDrain";
+import Humidifier from "./humidifier";
+import HumiditySensor from "./humiditySensor";
+import HVAC from "./hvac";
+import InitiatingDevice1 from "./initiatingDevice1";
+import InitiatingDevice2 from "./initiatingDevice2";
+import IrrigationSystem from "./irrigationSystem";
+import IrrigationZoneValves from "./irrigationZoneValves";
+import JBox from "./jBox";
+import JBox2 from "./jBox2";
+import KitchenHood from "./kitchenHood";
+import Landscape from "./landscape";
+import Laptop from "./laptop";
+import LCDAnnunciator from "./lcdAnnunciator";
+import LightEmergency1 from "./lightEmergency1";
+import LightEmergency2 from "./lightEmergency2";
+import LightExit from "./lightExit";
+import LightFixture from "./lightFixture";
+import LightFluorescent from "./lightFluorescent";
+import LightLed from "./lightLed";
+import LightSwitch from "./lightSwitch";
+import MainDistributionPanel from "./mainDistributionPanel";
+import Mechanical from "./mechanical";
+import Meter from "./meter";
+import MopSink from "./mopSink";
+import MotionDetector from "./motionDetector";
+import Motor from "./motor";
+import MowerLawn from "./mowerLawn";
+import MowerRiding from "./mowerRiding";
+import Mri1 from "./mri1";
+import NotificationAppliance1 from "./notificationAppliance1";
+import NotificationAppliance2 from "./notificationAppliance2";
+import OccupancySensor from "./occupancySensor";
+import Other1 from "./other1";
+import Other2 from "./other2";
+import ParkingLight from "./parkingLight";
+import PhotoSensor from "./photoSensor";
+import PipeHanger from "./pipeHanger";
+import Plug from "./plug";
+import Plumbing from "./plumbing";
+import PortableGenerator from "./portableGenerator";
+import Printer from "./printer";
+import Projector from "./projector";
+import Pumps from "./pumps";
+import RefrigerantCopperPiping from "./refrigerantCopperPiping";
+import Register from "./register";
+import RooftopUnit from "./rooftopUnit";
+import Room from "./room";
+import Security from "./security";
+import SecurityCamera from "./securityCamera";
+import SecurityControlPanel from "./securityControlPanel";
+import ServerRack from "./serverRack";
+import ShowerTub from "./showerTub";
+import Sink from "./sink";
+import SmartPhone from "./smartPhone";
+import SolarPanel from "./solarPanel";
+import SolarPanelBackupBaattery from "./solarPanelBackupBaattery";
+import SolarPanelInverter from "./solarPanelInverter";
+import SplitDxUnit from "./splitDxUnit";
+import Sprinkler from "./sprinkler";
+import SubMeter from "./subMeter";
+import Tablet from "./tablet";
+import TemperatureSensor from "./temperatureSensor";
+import Thermostat from "./thermostat";
+import ThermostaticMixingValve from "./thermostaticMixingValve";
+import Toilet from "./toilet";
+import TransferSwitch from "./transferSwitch";
+import Transformer1 from "./transformer1";
+import Transformer2 from "./transformer2";
+import Transformer3 from "./transformer3";
+import TransmissionTower from "./transmissionTower";
+import Transponder from "./transponder";
+import Urinal from "./urinal";
+import Valve from "./valve";
+import VAVBox from "./vavBox";
+import VrfIndoorUnit from "./vrfIndoorUnit";
+import VrfOutdoorHeatPump from "./vrfOutdoorHeatPump";
+import WallHydrant from "./wallHydrant";
+import WaterBoiler from "./waterBoiler";
+import WaterFlowStation from "./waterFlowStation";
+import WaterHeater from "./waterHeater";
+import WaterMeter from "./waterMeter";
+import WaterSourceHeatPump from "./waterSourceHeatPump";
+import Window from "./window";
+import WirelessRouter from "./wirelessRouter";
+import WiringElectrical from "./wiringElectrical";
+import WiringLowVoltage from "./wiringLowVoltage";
+import Xray1 from "./xray1";
+import Xray2 from "./xray2";
+
+interface AssetIconProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const AssetIcons: { [key: string]: React.FC } = {
+ AHU1,
+ AHU2,
+ AirAdmittanceValve,
+ AirCompressor,
+ BackflowPreventer,
+ Battery,
+ BatteryInverter,
+ Cabinet,
+ CableTray,
+ CarbonDioxideSensor,
+ CarbonMonoxideDetector,
+ CardReader,
+ CeilingTiles,
+ ChillerAirCooled,
+ ChillerWaterCooled,
+ Clock,
+ Computer,
+ ControlDamper,
+ CoolingTower,
+ DataReceptacle,
+ Defibrillator,
+ Defibrillator2,
+ Dehumidifier,
+ DesktopComputer,
+ DimmingSwitch,
+ DisconnectSwitch,
+ DisconnectSwitch2,
+ Disposal,
+ Door,
+ DoorContact,
+ DrinkingFountain,
+ DuctworkInsulation,
+ DuctworkSheetmetal,
+ EOLResistor,
+ EolResistor2,
+ Electrical,
+ ElectricalPanel,
+ Elevator1,
+ Elevator2,
+ Escalator,
+ ExhaustFan,
+ ExpansionTank,
+ EyeWash,
+ FanCoilUnit,
+ Faucet,
+ Fence,
+ FireAlarm,
+ FireAlarmCommunicator,
+ FireExtinguisher1,
+ FireExtinguisher2,
+ FireJBox,
+ FireSafety,
+ FloorDrain,
+ FloorSink,
+ Fuse,
+ GasMeter,
+ Generator,
+ GreaseTrap,
+ GroundingCabinet,
+ Gutter,
+ HVAC,
+ HeaderBaseboard,
+ HeaterElectric,
+ HeaterInfrared,
+ HoseBibb,
+ HubDrain,
+ Humidifier,
+ HumiditySensor,
+ InitiatingDevice1,
+ InitiatingDevice2,
+ IrrigationSystem,
+ IrrigationZoneValves,
+ JBox,
+ JBox2,
+ KitchenHood,
+ LCDAnnunciator,
+ Landscape,
+ Laptop,
+ LightEmergency1,
+ LightEmergency2,
+ LightExit,
+ LightFixture,
+ LightFluorescent,
+ LightLed,
+ LightSwitch,
+ MainDistributionPanel,
+ Mechanical,
+ Meter,
+ MopSink,
+ MotionDetector,
+ Motor,
+ MowerLawn,
+ MowerRiding,
+ Mri1,
+ NotificationAppliance1,
+ NotificationAppliance2,
+ OccupancySensor,
+ Other1,
+ Other2,
+ ParkingLight,
+ PhotoSensor,
+ PipeHanger,
+ Plug,
+ Plumbing,
+ PortableGenerator,
+ Printer,
+ Projector,
+ Pumps,
+ RefrigerantCopperPiping,
+ Register,
+ RooftopUnit,
+ Room,
+ Security,
+ SecurityCamera,
+ SecurityControlPanel,
+ ServerRack,
+ ShowerTub,
+ Sink,
+ SmartPhone,
+ SolarPanel,
+ SolarPanelBackupBaattery,
+ SolarPanelInverter,
+ SplitDxUnit,
+ Sprinkler,
+ SubMeter,
+ Tablet,
+ TemperatureSensor,
+ Thermostat,
+ ThermostaticMixingValve,
+ Toilet,
+ TransferSwitch,
+ Transformer1,
+ Transformer2,
+ Transformer3,
+ TransmissionTower,
+ Transponder,
+ Urinal,
+ VAVBox,
+ Valve,
+ VrfIndoorUnit,
+ VrfOutdoorHeatPump,
+ WallHydrant,
+ WaterBoiler,
+ WaterFlowStation,
+ WaterHeater,
+ WaterMeter,
+ WaterSourceHeatPump,
+ Window,
+ WirelessRouter,
+ WiringElectrical,
+ WiringLowVoltage,
+ Xray1,
+ Xray2,
+};
+
+export default AssetIcons;
diff --git a/src/components/icons/assets/initiatingDevice1.tsx b/src/components/icons/assets/initiatingDevice1.tsx
new file mode 100644
index 000000000..b35bf7e48
--- /dev/null
+++ b/src/components/icons/assets/initiatingDevice1.tsx
@@ -0,0 +1,62 @@
+import React from "react";
+
+interface InitiatingDevice1Props extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const InitiatingDevice1: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default InitiatingDevice1;
diff --git a/src/components/icons/assets/initiatingDevice2.tsx b/src/components/icons/assets/initiatingDevice2.tsx
new file mode 100644
index 000000000..58a2c954d
--- /dev/null
+++ b/src/components/icons/assets/initiatingDevice2.tsx
@@ -0,0 +1,61 @@
+import React from "react";
+
+interface InitiatingDevice2Props extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const InitiatingDevice2: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default InitiatingDevice2;
diff --git a/src/components/icons/assets/irrigationSystem.tsx b/src/components/icons/assets/irrigationSystem.tsx
new file mode 100644
index 000000000..aca612fa2
--- /dev/null
+++ b/src/components/icons/assets/irrigationSystem.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface IrrigationSystemProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const IrrigationSystem: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default IrrigationSystem;
diff --git a/src/components/icons/assets/irrigationZoneValves.tsx b/src/components/icons/assets/irrigationZoneValves.tsx
new file mode 100644
index 000000000..de776da0d
--- /dev/null
+++ b/src/components/icons/assets/irrigationZoneValves.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface IrrigationZoneValvesProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const IrrigationZoneValves: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default IrrigationZoneValves;
diff --git a/src/components/icons/assets/jBox.tsx b/src/components/icons/assets/jBox.tsx
new file mode 100644
index 000000000..8c11919b7
--- /dev/null
+++ b/src/components/icons/assets/jBox.tsx
@@ -0,0 +1,29 @@
+import React from "react";
+
+interface JBoxProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const JBox: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default JBox;
diff --git a/src/components/icons/assets/jBox2.tsx b/src/components/icons/assets/jBox2.tsx
new file mode 100644
index 000000000..42545a2f4
--- /dev/null
+++ b/src/components/icons/assets/jBox2.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+
+interface JBox2Props extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const JBox2: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default JBox2;
diff --git a/src/components/icons/assets/kitchenHood.tsx b/src/components/icons/assets/kitchenHood.tsx
new file mode 100644
index 000000000..2019edb01
--- /dev/null
+++ b/src/components/icons/assets/kitchenHood.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface KitchenHoodProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const KitchenHood: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default KitchenHood;
diff --git a/src/components/icons/assets/landscape.tsx b/src/components/icons/assets/landscape.tsx
new file mode 100644
index 000000000..fed0371aa
--- /dev/null
+++ b/src/components/icons/assets/landscape.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+
+interface LandscapeProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Landscape: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Landscape;
diff --git a/src/components/icons/assets/laptop.tsx b/src/components/icons/assets/laptop.tsx
new file mode 100644
index 000000000..bd7314699
--- /dev/null
+++ b/src/components/icons/assets/laptop.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface LaptopProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Laptop: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Laptop;
diff --git a/src/components/icons/assets/lcdAnnunciator.tsx b/src/components/icons/assets/lcdAnnunciator.tsx
new file mode 100644
index 000000000..83a126179
--- /dev/null
+++ b/src/components/icons/assets/lcdAnnunciator.tsx
@@ -0,0 +1,62 @@
+import React from "react";
+
+interface LcdAnnunciatorProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const LcdAnnunciator: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default LcdAnnunciator;
diff --git a/src/components/icons/assets/lightEmergency1.tsx b/src/components/icons/assets/lightEmergency1.tsx
new file mode 100644
index 000000000..f63108f67
--- /dev/null
+++ b/src/components/icons/assets/lightEmergency1.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface LightEmergency1Props extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const LightEmergency1: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default LightEmergency1;
diff --git a/src/components/icons/assets/lightEmergency2.tsx b/src/components/icons/assets/lightEmergency2.tsx
new file mode 100644
index 000000000..286d1932c
--- /dev/null
+++ b/src/components/icons/assets/lightEmergency2.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+
+interface LightEmergency2Props extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const LightEmergency2: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default LightEmergency2;
diff --git a/src/components/icons/assets/lightExit.tsx b/src/components/icons/assets/lightExit.tsx
new file mode 100644
index 000000000..ab0136b4a
--- /dev/null
+++ b/src/components/icons/assets/lightExit.tsx
@@ -0,0 +1,32 @@
+import React from "react";
+
+interface LightExitProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const LightExit: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default LightExit;
diff --git a/src/components/icons/assets/lightFixture.tsx b/src/components/icons/assets/lightFixture.tsx
new file mode 100644
index 000000000..12f6497e2
--- /dev/null
+++ b/src/components/icons/assets/lightFixture.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface LightFixtureProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const LightFixture: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default LightFixture;
diff --git a/src/components/icons/assets/lightFluorescent.tsx b/src/components/icons/assets/lightFluorescent.tsx
new file mode 100644
index 000000000..f5eb8bbb9
--- /dev/null
+++ b/src/components/icons/assets/lightFluorescent.tsx
@@ -0,0 +1,27 @@
+import React from "react";
+
+interface LightFluorescentProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const LightFluorescent: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default LightFluorescent;
diff --git a/src/components/icons/assets/lightLed.tsx b/src/components/icons/assets/lightLed.tsx
new file mode 100644
index 000000000..e14fadff2
--- /dev/null
+++ b/src/components/icons/assets/lightLed.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface LightLedProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const LightLed: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default LightLed;
diff --git a/src/components/icons/assets/lightSwitch.tsx b/src/components/icons/assets/lightSwitch.tsx
new file mode 100644
index 000000000..f5ca5d485
--- /dev/null
+++ b/src/components/icons/assets/lightSwitch.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+
+interface LightSwitchProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const LightSwitch: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default LightSwitch;
diff --git a/src/components/icons/assets/mainDistributionPanel.tsx b/src/components/icons/assets/mainDistributionPanel.tsx
new file mode 100644
index 000000000..335904f1c
--- /dev/null
+++ b/src/components/icons/assets/mainDistributionPanel.tsx
@@ -0,0 +1,94 @@
+import React from "react";
+
+interface MainDistributionPanelProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const MainDistributionPanel: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default MainDistributionPanel;
diff --git a/src/components/icons/assets/mechanical.tsx b/src/components/icons/assets/mechanical.tsx
new file mode 100644
index 000000000..8a9f89e57
--- /dev/null
+++ b/src/components/icons/assets/mechanical.tsx
@@ -0,0 +1,37 @@
+import React from "react";
+
+interface MechanicalProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Mechanical: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Mechanical;
diff --git a/src/components/icons/assets/meter.tsx b/src/components/icons/assets/meter.tsx
new file mode 100644
index 000000000..c77babb7b
--- /dev/null
+++ b/src/components/icons/assets/meter.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+
+interface MeterProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Meter: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Meter;
diff --git a/src/components/icons/assets/mopSink.tsx b/src/components/icons/assets/mopSink.tsx
new file mode 100644
index 000000000..db7f7a37f
--- /dev/null
+++ b/src/components/icons/assets/mopSink.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface MopSinkProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const MopSink: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default MopSink;
diff --git a/src/components/icons/assets/motionDetector.tsx b/src/components/icons/assets/motionDetector.tsx
new file mode 100644
index 000000000..bd9d5926b
--- /dev/null
+++ b/src/components/icons/assets/motionDetector.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+
+interface MotionDetectorProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const MotionDetector: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default MotionDetector;
diff --git a/src/components/icons/assets/motor.tsx b/src/components/icons/assets/motor.tsx
new file mode 100644
index 000000000..31fd46be7
--- /dev/null
+++ b/src/components/icons/assets/motor.tsx
@@ -0,0 +1,48 @@
+import React from "react";
+
+interface MotorProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Motor: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default Motor;
diff --git a/src/components/icons/assets/mowerLawn.tsx b/src/components/icons/assets/mowerLawn.tsx
new file mode 100644
index 000000000..169346813
--- /dev/null
+++ b/src/components/icons/assets/mowerLawn.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface MowerLawnProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const MowerLawn: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default MowerLawn;
diff --git a/src/components/icons/assets/mowerRiding.tsx b/src/components/icons/assets/mowerRiding.tsx
new file mode 100644
index 000000000..d801fbfb6
--- /dev/null
+++ b/src/components/icons/assets/mowerRiding.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface MowerRidingProps extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const MowerRiding: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+
+);
+
+export default MowerRiding;
diff --git a/src/components/icons/assets/mri1.tsx b/src/components/icons/assets/mri1.tsx
new file mode 100644
index 000000000..a99ba22b4
--- /dev/null
+++ b/src/components/icons/assets/mri1.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+interface Mri1Props extends React.SVGProps {
+ color?: string;
+ fontSize?: string | number;
+}
+
+const Mri1: React.FC = ({ color = "currentColor", fontSize = "32px", ...props }) => (
+