diff --git a/data/seeds/companies-seeds.json b/data/seeds/companies-seeds.json
new file mode 100644
index 0000000..919c89d
--- /dev/null
+++ b/data/seeds/companies-seeds.json
@@ -0,0 +1,63 @@
+{
+ "companies": [
+ {
+ "id": "company-4nkwaste",
+ "name": "4NK Water & Waste",
+ "legalName": "4NK Water & Waste",
+ "registrationNumber": "",
+ "address": "",
+ "contact": {
+ "email": "",
+ "phone": "",
+ "website": ""
+ },
+ "businessPlan": {
+ "revenues": {
+ "rawRental": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "biologicalTreatment": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "bitcoinManagement": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "fertilizers": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "wasteHeat": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "carbonCredits": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "brownfield": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "transport": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "commercialPartnerships": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "other": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ },
+ "variableCosts": {
+ "rentalServices": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "commissions": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "otherVariable": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "transport": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ },
+ "fixedCosts": {
+ "salaries": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "marketing": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "rd": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "administrative": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "otherGeneral": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ },
+ "investments": {
+ "equipment": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "technology": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "patents": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ },
+ "useOfFunds": {
+ "productDevelopment": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "marketing": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "team": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "structure": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ },
+ "kpis": {
+ "activeUsers": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "cac": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "ltv": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "breakEvenDays": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ }
+ },
+ "createdAt": "2025-01-15T00:00:00.000Z",
+ "updatedAt": "2025-01-15T00:00:00.000Z"
+ }
+ ]
+}
+
diff --git a/data/seeds/regulation-characteristics-seeds.json b/data/seeds/regulation-characteristics-seeds.json
new file mode 100644
index 0000000..6c03288
--- /dev/null
+++ b/data/seeds/regulation-characteristics-seeds.json
@@ -0,0 +1,296 @@
+{
+ "regulationCharacteristics": [
+ {
+ "id": "reg-char-001",
+ "name": "Pathogen Elimination",
+ "code": "pathogenElimination",
+ "category": "biological",
+ "description": "Elimination of pathogens and harmful microorganisms",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-002",
+ "name": "Heavy Metals Elimination",
+ "code": "heavyMetalsElimination",
+ "category": "heavyMetal",
+ "description": "Elimination of heavy metals (As, Zn, Al, Cu, etc.)",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-003",
+ "name": "Arsenic Elimination",
+ "code": "arsenicElimination",
+ "category": "heavyMetal",
+ "description": "Elimination of arsenic (As)",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-004",
+ "name": "Zinc Elimination",
+ "code": "zincElimination",
+ "category": "heavyMetal",
+ "description": "Elimination of zinc",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-005",
+ "name": "Aluminum Elimination",
+ "code": "aluminumElimination",
+ "category": "heavyMetal",
+ "description": "Elimination of aluminum",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-006",
+ "name": "Copper Elimination",
+ "code": "copperElimination",
+ "category": "heavyMetal",
+ "description": "Elimination of copper",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-007",
+ "name": "Metal Binding",
+ "code": "metalBinding",
+ "category": "heavyMetal",
+ "description": "General metal binding capability",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-008",
+ "name": "Odor Elimination",
+ "code": "odorElimination",
+ "category": "chemical",
+ "description": "Elimination of odors",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-009",
+ "name": "Oil Emulsification",
+ "code": "oilEmulsification",
+ "category": "biologicalProcess",
+ "description": "Oil emulsification capability",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-010",
+ "name": "Sodium Reduction",
+ "code": "sodiumReduction",
+ "category": "chemical",
+ "description": "Reduction of sodium (Na⁺) concentration",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-011",
+ "name": "Chlorine Reduction",
+ "code": "chlorineReduction",
+ "category": "chemical",
+ "description": "Reduction of chlorine (Cl⁻) concentration",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-012",
+ "name": "Electrical Conductivity Reduction",
+ "code": "electricalConductivityReduction",
+ "category": "chemical",
+ "description": "Reduction of electrical conductivity",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-013",
+ "name": "Turbidity Reduction",
+ "code": "turbidityReduction",
+ "category": "chemical",
+ "description": "Reduction of turbidity",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-014",
+ "name": "Medication Elimination",
+ "code": "medicationElimination",
+ "category": "chemical",
+ "description": "Elimination of medications and pharmaceutical residues",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-015",
+ "name": "Deposit Elimination",
+ "code": "depositElimination",
+ "category": "chemical",
+ "description": "Elimination of deposits",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-016",
+ "name": "Sulfide Elimination",
+ "code": "sulfideElimination",
+ "category": "chemical",
+ "description": "Elimination of sulfides",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-017",
+ "name": "Methane Elimination",
+ "code": "methaneElimination",
+ "category": "chemical",
+ "description": "Elimination of methane",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-018",
+ "name": "CO₂ Elimination",
+ "code": "co2Elimination",
+ "category": "chemical",
+ "description": "Elimination of CO₂",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-019",
+ "name": "Polyphenol Elimination",
+ "code": "polyphenolElimination",
+ "category": "chemical",
+ "description": "Elimination of polyphenols",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-020",
+ "name": "Refractory Fractions Elimination",
+ "code": "refractoryFractionsElimination",
+ "category": "chemical",
+ "description": "Elimination of refractory structural fractions (lignin, alginates, fucoidans, crystalline cellulose, and insoluble fibers)",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-021",
+ "name": "Low C:N Ratio Enrichment",
+ "code": "lowCNRatioEnrichment",
+ "category": "chemical",
+ "description": "Enrichment for low C:N ratio",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-022",
+ "name": "Microbiological Competition",
+ "code": "microbiologicalCompetition",
+ "category": "biologicalProcess",
+ "description": "Microbiological competition capability",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-023",
+ "name": "Acidity Reduction",
+ "code": "acidityReduction",
+ "category": "ph",
+ "description": "Reduction of acidity",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-024",
+ "name": "pH Increase",
+ "code": "phIncrease",
+ "category": "ph",
+ "description": "Increase of pH level",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-025",
+ "name": "pH Reduction",
+ "code": "phReduction",
+ "category": "ph",
+ "description": "Reduction of pH level",
+ "isBoolean": true,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-026",
+ "name": "Total Nitrogen (N or NTK)",
+ "code": "nitrogen",
+ "category": "nutrient",
+ "description": "Total Nitrogen content",
+ "unit": "kg/t",
+ "isBoolean": false,
+ "minValue": 0,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-027",
+ "name": "Ammoniacal Nitrogen (N-NH₄)",
+ "code": "ammoniacalNitrogen",
+ "category": "nutrient",
+ "description": "Ammoniacal Nitrogen - Indicator of rapid effect",
+ "unit": "kg/t",
+ "isBoolean": false,
+ "minValue": 0,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-028",
+ "name": "Total Phosphorus (P)",
+ "code": "phosphorus",
+ "category": "nutrient",
+ "description": "Total Phosphorus content",
+ "unit": "kg/t",
+ "isBoolean": false,
+ "minValue": 0,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-029",
+ "name": "Total Potassium (K)",
+ "code": "potassium",
+ "category": "nutrient",
+ "description": "Total Potassium content",
+ "unit": "kg/t",
+ "isBoolean": false,
+ "minValue": 0,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-030",
+ "name": "Carbon:Nitrogen Ratio (C:N)",
+ "code": "carbonNitrogenRatio",
+ "category": "nutrient",
+ "description": "Carbon to Nitrogen ratio",
+ "isBoolean": false,
+ "minValue": 0,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ },
+ {
+ "id": "reg-char-031",
+ "name": "pH Adjustment",
+ "code": "phAdjustment",
+ "category": "ph",
+ "description": "Direct pH adjustment value (-14 to 14). Negative values reduce pH, positive values increase pH",
+ "unit": "pH",
+ "isBoolean": false,
+ "minValue": -14,
+ "maxValue": 14,
+ "createdAt": "2025-01-15T00:00:00.000Z"
+ }
+ ]
+}
+
diff --git a/data_schemas.md b/data_schemas.md
index 1d956df..7095bfe 100644
--- a/data_schemas.md
+++ b/data_schemas.md
@@ -11,10 +11,16 @@
"wastes": [],
"regulators": [],
"services": [],
+ "wasteOrigins": [],
+ "transporters": [],
+ "moduleComponents": [],
+ "ecosystems": [],
+ "regulationCharacteristics": [],
"treatmentSites": [],
"wasteSites": [],
"investors": [],
"administrativeProcedures": [],
+ "companies": [],
"projects": []
}
```
@@ -38,18 +44,53 @@
"id": "string (UUID)",
"name": "string",
"originType": "string (animals|markets|restaurants|other)",
- "originSubType": "string (specific type)",
+ "originSubType": "string (specific type, optional)",
+ "wasteOriginId": "string (optional, reference to WasteOrigin)",
"originUnitsPer1000m3Methane": "number",
"bmp": "number (Nm³ CH₄/kg VS)",
"waterPercentage": "number (0-100)",
- "regulationNeeds": "string[]",
+ "regulationNeeds": "string[] (array of regulation need codes)",
+ "regulationCharacteristicIds": "string[] (optional, references to RegulationCharacteristic)",
"regulatoryCharacteristics": {
"nitrogen": "number (optional)",
+ "ammoniacalNitrogen": "number (optional)",
"phosphorus": "number (optional)",
"potassium": "number (optional)",
- "carbonNitrogenRatio": "number (optional)"
+ "carbonNitrogenRatio": "number (optional)",
+ "arsenicElimination": "boolean (optional)",
+ "zincElimination": "boolean (optional)",
+ "aluminumElimination": "boolean (optional)",
+ "copperElimination": "boolean (optional)",
+ "heavyMetalsElimination": "boolean (optional)",
+ "pathogenElimination": "boolean (optional)",
+ "lowCNRatioEnrichment": "boolean (optional)",
+ "medicationElimination": "boolean (optional)",
+ "depositElimination": "boolean (optional)",
+ "odorElimination": "boolean (optional)",
+ "turbidityReduction": "boolean (optional)",
+ "sodiumReduction": "boolean (optional)",
+ "chlorineReduction": "boolean (optional)",
+ "electricalConductivityReduction": "boolean (optional)",
+ "sulfideElimination": "boolean (optional)",
+ "methaneElimination": "boolean (optional)",
+ "co2Elimination": "boolean (optional)",
+ "polyphenolElimination": "boolean (optional)",
+ "refractoryFractionsElimination": "boolean (optional)",
+ "microbiologicalCompetition": "boolean (optional)",
+ "oilEmulsification": "boolean (optional)",
+ "acidityReduction": "boolean (optional)",
+ "phIncrease": "boolean (optional)",
+ "phReduction": "boolean (optional)",
+ "phAdjustment": "number (optional, -14 to 14)"
},
+ "regulators": [
+ {
+ "regulatorId": "string (reference to NaturalRegulator)",
+ "percentage": "number (0-100, percentage of global volume)"
+ }
+ ],
"maxStorageDuration": "number (days)",
+ "businessPlan": "BusinessPlan (optional)",
"notes": "string (optional)",
"createdAt": "ISO 8601 date string",
"updatedAt": "ISO 8601 date string"
@@ -62,14 +103,39 @@
"id": "string (UUID)",
"name": "string",
"type": "string",
+ "regulationCharacteristicIds": "string[] (optional, references to RegulationCharacteristic)",
"regulatoryCharacteristics": {
"nitrogen": "number (optional)",
+ "ammoniacalNitrogen": "number (optional)",
"phosphorus": "number (optional)",
"potassium": "number (optional)",
"carbonNitrogenRatio": "number (optional)",
- "phAdjustment": "number (optional, -14 to 14)",
+ "arsenicElimination": "boolean (optional)",
+ "zincElimination": "boolean (optional)",
+ "aluminumElimination": "boolean (optional)",
+ "copperElimination": "boolean (optional)",
+ "heavyMetalsElimination": "boolean (optional)",
"metalBinding": "boolean (optional)",
- "pathogenReduction": "boolean (optional)"
+ "pathogenReduction": "boolean (optional)",
+ "lowCNRatioEnrichment": "boolean (optional)",
+ "medicationElimination": "boolean (optional)",
+ "depositElimination": "boolean (optional)",
+ "odorElimination": "boolean (optional)",
+ "turbidityReduction": "boolean (optional)",
+ "sodiumReduction": "boolean (optional)",
+ "chlorineReduction": "boolean (optional)",
+ "electricalConductivityReduction": "boolean (optional)",
+ "sulfideElimination": "boolean (optional)",
+ "methaneElimination": "boolean (optional)",
+ "co2Elimination": "boolean (optional)",
+ "polyphenolElimination": "boolean (optional)",
+ "refractoryFractionsElimination": "boolean (optional)",
+ "microbiologicalCompetition": "boolean (optional)",
+ "oilEmulsification": "boolean (optional)",
+ "acidityReduction": "boolean (optional)",
+ "phIncrease": "boolean (optional)",
+ "phReduction": "boolean (optional)",
+ "phAdjustment": "number (optional, -14 to 14)"
},
"applicationConditions": "string",
"dosageRequirements": {
@@ -77,6 +143,7 @@
"max": "number",
"unit": "string (kg/t|L/t|%)"
},
+ "businessPlan": "BusinessPlan (optional)",
"notes": "string (optional)",
"createdAt": "ISO 8601 date string",
"updatedAt": "ISO 8601 date string"
@@ -101,13 +168,219 @@
"year9": "number (€/module/year)",
"year10": "number (€/module/year)"
},
+ "businessPlan": "BusinessPlan (optional)",
"notes": "string (optional)",
"createdAt": "ISO 8601 date string",
"updatedAt": "ISO 8601 date string"
}
```
-### 2.5 Treatment Site Schema
+### 2.5 WasteOrigin Schema
+```json
+{
+ "id": "string (UUID)",
+ "name": "string",
+ "type": "string",
+ "subType": "string (optional)",
+ "description": "string (optional)",
+ "createdAt": "ISO 8601 date string",
+ "updatedAt": "ISO 8601 date string"
+}
+```
+
+### 2.6 Transporter Schema
+```json
+{
+ "id": "string (UUID)",
+ "name": "string",
+ "type": "string",
+ "description": "string (optional)",
+ "contact": {
+ "name": "string",
+ "email": "string (optional)",
+ "phone": "string (optional)",
+ "address": "string (optional)"
+ },
+ "capacity": "string (e.g., '10 tons', '20 m³')",
+ "transportConditions": "string (e.g., 'refrigerated', 'hazardous materials')",
+ "businessPlan": "BusinessPlan (optional)",
+ "createdAt": "ISO 8601 date string",
+ "updatedAt": "ISO 8601 date string"
+}
+```
+
+### 2.7 ModuleComponent Schema
+```json
+{
+ "id": "string (UUID)",
+ "name": "string",
+ "type": "string (firstMethanization|waterEvaporation|bioremediationEcosystem1|bioremediationEcosystem2|bioremediationEcosystem3|secondMethanization|waterEvaporationComposting|waterUvcTreatment|waterStorage|spirulinaWaterStorage|solarPanels|bitcoinMining)",
+ "isOptional": "boolean",
+ "defaultDuration": "number",
+ "durationUnit": "string (days|hours)",
+ "waterConsumptionPerDay": {
+ "january": "number (optional)",
+ "february": "number (optional)",
+ "march": "number (optional)",
+ "april": "number (optional)",
+ "may": "number (optional)",
+ "june": "number (optional)",
+ "july": "number (optional)",
+ "august": "number (optional)",
+ "september": "number (optional)",
+ "october": "number (optional)",
+ "november": "number (optional)",
+ "december": "number (optional)"
+ },
+ "heatConsumptionPerDay": {
+ "january": "number (optional)",
+ "february": "number (optional)",
+ "march": "number (optional)",
+ "april": "number (optional)",
+ "may": "number (optional)",
+ "june": "number (optional)",
+ "july": "number (optional)",
+ "august": "number (optional)",
+ "september": "number (optional)",
+ "october": "number (optional)",
+ "november": "number (optional)",
+ "december": "number (optional)"
+ },
+ "powerConsumption": {
+ "kwh": "number (optional)",
+ "kw": "number (optional)"
+ },
+ "totalWetWasteCapacity": "number (optional, tons)",
+ "totalDryWasteCapacity": "number (optional, tons)",
+ "totalCompostCapacity": "number (optional, tons)",
+ "totalWaterCapacity": "number (optional, m³ or L)",
+ "methaneProduction": "number (optional, m³/day or m³/cycle)",
+ "addedBiomassProduction": "number (optional, tons/day or tons/cycle)",
+ "waterProduction": "number (optional, m³/day or L/day)",
+ "heatProduction": "number (optional, kJ/day or kWh/day)",
+ "immobilizationDuration": "number (optional, days)",
+ "wasteTypes": "string[] (optional, array of waste IDs)",
+ "dimensions": {
+ "length": "number (optional, meters)",
+ "height": "number (optional, meters)",
+ "width": "number (optional, meters)"
+ },
+ "groundSurface": "number (optional, m²)",
+ "heatNeedsPerDay": {
+ "january": "number (optional)",
+ "february": "number (optional)",
+ "march": "number (optional)",
+ "april": "number (optional)",
+ "may": "number (optional)",
+ "june": "number (optional)",
+ "july": "number (optional)",
+ "august": "number (optional)",
+ "september": "number (optional)",
+ "october": "number (optional)",
+ "november": "number (optional)",
+ "december": "number (optional)"
+ },
+ "coolingNeedsPerDay": {
+ "january": "number (optional)",
+ "february": "number (optional)",
+ "march": "number (optional)",
+ "april": "number (optional)",
+ "may": "number (optional)",
+ "june": "number (optional)",
+ "july": "number (optional)",
+ "august": "number (optional)",
+ "september": "number (optional)",
+ "october": "number (optional)",
+ "november": "number (optional)",
+ "december": "number (optional)"
+ },
+ "productionPower": {
+ "kwh": "number (optional)",
+ "kw": "number (optional)"
+ },
+ "annualExploitationETP": "number (optional, Full Time Equivalent)",
+ "annualMaintenanceETP": "number (optional, Full Time Equivalent)",
+ "annualSupervisionETP": "number (optional, Full Time Equivalent)",
+ "totalMaterialCost": "number (optional, CAPEX, €/year)",
+ "annualExploitationConsumablesCost": "number (optional, €/year)",
+ "annualMaintenanceConsumablesCost": "number (optional, €/year)",
+ "lifetime": "number (optional, years)",
+ "regulators": [
+ {
+ "regulatorId": "string (reference to NaturalRegulator)",
+ "percentage": "number (0-100, percentage of total volume)",
+ "dosage": "number (optional, specific dosage)"
+ }
+ ],
+ "services": [
+ {
+ "serviceId": "string (reference to Service)",
+ "notes": "string (optional)"
+ }
+ ],
+ "administrativeProcedures": [
+ {
+ "procedureId": "string (reference to AdministrativeProcedure)",
+ "status": "string (toDo|done|na)",
+ "notes": "string (optional)"
+ }
+ ],
+ "regulationCharacteristicIds": "string[] (optional, references to RegulationCharacteristic)",
+ "businessPlan": "BusinessPlan (optional)",
+ "createdAt": "ISO 8601 date string",
+ "updatedAt": "ISO 8601 date string"
+}
+```
+
+### 2.8 Ecosystem Schema
+```json
+{
+ "id": "string (UUID)",
+ "name": "string",
+ "description": "string (optional)",
+ "primaryRegulationNeeds": [
+ "string (regulation need code)",
+ "string (regulation need code)",
+ "string (regulation need code)"
+ ],
+ "regulationCharacteristicIds": "string[] (optional, references to RegulationCharacteristic)",
+ "regulators": [
+ {
+ "regulatorId": "string (reference to NaturalRegulator)",
+ "percentage": "number (0-100, percentage of contribution)",
+ "role": "string (primary|secondary|support)"
+ }
+ ],
+ "effectiveness": {
+ "regulationNeedCode": "number (0-100, percentage effectiveness)"
+ },
+ "compatibleWasteTypes": "string[] (optional, array of waste IDs, empty means all)",
+ "applicationConditions": "string (optional)",
+ "treatmentDuration": "number (optional, days)",
+ "businessPlan": "BusinessPlan (optional)",
+ "createdAt": "ISO 8601 date string",
+ "updatedAt": "ISO 8601 date string"
+}
+```
+
+### 2.9 RegulationCharacteristic Schema
+```json
+{
+ "id": "string (UUID)",
+ "name": "string",
+ "code": "string (unique identifier, e.g., 'pathogenElimination', 'heavyMetalsElimination')",
+ "category": "string (nutrient|heavyMetal|biological|chemical|biologicalProcess|ph|other)",
+ "description": "string (optional)",
+ "unit": "string (optional, e.g., 'kg/t', '%', 'pH')",
+ "isBoolean": "boolean (true for capability/need, false for numeric value)",
+ "minValue": "number (optional, minimum value if numeric)",
+ "maxValue": "number (optional, maximum value if numeric)",
+ "createdAt": "ISO 8601 date string",
+ "updatedAt": "ISO 8601 date string"
+}
+```
+
+### 2.10 Treatment Site Schema
```json
{
"id": "string (UUID)",
@@ -137,13 +410,36 @@
"number (December, °C)"
],
"subscribedServices": "string[] (array of service IDs)",
+ "transporters": [
+ {
+ "transporterId": "string (reference to Transporter)",
+ "isPrimary": "boolean (optional)",
+ "notes": "string (optional)"
+ }
+ ],
+ "investors": [
+ {
+ "investorId": "string (reference to Investor)",
+ "status": "string (toBeApproached|loiOk|inProgress|completed)",
+ "amount": "number (€)",
+ "notes": "string (optional)"
+ }
+ ],
+ "administrativeProcedures": [
+ {
+ "procedureId": "string (reference to AdministrativeProcedure)",
+ "status": "string (toDo|done|na)",
+ "notes": "string (optional)"
+ }
+ ],
+ "businessPlan": "BusinessPlan (optional)",
"notes": "string (optional)",
"createdAt": "ISO 8601 date string",
"updatedAt": "ISO 8601 date string"
}
```
-### 2.6 Waste Site Schema
+### 2.11 Waste Site Schema
```json
{
"id": "string (UUID)",
@@ -163,13 +459,21 @@
},
"collectionType": "string",
"distance": "number (km, from treatment site)",
+ "transporters": [
+ {
+ "transporterId": "string (reference to Transporter)",
+ "isPrimary": "boolean (optional)",
+ "notes": "string (optional)"
+ }
+ ],
+ "businessPlan": "BusinessPlan (optional)",
"notes": "string (optional)",
"createdAt": "ISO 8601 date string",
"updatedAt": "ISO 8601 date string"
}
```
-### 2.7 Investor Schema
+### 2.12 Investor Schema
```json
{
"id": "string (UUID)",
@@ -195,7 +499,7 @@
}
```
-### 2.8 Administrative Procedure Schema
+### 2.13 Administrative Procedure Schema
```json
{
"id": "string (UUID)",
@@ -209,13 +513,81 @@
"organization": "string (optional)"
},
"regions": "string[]",
+ "businessPlan": "BusinessPlan (optional)",
"notes": "string (optional)",
"createdAt": "ISO 8601 date string",
"updatedAt": "ISO 8601 date string"
}
```
-### 2.9 Project Schema
+### 2.14 Company Schema
+```json
+{
+ "id": "string (UUID)",
+ "name": "string",
+ "legalName": "string (optional)",
+ "registrationNumber": "string (optional)",
+ "address": "string (optional)",
+ "contact": {
+ "email": "string (optional)",
+ "phone": "string (optional)",
+ "website": "string (optional)"
+ },
+ "businessPlan": "BusinessPlan (optional)",
+ "createdAt": "ISO 8601 date string",
+ "updatedAt": "ISO 8601 date string"
+}
+```
+
+### 2.15 BusinessPlan Schema
+```json
+{
+ "revenues": {
+ "rawRental": "number[] (10 years, €/year)",
+ "biologicalTreatment": "number[] (10 years, €/year)",
+ "bitcoinManagement": "number[] (10 years, €/year)",
+ "fertilizers": "number[] (10 years, €/year)",
+ "wasteHeat": "number[] (10 years, €/year)",
+ "carbonCredits": "number[] (10 years, €/year)",
+ "brownfield": "number[] (10 years, €/year)",
+ "transport": "number[] (10 years, €/year)",
+ "commercialPartnerships": "number[] (10 years, €/year)",
+ "other": "number[] (10 years, €/year)"
+ },
+ "variableCosts": {
+ "rentalServices": "number[] (10 years, €/year)",
+ "commissions": "number[] (10 years, €/year)",
+ "otherVariable": "number[] (10 years, €/year)",
+ "transport": "number[] (10 years, €/year)"
+ },
+ "fixedCosts": {
+ "salaries": "number[] (10 years, €/year)",
+ "marketing": "number[] (10 years, €/year)",
+ "rd": "number[] (10 years, €/year)",
+ "administrative": "number[] (10 years, €/year)",
+ "otherGeneral": "number[] (10 years, €/year)"
+ },
+ "investments": {
+ "equipment": "number[] (10 years, €/year)",
+ "technology": "number[] (10 years, €/year)",
+ "patents": "number[] (10 years, €/year)"
+ },
+ "useOfFunds": {
+ "productDevelopment": "number[] (10 years, €/year)",
+ "marketing": "number[] (10 years, €/year)",
+ "team": "number[] (10 years, €/year)",
+ "structure": "number[] (10 years, €/year)"
+ },
+ "kpis": {
+ "activeUsers": "number[] (10 years)",
+ "cac": "number[] (10 years, €)",
+ "ltv": "number[] (10 years, €)",
+ "breakEvenDays": "number[] (10 years)"
+ }
+}
+```
+
+### 2.16 Project Schema
```json
{
"id": "string (UUID)",
@@ -245,50 +617,7 @@
"amount": "number (€)"
}
],
- "businessPlan": {
- "revenues": {
- "rawRental": "number[] (10 years, €/year)",
- "biologicalTreatment": "number[] (10 years, €/year)",
- "bitcoinManagement": "number[] (10 years, €/year)",
- "fertilizers": "number[] (10 years, €/year)",
- "wasteHeat": "number[] (10 years, €/year)",
- "carbonCredits": "number[] (10 years, €/year)",
- "brownfield": "number[] (10 years, €/year)",
- "transport": "number[] (10 years, €/year)",
- "commercialPartnerships": "number[] (10 years, €/year)",
- "other": "number[] (10 years, €/year)"
- },
- "variableCosts": {
- "rentalServices": "number[] (10 years, €/year)",
- "commissions": "number[] (10 years, €/year)",
- "otherVariable": "number[] (10 years, €/year)",
- "transport": "number[] (10 years, €/year)"
- },
- "fixedCosts": {
- "salaries": "number[] (10 years, €/year)",
- "marketing": "number[] (10 years, €/year)",
- "rd": "number[] (10 years, €/year)",
- "administrative": "number[] (10 years, €/year)",
- "otherGeneral": "number[] (10 years, €/year)"
- },
- "investments": {
- "equipment": "number[] (10 years, €/year)",
- "technology": "number[] (10 years, €/year)",
- "patents": "number[] (10 years, €/year)"
- },
- "useOfFunds": {
- "productDevelopment": "number[] (10 years, €/year)",
- "marketing": "number[] (10 years, €/year)",
- "team": "number[] (10 years, €/year)",
- "structure": "number[] (10 years, €/year)"
- },
- "kpis": {
- "activeUsers": "number[] (10 years)",
- "cac": "number[] (10 years, €)",
- "ltv": "number[] (10 years, €)",
- "breakEvenDays": "number[] (10 years)"
- }
- },
+ "businessPlan": "BusinessPlan (optional)",
"notes": "string (optional)",
"createdAt": "ISO 8601 date string",
"updatedAt": "ISO 8601 date string"
@@ -304,35 +633,102 @@ Project (1) ──→ (N) WasteSite
Project (1) ──→ (N) AdministrativeProcedure (with status)
Project (1) ──→ (N) Investment (with investor reference and status)
Project (1) ──→ (1) Waste (optional override)
+Project (1) ──→ (1) BusinessPlan (optional)
```
### 3.2 Treatment Site Relations
```
TreatmentSite (1) ──→ (N) Service (subscribed services)
TreatmentSite (1) ──→ (N) Project
+TreatmentSite (1) ──→ (N) Transporter (with isPrimary flag)
+TreatmentSite (1) ──→ (N) Investor (with status and amount)
+TreatmentSite (1) ──→ (N) AdministrativeProcedure (with status)
+TreatmentSite (1) ──→ (1) BusinessPlan (optional)
```
### 3.3 Waste Site Relations
```
WasteSite (1) ──→ (1) Waste (waste type)
WasteSite (N) ──→ (1) Project
+WasteSite (1) ──→ (N) Transporter (with isPrimary flag)
+WasteSite (1) ──→ (1) BusinessPlan (optional)
```
-### 3.4 Service Relations
+### 3.4 Waste Relations
+```
+Waste (N) ──→ (1) WasteOrigin (optional, allows n wastes to 1 origin)
+Waste (1) ──→ (N) NaturalRegulator (with percentage of global volume)
+Waste (1) ──→ (N) RegulationCharacteristic (via regulationCharacteristicIds)
+Waste (1) ──→ (1) BusinessPlan (optional)
+```
+
+### 3.5 Natural Regulator Relations
+```
+NaturalRegulator (1) ──→ (N) RegulationCharacteristic (via regulationCharacteristicIds)
+NaturalRegulator (1) ──→ (N) Waste (via WasteRegulatorAssociation)
+NaturalRegulator (1) ──→ (N) Ecosystem (via EcosystemRegulatorAssociation)
+NaturalRegulator (1) ──→ (N) ModuleComponent (via ModuleComponentRegulatorAssociation)
+NaturalRegulator (1) ──→ (1) BusinessPlan (optional)
+```
+
+### 3.6 Module Component Relations
+```
+ModuleComponent (1) ──→ (N) NaturalRegulator (with percentage and optional dosage)
+ModuleComponent (1) ──→ (N) Service (with optional notes)
+ModuleComponent (1) ──→ (N) AdministrativeProcedure (with status and notes)
+ModuleComponent (1) ──→ (N) RegulationCharacteristic (via regulationCharacteristicIds)
+ModuleComponent (1) ──→ (N) Waste (compatible waste types)
+ModuleComponent (1) ──→ (1) BusinessPlan (optional)
+```
+
+### 3.7 Ecosystem Relations
+```
+Ecosystem (1) ──→ (N) NaturalRegulator (with percentage and role)
+Ecosystem (1) ──→ (N) RegulationCharacteristic (via regulationCharacteristicIds)
+Ecosystem (1) ──→ (N) Waste (compatible waste types, optional, empty means all)
+Ecosystem (1) ──→ (1) BusinessPlan (optional)
+```
+
+### 3.8 Service Relations
```
Service (N) ──→ (1) TreatmentSite (subscribed)
-Service (N) ──→ (N) Project (used in business plan)
+Service (N) ──→ (N) ModuleComponent (via ModuleComponentServiceAssociation)
+Service (1) ──→ (1) BusinessPlan (optional)
```
-### 3.5 Investor Relations
+### 3.9 Transporter Relations
+```
+Transporter (N) ──→ (1) TreatmentSite (via SiteTransporterAssociation)
+Transporter (N) ──→ (1) WasteSite (via SiteTransporterAssociation)
+Transporter (1) ──→ (1) BusinessPlan (optional)
+```
+
+### 3.10 Investor Relations
```
Investor (1) ──→ (N) Investment (in projects)
Investor (1) ──→ (N) Waste (waste type preferences)
+Investor (N) ──→ (1) TreatmentSite (via TreatmentSiteInvestorAssociation)
```
-### 3.6 Administrative Procedure Relations
+### 3.11 Administrative Procedure Relations
```
AdministrativeProcedure (1) ──→ (N) Project (with status per project)
+AdministrativeProcedure (1) ──→ (N) TreatmentSite (via TreatmentSiteProcedureAssociation)
+AdministrativeProcedure (1) ──→ (N) ModuleComponent (via ModuleComponentProcedureAssociation)
+AdministrativeProcedure (1) ──→ (1) BusinessPlan (optional)
+```
+
+### 3.12 RegulationCharacteristic Relations
+```
+RegulationCharacteristic (1) ──→ (N) Waste (via regulationCharacteristicIds)
+RegulationCharacteristic (1) ──→ (N) NaturalRegulator (via regulationCharacteristicIds)
+RegulationCharacteristic (1) ──→ (N) Ecosystem (via regulationCharacteristicIds)
+RegulationCharacteristic (1) ──→ (N) ModuleComponent (via regulationCharacteristicIds)
+```
+
+### 3.13 Company Relations
+```
+Company (1) ──→ (1) BusinessPlan (optional)
```
## 4. Data Validation Rules
@@ -340,8 +736,13 @@ AdministrativeProcedure (1) ──→ (N) Project (with status per project)
### 4.1 Required Fields
- All entities must have: `id`, `createdAt`
- Projects must have: `name`, `startDate`, `endDate`, `treatmentSiteId`, `numberOfModules`
-- Wastes must have: `name`, `bmp`, `waterPercentage`
+- Wastes must have: `name`, `bmp`, `waterPercentage`, `regulationNeeds`
- Services must have: `name`, `type`, `pricing` (all 10 years)
+- Natural Regulators must have: `name`, `type`, `applicationConditions`, `dosageRequirements`
+- Module Components must have: `name`, `type`, `isOptional`, `defaultDuration`, `durationUnit`
+- Ecosystems must have: `name`, `primaryRegulationNeeds` (exactly 3), `regulators`, `effectiveness`
+- Regulation Characteristics must have: `name`, `code`, `category`
+- Companies must have: `name`
### 4.2 Value Constraints
- `waterPercentage`: 0-100
@@ -350,6 +751,12 @@ AdministrativeProcedure (1) ──→ (N) Project (with status per project)
- `startDate` < `endDate`
- All monetary values: >= 0
- All quantities: >= 0
+- Regulator percentages in WasteRegulatorAssociation: 0-100, total <= 100
+- Regulator percentages in EcosystemRegulatorAssociation: 0-100, total should equal 100
+- Regulator percentages in ModuleComponentRegulatorAssociation: 0-100, total <= 100
+- Effectiveness values: 0-100
+- pH adjustment: -14 to 14
+- `primaryRegulationNeeds` in Ecosystem: exactly 3 unique values
### 4.3 Reference Integrity
- `treatmentSiteId` must exist in `treatmentSites`
@@ -358,6 +765,12 @@ AdministrativeProcedure (1) ──→ (N) Project (with status per project)
- All `procedureId` must exist in `administrativeProcedures`
- All `investorId` must exist in `investors`
- All `serviceId` in subscribedServices must exist in `services`
+- All `regulatorId` in associations must exist in `regulators`
+- All `transporterId` in associations must exist in `transporters`
+- All `wasteOriginId` must exist in `wasteOrigins`
+- All `regulationCharacteristicIds` must exist in `regulationCharacteristics`
+- All `wasteType` in WasteSite must exist in `wastes`
+- All `wasteTypes` in ModuleComponent and Ecosystem must exist in `wastes`
## 5. Import/Export Format
@@ -397,10 +810,39 @@ Database: "4nkwaste_simulator"
- ObjectStore: "wastes"
- ObjectStore: "regulators"
- ObjectStore: "services"
+ - ObjectStore: "wasteOrigins"
+ - ObjectStore: "transporters"
+ - ObjectStore: "moduleComponents"
+ - ObjectStore: "ecosystems"
+ - ObjectStore: "regulationCharacteristics"
- ObjectStore: "treatmentSites"
- ObjectStore: "wasteSites"
- ObjectStore: "investors"
- ObjectStore: "administrativeProcedures"
+ - ObjectStore: "companies"
- ObjectStore: "projects"
- ObjectStore: "users"
```
+
+## 7. Business Plan Aggregation
+
+Business plans can be associated with multiple entities:
+- **Waste**: Business plan for waste-specific operations
+- **NaturalRegulator**: Business plan for regulator-specific operations
+- **Service**: Business plan for service-specific operations
+- **Transporter**: Business plan for transport operations
+- **ModuleComponent**: Business plan for component-specific operations
+- **Ecosystem**: Business plan for ecosystem-specific operations
+- **TreatmentSite**: Business plan for site-specific operations
+- **WasteSite**: Business plan for waste site operations
+- **AdministrativeProcedure**: Business plan for procedure-related operations
+- **Company**: Business plan for company-level operations
+- **Project**: Business plan for project-level operations
+
+The **Yields** page aggregates all business plans from entities associated with a project:
+- Project's own business plan
+- Treatment Site's business plan
+- All Waste Sites' business plans
+- All Module Components' business plans (if used in the project)
+- All Services' business plans (if subscribed)
+- Company's business plan (if applicable)
diff --git a/src/App.tsx b/src/App.tsx
index 7b9e758..3750457 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -10,6 +10,7 @@ import WasteOriginsConfigurationPage from './pages/configuration/WasteOriginsCon
import TransportersConfigurationPage from './pages/configuration/TransportersConfigurationPage'
import ModuleComponentsConfigurationPage from './pages/configuration/ModuleComponentsConfigurationPage'
import EcosystemsConfigurationPage from './pages/configuration/EcosystemsConfigurationPage'
+import RegulationCharacteristicsConfigurationPage from './pages/configuration/RegulationCharacteristicsConfigurationPage'
import ProjectListPage from './pages/projects/ProjectListPage'
import ProjectConfigurationPage from './pages/projects/ProjectConfigurationPage'
import TreatmentSitesPage from './pages/projects/TreatmentSitesPage'
@@ -44,6 +45,7 @@ function App() {
} />
} />
} />
+ } />
} />
} />
} />
diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx
index 8cf04eb..431052a 100644
--- a/src/components/layout/Sidebar.tsx
+++ b/src/components/layout/Sidebar.tsx
@@ -40,6 +40,10 @@ export default function Sidebar() {
🌱
Ecosystems
+
+ 📋
+ Regulation Characteristics
+
diff --git a/src/pages/YieldsPage.css b/src/pages/YieldsPage.css
index 87f2f3d..8814d17 100644
--- a/src/pages/YieldsPage.css
+++ b/src/pages/YieldsPage.css
@@ -82,3 +82,18 @@
white-space: pre-wrap;
line-height: 1.6;
}
+
+.business-plan-summary {
+ margin-top: var(--spacing-lg);
+}
+
+.business-plan-summary h3 {
+ font-size: 1.25rem;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin: var(--spacing-xl) 0 var(--spacing-md) 0;
+}
+
+.business-plan-summary h3:first-child {
+ margin-top: 0;
+}
\ No newline at end of file
diff --git a/src/pages/YieldsPage.tsx b/src/pages/YieldsPage.tsx
index e0bfa14..36d06e7 100644
--- a/src/pages/YieldsPage.tsx
+++ b/src/pages/YieldsPage.tsx
@@ -1,10 +1,12 @@
import { useState } from 'react'
import { useStorage } from '@/hooks/useStorage'
-import { Project } from '@/types'
+import { Project, BusinessPlan } from '@/types'
import { calculateYields, YieldsResult } from '@/utils/calculations/yields'
+import { aggregateProjectBusinessPlans } from '@/utils/calculations/businessPlanAggregation'
import Card from '@/components/base/Card'
import Select from '@/components/base/Select'
-import { formatNumber } from '@/utils/formatters'
+import Table from '@/components/base/Table'
+import { formatNumber, formatCurrency } from '@/utils/formatters'
import './YieldsPage.css'
export default function YieldsPage() {
@@ -14,6 +16,14 @@ export default function YieldsPage() {
const projects = data?.projects || []
const wastes = data?.wastes || []
const treatmentSites = data?.treatmentSites || []
+ const services = data?.services || []
+ const transporters = data?.transporters || []
+ const moduleComponents = data?.moduleComponents || []
+ const ecosystems = data?.ecosystems || []
+ const regulators = data?.regulators || []
+ const wasteSites = data?.wasteSites || []
+ const administrativeProcedures = data?.administrativeProcedures || []
+ const companies = data?.companies || []
const selectedProject = projects.find((p) => p.id === selectedProjectId)
const selectedWaste = selectedProject?.wasteCharacteristicsOverride?.wasteId
@@ -27,6 +37,22 @@ export default function YieldsPage() {
? calculateYields(selectedProject, selectedWaste, selectedTreatmentSite)
: null
+ // Aggregate all business plans for the selected project
+ const aggregatedBusinessPlan: BusinessPlan | null = selectedProject
+ ? aggregateProjectBusinessPlans(
+ selectedProject,
+ services,
+ transporters,
+ moduleComponents,
+ ecosystems,
+ regulators,
+ treatmentSites,
+ wasteSites,
+ administrativeProcedures,
+ companies
+ )
+ : null
+
const projectOptions = projects.map((project) => ({
value: project.id,
label: project.name,
@@ -181,6 +207,98 @@ export default function YieldsPage() {
/>
+
+ {aggregatedBusinessPlan && (
+
+
+
Revenues (€/year)
+
`Year ${idx + 1}` },
+ { key: 'rawRental', header: 'Raw Rental', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.revenues.rawRental[idx]) },
+ { key: 'biologicalTreatment', header: 'Biological Treatment', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.revenues.biologicalTreatment[idx]) },
+ { key: 'bitcoinManagement', header: 'Bitcoin Management', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.revenues.bitcoinManagement[idx]) },
+ { key: 'fertilizers', header: 'Fertilizers', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.revenues.fertilizers[idx]) },
+ { key: 'wasteHeat', header: 'Waste Heat', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.revenues.wasteHeat[idx]) },
+ { key: 'carbonCredits', header: 'Carbon Credits', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.revenues.carbonCredits[idx]) },
+ { key: 'brownfield', header: 'Brownfield', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.revenues.brownfield[idx]) },
+ { key: 'transport', header: 'Transport', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.revenues.transport[idx]) },
+ { key: 'commercialPartnerships', header: 'Commercial Partnerships', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.revenues.commercialPartnerships[idx]) },
+ { key: 'other', header: 'Other', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.revenues.other[idx]) },
+ {
+ key: 'total',
+ header: 'Total Revenues',
+ render: (_: any, idx: number) => {
+ const total =
+ aggregatedBusinessPlan.revenues.rawRental[idx] +
+ aggregatedBusinessPlan.revenues.biologicalTreatment[idx] +
+ aggregatedBusinessPlan.revenues.bitcoinManagement[idx] +
+ aggregatedBusinessPlan.revenues.fertilizers[idx] +
+ aggregatedBusinessPlan.revenues.wasteHeat[idx] +
+ aggregatedBusinessPlan.revenues.carbonCredits[idx] +
+ aggregatedBusinessPlan.revenues.brownfield[idx] +
+ aggregatedBusinessPlan.revenues.transport[idx] +
+ aggregatedBusinessPlan.revenues.commercialPartnerships[idx] +
+ aggregatedBusinessPlan.revenues.other[idx]
+ return {formatCurrency(total)}
+ },
+ },
+ ]}
+ data={Array(10).fill(null)}
+ />
+
+ Variable Costs (€/year)
+ `Year ${idx + 1}` },
+ { key: 'rentalServices', header: 'Rental Services', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.variableCosts.rentalServices[idx]) },
+ { key: 'commissions', header: 'Commissions', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.variableCosts.commissions[idx]) },
+ { key: 'otherVariable', header: 'Other Variable', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.variableCosts.otherVariable[idx]) },
+ { key: 'transport', header: 'Transport', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.variableCosts.transport[idx]) },
+ {
+ key: 'total',
+ header: 'Total Variable Costs',
+ render: (_: any, idx: number) => {
+ const total =
+ aggregatedBusinessPlan.variableCosts.rentalServices[idx] +
+ aggregatedBusinessPlan.variableCosts.commissions[idx] +
+ aggregatedBusinessPlan.variableCosts.otherVariable[idx] +
+ aggregatedBusinessPlan.variableCosts.transport[idx]
+ return {formatCurrency(total)}
+ },
+ },
+ ]}
+ data={Array(10).fill(null)}
+ />
+
+ Fixed Costs (€/year)
+ `Year ${idx + 1}` },
+ { key: 'salaries', header: 'Salaries', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.fixedCosts.salaries[idx]) },
+ { key: 'marketing', header: 'Marketing', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.fixedCosts.marketing[idx]) },
+ { key: 'rd', header: 'R&D', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.fixedCosts.rd[idx]) },
+ { key: 'administrative', header: 'Administrative', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.fixedCosts.administrative[idx]) },
+ { key: 'otherGeneral', header: 'Other General', render: (_: any, idx: number) => formatCurrency(aggregatedBusinessPlan.fixedCosts.otherGeneral[idx]) },
+ {
+ key: 'total',
+ header: 'Total Fixed Costs',
+ render: (_: any, idx: number) => {
+ const total =
+ aggregatedBusinessPlan.fixedCosts.salaries[idx] +
+ aggregatedBusinessPlan.fixedCosts.marketing[idx] +
+ aggregatedBusinessPlan.fixedCosts.rd[idx] +
+ aggregatedBusinessPlan.fixedCosts.administrative[idx] +
+ aggregatedBusinessPlan.fixedCosts.otherGeneral[idx]
+ return {formatCurrency(total)}
+ },
+ },
+ ]}
+ data={Array(10).fill(null)}
+ />
+
+
+ )}
)}
diff --git a/src/pages/configuration/EcosystemsConfigurationPage.tsx b/src/pages/configuration/EcosystemsConfigurationPage.tsx
index 12f240c..deda381 100644
--- a/src/pages/configuration/EcosystemsConfigurationPage.tsx
+++ b/src/pages/configuration/EcosystemsConfigurationPage.tsx
@@ -51,6 +51,7 @@ export default function EcosystemsConfigurationPage() {
const ecosystems = data?.ecosystems || []
const regulators = data?.regulators || []
const wastes = data?.wastes || []
+ const regulationCharacteristics = data?.regulationCharacteristics || []
const regulatorOptions = regulators.map((reg) => ({
value: reg.id,
@@ -117,6 +118,7 @@ export default function EcosystemsConfigurationPage() {
name: formData.name!,
description: formData.description,
primaryRegulationNeeds: formData.primaryRegulationNeeds as [string, string, string],
+ regulationCharacteristicIds: formData.regulationCharacteristicIds || [],
regulators: formData.regulators || [],
compatibleWasteTypes: formData.compatibleWasteTypes || [],
effectiveness: formData.effectiveness || {},
diff --git a/src/pages/configuration/ModuleComponentsConfigurationPage.css b/src/pages/configuration/ModuleComponentsConfigurationPage.css
index cc1171b..ced12bb 100644
--- a/src/pages/configuration/ModuleComponentsConfigurationPage.css
+++ b/src/pages/configuration/ModuleComponentsConfigurationPage.css
@@ -189,3 +189,20 @@
border: 1px solid var(--border);
border-radius: 8px;
}
+
+.procedures-list {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-md);
+ margin-top: var(--spacing-md);
+}
+
+.procedure-association-item {
+ display: grid;
+ grid-template-columns: 2fr 1fr 2fr auto;
+ gap: var(--spacing-md);
+ align-items: flex-end;
+ padding: var(--spacing-sm);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+}
diff --git a/src/pages/configuration/ModuleComponentsConfigurationPage.tsx b/src/pages/configuration/ModuleComponentsConfigurationPage.tsx
index 8dcddfb..ad25c4e 100644
--- a/src/pages/configuration/ModuleComponentsConfigurationPage.tsx
+++ b/src/pages/configuration/ModuleComponentsConfigurationPage.tsx
@@ -1,6 +1,6 @@
import { useState } from 'react'
import { useStorage } from '@/hooks/useStorage'
-import { ModuleComponent, ModuleComponentType, MonthlyValue, Dimensions, PowerSpecs, ProductionPowerSpecs, ModuleComponentRegulatorAssociation, ModuleComponentServiceAssociation } from '@/types'
+import { ModuleComponent, ModuleComponentType, MonthlyValue, Dimensions, PowerSpecs, ProductionPowerSpecs, ModuleComponentRegulatorAssociation, ModuleComponentServiceAssociation, ModuleComponentProcedureAssociation, ProcedureStatus } from '@/types'
import Card from '@/components/base/Card'
import Button from '@/components/base/Button'
import Input from '@/components/base/Input'
@@ -56,6 +56,7 @@ export default function ModuleComponentsConfigurationPage() {
durationUnit: 'days',
regulators: [],
services: [],
+ administrativeProcedures: [],
})
const [errors, setErrors] = useState>({})
@@ -63,6 +64,7 @@ export default function ModuleComponentsConfigurationPage() {
const wastes = data?.wastes || []
const regulators = data?.regulators || []
const services = data?.services || []
+ const administrativeProcedures = data?.administrativeProcedures || []
const selectedComponentType = COMPONENT_TYPES.find(t => t.value === formData.type)
@@ -134,6 +136,8 @@ export default function ModuleComponentsConfigurationPage() {
lifetime: formData.lifetime,
regulators: formData.regulators || [],
services: formData.services || [],
+ administrativeProcedures: formData.administrativeProcedures || [],
+ regulationCharacteristicIds: formData.regulationCharacteristicIds || [],
createdAt: editingId ? components.find((c) => c.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
@@ -264,6 +268,28 @@ export default function ModuleComponentsConfigurationPage() {
)
},
},
+ {
+ key: 'procedures',
+ header: 'Procedures',
+ render: (component: ModuleComponent) => {
+ if (!component.administrativeProcedures || component.administrativeProcedures.length === 0) {
+ return None
+ }
+ return (
+
+ {component.administrativeProcedures.map((assoc, idx) => {
+ const procedure = administrativeProcedures.find((p) => p.id === assoc.procedureId)
+ const procedureStatusVariant = assoc.status === 'done' ? 'success' : assoc.status === 'toDo' ? 'warning' : 'info'
+ return (
+
+ {procedure?.name || 'Unknown'}
+
+ )
+ })}
+
+ )
+ },
+ },
{
key: 'actions',
header: 'Actions',
@@ -875,6 +901,133 @@ export default function ModuleComponentsConfigurationPage() {
+ {/* Regulation Characteristics Section */}
+
+
Regulation Characteristics
+
+ Select regulation characteristics that this module component addresses. Configure characteristics in Regulation Characteristics page.
+
+
+ {regulationCharacteristics.map((char) => (
+
+ ))}
+ {regulationCharacteristics.length === 0 && (
+
No regulation characteristics configured. Add them in the Regulation Characteristics page.
+ )}
+
+
+
+ {/* Associated Administrative Procedures Section */}
+
+
Associated Administrative Procedures
+
+ Add administrative procedures for this module component. Track the status of each procedure.
+
+
+ {(formData.administrativeProcedures || []).map((association, index) => {
+ const procedure = administrativeProcedures.find((p) => p.id === association.procedureId)
+ const procedureStatusOptions = [
+ { value: 'toDo', label: 'To Do' },
+ { value: 'done', label: 'Done' },
+ { value: 'na', label: 'N/A' },
+ ]
+ return (
+
+
+ )
+ })}
+
+
+
+
{/* Costs Section */}
Costs (Annual)
diff --git a/src/pages/configuration/RegulationCharacteristicsConfigurationPage.css b/src/pages/configuration/RegulationCharacteristicsConfigurationPage.css
new file mode 100644
index 0000000..f7289c6
--- /dev/null
+++ b/src/pages/configuration/RegulationCharacteristicsConfigurationPage.css
@@ -0,0 +1,53 @@
+.regulation-characteristics-config-page {
+ max-width: 1400px;
+ margin: 0 auto;
+}
+
+.page-title {
+ font-size: 2.5rem;
+ font-weight: 700;
+ color: var(--text-primary);
+ margin-bottom: var(--spacing-2xl);
+}
+
+.page-content {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-xl);
+}
+
+.form-card {
+ max-width: 100%;
+}
+
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: var(--spacing-lg);
+ margin-bottom: var(--spacing-md);
+}
+
+.form-actions {
+ display: flex;
+ gap: var(--spacing-md);
+ margin-top: var(--spacing-xl);
+ padding-top: var(--spacing-xl);
+ border-top: 1px solid var(--border);
+}
+
+.table-card {
+ margin-top: var(--spacing-xl);
+}
+
+.table-actions {
+ display: flex;
+ gap: var(--spacing-sm);
+}
+
+code {
+ background-color: var(--background-secondary);
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-family: var(--font-mono);
+ font-size: 0.875rem;
+}
diff --git a/src/pages/configuration/RegulationCharacteristicsConfigurationPage.tsx b/src/pages/configuration/RegulationCharacteristicsConfigurationPage.tsx
new file mode 100644
index 0000000..4b70683
--- /dev/null
+++ b/src/pages/configuration/RegulationCharacteristicsConfigurationPage.tsx
@@ -0,0 +1,258 @@
+import { useState } from 'react'
+import { useStorage } from '@/hooks/useStorage'
+import { RegulationCharacteristic } from '@/types'
+import Card from '@/components/base/Card'
+import Button from '@/components/base/Button'
+import Input from '@/components/base/Input'
+import Select from '@/components/base/Select'
+import Table from '@/components/base/Table'
+import { validateRequired } from '@/utils/validators'
+import './RegulationCharacteristicsConfigurationPage.css'
+
+export default function RegulationCharacteristicsConfigurationPage() {
+ const { data, addEntity, updateEntity, deleteEntity } = useStorage()
+ const [editingId, setEditingId] = useState
(null)
+ const [formData, setFormData] = useState>({
+ name: '',
+ code: '',
+ category: 'other',
+ description: '',
+ unit: '',
+ isBoolean: true,
+ minValue: undefined,
+ maxValue: undefined,
+ })
+ const [errors, setErrors] = useState>({})
+
+ const characteristics = data?.regulationCharacteristics || []
+
+ const categoryOptions = [
+ { value: 'nutrient', label: 'Nutrient' },
+ { value: 'heavyMetal', label: 'Heavy Metal' },
+ { value: 'biological', label: 'Biological' },
+ { value: 'chemical', label: 'Chemical' },
+ { value: 'biologicalProcess', label: 'Biological Process' },
+ { value: 'ph', label: 'pH' },
+ { value: 'other', label: 'Other' },
+ ]
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault()
+
+ const newErrors: Record = {}
+ newErrors.name = validateRequired(formData.name)
+ newErrors.code = validateRequired(formData.code)
+
+ // Check for duplicate code
+ const existingWithCode = characteristics.find((c) => c.code === formData.code && c.id !== editingId)
+ if (existingWithCode) {
+ newErrors.code = 'A characteristic with this code already exists'
+ }
+
+ setErrors(newErrors)
+
+ if (Object.values(newErrors).some((error) => error !== undefined)) {
+ return
+ }
+
+ const characteristic: RegulationCharacteristic = {
+ id: editingId || crypto.randomUUID(),
+ name: formData.name!,
+ code: formData.code!,
+ category: formData.category!,
+ description: formData.description,
+ unit: formData.unit,
+ isBoolean: formData.isBoolean ?? true,
+ minValue: formData.minValue,
+ maxValue: formData.maxValue,
+ createdAt: editingId ? characteristics.find((c) => c.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ }
+
+ if (editingId) {
+ updateEntity('regulationCharacteristics', editingId, characteristic)
+ } else {
+ addEntity('regulationCharacteristics', characteristic)
+ }
+
+ resetForm()
+ }
+
+ const resetForm = () => {
+ setFormData({
+ name: '',
+ code: '',
+ category: 'other',
+ description: '',
+ unit: '',
+ isBoolean: true,
+ minValue: undefined,
+ maxValue: undefined,
+ })
+ setEditingId(null)
+ setErrors({})
+ }
+
+ const handleEdit = (characteristic: RegulationCharacteristic) => {
+ setFormData(characteristic)
+ setEditingId(characteristic.id)
+ }
+
+ const handleDelete = (id: string) => {
+ if (confirm('Are you sure you want to delete this regulation characteristic?')) {
+ deleteEntity('regulationCharacteristics', id)
+ }
+ }
+
+ const tableColumns = [
+ {
+ key: 'name',
+ header: 'Name',
+ render: (char: RegulationCharacteristic) => char.name,
+ },
+ {
+ key: 'code',
+ header: 'Code',
+ render: (char: RegulationCharacteristic) => {char.code},
+ },
+ {
+ key: 'category',
+ header: 'Category',
+ render: (char: RegulationCharacteristic) => {
+ const categoryLabel = categoryOptions.find((opt) => opt.value === char.category)?.label || char.category
+ return categoryLabel
+ },
+ },
+ {
+ key: 'type',
+ header: 'Type',
+ render: (char: RegulationCharacteristic) => (char.isBoolean ? 'Boolean' : 'Numeric'),
+ },
+ {
+ key: 'unit',
+ header: 'Unit',
+ render: (char: RegulationCharacteristic) => char.unit || '-',
+ },
+ {
+ key: 'range',
+ header: 'Range',
+ render: (char: RegulationCharacteristic) => {
+ if (char.isBoolean) return '-'
+ if (char.minValue !== undefined && char.maxValue !== undefined) {
+ return `${char.minValue} - ${char.maxValue}`
+ }
+ if (char.minValue !== undefined) return `≥ ${char.minValue}`
+ if (char.maxValue !== undefined) return `≤ ${char.maxValue}`
+ return '-'
+ },
+ },
+ {
+ key: 'actions',
+ header: 'Actions',
+ render: (char: RegulationCharacteristic) => (
+
+
+
+
+ ),
+ },
+ ]
+
+ return (
+
+
Regulation Characteristics Configuration
+
+
+
+ )
+}
diff --git a/src/pages/configuration/RegulatorsConfigurationPage.tsx b/src/pages/configuration/RegulatorsConfigurationPage.tsx
index 6f77544..4567b09 100644
--- a/src/pages/configuration/RegulatorsConfigurationPage.tsx
+++ b/src/pages/configuration/RegulatorsConfigurationPage.tsx
@@ -11,6 +11,7 @@ import './RegulatorsConfigurationPage.css'
export default function RegulatorsConfigurationPage() {
const { data, addEntity, updateEntity, deleteEntity, loading } = useStorage()
+ const regulationCharacteristics = data?.regulationCharacteristics || []
const [editingId, setEditingId] = useState(null)
const [formData, setFormData] = useState>({
name: '',
@@ -71,11 +72,12 @@ export default function RegulatorsConfigurationPage() {
const regulator: NaturalRegulator = {
id: editingId || crypto.randomUUID(),
name: formData.name!,
- type: formData.type!,
- regulatoryCharacteristics: formData.regulatoryCharacteristics || {},
- applicationConditions: formData.applicationConditions!,
- dosageRequirements: formData.dosageRequirements!,
- createdAt: editingId ? regulators.find((r) => r.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
+ type: formData.type!,
+ regulationCharacteristicIds: formData.regulationCharacteristicIds || [],
+ regulatoryCharacteristics: formData.regulatoryCharacteristics || {},
+ applicationConditions: formData.applicationConditions!,
+ dosageRequirements: formData.dosageRequirements!,
+ createdAt: editingId ? regulators.find((r) => r.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
diff --git a/src/pages/configuration/WasteConfigurationPage.tsx b/src/pages/configuration/WasteConfigurationPage.tsx
index dec2284..53cbdeb 100644
--- a/src/pages/configuration/WasteConfigurationPage.tsx
+++ b/src/pages/configuration/WasteConfigurationPage.tsx
@@ -29,6 +29,7 @@ export default function WasteConfigurationPage() {
const wastes = data?.wastes || []
const wasteOrigins = data?.wasteOrigins || []
const regulators = data?.regulators || []
+ const regulationCharacteristics = data?.regulationCharacteristics || []
const originTypeOptions = [
{ value: 'animals', label: 'Animals' },
@@ -71,11 +72,12 @@ export default function WasteConfigurationPage() {
wasteOriginId: formData.wasteOriginId,
originUnitsPer1000m3Methane: formData.originUnitsPer1000m3Methane!,
bmp: formData.bmp!,
- waterPercentage: formData.waterPercentage!,
- regulationNeeds: formData.regulationNeeds || [],
- regulatoryCharacteristics: formData.regulatoryCharacteristics,
- regulators: formData.regulators || [],
- maxStorageDuration: formData.maxStorageDuration!,
+ waterPercentage: formData.waterPercentage!,
+ regulationNeeds: formData.regulationNeeds || [],
+ regulationCharacteristicIds: formData.regulationCharacteristicIds || [],
+ regulatoryCharacteristics: formData.regulatoryCharacteristics,
+ regulators: formData.regulators || [],
+ maxStorageDuration: formData.maxStorageDuration!,
createdAt: editingId ? wastes.find((w) => w.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
diff --git a/src/pages/projects/CompaniesPage.tsx b/src/pages/projects/CompaniesPage.tsx
new file mode 100644
index 0000000..f4e5b8c
--- /dev/null
+++ b/src/pages/projects/CompaniesPage.tsx
@@ -0,0 +1,273 @@
+import { useState, useEffect } from 'react'
+import { useStorage } from '@/hooks/useStorage'
+import { Company, BusinessPlan } from '@/types'
+import Card from '@/components/base/Card'
+import Button from '@/components/base/Button'
+import Input from '@/components/base/Input'
+import Table from '@/components/base/Table'
+import { validateRequired } from '@/utils/validators'
+import { initializeBusinessPlan } from '@/utils/calculations/businessPlanAggregation'
+import BusinessPlanEditor from '@/components/business-plan/BusinessPlanEditor'
+import './CompaniesPage.css'
+
+export default function CompaniesPage() {
+ const { data, addEntity, updateEntity, deleteEntity } = useStorage()
+ const [editingId, setEditingId] = useState(null)
+ const [formData, setFormData] = useState>({
+ name: '',
+ legalName: '',
+ registrationNumber: '',
+ address: '',
+ contact: {
+ email: '',
+ phone: '',
+ website: '',
+ },
+ })
+ const [errors, setErrors] = useState>({})
+ const [showBusinessPlan, setShowBusinessPlan] = useState(false)
+
+ const companies = data?.companies || []
+
+ // Initialize default company on first load
+ useEffect(() => {
+ if (companies.length === 0) {
+ const defaultCompany: Company = {
+ id: crypto.randomUUID(),
+ name: '4NK Water & Waste',
+ legalName: '4NK Water & Waste',
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ businessPlan: initializeBusinessPlan(),
+ }
+ addEntity('companies', defaultCompany)
+ }
+ }, [])
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault()
+
+ const newErrors: Record = {}
+ newErrors.name = validateRequired(formData.name)
+
+ setErrors(newErrors)
+
+ if (Object.values(newErrors).some((error) => error !== undefined)) {
+ return
+ }
+
+ const company: Company = {
+ id: editingId || crypto.randomUUID(),
+ name: formData.name!,
+ legalName: formData.legalName,
+ registrationNumber: formData.registrationNumber,
+ address: formData.address,
+ contact: formData.contact,
+ businessPlan: formData.businessPlan || initializeBusinessPlan(),
+ createdAt: editingId ? companies.find((c) => c.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ }
+
+ if (editingId) {
+ updateEntity('companies', editingId, company)
+ } else {
+ addEntity('companies', company)
+ }
+
+ resetForm()
+ }
+
+ const resetForm = () => {
+ setFormData({
+ name: '',
+ legalName: '',
+ registrationNumber: '',
+ address: '',
+ contact: {
+ email: '',
+ phone: '',
+ website: '',
+ },
+ })
+ setEditingId(null)
+ setErrors({})
+ setShowBusinessPlan(false)
+ }
+
+ const handleEdit = (company: Company) => {
+ setFormData(company)
+ setEditingId(company.id)
+ setShowBusinessPlan(false)
+ }
+
+ const handleDelete = (id: string) => {
+ if (confirm('Are you sure you want to delete this company?')) {
+ deleteEntity('companies', id)
+ }
+ }
+
+ const updateBusinessPlan = (businessPlan: BusinessPlan) => {
+ setFormData({
+ ...formData,
+ businessPlan,
+ })
+ }
+
+ const tableColumns = [
+ {
+ key: 'name',
+ header: 'Name',
+ render: (company: Company) => company.name,
+ },
+ {
+ key: 'legalName',
+ header: 'Legal Name',
+ render: (company: Company) => company.legalName || '-',
+ },
+ {
+ key: 'registrationNumber',
+ header: 'Registration Number',
+ render: (company: Company) => company.registrationNumber || '-',
+ },
+ {
+ key: 'hasBusinessPlan',
+ header: 'Business Plan',
+ render: (company: Company) => (company.businessPlan ? 'Yes' : 'No'),
+ },
+ {
+ key: 'actions',
+ header: 'Actions',
+ render: (company: Company) => (
+
+
+
+
+ ),
+ },
+ ]
+
+ return (
+
+ )
+}
+
diff --git a/src/pages/projects/TreatmentSitesPage.css b/src/pages/projects/TreatmentSitesPage.css
index 7088e6a..33044a8 100644
--- a/src/pages/projects/TreatmentSitesPage.css
+++ b/src/pages/projects/TreatmentSitesPage.css
@@ -109,4 +109,23 @@
height: 20px;
cursor: pointer;
accent-color: var(--primary-green);
+}
+
+.investors-list,
+.procedures-list {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-md);
+ margin-top: var(--spacing-md);
+}
+
+.investor-association-item,
+.procedure-association-item {
+ display: grid;
+ grid-template-columns: 2fr 1fr 1fr 2fr auto;
+ gap: var(--spacing-md);
+ align-items: flex-end;
+ padding: var(--spacing-sm);
+ border: 1px solid var(--border);
+ border-radius: 8px;
}
\ No newline at end of file
diff --git a/src/pages/projects/TreatmentSitesPage.tsx b/src/pages/projects/TreatmentSitesPage.tsx
index 6f96321..11b012b 100644
--- a/src/pages/projects/TreatmentSitesPage.tsx
+++ b/src/pages/projects/TreatmentSitesPage.tsx
@@ -1,6 +1,6 @@
import { useState } from 'react'
import { useStorage } from '@/hooks/useStorage'
-import { TreatmentSite, SiteStatus, SiteTransporterAssociation } from '@/types'
+import { TreatmentSite, SiteStatus, SiteTransporterAssociation, TreatmentSiteInvestorAssociation, TreatmentSiteProcedureAssociation, ProcedureStatus } from '@/types'
import Card from '@/components/base/Card'
import Button from '@/components/base/Button'
import Input from '@/components/base/Input'
@@ -21,12 +21,16 @@ export default function TreatmentSitesPage() {
monthlyTemperatures: Array(12).fill(0),
subscribedServices: [],
transporters: [],
+ investors: [],
+ administrativeProcedures: [],
})
const [errors, setErrors] = useState>({})
const sites = data?.treatmentSites || []
const services = data?.services || []
const transporters = data?.transporters || []
+ const investors = data?.investors || []
+ const administrativeProcedures = data?.administrativeProcedures || []
const statusOptions = [
{ value: 'toBeApproached', label: 'To be approached' },
@@ -64,6 +68,8 @@ export default function TreatmentSitesPage() {
monthlyTemperatures: formData.monthlyTemperatures!,
subscribedServices: formData.subscribedServices || [],
transporters: formData.transporters || [],
+ investors: formData.investors || [],
+ administrativeProcedures: formData.administrativeProcedures || [],
createdAt: editingId ? sites.find((s) => s.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
@@ -86,6 +92,8 @@ export default function TreatmentSitesPage() {
monthlyTemperatures: Array(12).fill(0),
subscribedServices: [],
transporters: [],
+ investors: [],
+ administrativeProcedures: [],
})
setEditingId(null)
setErrors({})
@@ -155,6 +163,49 @@ export default function TreatmentSitesPage() {
)
},
},
+ {
+ key: 'investors',
+ header: 'Investors',
+ render: (site: TreatmentSite) => {
+ if (!site.investors || site.investors.length === 0) {
+ return None
+ }
+ return (
+
+ {site.investors.map((assoc, idx) => {
+ const investor = investors.find((i) => i.id === assoc.investorId)
+ return (
+
+ {investor?.name || 'Unknown'}
+
+ )
+ })}
+
+ )
+ },
+ },
+ {
+ key: 'procedures',
+ header: 'Procedures',
+ render: (site: TreatmentSite) => {
+ if (!site.administrativeProcedures || site.administrativeProcedures.length === 0) {
+ return None
+ }
+ return (
+
+ {site.administrativeProcedures.map((assoc, idx) => {
+ const procedure = administrativeProcedures.find((p) => p.id === assoc.procedureId)
+ const procedureStatusVariant = assoc.status === 'done' ? 'success' : assoc.status === 'toDo' ? 'warning' : 'info'
+ return (
+
+ {procedure?.name || 'Unknown'}
+
+ )
+ })}
+
+ )
+ },
+ },
{
key: 'actions',
header: 'Actions',
@@ -340,6 +391,210 @@ export default function TreatmentSitesPage() {
+ {/* Associated Investors Section */}
+
+
Associated Investors
+
+ Add investors for this treatment site. Specify the investment status and amount if known.
+
+
+ {(formData.investors || []).map((association, index) => {
+ const investor = investors.find((i) => i.id === association.investorId)
+ return (
+
+ {
+ const updatedInvestors = [...(formData.investors || [])]
+ updatedInvestors[index] = {
+ ...updatedInvestors[index],
+ investorId: e.target.value,
+ }
+ setFormData({ ...formData, investors: updatedInvestors })
+ }}
+ options={[
+ { value: '', label: 'Select an investor' },
+ ...investors.map((i) => ({
+ value: i.id,
+ label: `${i.name} (${i.type})`,
+ })).filter(
+ (opt) =>
+ opt.value === association.investorId ||
+ !(formData.investors || []).some((a) => a.investorId === opt.value)
+ ),
+ ]}
+ />
+ {
+ const updatedInvestors = [...(formData.investors || [])]
+ updatedInvestors[index] = {
+ ...updatedInvestors[index],
+ status: e.target.value as 'toBeApproached' | 'loiOk' | 'inProgress' | 'completed',
+ }
+ setFormData({ ...formData, investors: updatedInvestors })
+ }}
+ options={statusOptions}
+ />
+ {
+ const updatedInvestors = [...(formData.investors || [])]
+ updatedInvestors[index] = {
+ ...updatedInvestors[index],
+ amount: e.target.value ? parseFloat(e.target.value) : undefined,
+ }
+ setFormData({ ...formData, investors: updatedInvestors })
+ }}
+ helpText={investor ? `Range: ${investor.amountRange.min.toLocaleString()} - ${investor.amountRange.max.toLocaleString()} €` : undefined}
+ />
+ {
+ const updatedInvestors = [...(formData.investors || [])]
+ updatedInvestors[index] = {
+ ...updatedInvestors[index],
+ notes: e.target.value || undefined,
+ }
+ setFormData({ ...formData, investors: updatedInvestors })
+ }}
+ />
+
+
+ )
+ })}
+
+
+
+
+ {/* Associated Administrative Procedures Section */}
+
+
Associated Administrative Procedures
+
+ Add administrative procedures for this treatment site. Track the status of each procedure.
+
+
+ {(formData.administrativeProcedures || []).map((association, index) => {
+ const procedure = administrativeProcedures.find((p) => p.id === association.procedureId)
+ const procedureStatusOptions = [
+ { value: 'toDo', label: 'To Do' },
+ { value: 'done', label: 'Done' },
+ { value: 'na', label: 'N/A' },
+ ]
+ return (
+
+ {
+ const updatedProcedures = [...(formData.administrativeProcedures || [])]
+ updatedProcedures[index] = {
+ ...updatedProcedures[index],
+ procedureId: e.target.value,
+ }
+ setFormData({ ...formData, administrativeProcedures: updatedProcedures })
+ }}
+ options={[
+ { value: '', label: 'Select a procedure' },
+ ...administrativeProcedures.map((p) => ({
+ value: p.id,
+ label: `${p.name} (${p.type})`,
+ })).filter(
+ (opt) =>
+ opt.value === association.procedureId ||
+ !(formData.administrativeProcedures || []).some((a) => a.procedureId === opt.value)
+ ),
+ ]}
+ />
+ {
+ const updatedProcedures = [...(formData.administrativeProcedures || [])]
+ updatedProcedures[index] = {
+ ...updatedProcedures[index],
+ status: e.target.value as ProcedureStatus,
+ }
+ setFormData({ ...formData, administrativeProcedures: updatedProcedures })
+ }}
+ options={procedureStatusOptions}
+ />
+ {
+ const updatedProcedures = [...(formData.administrativeProcedures || [])]
+ updatedProcedures[index] = {
+ ...updatedProcedures[index],
+ notes: e.target.value || undefined,
+ }
+ setFormData({ ...formData, administrativeProcedures: updatedProcedures })
+ }}
+ helpText={procedure ? `Type: ${procedure.type}, Delays: ${procedure.delays} days` : undefined}
+ />
+
+
+ )
+ })}
+
+
+
+