Add ecosystems configuration, fix regulators page, complete module components seeds
This commit is contained in:
parent
c7db6590f0
commit
3b8d130cbd
484
data/seeds/ecosystems-seeds.json
Normal file
484
data/seeds/ecosystems-seeds.json
Normal file
@ -0,0 +1,484 @@
|
|||||||
|
{
|
||||||
|
"ecosystems": [
|
||||||
|
{
|
||||||
|
"id": "eco-001",
|
||||||
|
"name": "Pathogen & Heavy Metal Elimination Ecosystem",
|
||||||
|
"description": "Combines bacterial regulators with biochar and phytoextractors to eliminate pathogens and heavy metals",
|
||||||
|
"primaryRegulationNeeds": [
|
||||||
|
"pathogenElimination",
|
||||||
|
"heavyMetalsElimination",
|
||||||
|
"metalBinding"
|
||||||
|
],
|
||||||
|
"regulators": [
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-002",
|
||||||
|
"percentage": 30,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-013",
|
||||||
|
"percentage": 25,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-014",
|
||||||
|
"percentage": 20,
|
||||||
|
"role": "secondary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-048",
|
||||||
|
"percentage": 15,
|
||||||
|
"role": "support"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-051",
|
||||||
|
"percentage": 10,
|
||||||
|
"role": "support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibleWasteTypes": [],
|
||||||
|
"effectiveness": {
|
||||||
|
"pathogenElimination": 85,
|
||||||
|
"heavyMetalsElimination": 80,
|
||||||
|
"metalBinding": 90
|
||||||
|
},
|
||||||
|
"applicationConditions": "Apply in mesophilic phase (20-40°C), ensure good mixing. Biochar provides adsorption surface, bacteria compete with pathogens, plants extract metals over 21-30 days.",
|
||||||
|
"treatmentDuration": 21,
|
||||||
|
"createdAt": "2025-12-09T20:00:00.000Z",
|
||||||
|
"updatedAt": "2025-12-09T20:00:00.000Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eco-002",
|
||||||
|
"name": "Odor & pH Regulation Ecosystem",
|
||||||
|
"description": "Uses acidifying and alkalizing regulators with odor-eliminating bacteria to control pH and eliminate odors",
|
||||||
|
"primaryRegulationNeeds": [
|
||||||
|
"odorElimination",
|
||||||
|
"phIncrease",
|
||||||
|
"acidityReduction"
|
||||||
|
],
|
||||||
|
"regulators": [
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-007",
|
||||||
|
"percentage": 35,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-020",
|
||||||
|
"percentage": 25,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-034",
|
||||||
|
"percentage": 20,
|
||||||
|
"role": "secondary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-004",
|
||||||
|
"percentage": 10,
|
||||||
|
"role": "support"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-063",
|
||||||
|
"percentage": 10,
|
||||||
|
"role": "support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibleWasteTypes": [],
|
||||||
|
"effectiveness": {
|
||||||
|
"odorElimination": 75,
|
||||||
|
"phIncrease": 80,
|
||||||
|
"acidityReduction": 85
|
||||||
|
},
|
||||||
|
"applicationConditions": "Apply during initial mixing phase. Monitor pH continuously. Ash and cinders raise pH, bacteria degrade odor compounds, pine needles provide gentle acidification if needed.",
|
||||||
|
"treatmentDuration": 14,
|
||||||
|
"createdAt": "2025-12-09T20:00:00.000Z",
|
||||||
|
"updatedAt": "2025-12-09T20:00:00.000Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eco-003",
|
||||||
|
"name": "Oil Emulsification & Medication Elimination Ecosystem",
|
||||||
|
"description": "Combines lipolytic bacteria with Pseudomonas species to emulsify oils and eliminate medications",
|
||||||
|
"primaryRegulationNeeds": [
|
||||||
|
"oilEmulsification",
|
||||||
|
"medicationElimination",
|
||||||
|
"microbiologicalCompetition"
|
||||||
|
],
|
||||||
|
"regulators": [
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-020",
|
||||||
|
"percentage": 30,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-034",
|
||||||
|
"percentage": 25,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-035",
|
||||||
|
"percentage": 20,
|
||||||
|
"role": "secondary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-003",
|
||||||
|
"percentage": 15,
|
||||||
|
"role": "support"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-013",
|
||||||
|
"percentage": 10,
|
||||||
|
"role": "support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibleWasteTypes": [],
|
||||||
|
"effectiveness": {
|
||||||
|
"oilEmulsification": 85,
|
||||||
|
"medicationElimination": 70,
|
||||||
|
"microbiologicalCompetition": 80
|
||||||
|
},
|
||||||
|
"applicationConditions": "Apply in aerobic or microaerophilic phase before anaerobic digestion. Requires good mixing and moderate aeration. Glucose provides energy for bacterial activity.",
|
||||||
|
"treatmentDuration": 18,
|
||||||
|
"createdAt": "2025-12-09T20:00:00.000Z",
|
||||||
|
"updatedAt": "2025-12-09T20:00:00.000Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eco-004",
|
||||||
|
"name": "Sodium & Chlorine Reduction Ecosystem",
|
||||||
|
"description": "Uses gypsum with denitrifying bacteria to reduce sodium, chlorine, and electrical conductivity",
|
||||||
|
"primaryRegulationNeeds": [
|
||||||
|
"sodiumReduction",
|
||||||
|
"chlorineReduction",
|
||||||
|
"electricalConductivityReduction"
|
||||||
|
],
|
||||||
|
"regulators": [
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-001",
|
||||||
|
"percentage": 40,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-017",
|
||||||
|
"percentage": 25,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-053",
|
||||||
|
"percentage": 20,
|
||||||
|
"role": "secondary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-054",
|
||||||
|
"percentage": 15,
|
||||||
|
"role": "support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibleWasteTypes": [],
|
||||||
|
"effectiveness": {
|
||||||
|
"sodiumReduction": 75,
|
||||||
|
"chlorineReduction": 70,
|
||||||
|
"electricalConductivityReduction": 80
|
||||||
|
},
|
||||||
|
"applicationConditions": "Apply gypsum as powder or granules, mix thoroughly. Requires light aeration for nitrifying bacteria. Monitor electrical conductivity regularly.",
|
||||||
|
"treatmentDuration": 21,
|
||||||
|
"createdAt": "2025-12-09T20:00:00.000Z",
|
||||||
|
"updatedAt": "2025-12-09T20:00:00.000Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eco-005",
|
||||||
|
"name": "Refractory Fractions & Polyphenol Elimination Ecosystem",
|
||||||
|
"description": "Uses lignolytic fungi and bacteria to break down refractory structural fractions and polyphenols",
|
||||||
|
"primaryRegulationNeeds": [
|
||||||
|
"refractoryFractionsElimination",
|
||||||
|
"polyphenolElimination",
|
||||||
|
"lowCNRatioEnrichment"
|
||||||
|
],
|
||||||
|
"regulators": [
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-041",
|
||||||
|
"percentage": 30,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-042",
|
||||||
|
"percentage": 25,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-018",
|
||||||
|
"percentage": 20,
|
||||||
|
"role": "secondary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-003",
|
||||||
|
"percentage": 15,
|
||||||
|
"role": "support"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-016",
|
||||||
|
"percentage": 10,
|
||||||
|
"role": "support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibleWasteTypes": [],
|
||||||
|
"effectiveness": {
|
||||||
|
"refractoryFractionsElimination": 70,
|
||||||
|
"polyphenolElimination": 75,
|
||||||
|
"lowCNRatioEnrichment": 80
|
||||||
|
},
|
||||||
|
"applicationConditions": "Requires good aeration and stable temperature (15-28°C). Fungi need structural support (wood chips or fibrous material). Maintain humidity but avoid saturation.",
|
||||||
|
"treatmentDuration": 28,
|
||||||
|
"createdAt": "2025-12-09T20:00:00.000Z",
|
||||||
|
"updatedAt": "2025-12-09T20:00:00.000Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eco-006",
|
||||||
|
"name": "CO₂ & Methane Management Ecosystem",
|
||||||
|
"description": "Uses photosynthetic bacteria and algae with CO₂/H₂ injection to manage gas ratios and eliminate excess gases",
|
||||||
|
"primaryRegulationNeeds": [
|
||||||
|
"co2Elimination",
|
||||||
|
"methaneElimination",
|
||||||
|
"sulfideElimination"
|
||||||
|
],
|
||||||
|
"regulators": [
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-031",
|
||||||
|
"percentage": 25,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-033",
|
||||||
|
"percentage": 25,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-037",
|
||||||
|
"percentage": 20,
|
||||||
|
"role": "secondary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-005",
|
||||||
|
"percentage": 15,
|
||||||
|
"role": "support"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-012",
|
||||||
|
"percentage": 15,
|
||||||
|
"role": "support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibleWasteTypes": [],
|
||||||
|
"effectiveness": {
|
||||||
|
"co2Elimination": 80,
|
||||||
|
"methaneElimination": 65,
|
||||||
|
"sulfideElimination": 75
|
||||||
|
},
|
||||||
|
"applicationConditions": "Requires light (natural or LED), gentle mixing, temperature 15-32°C. Algae fix CO₂, bacteria reduce sulfides. Monitor gas ratios continuously.",
|
||||||
|
"treatmentDuration": 21,
|
||||||
|
"createdAt": "2025-12-09T20:00:00.000Z",
|
||||||
|
"updatedAt": "2025-12-09T20:00:00.000Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eco-007",
|
||||||
|
"name": "Turbidity & Deposit Elimination Ecosystem",
|
||||||
|
"description": "Combines flocculating agents with earthworms and bacteria to reduce turbidity and eliminate deposits",
|
||||||
|
"primaryRegulationNeeds": [
|
||||||
|
"turbidityReduction",
|
||||||
|
"depositElimination",
|
||||||
|
"microbiologicalCompetition"
|
||||||
|
],
|
||||||
|
"regulators": [
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-001",
|
||||||
|
"percentage": 30,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-045",
|
||||||
|
"percentage": 25,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-046",
|
||||||
|
"percentage": 20,
|
||||||
|
"role": "secondary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-014",
|
||||||
|
"percentage": 15,
|
||||||
|
"role": "support"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-021",
|
||||||
|
"percentage": 10,
|
||||||
|
"role": "support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibleWasteTypes": [],
|
||||||
|
"effectiveness": {
|
||||||
|
"turbidityReduction": 85,
|
||||||
|
"depositElimination": 80,
|
||||||
|
"microbiologicalCompetition": 75
|
||||||
|
},
|
||||||
|
"applicationConditions": "Apply in aerobic phase with good mixing. Earthworms require stable temperature (10-28°C) and organic substrate. Gypsum flocculates colloids.",
|
||||||
|
"treatmentDuration": 21,
|
||||||
|
"createdAt": "2025-12-09T20:00:00.000Z",
|
||||||
|
"updatedAt": "2025-12-09T20:00:00.000Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eco-008",
|
||||||
|
"name": "Thermophilic Pathogen Elimination Ecosystem",
|
||||||
|
"description": "High-temperature bacterial consortium for rapid pathogen elimination in thermophilic conditions",
|
||||||
|
"primaryRegulationNeeds": [
|
||||||
|
"pathogenElimination",
|
||||||
|
"phIncrease",
|
||||||
|
"microbiologicalCompetition"
|
||||||
|
],
|
||||||
|
"regulators": [
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-058",
|
||||||
|
"percentage": 30,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-059",
|
||||||
|
"percentage": 25,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-060",
|
||||||
|
"percentage": 20,
|
||||||
|
"role": "secondary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-063",
|
||||||
|
"percentage": 15,
|
||||||
|
"role": "support"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-066",
|
||||||
|
"percentage": 10,
|
||||||
|
"role": "support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibleWasteTypes": [],
|
||||||
|
"effectiveness": {
|
||||||
|
"pathogenElimination": 95,
|
||||||
|
"phIncrease": 70,
|
||||||
|
"microbiologicalCompetition": 90
|
||||||
|
},
|
||||||
|
"applicationConditions": "Apply in thermophilic phase (40-65°C). Requires strict anaerobic conditions. High temperature eliminates most pathogens. Monitor pH and temperature continuously.",
|
||||||
|
"treatmentDuration": 18,
|
||||||
|
"createdAt": "2025-12-09T20:00:00.000Z",
|
||||||
|
"updatedAt": "2025-12-09T20:00:00.000Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eco-009",
|
||||||
|
"name": "Comprehensive Bioremediation Ecosystem",
|
||||||
|
"description": "Multi-phase ecosystem combining bacteria, fungi, plants, and earthworms for comprehensive waste treatment",
|
||||||
|
"primaryRegulationNeeds": [
|
||||||
|
"pathogenElimination",
|
||||||
|
"heavyMetalsElimination",
|
||||||
|
"refractoryFractionsElimination"
|
||||||
|
],
|
||||||
|
"regulators": [
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-002",
|
||||||
|
"percentage": 20,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-014",
|
||||||
|
"percentage": 15,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-042",
|
||||||
|
"percentage": 15,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-045",
|
||||||
|
"percentage": 15,
|
||||||
|
"role": "secondary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-048",
|
||||||
|
"percentage": 12,
|
||||||
|
"role": "secondary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-051",
|
||||||
|
"percentage": 10,
|
||||||
|
"role": "support"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-016",
|
||||||
|
"percentage": 8,
|
||||||
|
"role": "support"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-003",
|
||||||
|
"percentage": 5,
|
||||||
|
"role": "support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibleWasteTypes": [],
|
||||||
|
"effectiveness": {
|
||||||
|
"pathogenElimination": 80,
|
||||||
|
"heavyMetalsElimination": 85,
|
||||||
|
"refractoryFractionsElimination": 75
|
||||||
|
},
|
||||||
|
"applicationConditions": "Multi-phase treatment: Phase 1 (aerobic, 7 days) with bacteria and fungi, Phase 2 (aerobic, 14 days) with plants and earthworms, Phase 3 (anaerobic, 7 days) for stabilization. Requires good aeration in phases 1-2.",
|
||||||
|
"treatmentDuration": 28,
|
||||||
|
"createdAt": "2025-12-09T20:00:00.000Z",
|
||||||
|
"updatedAt": "2025-12-09T20:00:00.000Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eco-010",
|
||||||
|
"name": "Rapid Acidification & Pathogen Control Ecosystem",
|
||||||
|
"description": "Fast-acting acidifying bacteria and fungi for rapid pH reduction and pathogen control",
|
||||||
|
"primaryRegulationNeeds": [
|
||||||
|
"pathogenElimination",
|
||||||
|
"phReduction",
|
||||||
|
"acidityReduction"
|
||||||
|
],
|
||||||
|
"regulators": [
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-009",
|
||||||
|
"percentage": 30,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-010",
|
||||||
|
"percentage": 25,
|
||||||
|
"role": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-013",
|
||||||
|
"percentage": 20,
|
||||||
|
"role": "secondary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-004",
|
||||||
|
"percentage": 15,
|
||||||
|
"role": "support"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regulatorId": "reg-003",
|
||||||
|
"percentage": 10,
|
||||||
|
"role": "support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibleWasteTypes": [],
|
||||||
|
"effectiveness": {
|
||||||
|
"pathogenElimination": 80,
|
||||||
|
"phReduction": 85,
|
||||||
|
"acidityReduction": 75
|
||||||
|
},
|
||||||
|
"applicationConditions": "Apply in strict anaerobic conditions at 20-55°C. Glucose provides rapid carbon source. Monitor pH closely as acidification is rapid. Suitable for high-pH wastes.",
|
||||||
|
"treatmentDuration": 14,
|
||||||
|
"createdAt": "2025-12-09T20:00:00.000Z",
|
||||||
|
"updatedAt": "2025-12-09T20:00:00.000Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1138
data/seeds/module-components-seeds.json
Normal file
1138
data/seeds/module-components-seeds.json
Normal file
File diff suppressed because it is too large
Load Diff
932
data/seeds/regulators-seeds.json
Normal file
932
data/seeds/regulators-seeds.json
Normal file
@ -0,0 +1,932 @@
|
|||||||
|
{
|
||||||
|
"regulators": [
|
||||||
|
{
|
||||||
|
"id": "reg-001",
|
||||||
|
"name": "Gypse",
|
||||||
|
"type": "rgulateur-physico-chimique-minral-sulfate-de-calcium-hydrat",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"phAdjustment": -0.5
|
||||||
|
},
|
||||||
|
"applicationConditions": "Poudre ou granulé sec to doser en fonction du taux de salinité mesuré ; préhydratation facultative Température: 5-70°C. Effet: Réduction de la concentration en sodium (Na⁺), chlore (Cl⁻), conductivité ; floculation partielle des colloïdes ; régulation ionique.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.993Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-002",
|
||||||
|
"name": "Biochar",
|
||||||
|
"type": "support-physico-chimique-poreux-char-rsiduel-issu-de-pyrolyse-lente",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"metalBinding": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Sec, broyé ou granulé fin ; intégré par brassage ou dispersion ciblée Température: 0-70°C. Effet: Adsorption des impuretés, métaux lourds, composés volatils ou organochlorés ; microstructure favorable aux biofilms.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-003",
|
||||||
|
"name": "Glucose (fruits)",
|
||||||
|
"type": "substrat-biochimique-soluble-sucre-simple--haute-biodisponibilit",
|
||||||
|
"applicationConditions": "Incorporé en solution ou purée, bien mélangé dans les premières 24 h Température: 10-65°C. Effet: Apport immédiat de carbone pour bactéries acidogènes ; démarrage rapide de fermentation.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-004",
|
||||||
|
"name": "Aiguilles de pin (pH)",
|
||||||
|
"type": "substrat-vgtal-acidifiant-naturel",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"phAdjustment": -1
|
||||||
|
},
|
||||||
|
"applicationConditions": "Sec ou légèrement humidifié, pré-broyé si possible, en mélange progressif Température: 5-60°C. Effet: Réduction douce du pH, rééquilibrage de substrats trop alcalins ; effet antifongique léger.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-005",
|
||||||
|
"name": "CO₂ (source générateur ou apport externe)",
|
||||||
|
"type": "rgulateur-gazeux-tampon-atmosphre-et-substrat",
|
||||||
|
"applicationConditions": "Injection contrôlée dans digesteur fermé ; surveillance manométrique Température: 5-70°C. Effet: Ajustement du rapport CH₄/CO₂ ; maintien de la pression ; activation bactéries photosynthétiques (si couplé to algues).",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.5,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "%"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-006",
|
||||||
|
"name": "H₂ (gaz)",
|
||||||
|
"type": "gaz-rducteur-issu-du-traitement-uv-c-ou-lectrolyse-locale",
|
||||||
|
"applicationConditions": "Injection to faible débit dans environnement anaérobie strict, to température stabilisée Température: 15-65°C. Effet: Réduction chimique du CO₂ en CH₄ (réaction Sabatier) ; stimulation de certaines bactéries méthanogènes.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.5,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "%"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-007",
|
||||||
|
"name": "Cendres (pH)",
|
||||||
|
"type": "rgulateur-minral-alcalin-carbonate-oxydes",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"phAdjustment": 1.5
|
||||||
|
},
|
||||||
|
"applicationConditions": "Sec, broyé fin, incorporé lentement ; dosage en fonction du pH du digesta Température: 5-70°C. Effet: Augmentation pH, neutralisation acidité excessive, reminéralisation partielle.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-008",
|
||||||
|
"name": "Déchets STEP sableux",
|
||||||
|
"type": "support-bactrien-inerte-sables-et-agrgats-grossiers",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Sable sec ou semi-sec, intégré en phase de remplissage de cuve Température: 5-60°C. Effet: Création de microstructures portantes, fixation bactérienne, inertage des substrats colmatants.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-009",
|
||||||
|
"name": "Clostridium butyricum",
|
||||||
|
"type": "bactrie-anarobie-stricte-fermentative-acide-butyrique",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"phAdjustment": -1,
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Inoculation sous atmosphère anaérobie stricte, dans substrat riche en glucides Température: 20-55°C. Effet: Production d’acide butyrique to partir de sucres simples, amorçage acidogène.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-010",
|
||||||
|
"name": "Clostridium acetobutylicum",
|
||||||
|
"type": "bactrie-anarobie-stricte-fermentative-mixte-acides-et-solvants",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"phAdjustment": -1,
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Apport en substrat carboné (glucose, amidon), maintien d’un pH neutre Température: 25-55°C. Effet: Production d’acides acétique, butyrique, butanol et éthanol ; rendement énergétique élevé.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-011",
|
||||||
|
"name": "Enterobacter aerogenes",
|
||||||
|
"type": "bactrie-anarobie-facultative-productrice-dhydrogne",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Préfère des substrats sucrés, peut cohabiter temporairement avec O₂ résiduel Température: 20-50°C. Effet: Production rapide de H₂ et d’acides mixtes, stimulation de la phase acidogène.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-012",
|
||||||
|
"name": "Desulfobacter postgatei",
|
||||||
|
"type": "bactrie-anarobie-stricte-rductrice-de-sulfate-srb",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Anaérobie stricte, apport modéré en carbone ; éviter les excès de nitrates Température: 15-55°C. Effet: Réduction des sulfates en sulfure ; participation au cycle du soufre ; stabilisation redox.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-013",
|
||||||
|
"name": "Lactobacillus spp.",
|
||||||
|
"type": "bactries-arotolrantes-fermentatives-lactiques",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Inoculation au démarrage ou en phase de relance ; bon brassage nécessaire Température: 10-50°C. Effet: Acidification rapide du substrat, activation de la phase primaire de fermentation.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-014",
|
||||||
|
"name": "Bacillus subtilis",
|
||||||
|
"type": "bactrie-arobique-sporulante-utilise-aussi-en-anarobie-par-adaptation",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Peut être inoculé dès la phase mésophile, tolère les transitions thermiques Température: 15-60°C. Effet: Dégradation de protéines, activation enzymatique, équilibre microbien général.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-015",
|
||||||
|
"name": "Enterococcus faecium",
|
||||||
|
"type": "bactrie-anarobie-arotolrante-fermentative-lactique",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Apport en début de cycle, co-inoculation avec lactobacilles possible Température: 10-45°C. Effet: Stabilisation rapide du pH, activité antimicrobienne contre agents pathogènes.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-016",
|
||||||
|
"name": "Paenibacillus polymyxa",
|
||||||
|
"type": "bactrie-sporulante-du-sol-fixatrice-dazote-et-productrice-denzymes",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Inoculation dans substrats végétaux ou mixtes ; pH neutre to légèrement acide Température: 15-50°C. Effet: Fixation d’azote, dégradation de polysaccharides complexes, synergie avec plantes ou champignons.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-017",
|
||||||
|
"name": "Paracoccus denitrificans",
|
||||||
|
"type": "bactrie-arobique-et-anarobique-facultative-dnitrifiante",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "pH neutre, teneur suffisante en nitrate et en carbone, zone légèrement aérée Température: 10-45°C. Effet: Réduction des nitrates et nitrites, assainissement de substrats azotés.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-018",
|
||||||
|
"name": "Streptomyces spp.",
|
||||||
|
"type": "bactries-filamenteuses-arobies-productrices-dantibiotiques-naturels",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "En conditions aérobies en amont ou en bordure des digesteurs Température: 15-45°C. Effet: Dégradation de composés complexes, lutte contre les champignons pathogènes.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-019",
|
||||||
|
"name": "Corynebacterium glutamicum",
|
||||||
|
"type": "bactrie-arobique-facultative-productrice-dacides-amins",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"phAdjustment": -1,
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Apport modéré dans substrats mixtes (protéines + sucres) Température: 20-45°C. Effet: Amélioration du rapport C/N, conversion des protéines en acides organiques utiles to la digestion.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-020",
|
||||||
|
"name": "Mycobacterium smegmatis",
|
||||||
|
"type": "bactrie-arotolrante-non-pathogne-dgradant-les-lipides-et-composs-gras",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Apport fractionné, suivi de température et d’agitation recommandé Température: 20-50°C. Effet: Dégradation des lipides, initiation de la bioremédiation des graisses, réduction d’odeurs.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.994Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-021",
|
||||||
|
"name": "Bacillus megaterium",
|
||||||
|
"type": "bactrie-sporulante-arobie-tolrante--lanarobiose-multifonctionnelle",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Inoculation directe dans substrats organiques après brassage Température: 15-55°C. Effet: Production d’enzymes, stimulation de la biomasse microbienne, relance de la fermentation.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-022",
|
||||||
|
"name": "Glucose (fruits)",
|
||||||
|
"type": "substrat-biochimique-sucr--haute-biodisponibilit",
|
||||||
|
"applicationConditions": "Injection liquide ou purée après broyage, immédiatement mélangée Température: 10-65°C. Effet: Activation rapide des bactéries acidogènes ; substrat facilement dégradable.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-023",
|
||||||
|
"name": "Digesta (de lot précédent)",
|
||||||
|
"type": "rsidu-biologique-stabilis-riche-en-enzymes-et-en-bactries-actives",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Utilisation rapide après extraction ; ajout en mélange homogène Température: 15-65°C. Effet: Relance de l’activité microbiologique, stabilisation, ensemencement naturel.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-024",
|
||||||
|
"name": "Régulateur CO₂ du générateur",
|
||||||
|
"type": "gaz-tampon-atmosphrique-produit-par-combustion-ou-capt",
|
||||||
|
"applicationConditions": "Injection douce, surveiller la pression et le rapport CH₄/CO₂ Température: 0-70°C. Effet: Maintien de la pression, soutien du pH, activation bactéries algales (si traitement mixte).",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.5,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "%"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-025",
|
||||||
|
"name": "Corynebacterium glutamicum",
|
||||||
|
"type": "bactrie-arobique-facultative-productrice-dacides-amins",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"phAdjustment": -1,
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Apport modéré dans substrats mixtes (protéines + sucres) Température: 20-45°C. Effet: Amélioration du rapport C/N, conversion des protéines en acides organiques utiles to la digestion.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-026",
|
||||||
|
"name": "Mycobacterium smegmatis",
|
||||||
|
"type": "bactrie-arotolrante-non-pathogne-dgradant-les-lipides-et-composs-gras",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Apport fractionné, suivi de température et d’agitation recommandé Température: 20-50°C. Effet: Dégradation des lipides, initiation de la bioremédiation des graisses, réduction d’odeurs.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-027",
|
||||||
|
"name": "Bacillus megaterium",
|
||||||
|
"type": "bactrie-sporulante-arobie-tolrante--lanarobiose-multifonctionnelle",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Inoculation directe dans substrats organiques après brassage Température: 15-55°C. Effet: Production d’enzymes, stimulation de la biomasse microbienne, relance de la fermentation.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-028",
|
||||||
|
"name": "Glucose (fruits)",
|
||||||
|
"type": "substrat-biochimique-sucr--haute-biodisponibilit",
|
||||||
|
"applicationConditions": "Injection liquide ou purée après broyage, immédiatement mélangée Température: 10-65°C. Effet: Activation rapide des bactéries acidogènes ; substrat facilement dégradable.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-029",
|
||||||
|
"name": "Digesta (de lot précédent)",
|
||||||
|
"type": "rsidu-biologique-stabilis-riche-en-enzymes-et-en-bactries-actives",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Utilisation rapide après extraction ; ajout en mélange homogène Température: 15-65°C. Effet: Relance de l’activité microbiologique, stabilisation, ensemencement naturel.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-030",
|
||||||
|
"name": "Régulateur CO₂ du générateur",
|
||||||
|
"type": "gaz-tampon-atmosphrique-produit-par-combustion-ou-capt",
|
||||||
|
"applicationConditions": "Injection douce, surveiller la pression et le rapport CH₄/CO₂ Température: 0-70°C. Effet: Maintien de la pression, soutien du pH, activation bactéries algales (si traitement mixte).",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.5,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "%"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-031",
|
||||||
|
"name": "Scenedesmus obliquus",
|
||||||
|
"type": "micro-algue-verte-unicellulaire-deau-douce-photosynthtique",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"metalBinding": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Nécessite lumière naturelle ou LED spectre bleu/rouge ; brassage lent Température: 15-32°C. Effet: Fixation active du CO₂, adsorption des métaux légers, régulation des graisses en suspension.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-032",
|
||||||
|
"name": "Nannochloropsis oculata",
|
||||||
|
"type": "micro-algue-marine-unicellulaire-riche-en-lipides-et-pigments",
|
||||||
|
"applicationConditions": "Milieu salin ou saumâtre, apport régulier en lumière et CO₂ Température: 20-28°C. Effet: Fixation de CO₂, consommation de nutriments azotés, production d’oxygène local.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-033",
|
||||||
|
"name": "Chlorella vulgaris",
|
||||||
|
"type": "micro-algue-verte-deau-douce-rapide--crotre-photosynthtique",
|
||||||
|
"applicationConditions": "Lumière directe ou artificielle, injection contrôlée de CO₂, agitation douce Température: 18-30°C. Effet: Capture du CO₂, absorption d’ammonium, filtration organique fine.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-034",
|
||||||
|
"name": "Pseudomonas fluorescens",
|
||||||
|
"type": "bactrie-arobie-ou-microarophile-agent-de-bioremdiation-reconnu",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Inoculation dans substrats humides et oxygénés ; éviter l’anaérobie stricte Température: 15-35°C. Effet: Dégradation des graisses, hydrocarbures, pesticides ; production de biosurfactants.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-035",
|
||||||
|
"name": "Pseudomonas putida",
|
||||||
|
"type": "bactrie-arobie-versatile-hautement-mtabolique",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Nécessite un minimum d’oxygène ; substrat humide, lumière non nécessaire Température: 10-37°C. Effet: Dégradation de solvants, hydrocarbures, plastifiants ; prétraitement des antibiotiques.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-036",
|
||||||
|
"name": "Gluconobacter oxydans",
|
||||||
|
"type": "bactrie-arobique-actique-spcialise-dans-loxydation-de-sucres",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Phase aérobie légère ou microaérophile, suivi du pH recommandé Température: 20-35°C. Effet: Pré-oxydation douce des substrats glucidiques, baisse de DCO, stabilisation microbienne.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-037",
|
||||||
|
"name": "Rhodobacter sphaeroides",
|
||||||
|
"type": "bactrie-pourpre-non-soufre-photosynthtique-anarobie-facultative",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Lumière rouge ou naturelle indirecte, substrats riches en C organique Température: 15-40°C. Effet: Fixation de CO₂, dégradation de composés azotés, synergie avec algues.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-038",
|
||||||
|
"name": "Rhodospirillum rubrum",
|
||||||
|
"type": "bactrie-photosynthtique-anoxygnique-adaptable",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Milieu semi-stagnant, lumière indirecte, supplément CO₂ ou H₂ Température: 15-38°C. Effet: Fixation CO₂, réduction de NO₃⁻, assimilation de composés organiques légers.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-039",
|
||||||
|
"name": "H₂ (activateur photo-microbien)",
|
||||||
|
"type": "gaz-rducteur-cofacteur-de-certaines-ractions-bactriennes-photosynthse-anoxygnique",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Injection douce dans atmosphère contrôlée ou réacteur to algues Température: 15-65°C. Effet: Donneur d’électrons pour bactéries photosynthétiques, soutient la fixation CO₂.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-040",
|
||||||
|
"name": "CO₂ (activateur photosynthétique)",
|
||||||
|
"type": "gaz-carbon-substrat-de-fixation-pour-microalgues-et-bactries-phototrophes",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Injection sous pression faible, diffusion lente dans l’eau Température: 5-65°C. Effet: Apport en carbone organique, stimulation de la biomasse algale.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-041",
|
||||||
|
"name": "Ganoderma",
|
||||||
|
"type": "champignon-basidiomycte-ligninolytique-bois-morts",
|
||||||
|
"applicationConditions": "Température basse, bonne aération, humidité contrôlée ; nécessite structure porteuse Température: 5-28°C. Effet: Dégradation de lignine, transformation de composés phénoliques, réduction odeurs.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-042",
|
||||||
|
"name": "Pleurotus spp.",
|
||||||
|
"type": "champignons-saprophytes-lignocellulosiques-pleurotes",
|
||||||
|
"applicationConditions": "Apport de substrat fibreux, maintien d’une humidité constante, éviter l’immersion Température: 8-28°C. Effet: Dégradation de cellulose, lignine, microplastiques biodégradables ; fixation des graisses.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-043",
|
||||||
|
"name": "Penicillium chrysogenum",
|
||||||
|
"type": "moisissure-filamenteuse-saprophyte-productrice-de-pnicilline",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Aération légère, température fraîche, surface humide non saturée Température: 5-25°C. Effet: Dégradation fine de polysaccharides, activité antibactérienne naturelle.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-044",
|
||||||
|
"name": "Aspergillus niger",
|
||||||
|
"type": "moisissure-filamenteuse-noire-trs-active-sur-biomasse-vgtale",
|
||||||
|
"applicationConditions": "Aération minimale, éviter saturation en eau, pH légèrement acide Température: 10-40°C. Effet: Dégradation acide des celluloses, pectines, composés complexes ; production enzymatique (amylase, pectinase).",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-045",
|
||||||
|
"name": "Eisenia fetida",
|
||||||
|
"type": "ver-de-terre-composteur-lombric-rouge-dcomposeur-de-matire-organique",
|
||||||
|
"applicationConditions": "Litière végétale stable, humidité constante, éviter substrats toxiques ou trop gras Température: 10-28°C. Effet: Transformation mécanique et enzymatique de matière organique, mélange, oxygénation.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-046",
|
||||||
|
"name": "Lumbricus rubellus",
|
||||||
|
"type": "ver-de-terre-pig-dcomposeur-actif-de-surface",
|
||||||
|
"applicationConditions": "Substrat végétal non compacté, humidité constante, pas de forte lumière Température: 5-25°C. Effet: Aération du substrat, ingestion de particules fines, stabilisation microbiologique.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-047",
|
||||||
|
"name": "Streptomyces spp.",
|
||||||
|
"type": "bactries-filamenteuses-du-sol-actinomyctes--effet-fongicide",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Bonne aération, structure fibreuse, humidité contrôlée Température: 15-45°C. Effet: Ligninolyse bactérienne, inhibition des champignons pathogènes, structuration du sol.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.995Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-048",
|
||||||
|
"name": "Fougère (bioindicatrice / phytoextratrice)",
|
||||||
|
"type": "plante-vasculaire-prenne-hyperaccumulatrice-de-mtaux-lourds-ex-arsenic",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"metalBinding": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Plantation ou bouturage dans substrat filtrant, arrosage goutte-to-goutte Température: 10-30°C. Effet: Phytoaccumulation d’arsenic, zinc, plomb ; indicateur de pollution métallique.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 5,
|
||||||
|
"max": 50,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-049",
|
||||||
|
"name": "Ray-grass (Lolium perenne)",
|
||||||
|
"type": "plante-herbace-prenne-fixatrice-dazote-et-bioindicateur-nitrates",
|
||||||
|
"applicationConditions": "Semis direct sur couche filtrante, arrosage contrôlé Température: 8-30°C. Effet: Fixation de NO₃⁻, absorption azote minéral, indicateur de fertilisation.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 5,
|
||||||
|
"max": 50,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-050",
|
||||||
|
"name": "Trèfle blanc (Trifolium repens)",
|
||||||
|
"type": "lgumineuse-fixatrice-dazote-symbiotique-avec-rhizobium",
|
||||||
|
"applicationConditions": "Semis ou bouture, apport initial de Rhizobium recommandé Température: 5-28°C. Effet: Fixation biologique de l’azote, structuration du sol, bioremédiation douce.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 5,
|
||||||
|
"max": 50,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-051",
|
||||||
|
"name": "Moutarde indienne (Brassica juncea)",
|
||||||
|
"type": "plante-annuelle-phytoextractrice-puissante-de-mtaux-lourds",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"metalBinding": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Semis en surface ou substrat minéral, récolte après 30–40 jours Température: 10-32°C. Effet: Extraction de Pb, Zn, Cd ; stabilisation des sols contaminés ; absorption nitrates.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 5,
|
||||||
|
"max": 50,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-052",
|
||||||
|
"name": "Glomus spp.",
|
||||||
|
"type": "champignons-mycorhiziens-arbusculaires-endosymbiotes-racinaires",
|
||||||
|
"applicationConditions": "Co-inoculation avec plantes herbacées, ne pas stériliser le substrat Température: 12-30°C. Effet: Stimulation racinaire, amélioration de l’absorption phosphore, bioremédiation racinaire indirecte.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-053",
|
||||||
|
"name": "Nitrosomonas europaea",
|
||||||
|
"type": "bactrie-nitrifiante-arobie-oxydation-de-nh-en-no",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Apport d’oxygène, éviter pH < 6,5, température stabilisée Température: 10-35°C. Effet: Détoxification des ammoniums, première phase de nitrification.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-054",
|
||||||
|
"name": "Nitrobacter winogradskyi",
|
||||||
|
"type": "bactrie-nitrifiante-arobie-oxydation-de-no-en-no",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Oxygénation douce, neutralité du pH, interaction avec N. europaea Température: 10-35°C. Effet: Finalisation du cycle de nitrification, transformation des nitrites en nitrates assimilables.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-055",
|
||||||
|
"name": "Bacillus megaterium",
|
||||||
|
"type": "bactrie-sporulante-multifonctionnelle-du-sol-productrice-denzymes",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Apport direct au substrat, co-inoculation avec plantes possible Température: 15-55°C. Effet: Solubilisation du phosphore, dégradation des tensioactifs, activation microbienne.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-056",
|
||||||
|
"name": "Eisenia fetida",
|
||||||
|
"type": "ver-rouge-composteur-dcomposeur-organique--cycle-rapide",
|
||||||
|
"applicationConditions": "Substrat végétal ou semi-végétal, éviter acidité excessive Température: 10-28°C. Effet: Aération et structuration, réduction des charges microbiennes libres, digestion mécanique.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-057",
|
||||||
|
"name": "Lumbricus rubellus",
|
||||||
|
"type": "ver-de-terre-de-surface-dcomposeur-secondaire",
|
||||||
|
"applicationConditions": "Substrat non compressé, humidité constante, lumière indirecte Température: 5-25°C. Effet: Stabilisation microbienne, absorption de particules fines, filtration naturelle.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-058",
|
||||||
|
"name": "Clostridium spp. (haut rendement CH₄)",
|
||||||
|
"type": "bactries-anarobies-strictes-fermentatives-thermophiles",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Anaérobie strict, brassage lent, injection initiale en substrat chaud (> 45 °C) Température: 40-65°C. Effet: Fermentation rapide des substrats complexes, génération d’acétate et d’hydrogène précurseurs du méthane.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-059",
|
||||||
|
"name": "Bacillus subtilis (résistance thermique)",
|
||||||
|
"type": "bactrie-sporulante-du-sol-active-en-anarobiose-modre",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Injection fractionnée ou co-culture dans substrat riche, activation par température Température: 30-65°C. Effet: Résilience microbienne, dégradation protéique et soutien to la structuration enzymatique.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-060",
|
||||||
|
"name": "Lactobacillus spp.",
|
||||||
|
"type": "bactries-fermentatives-lactiques-arotolrantes",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Apport initial ou en renfort après agitation, éviter pH > 7,5 Température: 30-55°C. Effet: Amorçage acide du digesteur, suppression de pathogènes résiduels.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-061",
|
||||||
|
"name": "Myxococcus xanthus",
|
||||||
|
"type": "bactrie-sociale-glissante-structurante-du-biofilm-microbien",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"pathogenReduction": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Milieu homogène avec substrats fibreux, faible brassage Température: 25-50°C. Effet: Formation de biofilms, structuration microbienne, amélioration des surfaces de contact bactériennes.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 5,
|
||||||
|
"unit": "L/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-062",
|
||||||
|
"name": "H₂ (réacteur Sabatier)",
|
||||||
|
"type": "gaz-rducteur-utilis-pour-convertir-le-co-en-ch-par-voie-catalytique",
|
||||||
|
"applicationConditions": "Injection lente dans réacteur Sabatier ou en tête de digesteur ; catalyseur requis Température: 40-60°C. Effet: Réduction catalytique du CO₂, augmentation de la pureté du biogaz.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.5,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "%"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-063",
|
||||||
|
"name": "Cendres (régulation pH)",
|
||||||
|
"type": "minral-alcalin-issu-de-combustion-vgtale",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"phAdjustment": 1.5
|
||||||
|
},
|
||||||
|
"applicationConditions": "Dosage progressif depending on mesure pH, sous forme de poudre fine sèche Température: 5-70°C. Effet: Augmentation du pH, neutralisation d’acidité excessive post-fermentation.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-064",
|
||||||
|
"name": "CO₂ (tampon thermochimique)",
|
||||||
|
"type": "gaz-carbon-produit-ou-inject-dans-le-digesteur",
|
||||||
|
"applicationConditions": "Diffusion douce, sans surpression Température: 5-65°C. Effet: Stabilisation de la pression, soutien to la digestion bactérienne thermophile.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 0.5,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "%"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-065",
|
||||||
|
"name": "Biochar (stabilisation, filtration gaz)",
|
||||||
|
"type": "charbon-vgtal-microporeux-issu-de-pyrolyse-lente",
|
||||||
|
"regulatoryCharacteristics": {
|
||||||
|
"metalBinding": true
|
||||||
|
},
|
||||||
|
"applicationConditions": "Sec ou légèrement humide, bien réparti Température: 0-70°C. Effet: Adsorption de composés soufrés et ammoniaqués, support microbien.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reg-066",
|
||||||
|
"name": "Digesta (inoculum thermophile)",
|
||||||
|
"type": "rsidu-microbien-actif-issu-dun-digesteur-thermophile",
|
||||||
|
"applicationConditions": "Utilisation rapide après extraction, suivi température et pH Température: 30-65°C. Effet: Recyclage de l’activité microbienne thermophile, amorçage enzymatique.",
|
||||||
|
"dosageRequirements": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 10,
|
||||||
|
"unit": "kg/t"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.996Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
148
data/seeds/services-seeds.json
Normal file
148
data/seeds/services-seeds.json
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
{
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"id": "service-001",
|
||||||
|
"name": "Raw Rental",
|
||||||
|
"type": "rawRental",
|
||||||
|
"pricing": {
|
||||||
|
"year1": 0,
|
||||||
|
"year2": 0,
|
||||||
|
"year3": 0,
|
||||||
|
"year4": 0,
|
||||||
|
"year5": 0,
|
||||||
|
"year6": 0,
|
||||||
|
"year7": 0,
|
||||||
|
"year8": 0,
|
||||||
|
"year9": 0,
|
||||||
|
"year10": 0
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "service-002",
|
||||||
|
"name": "Biological Waste Treatment",
|
||||||
|
"type": "biologicalTreatment",
|
||||||
|
"pricing": {
|
||||||
|
"year1": 0,
|
||||||
|
"year2": 0,
|
||||||
|
"year3": 0,
|
||||||
|
"year4": 0,
|
||||||
|
"year5": 0,
|
||||||
|
"year6": 0,
|
||||||
|
"year7": 0,
|
||||||
|
"year8": 0,
|
||||||
|
"year9": 0,
|
||||||
|
"year10": 0
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "service-003",
|
||||||
|
"name": "Bitcoin Management",
|
||||||
|
"type": "bitcoinManagement",
|
||||||
|
"pricing": {
|
||||||
|
"year1": 0,
|
||||||
|
"year2": 0,
|
||||||
|
"year3": 0,
|
||||||
|
"year4": 0,
|
||||||
|
"year5": 0,
|
||||||
|
"year6": 0,
|
||||||
|
"year7": 0,
|
||||||
|
"year8": 0,
|
||||||
|
"year9": 0,
|
||||||
|
"year10": 0
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "service-004",
|
||||||
|
"name": "Provision of Standardized Fertilizers",
|
||||||
|
"type": "fertilizers",
|
||||||
|
"pricing": {
|
||||||
|
"year1": 0,
|
||||||
|
"year2": 0,
|
||||||
|
"year3": 0,
|
||||||
|
"year4": 0,
|
||||||
|
"year5": 0,
|
||||||
|
"year6": 0,
|
||||||
|
"year7": 0,
|
||||||
|
"year8": 0,
|
||||||
|
"year9": 0,
|
||||||
|
"year10": 0
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "service-005",
|
||||||
|
"name": "Provision of Waste Heat",
|
||||||
|
"type": "wasteHeat",
|
||||||
|
"pricing": {
|
||||||
|
"year1": 0,
|
||||||
|
"year2": 0,
|
||||||
|
"year3": 0,
|
||||||
|
"year4": 0,
|
||||||
|
"year5": 0,
|
||||||
|
"year6": 0,
|
||||||
|
"year7": 0,
|
||||||
|
"year8": 0,
|
||||||
|
"year9": 0,
|
||||||
|
"year10": 0
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "service-006",
|
||||||
|
"name": "Provision of Carbon Credit Indices",
|
||||||
|
"type": "carbonCredits",
|
||||||
|
"pricing": {
|
||||||
|
"year1": 0,
|
||||||
|
"year2": 0,
|
||||||
|
"year3": 0,
|
||||||
|
"year4": 0,
|
||||||
|
"year5": 0,
|
||||||
|
"year6": 0,
|
||||||
|
"year7": 0,
|
||||||
|
"year8": 0,
|
||||||
|
"year9": 0,
|
||||||
|
"year10": 0
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "service-007",
|
||||||
|
"name": "Brownfield Redevelopment",
|
||||||
|
"type": "brownfield",
|
||||||
|
"pricing": {
|
||||||
|
"year1": 0,
|
||||||
|
"year2": 0,
|
||||||
|
"year3": 0,
|
||||||
|
"year4": 0,
|
||||||
|
"year5": 0,
|
||||||
|
"year6": 0,
|
||||||
|
"year7": 0,
|
||||||
|
"year8": 0,
|
||||||
|
"year9": 0,
|
||||||
|
"year10": 0
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "service-008",
|
||||||
|
"name": "Transport",
|
||||||
|
"type": "transport",
|
||||||
|
"pricing": {
|
||||||
|
"year1": 0,
|
||||||
|
"year2": 0,
|
||||||
|
"year3": 0,
|
||||||
|
"year4": 0,
|
||||||
|
"year5": 0,
|
||||||
|
"year6": 0,
|
||||||
|
"year7": 0,
|
||||||
|
"year8": 0,
|
||||||
|
"year9": 0,
|
||||||
|
"year10": 0
|
||||||
|
},
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
88
data/seeds/transporters-seeds.json
Normal file
88
data/seeds/transporters-seeds.json
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
{
|
||||||
|
"transporters": [
|
||||||
|
{
|
||||||
|
"id": "transporter-001",
|
||||||
|
"name": "Gravity Pipeline",
|
||||||
|
"type": "gravity-pipeline",
|
||||||
|
"description": "Transport by internal gravity pipeline on site. Recommended for dairy cows.",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transporter-002",
|
||||||
|
"name": "PVC Pipes Under Slatted Floor",
|
||||||
|
"type": "pvc-pipeline",
|
||||||
|
"description": "Transport by PVC pipes under slatted floor. Recommended for fattening pigs.",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transporter-003",
|
||||||
|
"name": "Conveyor Belt",
|
||||||
|
"type": "conveyor-belt",
|
||||||
|
"description": "Transport by conveyor belt. Recommended for laying hens.",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transporter-004",
|
||||||
|
"name": "Tank Truck with Gentle Agitation",
|
||||||
|
"type": "tank-truck-gentle",
|
||||||
|
"description": "Tank truck with gentle agitation. Recommended for dairy cows, boars.",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transporter-005",
|
||||||
|
"name": "Peristaltic Pump",
|
||||||
|
"type": "peristaltic-pump",
|
||||||
|
"description": "Transport by peristaltic pump. Recommended for boars.",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transporter-006",
|
||||||
|
"name": "Stainless Steel Food Tank Truck",
|
||||||
|
"type": "stainless-tank-truck",
|
||||||
|
"description": "Stainless steel food tank truck. Recommended for industrial oils.",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transporter-007",
|
||||||
|
"name": "Screw Truck",
|
||||||
|
"type": "screw-truck",
|
||||||
|
"description": "Transport by screw truck. Recommended for wastewater treatment plant sludge.",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transporter-008",
|
||||||
|
"name": "Refrigerated Tanks/Containers",
|
||||||
|
"type": "refrigerated-tanks",
|
||||||
|
"description": "Transport in refrigerated tanks or containers. Recommended for food milk.",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transporter-009",
|
||||||
|
"name": "Refrigerated Bins",
|
||||||
|
"type": "refrigerated-bins",
|
||||||
|
"description": "Transport in refrigerated bins. Recommended for animal oils, fresh fruits/vegetables.",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transporter-010",
|
||||||
|
"name": "Insulated Bins",
|
||||||
|
"type": "insulated-bins",
|
||||||
|
"description": "Transport in insulated bins. Recommended for margarine oils, wastewater treatment plant fats.",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transporter-011",
|
||||||
|
"name": "Insulated Truck",
|
||||||
|
"type": "insulated-truck",
|
||||||
|
"description": "Transport by insulated truck. Recommended for fish, dishes, food meat.",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transporter-012",
|
||||||
|
"name": "Refrigerated Crates/Containers",
|
||||||
|
"type": "refrigerated-containers",
|
||||||
|
"description": "Transport in refrigerated crates or containers. Recommended for fresh fruits/vegetables.",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
165
data/seeds/waste-origins-seeds.json
Normal file
165
data/seeds/waste-origins-seeds.json
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
{
|
||||||
|
"wasteOrigins": [
|
||||||
|
{
|
||||||
|
"id": "origin-001",
|
||||||
|
"type": "animals",
|
||||||
|
"name": "Cow",
|
||||||
|
"description": "Bovine waste (slurry, manure)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-002",
|
||||||
|
"type": "animals",
|
||||||
|
"name": "Pig",
|
||||||
|
"description": "Porcine waste (slurry)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-003",
|
||||||
|
"type": "animals",
|
||||||
|
"name": "Piglet",
|
||||||
|
"description": "Porcine waste (young pigs)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-004",
|
||||||
|
"type": "animals",
|
||||||
|
"name": "Broiler Chickens",
|
||||||
|
"description": "Poultry waste (broiler chickens)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-005",
|
||||||
|
"type": "animals",
|
||||||
|
"name": "Laying Hens",
|
||||||
|
"description": "Poultry waste (laying hens)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-006",
|
||||||
|
"type": "markets",
|
||||||
|
"name": "Small Local Market",
|
||||||
|
"description": "Local market waste (fruits, vegetables)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-007",
|
||||||
|
"type": "markets",
|
||||||
|
"name": "Wholesale Market",
|
||||||
|
"description": "Wholesale market waste (fresh wet vegetables)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-008",
|
||||||
|
"type": "markets",
|
||||||
|
"name": "Fish Market",
|
||||||
|
"description": "Fish market waste (spoiled fish)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-009",
|
||||||
|
"type": "restaurants",
|
||||||
|
"name": "High Volume Restaurant",
|
||||||
|
"description": "High volume restaurant waste (oils)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-010",
|
||||||
|
"type": "restaurants",
|
||||||
|
"name": "Restaurant",
|
||||||
|
"description": "Restaurant waste (cooked food leftovers)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-011",
|
||||||
|
"type": "restaurants",
|
||||||
|
"name": "School Canteen",
|
||||||
|
"description": "School canteen waste (oils, food leftovers)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-012",
|
||||||
|
"type": "other",
|
||||||
|
"name": "Households",
|
||||||
|
"description": "Household waste (oils, mixed biowaste)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-013",
|
||||||
|
"type": "other",
|
||||||
|
"name": "Food Processing Plant",
|
||||||
|
"description": "Food processing plant waste",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-014",
|
||||||
|
"type": "other",
|
||||||
|
"name": "Margarine Plant",
|
||||||
|
"description": "Margarine plant waste (oils)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-015",
|
||||||
|
"type": "other",
|
||||||
|
"name": "Dairy Plant",
|
||||||
|
"description": "Dairy plant waste (expired milk, yogurt, cream)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-016",
|
||||||
|
"type": "other",
|
||||||
|
"name": "Bakery",
|
||||||
|
"description": "Bakery waste (stale bread)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-017",
|
||||||
|
"type": "other",
|
||||||
|
"name": "Slaughterhouse",
|
||||||
|
"description": "Slaughterhouse waste (spoiled meat)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-018",
|
||||||
|
"type": "other",
|
||||||
|
"name": "Urban Wastewater Treatment Plant",
|
||||||
|
"description": "Wastewater treatment plant waste (urban sludge, fats)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-019",
|
||||||
|
"type": "other",
|
||||||
|
"name": "Hectare of Herb Production",
|
||||||
|
"description": "Green waste (leaves, tender stems)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-020",
|
||||||
|
"type": "other",
|
||||||
|
"name": "Urban Tree",
|
||||||
|
"description": "Urban tree waste (leaves)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-021",
|
||||||
|
"type": "other",
|
||||||
|
"name": "Pruned Tree",
|
||||||
|
"description": "Pruned tree waste (branches)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-022",
|
||||||
|
"type": "other",
|
||||||
|
"name": "Hectare of Olive Trees",
|
||||||
|
"description": "Olive tree waste (leaves, fruits, twigs, pomace, olive mill wastewater)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "origin-023",
|
||||||
|
"type": "other",
|
||||||
|
"name": "Beach (Algae)",
|
||||||
|
"description": "Beach algae waste (sargassum, laminaria, gracilaria, ulva)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.997Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
555
data/seeds/wastes-seeds.json
Normal file
555
data/seeds/wastes-seeds.json
Normal file
@ -0,0 +1,555 @@
|
|||||||
|
{
|
||||||
|
"wastes": [
|
||||||
|
{
|
||||||
|
"id": "waste-001",
|
||||||
|
"name": "slurry (cow)",
|
||||||
|
"originType": "animals",
|
||||||
|
"originSubType": "cow",
|
||||||
|
"originUnitsPer1000m3Methane": 811.7,
|
||||||
|
"bmp": 0.02,
|
||||||
|
"waterPercentage": 92,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"pathogenElimination"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 7,2",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.983Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-002",
|
||||||
|
"name": "slurry (cow)",
|
||||||
|
"originType": "animals",
|
||||||
|
"originSubType": "cow",
|
||||||
|
"originUnitsPer1000m3Methane": 755.9,
|
||||||
|
"bmp": 0.02,
|
||||||
|
"waterPercentage": 91,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"pathogenElimination"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 7,1",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.984Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-003",
|
||||||
|
"name": "slurry (cow)",
|
||||||
|
"originType": "animals",
|
||||||
|
"originSubType": "cow",
|
||||||
|
"originUnitsPer1000m3Methane": 887.3,
|
||||||
|
"bmp": 0.02,
|
||||||
|
"waterPercentage": 93,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"pathogenElimination",
|
||||||
|
"heavyMetalTreatment"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 7,3",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.984Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-004",
|
||||||
|
"name": "slurry (pig)",
|
||||||
|
"originType": "animals",
|
||||||
|
"originSubType": "pig",
|
||||||
|
"originUnitsPer1000m3Methane": 728.9,
|
||||||
|
"bmp": 0.03,
|
||||||
|
"waterPercentage": 93,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"pathogenElimination"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 7,4",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.984Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-005",
|
||||||
|
"name": "slurry (pig)",
|
||||||
|
"originType": "animals",
|
||||||
|
"originSubType": "pig",
|
||||||
|
"originUnitsPer1000m3Methane": 661.4,
|
||||||
|
"bmp": 0.03,
|
||||||
|
"waterPercentage": 92,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"pathogenElimination",
|
||||||
|
"heavyMetalTreatment"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 7,3",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.984Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-006",
|
||||||
|
"name": "slurry (pigelet)",
|
||||||
|
"originType": "animals",
|
||||||
|
"originSubType": "pigelet",
|
||||||
|
"originUnitsPer1000m3Methane": 952.4,
|
||||||
|
"bmp": 0.03,
|
||||||
|
"waterPercentage": 94,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"pathogenElimination"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 7,5",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.984Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-007",
|
||||||
|
"name": "slurry (pig)",
|
||||||
|
"originType": "animals",
|
||||||
|
"originSubType": "pig",
|
||||||
|
"originUnitsPer1000m3Methane": 610.5,
|
||||||
|
"bmp": 0.03,
|
||||||
|
"waterPercentage": 91,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"pathogenElimination",
|
||||||
|
"heavyMetalTreatment"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 7,2",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.985Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-008",
|
||||||
|
"name": "slurry (pig)",
|
||||||
|
"originType": "animals",
|
||||||
|
"originSubType": "pig",
|
||||||
|
"originUnitsPer1000m3Methane": 661.4,
|
||||||
|
"bmp": 0.03,
|
||||||
|
"waterPercentage": 92,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"pathogenElimination"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 7,3",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.985Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-009",
|
||||||
|
"name": "slurry (laying hens)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "laying hens",
|
||||||
|
"originUnitsPer1000m3Methane": 330.7,
|
||||||
|
"bmp": 0.02,
|
||||||
|
"waterPercentage": 76,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,8",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.985Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-010",
|
||||||
|
"name": "slurry (broiler chickens)",
|
||||||
|
"originType": "animals",
|
||||||
|
"originSubType": "broiler chickens",
|
||||||
|
"originUnitsPer1000m3Methane": 324.7,
|
||||||
|
"bmp": 0.02,
|
||||||
|
"waterPercentage": 78,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"pathogenElimination"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 7,0",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.985Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-011",
|
||||||
|
"name": "oils (high volume restaurant)",
|
||||||
|
"originType": "restaurants",
|
||||||
|
"originSubType": "high volume restaurant",
|
||||||
|
"originUnitsPer1000m3Methane": 1.7,
|
||||||
|
"bmp": 0.85,
|
||||||
|
"waterPercentage": 1,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: neutre to légèrement acide (6,5–7,0)",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.985Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-012",
|
||||||
|
"name": "oils (school canteen (340 élèves))",
|
||||||
|
"originType": "restaurants",
|
||||||
|
"originSubType": "school canteen (340 élèves)",
|
||||||
|
"originUnitsPer1000m3Methane": 1.9,
|
||||||
|
"bmp": 0.78,
|
||||||
|
"waterPercentage": 5,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,6",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.985Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-013",
|
||||||
|
"name": "oils (households)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "households",
|
||||||
|
"originUnitsPer1000m3Methane": 2,
|
||||||
|
"bmp": 0.75,
|
||||||
|
"waterPercentage": 5,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,7",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.985Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-014",
|
||||||
|
"name": "oils (plant (alimentaire ou non précisée))",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "plant (alimentaire ou non précisée)",
|
||||||
|
"originUnitsPer1000m3Methane": 1.8,
|
||||||
|
"bmp": 0.82,
|
||||||
|
"waterPercentage": 3,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,9",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.985Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-015",
|
||||||
|
"name": "oils (plant de margarine)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "plant de margarine",
|
||||||
|
"originUnitsPer1000m3Methane": 1.9,
|
||||||
|
"bmp": 0.79,
|
||||||
|
"waterPercentage": 4,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,8",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.985Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-016",
|
||||||
|
"name": "green waste (leaves, tender stems) (hectare de production d’herbes)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "hectare de production d’herbes",
|
||||||
|
"originUnitsPer1000m3Methane": 43.3,
|
||||||
|
"bmp": 0.11,
|
||||||
|
"waterPercentage": 70,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,5",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.985Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-017",
|
||||||
|
"name": "leaves (urban tree)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "urban tree",
|
||||||
|
"originUnitsPer1000m3Methane": 39.7,
|
||||||
|
"bmp": 0.09,
|
||||||
|
"waterPercentage": 60,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,2",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.986Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-018",
|
||||||
|
"name": "branches (pruned tree)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "pruned tree",
|
||||||
|
"originUnitsPer1000m3Methane": 32.5,
|
||||||
|
"bmp": 0.08,
|
||||||
|
"waterPercentage": 45,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,1",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.986Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-019",
|
||||||
|
"name": "leaves, rameaux, fruits non récoltés (hectare d’oliviers en culture intensive)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "hectare d’oliviers en culture intensive",
|
||||||
|
"originUnitsPer1000m3Methane": 26.5,
|
||||||
|
"bmp": 0.12,
|
||||||
|
"waterPercentage": 55,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,3",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.986Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-020",
|
||||||
|
"name": "leaves, fruits abîmés, rameaux (hectare d’oliviers en culture biologique)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "hectare d’oliviers en culture biologique",
|
||||||
|
"originUnitsPer1000m3Methane": 31.1,
|
||||||
|
"bmp": 0.12,
|
||||||
|
"waterPercentage": 60,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,5",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.986Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-021",
|
||||||
|
"name": "grignons d’olive, olive mill wastewater (hectare – pressoir traditionnel d’olives)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "hectare – pressoir traditionnel d’olives",
|
||||||
|
"originUnitsPer1000m3Methane": 16.3,
|
||||||
|
"bmp": 0.25,
|
||||||
|
"waterPercentage": 65,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"pathogenElimination"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 5,5 to 6,0",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.986Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-022",
|
||||||
|
"name": "urban sludge (équivalent habitant (station d’épuration urbaine))",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "équivalent habitant (station d’épuration urbaine)",
|
||||||
|
"originUnitsPer1000m3Methane": 59.5,
|
||||||
|
"bmp": 0.12,
|
||||||
|
"waterPercentage": 80,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"heavyMetalTreatment"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,8",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.986Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-023",
|
||||||
|
"name": "urban fatty floatables (équivalent habitant (graisses de station d’épuration))",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "équivalent habitant (graisses de station d’épuration)",
|
||||||
|
"originUnitsPer1000m3Methane": 2.4,
|
||||||
|
"bmp": 0.85,
|
||||||
|
"waterPercentage": 30,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"pathogenElimination"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,5",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.986Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-024",
|
||||||
|
"name": "sargassum fraîches (m² de plage (algue fraîche))",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "m² de plage (algue fraîche)",
|
||||||
|
"originUnitsPer1000m3Methane": 158.7,
|
||||||
|
"bmp": 0.09,
|
||||||
|
"waterPercentage": 90,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,0 to 6,3",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.986Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-025",
|
||||||
|
"name": "sargassum vieillies en bord de mer (m² de plage par jour (2–3 jours))",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "m² de plage par jour (2–3 jours)",
|
||||||
|
"originUnitsPer1000m3Methane": 158.7,
|
||||||
|
"bmp": 0.06,
|
||||||
|
"waterPercentage": 85,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"odorElimination"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 5,5 to 6,0",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.986Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-026",
|
||||||
|
"name": "laminaria (macroalgues brunes) (hectare de culture de laminaria)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "hectare de culture de laminaria",
|
||||||
|
"originUnitsPer1000m3Methane": 79.4,
|
||||||
|
"bmp": 0.18,
|
||||||
|
"waterPercentage": 90,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,2",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.986Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-027",
|
||||||
|
"name": "gracilaria (algue rouge) (hectare de culture de gracilaria)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "hectare de culture de gracilaria",
|
||||||
|
"originUnitsPer1000m3Methane": 89.3,
|
||||||
|
"bmp": 0.16,
|
||||||
|
"waterPercentage": 90,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"saltReduction"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,0",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.986Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-028",
|
||||||
|
"name": "ulva (algue verte) (hectare de culture d’ulva)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "hectare de culture d’ulva",
|
||||||
|
"originUnitsPer1000m3Methane": 95.2,
|
||||||
|
"bmp": 0.15,
|
||||||
|
"waterPercentage": 90,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"odorElimination"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,5",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.986Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-029",
|
||||||
|
"name": "ripe bananas (small local market)",
|
||||||
|
"originType": "markets",
|
||||||
|
"originSubType": "small local market",
|
||||||
|
"originUnitsPer1000m3Methane": 54.9,
|
||||||
|
"bmp": 0.13,
|
||||||
|
"waterPercentage": 80,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 5,2 to 5,8",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.986Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-030",
|
||||||
|
"name": "unsold ripe mangoes (plant agroalimentaire)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "plant agroalimentaire",
|
||||||
|
"originUnitsPer1000m3Methane": 68,
|
||||||
|
"bmp": 0.14,
|
||||||
|
"waterPercentage": 85,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 4,5 to 5,2",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.987Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-031",
|
||||||
|
"name": "fresh wet vegetables (wholesale market)",
|
||||||
|
"originType": "markets",
|
||||||
|
"originSubType": "wholesale market",
|
||||||
|
"originUnitsPer1000m3Methane": 142.9,
|
||||||
|
"bmp": 0.1,
|
||||||
|
"waterPercentage": 90,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 5,8 to 6,5",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.987Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-032",
|
||||||
|
"name": "processed vegetable residues (food industrial site)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "food industrial site",
|
||||||
|
"originUnitsPer1000m3Methane": 129.9,
|
||||||
|
"bmp": 0.11,
|
||||||
|
"waterPercentage": 90,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,0 to 6,5",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.987Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-033",
|
||||||
|
"name": "cooked food leftovers (restaurant)",
|
||||||
|
"originType": "restaurants",
|
||||||
|
"originSubType": "restaurant",
|
||||||
|
"originUnitsPer1000m3Methane": 47.6,
|
||||||
|
"bmp": 0.15,
|
||||||
|
"waterPercentage": 80,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"pathogenElimination"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 5,0 to 6,0",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.987Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-034",
|
||||||
|
"name": "spoiled meat (slaughterhouse)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "slaughterhouse",
|
||||||
|
"originUnitsPer1000m3Methane": 23.8,
|
||||||
|
"bmp": 0.2,
|
||||||
|
"waterPercentage": 70,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"odorElimination"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,5",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.987Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-035",
|
||||||
|
"name": "milk, yogurt, expired cream (plant de produits milkiers)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "plant de produits milkiers",
|
||||||
|
"originUnitsPer1000m3Methane": 111.6,
|
||||||
|
"bmp": 0.16,
|
||||||
|
"waterPercentage": 92,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"pathogenElimination"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 4,5 to 6,2 depending on fermentation",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.987Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-036",
|
||||||
|
"name": "stale bread (bakery)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "bakery",
|
||||||
|
"originUnitsPer1000m3Methane": 8.5,
|
||||||
|
"bmp": 0.28,
|
||||||
|
"waterPercentage": 40,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 5,5 to 6,8",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.987Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-037",
|
||||||
|
"name": "bananes trop mûres (households)",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "households",
|
||||||
|
"originUnitsPer1000m3Methane": 73.3,
|
||||||
|
"bmp": 0.13,
|
||||||
|
"waterPercentage": 85,
|
||||||
|
"regulationNeeds": [],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 4,5 to 5,5",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.987Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-038",
|
||||||
|
"name": "spoiled fish (fish market)",
|
||||||
|
"originType": "markets",
|
||||||
|
"originSubType": "fish market",
|
||||||
|
"originUnitsPer1000m3Methane": 32.5,
|
||||||
|
"bmp": 0.22,
|
||||||
|
"waterPercentage": 80,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"odorElimination"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 6,5 to 6,8",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.987Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste-039",
|
||||||
|
"name": "mixed biowaste (équivalent habitant (household food waste))",
|
||||||
|
"originType": "other",
|
||||||
|
"originSubType": "équivalent habitant (household food waste)",
|
||||||
|
"originUnitsPer1000m3Methane": 59.5,
|
||||||
|
"bmp": 0.12,
|
||||||
|
"waterPercentage": 80,
|
||||||
|
"regulationNeeds": [
|
||||||
|
"pathogenElimination",
|
||||||
|
"oilEmulsification"
|
||||||
|
],
|
||||||
|
"maxStorageDuration": 30,
|
||||||
|
"notes": "pH: 5,0 to 6,5",
|
||||||
|
"createdAt": "2025-12-09T19:58:23.989Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
10258
déchets.md
Normal file
10258
déchets.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,6 +6,10 @@ import DashboardPage from './pages/DashboardPage'
|
|||||||
import WasteConfigurationPage from './pages/configuration/WasteConfigurationPage'
|
import WasteConfigurationPage from './pages/configuration/WasteConfigurationPage'
|
||||||
import RegulatorsConfigurationPage from './pages/configuration/RegulatorsConfigurationPage'
|
import RegulatorsConfigurationPage from './pages/configuration/RegulatorsConfigurationPage'
|
||||||
import ServicesConfigurationPage from './pages/configuration/ServicesConfigurationPage'
|
import ServicesConfigurationPage from './pages/configuration/ServicesConfigurationPage'
|
||||||
|
import WasteOriginsConfigurationPage from './pages/configuration/WasteOriginsConfigurationPage'
|
||||||
|
import TransportersConfigurationPage from './pages/configuration/TransportersConfigurationPage'
|
||||||
|
import ModuleComponentsConfigurationPage from './pages/configuration/ModuleComponentsConfigurationPage'
|
||||||
|
import EcosystemsConfigurationPage from './pages/configuration/EcosystemsConfigurationPage'
|
||||||
import ProjectListPage from './pages/projects/ProjectListPage'
|
import ProjectListPage from './pages/projects/ProjectListPage'
|
||||||
import ProjectConfigurationPage from './pages/projects/ProjectConfigurationPage'
|
import ProjectConfigurationPage from './pages/projects/ProjectConfigurationPage'
|
||||||
import TreatmentSitesPage from './pages/projects/TreatmentSitesPage'
|
import TreatmentSitesPage from './pages/projects/TreatmentSitesPage'
|
||||||
@ -36,6 +40,10 @@ function App() {
|
|||||||
<Route path="/configuration/waste" element={<WasteConfigurationPage />} />
|
<Route path="/configuration/waste" element={<WasteConfigurationPage />} />
|
||||||
<Route path="/configuration/regulators" element={<RegulatorsConfigurationPage />} />
|
<Route path="/configuration/regulators" element={<RegulatorsConfigurationPage />} />
|
||||||
<Route path="/configuration/services" element={<ServicesConfigurationPage />} />
|
<Route path="/configuration/services" element={<ServicesConfigurationPage />} />
|
||||||
|
<Route path="/configuration/waste-origins" element={<WasteOriginsConfigurationPage />} />
|
||||||
|
<Route path="/configuration/transporters" element={<TransportersConfigurationPage />} />
|
||||||
|
<Route path="/configuration/module-components" element={<ModuleComponentsConfigurationPage />} />
|
||||||
|
<Route path="/configuration/ecosystems" element={<EcosystemsConfigurationPage />} />
|
||||||
<Route path="/projects" element={<ProjectListPage />} />
|
<Route path="/projects" element={<ProjectListPage />} />
|
||||||
<Route path="/projects/new" element={<ProjectConfigurationPage />} />
|
<Route path="/projects/new" element={<ProjectConfigurationPage />} />
|
||||||
<Route path="/projects/:id" element={<ProjectConfigurationPage />} />
|
<Route path="/projects/:id" element={<ProjectConfigurationPage />} />
|
||||||
|
|||||||
@ -24,6 +24,22 @@ export default function Sidebar() {
|
|||||||
<span className="sidebar-icon">💼</span>
|
<span className="sidebar-icon">💼</span>
|
||||||
<span className="sidebar-label">Services</span>
|
<span className="sidebar-label">Services</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
<NavLink to="/configuration/waste-origins" className="sidebar-item">
|
||||||
|
<span className="sidebar-icon">📍</span>
|
||||||
|
<span className="sidebar-label">Waste Origins</span>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to="/configuration/transporters" className="sidebar-item">
|
||||||
|
<span className="sidebar-icon">🚛</span>
|
||||||
|
<span className="sidebar-label">Transporters</span>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to="/configuration/module-components" className="sidebar-item">
|
||||||
|
<span className="sidebar-icon">⚙️</span>
|
||||||
|
<span className="sidebar-label">Module Components</span>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to="/configuration/ecosystems" className="sidebar-item">
|
||||||
|
<span className="sidebar-icon">🌱</span>
|
||||||
|
<span className="sidebar-label">Ecosystems</span>
|
||||||
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sidebar-section">
|
<div className="sidebar-section">
|
||||||
|
|||||||
@ -12,6 +12,14 @@ export default function DashboardPage() {
|
|||||||
const wastes = data?.wastes || []
|
const wastes = data?.wastes || []
|
||||||
const services = data?.services || []
|
const services = data?.services || []
|
||||||
const treatmentSites = data?.treatmentSites || []
|
const treatmentSites = data?.treatmentSites || []
|
||||||
|
const regulators = data?.regulators || []
|
||||||
|
const wasteOrigins = data?.wasteOrigins || []
|
||||||
|
const transporters = data?.transporters || []
|
||||||
|
const wasteSites = data?.wasteSites || []
|
||||||
|
const investors = data?.investors || []
|
||||||
|
const administrativeProcedures = data?.administrativeProcedures || []
|
||||||
|
const moduleComponents = data?.moduleComponents || []
|
||||||
|
const ecosystems = data?.ecosystems || []
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dashboard">
|
<div className="dashboard">
|
||||||
@ -36,43 +44,41 @@ export default function DashboardPage() {
|
|||||||
<div className="dashboard-metric-value">{treatmentSites.length}</div>
|
<div className="dashboard-metric-value">{treatmentSites.length}</div>
|
||||||
<div className="dashboard-metric-label">Treatment Sites</div>
|
<div className="dashboard-metric-label">Treatment Sites</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="dashboard-metric-card">
|
||||||
|
<div className="dashboard-metric-value">{regulators.length}</div>
|
||||||
|
<div className="dashboard-metric-label">Regulators</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="dashboard-metric-card">
|
||||||
<div className="dashboard-section">
|
<div className="dashboard-metric-value">{services.length}</div>
|
||||||
<h2 className="dashboard-section-title">Recent Projects</h2>
|
<div className="dashboard-metric-label">Services</div>
|
||||||
{projects.length === 0 ? (
|
|
||||||
<div className="dashboard-empty">
|
|
||||||
<p>No projects yet. Create your first project to get started.</p>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
<div className="dashboard-metric-card">
|
||||||
<div className="dashboard-projects">
|
<div className="dashboard-metric-value">{wasteOrigins.length}</div>
|
||||||
{projects.slice(0, 5).map((project) => (
|
<div className="dashboard-metric-label">Waste Origins</div>
|
||||||
<div key={project.id} className="dashboard-project-card">
|
|
||||||
<h3 className="dashboard-project-name">{project.name}</h3>
|
|
||||||
<p className="dashboard-project-info">
|
|
||||||
{project.numberOfModules} modules • {treatmentSites.find(s => s.id === project.treatmentSiteId)?.name || 'Unknown site'}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
<div className="dashboard-metric-card">
|
||||||
|
<div className="dashboard-metric-value">{transporters.length}</div>
|
||||||
|
<div className="dashboard-metric-label">Transporters</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="dashboard-metric-card">
|
||||||
|
<div className="dashboard-metric-value">{wasteSites.length}</div>
|
||||||
|
<div className="dashboard-metric-label">Waste Sites</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="dashboard-metric-card">
|
||||||
<div className="dashboard-section">
|
<div className="dashboard-metric-value">{investors.length}</div>
|
||||||
<h2 className="dashboard-section-title">Quick Actions</h2>
|
<div className="dashboard-metric-label">Investors</div>
|
||||||
<div className="dashboard-actions">
|
</div>
|
||||||
<a href="/projects/new" className="dashboard-action-button">
|
<div className="dashboard-metric-card">
|
||||||
Create New Project
|
<div className="dashboard-metric-value">{administrativeProcedures.length}</div>
|
||||||
</a>
|
<div className="dashboard-metric-label">Administrative Procedures</div>
|
||||||
<a href="/configuration/waste" className="dashboard-action-button">
|
</div>
|
||||||
Configure Waste Types
|
<div className="dashboard-metric-card">
|
||||||
</a>
|
<div className="dashboard-metric-value">{moduleComponents.length}</div>
|
||||||
<a href="/yields" className="dashboard-action-button">
|
<div className="dashboard-metric-label">Module Components</div>
|
||||||
View Yields
|
</div>
|
||||||
</a>
|
<div className="dashboard-metric-card">
|
||||||
<a href="/business-plan" className="dashboard-action-button">
|
<div className="dashboard-metric-value">{ecosystems.length}</div>
|
||||||
Business Plan
|
<div className="dashboard-metric-label">Ecosystems</div>
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
144
src/pages/configuration/EcosystemsConfigurationPage.css
Normal file
144
src/pages/configuration/EcosystemsConfigurationPage.css
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
.ecosystems-config-page {
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecosystems-config-page .page-title {
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecosystems-config-page .page-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
margin: var(--spacing-lg) 0;
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section-title {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.regulation-needs-inputs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.regulators-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
margin-top: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.regulator-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 1fr 1fr auto;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: var(--spacing-sm);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-regulator-btn {
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.regulators-summary {
|
||||||
|
margin-top: var(--spacing-md);
|
||||||
|
padding: var(--spacing-sm);
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.regulators-summary .error-text {
|
||||||
|
color: var(--error);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-wastes {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
margin-top: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.waste-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
padding: 4px 12px;
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-tag-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-tag-btn:hover {
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-text {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.regulation-needs-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.regulation-need-tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 8px;
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: var(--error);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-top: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.regulation-needs-inputs {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.regulator-item {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
469
src/pages/configuration/EcosystemsConfigurationPage.tsx
Normal file
469
src/pages/configuration/EcosystemsConfigurationPage.tsx
Normal file
@ -0,0 +1,469 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { useStorage } from '@/hooks/useStorage'
|
||||||
|
import { Ecosystem, EcosystemRegulatorAssociation, NaturalRegulator, Waste } 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, validateNumber } from '@/utils/validators'
|
||||||
|
import './EcosystemsConfigurationPage.css'
|
||||||
|
|
||||||
|
const REGULATION_NEEDS_OPTIONS = [
|
||||||
|
{ value: 'pathogenElimination', label: 'Pathogen Elimination' },
|
||||||
|
{ value: 'heavyMetalsElimination', label: 'Heavy Metals Elimination' },
|
||||||
|
{ value: 'metalBinding', label: 'Metal Binding' },
|
||||||
|
{ value: 'odorElimination', label: 'Odor Elimination' },
|
||||||
|
{ value: 'phIncrease', label: 'pH Increase' },
|
||||||
|
{ value: 'phReduction', label: 'pH Reduction' },
|
||||||
|
{ value: 'acidityReduction', label: 'Acidity Reduction' },
|
||||||
|
{ value: 'oilEmulsification', label: 'Oil Emulsification' },
|
||||||
|
{ value: 'medicationElimination', label: 'Medication Elimination' },
|
||||||
|
{ value: 'microbiologicalCompetition', label: 'Microbiological Competition' },
|
||||||
|
{ value: 'sodiumReduction', label: 'Sodium Reduction' },
|
||||||
|
{ value: 'chlorineReduction', label: 'Chlorine Reduction' },
|
||||||
|
{ value: 'electricalConductivityReduction', label: 'Electrical Conductivity Reduction' },
|
||||||
|
{ value: 'co2Elimination', label: 'CO₂ Elimination' },
|
||||||
|
{ value: 'methaneElimination', label: 'Methane Elimination' },
|
||||||
|
{ value: 'sulfideElimination', label: 'Sulfide Elimination' },
|
||||||
|
{ value: 'turbidityReduction', label: 'Turbidity Reduction' },
|
||||||
|
{ value: 'depositElimination', label: 'Deposit Elimination' },
|
||||||
|
{ value: 'polyphenolElimination', label: 'Polyphenol Elimination' },
|
||||||
|
{ value: 'refractoryFractionsElimination', label: 'Refractory Fractions Elimination' },
|
||||||
|
{ value: 'lowCNRatioEnrichment', label: 'Low C:N Ratio Enrichment' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function EcosystemsConfigurationPage() {
|
||||||
|
const { data, addEntity, updateEntity, deleteEntity } = useStorage()
|
||||||
|
const [editingId, setEditingId] = useState<string | null>(null)
|
||||||
|
const [formData, setFormData] = useState<Partial<Ecosystem>>({
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
primaryRegulationNeeds: ['', '', ''],
|
||||||
|
regulators: [],
|
||||||
|
compatibleWasteTypes: [],
|
||||||
|
effectiveness: {},
|
||||||
|
applicationConditions: '',
|
||||||
|
treatmentDuration: 21,
|
||||||
|
})
|
||||||
|
const [errors, setErrors] = useState<Record<string, string>>({})
|
||||||
|
|
||||||
|
const ecosystems = data?.ecosystems || []
|
||||||
|
const regulators = data?.regulators || []
|
||||||
|
const wastes = data?.wastes || []
|
||||||
|
|
||||||
|
const regulatorOptions = regulators.map((reg) => ({
|
||||||
|
value: reg.id,
|
||||||
|
label: reg.name,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const wasteOptions = wastes.map((waste) => ({
|
||||||
|
value: waste.id,
|
||||||
|
label: waste.name,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const newErrors: Record<string, string> = {}
|
||||||
|
newErrors.name = validateRequired(formData.name)
|
||||||
|
|
||||||
|
// Validate that exactly 3 regulation needs are selected
|
||||||
|
if (!formData.primaryRegulationNeeds || formData.primaryRegulationNeeds.length !== 3) {
|
||||||
|
newErrors.primaryRegulationNeeds = 'Exactly 3 regulation needs must be selected'
|
||||||
|
} else {
|
||||||
|
const hasEmpty = formData.primaryRegulationNeeds.some(need => !need)
|
||||||
|
if (hasEmpty) {
|
||||||
|
newErrors.primaryRegulationNeeds = 'All 3 regulation needs must be selected'
|
||||||
|
}
|
||||||
|
const hasDuplicates = new Set(formData.primaryRegulationNeeds).size !== 3
|
||||||
|
if (hasDuplicates) {
|
||||||
|
newErrors.primaryRegulationNeeds = 'Regulation needs must be unique'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate regulators
|
||||||
|
if (!formData.regulators || formData.regulators.length === 0) {
|
||||||
|
newErrors.regulators = 'At least one regulator must be added'
|
||||||
|
} else {
|
||||||
|
const totalPercentage = formData.regulators.reduce((sum, r) => sum + (r.percentage || 0), 0)
|
||||||
|
if (Math.abs(totalPercentage - 100) > 0.1) {
|
||||||
|
newErrors.regulators = `Total regulator percentage must equal 100% (currently ${totalPercentage.toFixed(1)}%)`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate effectiveness for each regulation need
|
||||||
|
if (formData.primaryRegulationNeeds) {
|
||||||
|
formData.primaryRegulationNeeds.forEach((need) => {
|
||||||
|
if (need && (!formData.effectiveness || !formData.effectiveness[need])) {
|
||||||
|
newErrors[`effectiveness_${need}`] = `Effectiveness for ${need} is required`
|
||||||
|
} else if (need && formData.effectiveness && formData.effectiveness[need]) {
|
||||||
|
const eff = formData.effectiveness[need]
|
||||||
|
if (eff < 0 || eff > 100) {
|
||||||
|
newErrors[`effectiveness_${need}`] = 'Effectiveness must be between 0 and 100'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setErrors(newErrors)
|
||||||
|
|
||||||
|
if (Object.values(newErrors).some((error) => error !== undefined)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const ecosystem: Ecosystem = {
|
||||||
|
id: editingId || crypto.randomUUID(),
|
||||||
|
name: formData.name!,
|
||||||
|
description: formData.description,
|
||||||
|
primaryRegulationNeeds: formData.primaryRegulationNeeds as [string, string, string],
|
||||||
|
regulators: formData.regulators || [],
|
||||||
|
compatibleWasteTypes: formData.compatibleWasteTypes || [],
|
||||||
|
effectiveness: formData.effectiveness || {},
|
||||||
|
applicationConditions: formData.applicationConditions,
|
||||||
|
treatmentDuration: formData.treatmentDuration,
|
||||||
|
createdAt: editingId ? ecosystems.find((e) => e.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editingId) {
|
||||||
|
updateEntity('ecosystems', editingId, ecosystem)
|
||||||
|
} else {
|
||||||
|
addEntity('ecosystems', ecosystem)
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
setFormData({
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
primaryRegulationNeeds: ['', '', ''],
|
||||||
|
regulators: [],
|
||||||
|
compatibleWasteTypes: [],
|
||||||
|
effectiveness: {},
|
||||||
|
applicationConditions: '',
|
||||||
|
treatmentDuration: 21,
|
||||||
|
})
|
||||||
|
setEditingId(null)
|
||||||
|
setErrors({})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEdit = (ecosystem: Ecosystem) => {
|
||||||
|
setFormData({
|
||||||
|
...ecosystem,
|
||||||
|
primaryRegulationNeeds: ecosystem.primaryRegulationNeeds || ['', '', ''],
|
||||||
|
regulators: ecosystem.regulators || [],
|
||||||
|
compatibleWasteTypes: ecosystem.compatibleWasteTypes || [],
|
||||||
|
effectiveness: ecosystem.effectiveness || {},
|
||||||
|
})
|
||||||
|
setEditingId(ecosystem.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = (id: string) => {
|
||||||
|
if (confirm('Are you sure you want to delete this ecosystem?')) {
|
||||||
|
deleteEntity('ecosystems', id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addRegulator = () => {
|
||||||
|
const newRegulator: EcosystemRegulatorAssociation = {
|
||||||
|
regulatorId: '',
|
||||||
|
percentage: 0,
|
||||||
|
role: 'support',
|
||||||
|
}
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
regulators: [...(formData.regulators || []), newRegulator],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeRegulator = (index: number) => {
|
||||||
|
const updatedRegulators = (formData.regulators || []).filter((_, i) => i !== index)
|
||||||
|
setFormData({ ...formData, regulators: updatedRegulators })
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateRegulator = (index: number, updates: Partial<EcosystemRegulatorAssociation>) => {
|
||||||
|
const updatedRegulators = [...(formData.regulators || [])]
|
||||||
|
updatedRegulators[index] = { ...updatedRegulators[index], ...updates }
|
||||||
|
setFormData({ ...formData, regulators: updatedRegulators })
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalRegulatorPercentage = (formData.regulators || []).reduce((sum, r) => sum + (r.percentage || 0), 0)
|
||||||
|
|
||||||
|
const tableColumns = [
|
||||||
|
{
|
||||||
|
key: 'name',
|
||||||
|
header: 'Name',
|
||||||
|
render: (ecosystem: Ecosystem) => ecosystem.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'regulationNeeds',
|
||||||
|
header: 'Primary Regulation Needs',
|
||||||
|
render: (ecosystem: Ecosystem) => (
|
||||||
|
<div className="regulation-needs-list">
|
||||||
|
{ecosystem.primaryRegulationNeeds.map((need, idx) => (
|
||||||
|
<span key={idx} className="regulation-need-tag">
|
||||||
|
{REGULATION_NEEDS_OPTIONS.find(opt => opt.value === need)?.label || need}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'regulators',
|
||||||
|
header: 'Regulators',
|
||||||
|
render: (ecosystem: Ecosystem) => `${ecosystem.regulators.length} regulator(s)`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'effectiveness',
|
||||||
|
header: 'Avg Effectiveness',
|
||||||
|
render: (ecosystem: Ecosystem) => {
|
||||||
|
const values = ecosystem.primaryRegulationNeeds.map(need => ecosystem.effectiveness[need] || 0)
|
||||||
|
const avg = values.reduce((sum, v) => sum + v, 0) / values.length
|
||||||
|
return `${avg.toFixed(0)}%`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'actions',
|
||||||
|
header: 'Actions',
|
||||||
|
render: (ecosystem: Ecosystem) => (
|
||||||
|
<div className="table-actions">
|
||||||
|
<Button variant="secondary" onClick={() => handleEdit(ecosystem)}>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
<Button variant="danger" onClick={() => handleDelete(ecosystem.id)}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ecosystems-config-page">
|
||||||
|
<h1 className="page-title">Ecosystems Configuration</h1>
|
||||||
|
|
||||||
|
<div className="page-content">
|
||||||
|
<Card title={editingId ? 'Edit Ecosystem' : 'Add Ecosystem'} className="form-card">
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<Input
|
||||||
|
label="Name *"
|
||||||
|
value={formData.name || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
error={errors.name}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
label="Description"
|
||||||
|
value={formData.description || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">Primary Regulation Needs (exactly 3)</h3>
|
||||||
|
<div className="regulation-needs-inputs">
|
||||||
|
{[0, 1, 2].map((idx) => (
|
||||||
|
<Select
|
||||||
|
key={idx}
|
||||||
|
label={`Regulation Need ${idx + 1} *`}
|
||||||
|
value={formData.primaryRegulationNeeds?.[idx] || ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const updated = [...(formData.primaryRegulationNeeds || ['', '', ''])]
|
||||||
|
updated[idx] = e.target.value
|
||||||
|
setFormData({ ...formData, primaryRegulationNeeds: updated as [string, string, string] })
|
||||||
|
}}
|
||||||
|
options={[
|
||||||
|
{ value: '', label: 'Select a regulation need' },
|
||||||
|
...REGULATION_NEEDS_OPTIONS.filter(
|
||||||
|
(opt) =>
|
||||||
|
opt.value === formData.primaryRegulationNeeds?.[idx] ||
|
||||||
|
!(formData.primaryRegulationNeeds || []).includes(opt.value)
|
||||||
|
),
|
||||||
|
]}
|
||||||
|
error={errors.primaryRegulationNeeds}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">Regulators</h3>
|
||||||
|
<div className="regulators-list">
|
||||||
|
{(formData.regulators || []).map((regulator, index) => {
|
||||||
|
const regulatorObj = regulators.find((r) => r.id === regulator.regulatorId)
|
||||||
|
return (
|
||||||
|
<div key={index} className="regulator-item">
|
||||||
|
<Select
|
||||||
|
label="Regulator *"
|
||||||
|
value={regulator.regulatorId || ''}
|
||||||
|
onChange={(e) => updateRegulator(index, { regulatorId: e.target.value })}
|
||||||
|
options={[
|
||||||
|
{ value: '', label: 'Select a regulator' },
|
||||||
|
...regulatorOptions.filter(
|
||||||
|
(opt) =>
|
||||||
|
opt.value === regulator.regulatorId ||
|
||||||
|
!(formData.regulators || []).some((r) => r.regulatorId === opt.value)
|
||||||
|
),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Percentage (%) *"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
step="0.1"
|
||||||
|
value={regulator.percentage || ''}
|
||||||
|
onChange={(e) => updateRegulator(index, { percentage: parseFloat(e.target.value) || 0 })}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
label="Role *"
|
||||||
|
value={regulator.role || 'support'}
|
||||||
|
onChange={(e) => updateRegulator(index, { role: e.target.value as 'primary' | 'secondary' | 'support' })}
|
||||||
|
options={[
|
||||||
|
{ value: 'primary', label: 'Primary' },
|
||||||
|
{ value: 'secondary', label: 'Secondary' },
|
||||||
|
{ value: 'support', label: 'Support' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="danger"
|
||||||
|
onClick={() => removeRegulator(index)}
|
||||||
|
className="remove-regulator-btn"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<Button type="button" variant="secondary" onClick={addRegulator}>
|
||||||
|
Add Regulator
|
||||||
|
</Button>
|
||||||
|
{totalRegulatorPercentage > 0 && (
|
||||||
|
<div className="regulators-summary">
|
||||||
|
<strong>
|
||||||
|
Total: {totalRegulatorPercentage.toFixed(1)}%{' '}
|
||||||
|
{Math.abs(totalRegulatorPercentage - 100) > 0.1 && (
|
||||||
|
<span className="error-text">(must equal 100%)</span>
|
||||||
|
)}
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{errors.regulators && <div className="error-message">{errors.regulators}</div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">Effectiveness (0-100%)</h3>
|
||||||
|
{formData.primaryRegulationNeeds?.map((need, idx) => {
|
||||||
|
if (!need) return null
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
key={idx}
|
||||||
|
label={`${REGULATION_NEEDS_OPTIONS.find(opt => opt.value === need)?.label || need} (%) *`}
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
step="1"
|
||||||
|
value={formData.effectiveness?.[need] || ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = parseFloat(e.target.value) || 0
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
effectiveness: {
|
||||||
|
...(formData.effectiveness || {}),
|
||||||
|
[need]: value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
error={errors[`effectiveness_${need}`]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">Compatible Waste Types (optional)</h3>
|
||||||
|
<Select
|
||||||
|
label="Waste Types"
|
||||||
|
value=""
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.value && !(formData.compatibleWasteTypes || []).includes(e.target.value)) {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
compatibleWasteTypes: [...(formData.compatibleWasteTypes || []), e.target.value],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
options={[
|
||||||
|
{ value: '', label: 'Select a waste type to add' },
|
||||||
|
...wasteOptions.filter((opt) => !(formData.compatibleWasteTypes || []).includes(opt.value)),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
{(formData.compatibleWasteTypes || []).length > 0 && (
|
||||||
|
<div className="selected-wastes">
|
||||||
|
{(formData.compatibleWasteTypes || []).map((wasteId) => {
|
||||||
|
const waste = wastes.find((w) => w.id === wasteId)
|
||||||
|
return (
|
||||||
|
<span key={wasteId} className="waste-tag">
|
||||||
|
{waste?.name || wasteId}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
compatibleWasteTypes: (formData.compatibleWasteTypes || []).filter((id) => id !== wasteId),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
className="remove-tag-btn"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<p className="help-text">Leave empty to make this ecosystem compatible with all waste types</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
label="Application Conditions"
|
||||||
|
value={formData.applicationConditions || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, applicationConditions: e.target.value })}
|
||||||
|
multiline
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
label="Treatment Duration (days)"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
value={formData.treatmentDuration || 21}
|
||||||
|
onChange={(e) => setFormData({ ...formData, treatmentDuration: parseInt(e.target.value) || 21 })}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="form-actions">
|
||||||
|
<Button type="submit" variant="primary">
|
||||||
|
{editingId ? 'Update' : 'Add'} Ecosystem
|
||||||
|
</Button>
|
||||||
|
{editingId && (
|
||||||
|
<Button type="button" variant="secondary" onClick={resetForm}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="Ecosystems" className="table-card">
|
||||||
|
<Table columns={tableColumns} data={ecosystems} emptyMessage="No ecosystems configured" />
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
136
src/pages/configuration/ModuleComponentsConfigurationPage.css
Normal file
136
src/pages/configuration/ModuleComponentsConfigurationPage.css
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
.module-components-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-section {
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
padding-top: var(--spacing-xl);
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-subsection {
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-subsection-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-checkbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-checkbox label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-checkbox input[type="checkbox"] {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: var(--primary-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.monthly-inputs-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
margin-top: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
margin-top: var(--spacing-md);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: var(--spacing-xs);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 200ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item:hover {
|
||||||
|
background-color: var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item input[type="checkbox"] {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: var(--primary-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
698
src/pages/configuration/ModuleComponentsConfigurationPage.tsx
Normal file
698
src/pages/configuration/ModuleComponentsConfigurationPage.tsx
Normal file
@ -0,0 +1,698 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { useStorage } from '@/hooks/useStorage'
|
||||||
|
import { ModuleComponent, ModuleComponentType, MonthlyValue, Dimensions, PowerSpecs, ProductionPowerSpecs } 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 Badge from '@/components/base/Badge'
|
||||||
|
import { validateRequired, validateNumber } from '@/utils/validators'
|
||||||
|
import './ModuleComponentsConfigurationPage.css'
|
||||||
|
|
||||||
|
const MONTHS = [
|
||||||
|
'january', 'february', 'march', 'april', 'may', 'june',
|
||||||
|
'july', 'august', 'september', 'october', 'november', 'december'
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const MONTH_LABELS: Record<string, string> = {
|
||||||
|
january: 'January',
|
||||||
|
february: 'February',
|
||||||
|
march: 'March',
|
||||||
|
april: 'April',
|
||||||
|
may: 'May',
|
||||||
|
june: 'June',
|
||||||
|
july: 'July',
|
||||||
|
august: 'August',
|
||||||
|
september: 'September',
|
||||||
|
october: 'October',
|
||||||
|
november: 'November',
|
||||||
|
december: 'December',
|
||||||
|
}
|
||||||
|
|
||||||
|
const COMPONENT_TYPES: { value: ModuleComponentType; label: string; isOptional: boolean; defaultDuration: number; durationUnit: 'days' | 'hours' }[] = [
|
||||||
|
{ value: 'firstMethanization', label: '1st Methanization (Required)', isOptional: false, defaultDuration: 21, durationUnit: 'days' },
|
||||||
|
{ value: 'waterEvaporation', label: 'Water Evaporation (Optional)', isOptional: true, defaultDuration: 21, durationUnit: 'days' },
|
||||||
|
{ value: 'bioremediationEcosystem1', label: 'Bioremediation Ecosystem 1 (Optional)', isOptional: true, defaultDuration: 21, durationUnit: 'days' },
|
||||||
|
{ value: 'bioremediationEcosystem2', label: 'Bioremediation Ecosystem 2 (Optional)', isOptional: true, defaultDuration: 21, durationUnit: 'days' },
|
||||||
|
{ value: 'bioremediationEcosystem3', label: 'Bioremediation Ecosystem 3 (Optional)', isOptional: true, defaultDuration: 21, durationUnit: 'days' },
|
||||||
|
{ value: 'secondMethanization', label: '2nd Methanization (Required unless no remediation)', isOptional: false, defaultDuration: 21, durationUnit: 'days' },
|
||||||
|
{ value: 'waterEvaporationComposting', label: 'Water Evaporation & Composting (Optional)', isOptional: true, defaultDuration: 18, durationUnit: 'days' },
|
||||||
|
{ value: 'waterUvcTreatment', label: 'Water UV-C Treatment (Optional)', isOptional: true, defaultDuration: 72, durationUnit: 'hours' },
|
||||||
|
{ value: 'waterStorage', label: 'Water Storage', isOptional: false, defaultDuration: 72, durationUnit: 'hours' },
|
||||||
|
{ value: 'spirulinaWaterStorage', label: 'Spirulina in Water Storage (Optional)', isOptional: true, defaultDuration: 21, durationUnit: 'days' },
|
||||||
|
{ value: 'solarPanels', label: 'Solar Panels (Optional)', isOptional: true, defaultDuration: 0, durationUnit: 'days' },
|
||||||
|
{ value: 'bitcoinMining', label: 'Bitcoin Mining (Optional)', isOptional: true, defaultDuration: 0, durationUnit: 'days' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function ModuleComponentsConfigurationPage() {
|
||||||
|
const { data, addEntity, updateEntity, deleteEntity } = useStorage()
|
||||||
|
const [editingId, setEditingId] = useState<string | null>(null)
|
||||||
|
const [formData, setFormData] = useState<Partial<ModuleComponent>>({
|
||||||
|
name: '',
|
||||||
|
type: 'firstMethanization',
|
||||||
|
isOptional: false,
|
||||||
|
defaultDuration: 21,
|
||||||
|
durationUnit: 'days',
|
||||||
|
})
|
||||||
|
const [errors, setErrors] = useState<Record<string, string>>({})
|
||||||
|
|
||||||
|
const components = data?.moduleComponents || []
|
||||||
|
const wastes = data?.wastes || []
|
||||||
|
|
||||||
|
const selectedComponentType = COMPONENT_TYPES.find(t => t.value === formData.type)
|
||||||
|
|
||||||
|
// Initialize form data when component type changes
|
||||||
|
const handleTypeChange = (type: ModuleComponentType) => {
|
||||||
|
const typeConfig = COMPONENT_TYPES.find(t => t.value === type)
|
||||||
|
if (typeConfig) {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
type,
|
||||||
|
isOptional: typeConfig.isOptional,
|
||||||
|
defaultDuration: typeConfig.defaultDuration,
|
||||||
|
durationUnit: typeConfig.durationUnit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const newErrors: Record<string, string> = {}
|
||||||
|
newErrors.name = validateRequired(formData.name)
|
||||||
|
newErrors.type = validateRequired(formData.type)
|
||||||
|
newErrors.defaultDuration = validateNumber(formData.defaultDuration, 0)
|
||||||
|
|
||||||
|
setErrors(newErrors)
|
||||||
|
|
||||||
|
if (Object.values(newErrors).some((error) => error !== undefined)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const component: ModuleComponent = {
|
||||||
|
id: editingId || crypto.randomUUID(),
|
||||||
|
name: formData.name!,
|
||||||
|
type: formData.type!,
|
||||||
|
isOptional: formData.isOptional ?? false,
|
||||||
|
defaultDuration: formData.defaultDuration!,
|
||||||
|
durationUnit: formData.durationUnit || 'days',
|
||||||
|
waterConsumptionPerDay: formData.waterConsumptionPerDay,
|
||||||
|
heatConsumptionPerDay: formData.heatConsumptionPerDay,
|
||||||
|
powerConsumption: formData.powerConsumption,
|
||||||
|
totalWetWasteCapacity: formData.totalWetWasteCapacity,
|
||||||
|
totalDryWasteCapacity: formData.totalDryWasteCapacity,
|
||||||
|
totalCompostCapacity: formData.totalCompostCapacity,
|
||||||
|
totalWaterCapacity: formData.totalWaterCapacity,
|
||||||
|
methaneProduction: formData.methaneProduction,
|
||||||
|
addedBiomassProduction: formData.addedBiomassProduction,
|
||||||
|
waterProduction: formData.waterProduction,
|
||||||
|
heatProduction: formData.heatProduction,
|
||||||
|
immobilizationDuration: formData.immobilizationDuration,
|
||||||
|
wasteTypes: formData.wasteTypes,
|
||||||
|
dimensions: formData.dimensions,
|
||||||
|
groundSurface: formData.groundSurface,
|
||||||
|
heatNeedsPerDay: formData.heatNeedsPerDay,
|
||||||
|
coolingNeedsPerDay: formData.coolingNeedsPerDay,
|
||||||
|
productionPower: formData.productionPower,
|
||||||
|
annualExploitationETP: formData.annualExploitationETP,
|
||||||
|
annualMaintenanceETP: formData.annualMaintenanceETP,
|
||||||
|
annualSupervisionETP: formData.annualSupervisionETP,
|
||||||
|
totalMaterialCost: formData.totalMaterialCost,
|
||||||
|
annualExploitationConsumablesCost: formData.annualExploitationConsumablesCost,
|
||||||
|
annualMaintenanceConsumablesCost: formData.annualMaintenanceConsumablesCost,
|
||||||
|
lifetime: formData.lifetime,
|
||||||
|
createdAt: editingId ? components.find((c) => c.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editingId) {
|
||||||
|
updateEntity('moduleComponents', editingId, component)
|
||||||
|
} else {
|
||||||
|
addEntity('moduleComponents', component)
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
setFormData({
|
||||||
|
name: '',
|
||||||
|
type: 'firstMethanization',
|
||||||
|
isOptional: false,
|
||||||
|
defaultDuration: 21,
|
||||||
|
durationUnit: 'days',
|
||||||
|
})
|
||||||
|
setEditingId(null)
|
||||||
|
setErrors({})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEdit = (component: ModuleComponent) => {
|
||||||
|
setFormData(component)
|
||||||
|
setEditingId(component.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = (id: string) => {
|
||||||
|
if (confirm('Are you sure you want to delete this module component?')) {
|
||||||
|
deleteEntity('moduleComponents', id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to create empty monthly value
|
||||||
|
const createEmptyMonthlyValue = (): MonthlyValue => ({
|
||||||
|
january: 0,
|
||||||
|
february: 0,
|
||||||
|
march: 0,
|
||||||
|
april: 0,
|
||||||
|
may: 0,
|
||||||
|
june: 0,
|
||||||
|
july: 0,
|
||||||
|
august: 0,
|
||||||
|
september: 0,
|
||||||
|
october: 0,
|
||||||
|
november: 0,
|
||||||
|
december: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Helper to update monthly value
|
||||||
|
const updateMonthlyValue = (
|
||||||
|
field: 'waterConsumptionPerDay' | 'heatConsumptionPerDay' | 'heatNeedsPerDay' | 'coolingNeedsPerDay',
|
||||||
|
month: keyof MonthlyValue,
|
||||||
|
value: number
|
||||||
|
) => {
|
||||||
|
const current = formData[field] || createEmptyMonthlyValue()
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
[field]: {
|
||||||
|
...current,
|
||||||
|
[month]: value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableColumns = [
|
||||||
|
{
|
||||||
|
key: 'name',
|
||||||
|
header: 'Name',
|
||||||
|
render: (component: ModuleComponent) => component.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'type',
|
||||||
|
header: 'Type',
|
||||||
|
render: (component: ModuleComponent) => {
|
||||||
|
const typeConfig = COMPONENT_TYPES.find(t => t.value === component.type)
|
||||||
|
return <Badge variant={component.isOptional ? 'info' : 'success'}>{typeConfig?.label || component.type}</Badge>
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'duration',
|
||||||
|
header: 'Duration',
|
||||||
|
render: (component: ModuleComponent) => `${component.defaultDuration} ${component.durationUnit}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'actions',
|
||||||
|
header: 'Actions',
|
||||||
|
render: (component: ModuleComponent) => (
|
||||||
|
<div className="table-actions">
|
||||||
|
<Button variant="secondary" onClick={() => handleEdit(component)}>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
<Button variant="danger" onClick={() => handleDelete(component.id)}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="module-components-config-page">
|
||||||
|
<h1 className="page-title">Module Components Configuration</h1>
|
||||||
|
|
||||||
|
<div className="page-content">
|
||||||
|
<Card title={editingId ? 'Edit Module Component' : 'Add Module Component'} className="form-card">
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{/* Basic Information */}
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">Basic Information</h3>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Name *"
|
||||||
|
value={formData.name || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
error={errors.name}
|
||||||
|
placeholder="Enter component name"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
label="Component Type *"
|
||||||
|
value={formData.type || 'firstMethanization'}
|
||||||
|
onChange={(e) => handleTypeChange(e.target.value as ModuleComponentType)}
|
||||||
|
options={COMPONENT_TYPES.map(t => ({ value: t.value, label: t.label }))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label={`Default Duration (${formData.durationUnit}) *`}
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
value={formData.defaultDuration || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, defaultDuration: parseFloat(e.target.value) || 0 })}
|
||||||
|
error={errors.defaultDuration}
|
||||||
|
/>
|
||||||
|
<div className="form-checkbox">
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.isOptional || false}
|
||||||
|
onChange={(e) => setFormData({ ...formData, isOptional: e.target.checked })}
|
||||||
|
/>
|
||||||
|
<span>Optional Component</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Consumption Section */}
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">Consumption (per day, by month, at fixed temperature 13°C)</h3>
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">Water Consumption (L/day or m³/day)</h4>
|
||||||
|
<div className="monthly-inputs-grid">
|
||||||
|
{MONTHS.map((month) => (
|
||||||
|
<Input
|
||||||
|
key={month}
|
||||||
|
label={MONTH_LABELS[month]}
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.waterConsumptionPerDay?.[month] || ''}
|
||||||
|
onChange={(e) => updateMonthlyValue('waterConsumptionPerDay', month, parseFloat(e.target.value) || 0)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">Heat Consumption (kJ/day or kWh/day at 13°C)</h4>
|
||||||
|
<div className="monthly-inputs-grid">
|
||||||
|
{MONTHS.map((month) => (
|
||||||
|
<Input
|
||||||
|
key={month}
|
||||||
|
label={MONTH_LABELS[month]}
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.heatConsumptionPerDay?.[month] || ''}
|
||||||
|
onChange={(e) => updateMonthlyValue('heatConsumptionPerDay', month, parseFloat(e.target.value) || 0)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">Power Consumption</h4>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="kW.h"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.powerConsumption?.kwh || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
powerConsumption: {
|
||||||
|
...formData.powerConsumption,
|
||||||
|
kwh: parseFloat(e.target.value) || 0,
|
||||||
|
kw: formData.powerConsumption?.kw || 0,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="kW équipés"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.powerConsumption?.kw || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
powerConsumption: {
|
||||||
|
...formData.powerConsumption,
|
||||||
|
kwh: formData.powerConsumption?.kwh || 0,
|
||||||
|
kw: parseFloat(e.target.value) || 0,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Capacities Section */}
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">Capacities</h3>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Total Wet Waste Capacity (t)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.totalWetWasteCapacity || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, totalWetWasteCapacity: parseFloat(e.target.value) || undefined })}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Total Dry Waste Capacity (t)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.totalDryWasteCapacity || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, totalDryWasteCapacity: parseFloat(e.target.value) || undefined })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Total Compost Capacity (t)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.totalCompostCapacity || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, totalCompostCapacity: parseFloat(e.target.value) || undefined })}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Total Water Capacity (m³ or L)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.totalWaterCapacity || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, totalWaterCapacity: parseFloat(e.target.value) || undefined })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Production Section */}
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">Production</h3>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Methane Production (m³/day or m³/cycle)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.methaneProduction || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, methaneProduction: parseFloat(e.target.value) || undefined })}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Added Biomass Production (t/day or t/cycle)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.addedBiomassProduction || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, addedBiomassProduction: parseFloat(e.target.value) || undefined })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Water Production (m³/day or L/day)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.waterProduction || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, waterProduction: parseFloat(e.target.value) || undefined })}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Heat Production (kJ/day or kWh/day)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.heatProduction || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, heatProduction: parseFloat(e.target.value) || undefined })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Immobilization & Waste Types */}
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">Immobilization & Waste Types</h3>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Immobilization Duration (days)"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
value={formData.immobilizationDuration || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, immobilizationDuration: parseFloat(e.target.value) || undefined })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">Waste Types (select waste IDs that can be processed)</h4>
|
||||||
|
<div className="checkbox-grid">
|
||||||
|
{wastes.map((waste) => (
|
||||||
|
<label key={waste.id} className="checkbox-item">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.wasteTypes?.includes(waste.id) || false}
|
||||||
|
onChange={(e) => {
|
||||||
|
const currentTypes = formData.wasteTypes || []
|
||||||
|
if (e.target.checked) {
|
||||||
|
setFormData({ ...formData, wasteTypes: [...currentTypes, waste.id] })
|
||||||
|
} else {
|
||||||
|
setFormData({ ...formData, wasteTypes: currentTypes.filter((id) => id !== waste.id) })
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span>{waste.name}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Dimensions Section */}
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">Dimensions</h3>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Length (m)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.dimensions?.length || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
dimensions: {
|
||||||
|
...formData.dimensions,
|
||||||
|
length: parseFloat(e.target.value) || 0,
|
||||||
|
height: formData.dimensions?.height || 0,
|
||||||
|
width: formData.dimensions?.width || 0,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Height (m)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.dimensions?.height || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
dimensions: {
|
||||||
|
...formData.dimensions,
|
||||||
|
length: formData.dimensions?.length || 0,
|
||||||
|
height: parseFloat(e.target.value) || 0,
|
||||||
|
width: formData.dimensions?.width || 0,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Width (m)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.dimensions?.width || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
dimensions: {
|
||||||
|
...formData.dimensions,
|
||||||
|
length: formData.dimensions?.length || 0,
|
||||||
|
height: formData.dimensions?.height || 0,
|
||||||
|
width: parseFloat(e.target.value) || 0,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Ground Surface (m²)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.groundSurface || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, groundSurface: parseFloat(e.target.value) || undefined })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Needs Section */}
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">Needs (per day, by month, at fixed temperature 13°C)</h3>
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">Heat Needs (kJ/day or kWh/day at 13°C)</h4>
|
||||||
|
<div className="monthly-inputs-grid">
|
||||||
|
{MONTHS.map((month) => (
|
||||||
|
<Input
|
||||||
|
key={month}
|
||||||
|
label={MONTH_LABELS[month]}
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.heatNeedsPerDay?.[month] || ''}
|
||||||
|
onChange={(e) => updateMonthlyValue('heatNeedsPerDay', month, parseFloat(e.target.value) || 0)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">Cooling Needs (kJ/day or kWh/day at 13°C)</h4>
|
||||||
|
<div className="monthly-inputs-grid">
|
||||||
|
{MONTHS.map((month) => (
|
||||||
|
<Input
|
||||||
|
key={month}
|
||||||
|
label={MONTH_LABELS[month]}
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.coolingNeedsPerDay?.[month] || ''}
|
||||||
|
onChange={(e) => updateMonthlyValue('coolingNeedsPerDay', month, parseFloat(e.target.value) || 0)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Production Power Section */}
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">Production Power</h3>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="kW.h possibles"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.productionPower?.kwh || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
productionPower: {
|
||||||
|
...formData.productionPower,
|
||||||
|
kwh: parseFloat(e.target.value) || 0,
|
||||||
|
kw: formData.productionPower?.kw || 0,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="kW équipés possibles"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.productionPower?.kw || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
productionPower: {
|
||||||
|
...formData.productionPower,
|
||||||
|
kwh: formData.productionPower?.kwh || 0,
|
||||||
|
kw: parseFloat(e.target.value) || 0,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ETP Section */}
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">ETP (Full Time Equivalent - Annual)</h3>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="ETP Exploitation (annual)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.annualExploitationETP || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, annualExploitationETP: parseFloat(e.target.value) || undefined })}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="ETP Maintenance (annual)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.annualMaintenanceETP || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, annualMaintenanceETP: parseFloat(e.target.value) || undefined })}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="ETP Supervision (annual)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.annualSupervisionETP || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, annualSupervisionETP: parseFloat(e.target.value) || undefined })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Costs Section */}
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">Costs (Annual)</h3>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Total Material Cost - CAPEX (€/year)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.totalMaterialCost || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, totalMaterialCost: parseFloat(e.target.value) || undefined })}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Exploitation & Consumables Cost (€/year)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.annualExploitationConsumablesCost || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, annualExploitationConsumablesCost: parseFloat(e.target.value) || undefined })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Maintenance & Consumables Cost (€/year)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.annualMaintenanceConsumablesCost || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, annualMaintenanceConsumablesCost: parseFloat(e.target.value) || undefined })}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Lifetime (years)"
|
||||||
|
type="number"
|
||||||
|
step="0.1"
|
||||||
|
min="0"
|
||||||
|
value={formData.lifetime || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, lifetime: parseFloat(e.target.value) || undefined })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-actions">
|
||||||
|
<Button type="submit" variant="primary">
|
||||||
|
{editingId ? 'Update' : 'Add'} Component
|
||||||
|
</Button>
|
||||||
|
{editingId && (
|
||||||
|
<Button type="button" variant="secondary" onClick={resetForm}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="Module Components" className="table-card">
|
||||||
|
<Table columns={tableColumns} data={components} emptyMessage="No module components configured" />
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -33,3 +33,70 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-sm);
|
gap: var(--spacing-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
padding-top: var(--spacing-xl);
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-subsection {
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-subsection-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
margin-top: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: var(--spacing-xs);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 200ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item:hover {
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item input[type="checkbox"] {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: var(--primary-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-loading {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 400px;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-empty {
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
@ -9,7 +9,7 @@ import { validateRequired, validateNumber } from '@/utils/validators'
|
|||||||
import './RegulatorsConfigurationPage.css'
|
import './RegulatorsConfigurationPage.css'
|
||||||
|
|
||||||
export default function RegulatorsConfigurationPage() {
|
export default function RegulatorsConfigurationPage() {
|
||||||
const { data, addEntity, updateEntity, deleteEntity } = useStorage()
|
const { data, addEntity, updateEntity, deleteEntity, loading } = useStorage()
|
||||||
const [editingId, setEditingId] = useState<string | null>(null)
|
const [editingId, setEditingId] = useState<string | null>(null)
|
||||||
const [formData, setFormData] = useState<Partial<NaturalRegulator>>({
|
const [formData, setFormData] = useState<Partial<NaturalRegulator>>({
|
||||||
name: '',
|
name: '',
|
||||||
@ -20,10 +20,36 @@ export default function RegulatorsConfigurationPage() {
|
|||||||
max: 0,
|
max: 0,
|
||||||
unit: 'kg/t',
|
unit: 'kg/t',
|
||||||
},
|
},
|
||||||
|
regulatoryCharacteristics: {},
|
||||||
})
|
})
|
||||||
const [errors, setErrors] = useState<Record<string, string>>({})
|
const [errors, setErrors] = useState<Record<string, string>>({})
|
||||||
|
|
||||||
const regulators = data?.regulators || []
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<div className="regulators-config-page">
|
||||||
|
<div className="page-loading">Loading...</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safely normalize regulators to ensure all required fields exist
|
||||||
|
const regulators: NaturalRegulator[] = (data.regulators || [])
|
||||||
|
.filter(reg => reg && reg.id && reg.name) // Filter out invalid entries
|
||||||
|
.map(reg => ({
|
||||||
|
id: reg.id,
|
||||||
|
name: reg.name || '',
|
||||||
|
type: reg.type || '',
|
||||||
|
applicationConditions: reg.applicationConditions || '',
|
||||||
|
dosageRequirements: reg.dosageRequirements || {
|
||||||
|
min: 0,
|
||||||
|
max: 0,
|
||||||
|
unit: 'kg/t' as const,
|
||||||
|
},
|
||||||
|
regulatoryCharacteristics: reg.regulatoryCharacteristics || {},
|
||||||
|
createdAt: reg.createdAt || new Date().toISOString(),
|
||||||
|
updatedAt: reg.updatedAt,
|
||||||
|
notes: reg.notes,
|
||||||
|
}))
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@ -45,7 +71,7 @@ export default function RegulatorsConfigurationPage() {
|
|||||||
id: editingId || crypto.randomUUID(),
|
id: editingId || crypto.randomUUID(),
|
||||||
name: formData.name!,
|
name: formData.name!,
|
||||||
type: formData.type!,
|
type: formData.type!,
|
||||||
regulatoryCharacteristics: formData.regulatoryCharacteristics,
|
regulatoryCharacteristics: formData.regulatoryCharacteristics || {},
|
||||||
applicationConditions: formData.applicationConditions!,
|
applicationConditions: formData.applicationConditions!,
|
||||||
dosageRequirements: formData.dosageRequirements!,
|
dosageRequirements: formData.dosageRequirements!,
|
||||||
createdAt: editingId ? regulators.find((r) => r.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
|
createdAt: editingId ? regulators.find((r) => r.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
|
||||||
@ -71,13 +97,24 @@ export default function RegulatorsConfigurationPage() {
|
|||||||
max: 0,
|
max: 0,
|
||||||
unit: 'kg/t',
|
unit: 'kg/t',
|
||||||
},
|
},
|
||||||
|
regulatoryCharacteristics: {},
|
||||||
})
|
})
|
||||||
setEditingId(null)
|
setEditingId(null)
|
||||||
setErrors({})
|
setErrors({})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleEdit = (regulator: NaturalRegulator) => {
|
const handleEdit = (regulator: NaturalRegulator) => {
|
||||||
setFormData(regulator)
|
setFormData({
|
||||||
|
name: regulator.name || '',
|
||||||
|
type: regulator.type || '',
|
||||||
|
applicationConditions: regulator.applicationConditions || '',
|
||||||
|
dosageRequirements: regulator.dosageRequirements || {
|
||||||
|
min: 0,
|
||||||
|
max: 0,
|
||||||
|
unit: 'kg/t',
|
||||||
|
},
|
||||||
|
regulatoryCharacteristics: regulator.regulatoryCharacteristics || {},
|
||||||
|
})
|
||||||
setEditingId(regulator.id)
|
setEditingId(regulator.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,18 +128,21 @@ export default function RegulatorsConfigurationPage() {
|
|||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
header: 'Name',
|
header: 'Name',
|
||||||
render: (regulator: NaturalRegulator) => regulator.name,
|
render: (regulator: NaturalRegulator) => regulator.name || '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'type',
|
key: 'type',
|
||||||
header: 'Type',
|
header: 'Type',
|
||||||
render: (regulator: NaturalRegulator) => regulator.type,
|
render: (regulator: NaturalRegulator) => regulator.type || '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'dosage',
|
key: 'dosage',
|
||||||
header: 'Dosage',
|
header: 'Dosage',
|
||||||
render: (regulator: NaturalRegulator) =>
|
render: (regulator: NaturalRegulator) => {
|
||||||
`${regulator.dosageRequirements.min} - ${regulator.dosageRequirements.max} ${regulator.dosageRequirements.unit}`,
|
const dosage = regulator.dosageRequirements
|
||||||
|
if (!dosage) return '-'
|
||||||
|
return `${dosage.min} - ${dosage.max} ${dosage.unit}`
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
@ -182,6 +222,276 @@ export default function RegulatorsConfigurationPage() {
|
|||||||
}
|
}
|
||||||
error={errors.dosageMax}
|
error={errors.dosageMax}
|
||||||
/>
|
/>
|
||||||
|
<Select
|
||||||
|
label="Dosage Unit *"
|
||||||
|
value={formData.dosageRequirements?.unit || 'kg/t'}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
dosageRequirements: {
|
||||||
|
...formData.dosageRequirements!,
|
||||||
|
unit: e.target.value as 'kg/t' | 'L/t' | '%',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
options={[
|
||||||
|
{ value: 'kg/t', label: 'kg/t' },
|
||||||
|
{ value: 'L/t', label: 'L/t' },
|
||||||
|
{ value: '%', label: '%' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Regulatory Characteristics Section */}
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">Regulatory Characteristics (Typical)</h3>
|
||||||
|
|
||||||
|
{/* Nutrient Requirements */}
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">Nutrient Requirements</h4>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Nitrogen (N or NTK)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.regulatoryCharacteristics?.nitrogen || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...(formData.regulatoryCharacteristics || {}),
|
||||||
|
nitrogen: e.target.value ? parseFloat(e.target.value) : undefined,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Ammoniacal Nitrogen (N-NH₄)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.regulatoryCharacteristics?.ammoniacalNitrogen || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...(formData.regulatoryCharacteristics || {}),
|
||||||
|
ammoniacalNitrogen: e.target.value ? parseFloat(e.target.value) : undefined,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Phosphorus (P)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.regulatoryCharacteristics?.phosphorus || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...(formData.regulatoryCharacteristics || {}),
|
||||||
|
phosphorus: e.target.value ? parseFloat(e.target.value) : undefined,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Potassium (K)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.regulatoryCharacteristics?.potassium || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...(formData.regulatoryCharacteristics || {}),
|
||||||
|
potassium: e.target.value ? parseFloat(e.target.value) : undefined,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Carbon:Nitrogen Ratio (C:N)"
|
||||||
|
type="number"
|
||||||
|
step="0.1"
|
||||||
|
min="0"
|
||||||
|
value={formData.regulatoryCharacteristics?.carbonNitrogenRatio || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...(formData.regulatoryCharacteristics || {}),
|
||||||
|
carbonNitrogenRatio: e.target.value ? parseFloat(e.target.value) : undefined,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="pH Adjustment (-14 to 14)"
|
||||||
|
type="number"
|
||||||
|
step="0.1"
|
||||||
|
min="-14"
|
||||||
|
max="14"
|
||||||
|
value={formData.regulatoryCharacteristics?.phAdjustment || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...(formData.regulatoryCharacteristics || {}),
|
||||||
|
phAdjustment: e.target.value ? parseFloat(e.target.value) : undefined,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
helpText="Negative values reduce pH, positive values increase pH"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Heavy Metals and Toxic Elements */}
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">Heavy Metals and Toxic Elements (Capabilities)</h4>
|
||||||
|
<div className="checkbox-grid">
|
||||||
|
{[
|
||||||
|
{ key: 'arsenicElimination', label: 'Arsenic (As) Elimination' },
|
||||||
|
{ key: 'zincElimination', label: 'Zinc Elimination' },
|
||||||
|
{ key: 'aluminumElimination', label: 'Aluminum Elimination' },
|
||||||
|
{ key: 'copperElimination', label: 'Copper Elimination' },
|
||||||
|
{ key: 'heavyMetalsElimination', label: 'Heavy Metals Elimination' },
|
||||||
|
{ key: 'metalBinding', label: 'Metal Binding' },
|
||||||
|
].map((item) => (
|
||||||
|
<label key={item.key} className="checkbox-item">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={Boolean((formData.regulatoryCharacteristics?.[item.key as keyof NonNullable<typeof formData.regulatoryCharacteristics>] as boolean))}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...(formData.regulatoryCharacteristics || {}),
|
||||||
|
[item.key]: e.target.checked,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Biological Contaminants */}
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">Biological Contaminants (Capabilities)</h4>
|
||||||
|
<div className="checkbox-grid">
|
||||||
|
<label className="checkbox-item">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.regulatoryCharacteristics?.pathogenReduction || false}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...(formData.regulatoryCharacteristics || {}),
|
||||||
|
pathogenReduction: e.target.checked,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<span>Pathogen Reduction</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Chemical Parameters */}
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">Chemical Parameters (Capabilities)</h4>
|
||||||
|
<div className="checkbox-grid">
|
||||||
|
{[
|
||||||
|
{ key: 'lowCNRatioEnrichment', label: 'Low C:N Ratio Enrichment' },
|
||||||
|
{ key: 'medicationElimination', label: 'Medication Elimination' },
|
||||||
|
{ key: 'depositElimination', label: 'Deposit Elimination' },
|
||||||
|
{ key: 'odorElimination', label: 'Odor Elimination' },
|
||||||
|
{ key: 'turbidityReduction', label: 'Turbidity Reduction' },
|
||||||
|
{ key: 'sodiumReduction', label: 'Sodium (Na⁺) Reduction' },
|
||||||
|
{ key: 'chlorineReduction', label: 'Chlorine (Cl⁻) Reduction' },
|
||||||
|
{ key: 'electricalConductivityReduction', label: 'Electrical Conductivity Reduction' },
|
||||||
|
{ key: 'sulfideElimination', label: 'Sulfide Elimination' },
|
||||||
|
{ key: 'methaneElimination', label: 'Methane Elimination' },
|
||||||
|
{ key: 'co2Elimination', label: 'CO₂ Elimination' },
|
||||||
|
{ key: 'polyphenolElimination', label: 'Polyphenol Elimination' },
|
||||||
|
{ key: 'refractoryFractionsElimination', label: 'Refractory Structural Fractions Elimination' },
|
||||||
|
].map((item) => {
|
||||||
|
const key = item.key as keyof NonNullable<typeof formData.regulatoryCharacteristics>
|
||||||
|
return (
|
||||||
|
<label key={item.key} className="checkbox-item">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={Boolean(formData.regulatoryCharacteristics?.[key])}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...(formData.regulatoryCharacteristics || {}),
|
||||||
|
[item.key]: e.target.checked,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Biological Processes */}
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">Biological Processes (Capabilities)</h4>
|
||||||
|
<div className="checkbox-grid">
|
||||||
|
{[
|
||||||
|
{ key: 'microbiologicalCompetition', label: 'Microbiological Competition' },
|
||||||
|
{ key: 'oilEmulsification', label: 'Oil Emulsification' },
|
||||||
|
].map((item) => {
|
||||||
|
const key = item.key as keyof NonNullable<typeof formData.regulatoryCharacteristics>
|
||||||
|
return (
|
||||||
|
<label key={item.key} className="checkbox-item">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={Boolean(formData.regulatoryCharacteristics?.[key])}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...(formData.regulatoryCharacteristics || {}),
|
||||||
|
[item.key]: e.target.checked,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* pH Regulation */}
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">pH Regulation (Capabilities)</h4>
|
||||||
|
<div className="checkbox-grid">
|
||||||
|
{[
|
||||||
|
{ key: 'acidityReduction', label: 'Acidity Reduction' },
|
||||||
|
{ key: 'phIncrease', label: 'pH Increase' },
|
||||||
|
{ key: 'phReduction', label: 'pH Reduction' },
|
||||||
|
].map((item) => {
|
||||||
|
const key = item.key as keyof NonNullable<typeof formData.regulatoryCharacteristics>
|
||||||
|
return (
|
||||||
|
<label key={item.key} className="checkbox-item">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={Boolean(formData.regulatoryCharacteristics?.[key])}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...(formData.regulatoryCharacteristics || {}),
|
||||||
|
[item.key]: e.target.checked,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-actions">
|
<div className="form-actions">
|
||||||
@ -198,7 +508,13 @@ export default function RegulatorsConfigurationPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card title="Natural Regulators" className="table-card">
|
<Card title="Natural Regulators" className="table-card">
|
||||||
|
{regulators.length > 0 ? (
|
||||||
<Table columns={tableColumns} data={regulators} emptyMessage="No regulators configured" />
|
<Table columns={tableColumns} data={regulators} emptyMessage="No regulators configured" />
|
||||||
|
) : (
|
||||||
|
<div className="table-empty">
|
||||||
|
<p>No regulators configured</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
63
src/pages/configuration/TransportersConfigurationPage.css
Normal file
63
src/pages/configuration/TransportersConfigurationPage.css
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
.transporters-config-page {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
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 {
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
padding-top: var(--spacing-xl);
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
margin-top: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-card {
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: var(--spacing-2xl);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
209
src/pages/configuration/TransportersConfigurationPage.tsx
Normal file
209
src/pages/configuration/TransportersConfigurationPage.tsx
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { useStorage } from '@/hooks/useStorage'
|
||||||
|
import { Transporter } 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 './TransportersConfigurationPage.css'
|
||||||
|
|
||||||
|
export default function TransportersConfigurationPage() {
|
||||||
|
const { data, addEntity, updateEntity, deleteEntity } = useStorage()
|
||||||
|
const [editingId, setEditingId] = useState<string | null>(null)
|
||||||
|
const [formData, setFormData] = useState<Partial<Transporter>>({
|
||||||
|
name: '',
|
||||||
|
type: '',
|
||||||
|
description: '',
|
||||||
|
contact: {
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
address: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const [errors, setErrors] = useState<Record<string, string>>({})
|
||||||
|
|
||||||
|
const transporters = data?.transporters || []
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const newErrors: Record<string, string> = {}
|
||||||
|
newErrors.name = validateRequired(formData.name)
|
||||||
|
newErrors.type = validateRequired(formData.type)
|
||||||
|
|
||||||
|
setErrors(newErrors)
|
||||||
|
|
||||||
|
if (Object.values(newErrors).some((error) => error !== undefined)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const transporter: Transporter = {
|
||||||
|
id: editingId || crypto.randomUUID(),
|
||||||
|
name: formData.name!,
|
||||||
|
type: formData.type!,
|
||||||
|
description: formData.description,
|
||||||
|
contact: formData.contact,
|
||||||
|
createdAt: editingId ? transporters.find((t) => t.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editingId) {
|
||||||
|
updateEntity('transporters', editingId, transporter)
|
||||||
|
} else {
|
||||||
|
addEntity('transporters', transporter)
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
setFormData({
|
||||||
|
name: '',
|
||||||
|
type: '',
|
||||||
|
description: '',
|
||||||
|
contact: {
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
address: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
setEditingId(null)
|
||||||
|
setErrors({})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEdit = (transporter: Transporter) => {
|
||||||
|
setFormData(transporter)
|
||||||
|
setEditingId(transporter.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = (id: string) => {
|
||||||
|
if (confirm('Are you sure you want to delete this transporter?')) {
|
||||||
|
deleteEntity('transporters', id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableColumns = [
|
||||||
|
{
|
||||||
|
key: 'name',
|
||||||
|
header: 'Name',
|
||||||
|
render: (transporter: Transporter) => transporter.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'type',
|
||||||
|
header: 'Type',
|
||||||
|
render: (transporter: Transporter) => transporter.type,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'description',
|
||||||
|
header: 'Description',
|
||||||
|
render: (transporter: Transporter) => transporter.description || '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'contact',
|
||||||
|
header: 'Contact',
|
||||||
|
render: (transporter: Transporter) => transporter.contact?.name || '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'actions',
|
||||||
|
header: 'Actions',
|
||||||
|
render: (transporter: Transporter) => (
|
||||||
|
<div className="table-actions">
|
||||||
|
<Button variant="secondary" onClick={() => handleEdit(transporter)}>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
<Button variant="danger" onClick={() => handleDelete(transporter.id)}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="transporters-config-page">
|
||||||
|
<h1 className="page-title">Transporters Configuration</h1>
|
||||||
|
|
||||||
|
<div className="page-content">
|
||||||
|
<Card title={editingId ? 'Edit Transporter' : 'Add Transporter'} className="form-card">
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Name *"
|
||||||
|
value={formData.name || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
error={errors.name}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Type *"
|
||||||
|
value={formData.type || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, type: e.target.value })}
|
||||||
|
error={errors.type}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
label="Description"
|
||||||
|
value={formData.description || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">Contact Information</h3>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Contact Name"
|
||||||
|
value={formData.contact?.name || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, contact: { ...formData.contact, name: e.target.value } })}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Email"
|
||||||
|
type="email"
|
||||||
|
value={formData.contact?.email || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, contact: { ...formData.contact, email: e.target.value } })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Phone"
|
||||||
|
value={formData.contact?.phone || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, contact: { ...formData.contact, phone: e.target.value } })}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Address"
|
||||||
|
value={formData.contact?.address || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, contact: { ...formData.contact, address: e.target.value } })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-actions">
|
||||||
|
<Button type="submit" variant="primary">
|
||||||
|
{editingId ? 'Update' : 'Add'} Transporter
|
||||||
|
</Button>
|
||||||
|
{editingId && (
|
||||||
|
<Button type="button" variant="secondary" onClick={resetForm}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="Transporters" className="table-card">
|
||||||
|
{transporters.length === 0 ? (
|
||||||
|
<div className="empty-state">
|
||||||
|
<p>No transporters configured yet. Add your first transporter to get started.</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Table columns={tableColumns} data={transporters} />
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -40,3 +40,89 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-sm);
|
gap: var(--spacing-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
padding-top: var(--spacing-xl);
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-subsection {
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-subsection-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
margin-top: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: var(--spacing-xs);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 200ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item:hover {
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item input[type="checkbox"] {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: var(--primary-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-help-text {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.regulators-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
margin-top: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.regulator-association-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 1fr auto;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
align-items: end;
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.regulators-summary {
|
||||||
|
margin-top: var(--spacing-md);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
background-color: var(--background);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
text-align: right;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useStorage } from '@/hooks/useStorage'
|
import { useStorage } from '@/hooks/useStorage'
|
||||||
import { Waste } from '@/types'
|
import { Waste, WasteRegulatorAssociation, NaturalRegulator } from '@/types'
|
||||||
import Card from '@/components/base/Card'
|
import Card from '@/components/base/Card'
|
||||||
import Button from '@/components/base/Button'
|
import Button from '@/components/base/Button'
|
||||||
import Input from '@/components/base/Input'
|
import Input from '@/components/base/Input'
|
||||||
@ -27,6 +27,8 @@ export default function WasteConfigurationPage() {
|
|||||||
const [errors, setErrors] = useState<Record<string, string>>({})
|
const [errors, setErrors] = useState<Record<string, string>>({})
|
||||||
|
|
||||||
const wastes = data?.wastes || []
|
const wastes = data?.wastes || []
|
||||||
|
const wasteOrigins = data?.wasteOrigins || []
|
||||||
|
const regulators = data?.regulators || []
|
||||||
|
|
||||||
const originTypeOptions = [
|
const originTypeOptions = [
|
||||||
{ value: 'animals', label: 'Animals' },
|
{ value: 'animals', label: 'Animals' },
|
||||||
@ -35,6 +37,16 @@ export default function WasteConfigurationPage() {
|
|||||||
{ value: 'other', label: 'Other' },
|
{ value: 'other', label: 'Other' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const wasteOriginOptions = wasteOrigins.map((origin) => ({
|
||||||
|
value: origin.id,
|
||||||
|
label: `${origin.name} (${origin.type})`,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const regulatorOptions = regulators.map((reg) => ({
|
||||||
|
value: reg.id,
|
||||||
|
label: reg.name,
|
||||||
|
}))
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
@ -56,11 +68,13 @@ export default function WasteConfigurationPage() {
|
|||||||
name: formData.name!,
|
name: formData.name!,
|
||||||
originType: formData.originType!,
|
originType: formData.originType!,
|
||||||
originSubType: formData.originSubType,
|
originSubType: formData.originSubType,
|
||||||
|
wasteOriginId: formData.wasteOriginId,
|
||||||
originUnitsPer1000m3Methane: formData.originUnitsPer1000m3Methane!,
|
originUnitsPer1000m3Methane: formData.originUnitsPer1000m3Methane!,
|
||||||
bmp: formData.bmp!,
|
bmp: formData.bmp!,
|
||||||
waterPercentage: formData.waterPercentage!,
|
waterPercentage: formData.waterPercentage!,
|
||||||
regulationNeeds: formData.regulationNeeds || [],
|
regulationNeeds: formData.regulationNeeds || [],
|
||||||
regulatoryCharacteristics: formData.regulatoryCharacteristics,
|
regulatoryCharacteristics: formData.regulatoryCharacteristics,
|
||||||
|
regulators: formData.regulators || [],
|
||||||
maxStorageDuration: formData.maxStorageDuration!,
|
maxStorageDuration: formData.maxStorageDuration!,
|
||||||
createdAt: editingId ? wastes.find((w) => w.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
|
createdAt: editingId ? wastes.find((w) => w.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
@ -80,10 +94,12 @@ export default function WasteConfigurationPage() {
|
|||||||
name: '',
|
name: '',
|
||||||
originType: 'animals',
|
originType: 'animals',
|
||||||
originSubType: '',
|
originSubType: '',
|
||||||
|
wasteOriginId: undefined,
|
||||||
originUnitsPer1000m3Methane: 0,
|
originUnitsPer1000m3Methane: 0,
|
||||||
bmp: 0,
|
bmp: 0,
|
||||||
waterPercentage: 75,
|
waterPercentage: 75,
|
||||||
regulationNeeds: [],
|
regulationNeeds: [],
|
||||||
|
regulators: [],
|
||||||
maxStorageDuration: 30,
|
maxStorageDuration: 30,
|
||||||
})
|
})
|
||||||
setEditingId(null)
|
setEditingId(null)
|
||||||
@ -166,12 +182,24 @@ export default function WasteConfigurationPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="form-row">
|
||||||
<Input
|
<Input
|
||||||
label="Origin Sub Type"
|
label="Origin Sub Type"
|
||||||
value={formData.originSubType || ''}
|
value={formData.originSubType || ''}
|
||||||
onChange={(e) => setFormData({ ...formData, originSubType: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, originSubType: e.target.value })}
|
||||||
placeholder="Enter sub type (optional)"
|
placeholder="Enter sub type (optional)"
|
||||||
/>
|
/>
|
||||||
|
<Select
|
||||||
|
label="Waste Origin"
|
||||||
|
value={formData.wasteOriginId || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, wasteOriginId: e.target.value || undefined })}
|
||||||
|
options={[
|
||||||
|
{ value: '', label: 'None' },
|
||||||
|
...wasteOriginOptions,
|
||||||
|
]}
|
||||||
|
helpText="Associate this waste with a waste origin (optional)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="form-row">
|
<div className="form-row">
|
||||||
<Input
|
<Input
|
||||||
@ -216,6 +244,348 @@ export default function WasteConfigurationPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Regulation Needs Section */}
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">Typical Regulation Needs</h3>
|
||||||
|
<div className="checkbox-grid">
|
||||||
|
{[
|
||||||
|
{ key: 'pathogenElimination', label: 'Pathogen Elimination' },
|
||||||
|
{ key: 'heavyMetalTreatment', label: 'Heavy Metal Treatment' },
|
||||||
|
{ key: 'odorElimination', label: 'Odor Elimination' },
|
||||||
|
{ key: 'oilEmulsification', label: 'Oil Emulsification' },
|
||||||
|
{ key: 'saltReduction', label: 'Salt Reduction' },
|
||||||
|
].map((need) => (
|
||||||
|
<label key={need.key} className="checkbox-item">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.regulationNeeds?.includes(need.key) || false}
|
||||||
|
onChange={(e) => {
|
||||||
|
const currentNeeds = formData.regulationNeeds || []
|
||||||
|
if (e.target.checked) {
|
||||||
|
setFormData({ ...formData, regulationNeeds: [...currentNeeds, need.key] })
|
||||||
|
} else {
|
||||||
|
setFormData({ ...formData, regulationNeeds: currentNeeds.filter((n) => n !== need.key) })
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span>{need.label}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Regulatory Characteristics Section */}
|
||||||
|
<div className="form-section">
|
||||||
|
<h3 className="form-section-title">Regulatory Characteristics</h3>
|
||||||
|
|
||||||
|
{/* Nutrient Requirements */}
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">Nutrient Requirements</h4>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Nitrogen (N or NTK)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.regulatoryCharacteristics?.nitrogen || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...formData.regulatoryCharacteristics,
|
||||||
|
nitrogen: parseFloat(e.target.value) || undefined,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Ammoniacal Nitrogen (N-NH₄)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.regulatoryCharacteristics?.ammoniacalNitrogen || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...formData.regulatoryCharacteristics,
|
||||||
|
ammoniacalNitrogen: parseFloat(e.target.value) || undefined,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Phosphorus (P)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.regulatoryCharacteristics?.phosphorus || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...formData.regulatoryCharacteristics,
|
||||||
|
phosphorus: parseFloat(e.target.value) || undefined,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Potassium (K)"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={formData.regulatoryCharacteristics?.potassium || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...formData.regulatoryCharacteristics,
|
||||||
|
potassium: parseFloat(e.target.value) || undefined,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-row">
|
||||||
|
<Input
|
||||||
|
label="Carbon:Nitrogen Ratio (C:N)"
|
||||||
|
type="number"
|
||||||
|
step="0.1"
|
||||||
|
min="0"
|
||||||
|
value={formData.regulatoryCharacteristics?.carbonNitrogenRatio || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...formData.regulatoryCharacteristics,
|
||||||
|
carbonNitrogenRatio: parseFloat(e.target.value) || undefined,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Heavy Metals and Toxic Elements */}
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">Heavy Metals and Toxic Elements (Needed)</h4>
|
||||||
|
<div className="checkbox-grid">
|
||||||
|
{[
|
||||||
|
{ key: 'arsenicElimination', label: 'Arsenic (As) Elimination' },
|
||||||
|
{ key: 'zincElimination', label: 'Zinc Elimination' },
|
||||||
|
{ key: 'aluminumElimination', label: 'Aluminum Elimination' },
|
||||||
|
{ key: 'copperElimination', label: 'Copper Elimination' },
|
||||||
|
{ key: 'heavyMetalsElimination', label: 'Heavy Metals Elimination' },
|
||||||
|
].map((item) => (
|
||||||
|
<label key={item.key} className="checkbox-item">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.regulatoryCharacteristics?.[item.key as keyof typeof formData.regulatoryCharacteristics] || false}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...formData.regulatoryCharacteristics,
|
||||||
|
[item.key]: e.target.checked,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Biological Contaminants */}
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">Biological Contaminants (Needed)</h4>
|
||||||
|
<div className="checkbox-grid">
|
||||||
|
<label className="checkbox-item">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.regulatoryCharacteristics?.pathogenElimination || false}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...formData.regulatoryCharacteristics,
|
||||||
|
pathogenElimination: e.target.checked,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<span>Pathogen Elimination</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Chemical Parameters */}
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">Chemical Parameters (Needed)</h4>
|
||||||
|
<div className="checkbox-grid">
|
||||||
|
{[
|
||||||
|
{ key: 'lowCNRatioEnrichment', label: 'Low C:N Ratio Enrichment' },
|
||||||
|
{ key: 'medicationElimination', label: 'Medication Elimination' },
|
||||||
|
{ key: 'depositElimination', label: 'Deposit Elimination' },
|
||||||
|
{ key: 'odorElimination', label: 'Odor Elimination' },
|
||||||
|
{ key: 'turbidityReduction', label: 'Turbidity Reduction' },
|
||||||
|
{ key: 'sodiumReduction', label: 'Sodium (Na⁺) Reduction' },
|
||||||
|
{ key: 'chlorineReduction', label: 'Chlorine (Cl⁻) Reduction' },
|
||||||
|
{ key: 'electricalConductivityReduction', label: 'Electrical Conductivity Reduction' },
|
||||||
|
{ key: 'sulfideElimination', label: 'Sulfide Elimination' },
|
||||||
|
{ key: 'methaneElimination', label: 'Methane Elimination' },
|
||||||
|
{ key: 'co2Elimination', label: 'CO₂ Elimination' },
|
||||||
|
{ key: 'polyphenolElimination', label: 'Polyphenol Elimination' },
|
||||||
|
{ key: 'refractoryFractionsElimination', label: 'Refractory Structural Fractions Elimination' },
|
||||||
|
].map((item) => (
|
||||||
|
<label key={item.key} className="checkbox-item">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.regulatoryCharacteristics?.[item.key as keyof typeof formData.regulatoryCharacteristics] || false}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...formData.regulatoryCharacteristics,
|
||||||
|
[item.key]: e.target.checked,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Biological Processes */}
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">Biological Processes (Needed)</h4>
|
||||||
|
<div className="checkbox-grid">
|
||||||
|
{[
|
||||||
|
{ key: 'microbiologicalCompetition', label: 'Microbiological Competition' },
|
||||||
|
{ key: 'oilEmulsification', label: 'Oil Emulsification' },
|
||||||
|
].map((item) => (
|
||||||
|
<label key={item.key} className="checkbox-item">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.regulatoryCharacteristics?.[item.key as keyof typeof formData.regulatoryCharacteristics] || false}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...formData.regulatoryCharacteristics,
|
||||||
|
[item.key]: e.target.checked,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* pH Regulation */}
|
||||||
|
<div className="form-subsection">
|
||||||
|
<h4 className="form-subsection-title">pH Regulation (Needed)</h4>
|
||||||
|
<div className="checkbox-grid">
|
||||||
|
{[
|
||||||
|
{ key: 'acidityReduction', label: 'Acidity Reduction' },
|
||||||
|
{ key: 'phIncrease', label: 'pH Increase' },
|
||||||
|
{ key: 'phReduction', label: 'pH Reduction' },
|
||||||
|
].map((item) => (
|
||||||
|
<label key={item.key} className="checkbox-item">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.regulatoryCharacteristics?.[item.key as keyof typeof formData.regulatoryCharacteristics] || false}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
regulatoryCharacteristics: {
|
||||||
|
...formData.regulatoryCharacteristics,
|
||||||
|
[item.key]: e.target.checked,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Regulators Association Section */}
|
||||||
|
<Card title="Associated Regulators" className="form-section">
|
||||||
|
<p className="form-help-text">
|
||||||
|
Add regulators to this waste to address its regulation needs. Specify the percentage of total volume for each regulator.
|
||||||
|
</p>
|
||||||
|
<div className="regulators-list">
|
||||||
|
{(formData.regulators || []).map((association, index) => {
|
||||||
|
const regulator = regulators.find((r) => r.id === association.regulatorId)
|
||||||
|
return (
|
||||||
|
<div key={index} className="regulator-association-item">
|
||||||
|
<Select
|
||||||
|
label="Regulator"
|
||||||
|
value={association.regulatorId}
|
||||||
|
onChange={(e) => {
|
||||||
|
const updatedRegulators = [...(formData.regulators || [])]
|
||||||
|
updatedRegulators[index] = {
|
||||||
|
...updatedRegulators[index],
|
||||||
|
regulatorId: e.target.value,
|
||||||
|
}
|
||||||
|
setFormData({ ...formData, regulators: updatedRegulators })
|
||||||
|
}}
|
||||||
|
options={[
|
||||||
|
{ value: '', label: 'Select a regulator' },
|
||||||
|
...regulatorOptions.filter(
|
||||||
|
(opt) =>
|
||||||
|
opt.value === association.regulatorId ||
|
||||||
|
!(formData.regulators || []).some((a) => a.regulatorId === opt.value)
|
||||||
|
),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Volume Percentage (%)"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
step="0.1"
|
||||||
|
value={association.percentage || ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const updatedRegulators = [...(formData.regulators || [])]
|
||||||
|
updatedRegulators[index] = {
|
||||||
|
...updatedRegulators[index],
|
||||||
|
percentage: parseFloat(e.target.value) || 0,
|
||||||
|
}
|
||||||
|
setFormData({ ...formData, regulators: updatedRegulators })
|
||||||
|
}}
|
||||||
|
helpText={regulator ? `Regulator: ${regulator.name}` : undefined}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="danger"
|
||||||
|
onClick={() => {
|
||||||
|
const updatedRegulators = (formData.regulators || []).filter((_, i) => i !== index)
|
||||||
|
setFormData({ ...formData, regulators: updatedRegulators })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
const newAssociation: WasteRegulatorAssociation = {
|
||||||
|
regulatorId: '',
|
||||||
|
percentage: 0,
|
||||||
|
}
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
regulators: [...(formData.regulators || []), newAssociation],
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add Regulator
|
||||||
|
</Button>
|
||||||
|
{(formData.regulators || []).length > 0 && (
|
||||||
|
<div className="regulators-summary">
|
||||||
|
<strong>
|
||||||
|
Total: {(formData.regulators || []).reduce((sum, a) => sum + (a.percentage || 0), 0).toFixed(1)}%
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<div className="form-actions">
|
<div className="form-actions">
|
||||||
<Button type="submit" variant="primary">
|
<Button type="submit" variant="primary">
|
||||||
{editingId ? 'Update' : 'Add'} Waste Type
|
{editingId ? 'Update' : 'Add'} Waste Type
|
||||||
|
|||||||
50
src/pages/configuration/WasteOriginsConfigurationPage.css
Normal file
50
src/pages/configuration/WasteOriginsConfigurationPage.css
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
.waste-origins-config-page {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
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 {
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
margin-top: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-card {
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: var(--spacing-2xl);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
171
src/pages/configuration/WasteOriginsConfigurationPage.tsx
Normal file
171
src/pages/configuration/WasteOriginsConfigurationPage.tsx
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { useStorage } from '@/hooks/useStorage'
|
||||||
|
import { WasteOrigin } 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 './WasteOriginsConfigurationPage.css'
|
||||||
|
|
||||||
|
export default function WasteOriginsConfigurationPage() {
|
||||||
|
const { data, addEntity, updateEntity, deleteEntity } = useStorage()
|
||||||
|
const [editingId, setEditingId] = useState<string | null>(null)
|
||||||
|
const [formData, setFormData] = useState<Partial<WasteOrigin>>({
|
||||||
|
type: 'animals',
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
})
|
||||||
|
const [errors, setErrors] = useState<Record<string, string>>({})
|
||||||
|
|
||||||
|
const wasteOrigins = data?.wasteOrigins || []
|
||||||
|
|
||||||
|
const originTypeOptions = [
|
||||||
|
{ value: 'animals', label: 'Animals' },
|
||||||
|
{ value: 'markets', label: 'Markets' },
|
||||||
|
{ value: 'restaurants', label: 'Restaurants' },
|
||||||
|
{ value: 'other', label: 'Other' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const newErrors: Record<string, string> = {}
|
||||||
|
newErrors.name = validateRequired(formData.name)
|
||||||
|
newErrors.type = validateRequired(formData.type)
|
||||||
|
|
||||||
|
setErrors(newErrors)
|
||||||
|
|
||||||
|
if (Object.values(newErrors).some((error) => error !== undefined)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const wasteOrigin: WasteOrigin = {
|
||||||
|
id: editingId || crypto.randomUUID(),
|
||||||
|
type: formData.type!,
|
||||||
|
name: formData.name!,
|
||||||
|
description: formData.description,
|
||||||
|
createdAt: editingId ? wasteOrigins.find((o) => o.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editingId) {
|
||||||
|
updateEntity('wasteOrigins', editingId, wasteOrigin)
|
||||||
|
} else {
|
||||||
|
addEntity('wasteOrigins', wasteOrigin)
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
setFormData({
|
||||||
|
type: 'animals',
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
})
|
||||||
|
setEditingId(null)
|
||||||
|
setErrors({})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEdit = (wasteOrigin: WasteOrigin) => {
|
||||||
|
setFormData(wasteOrigin)
|
||||||
|
setEditingId(wasteOrigin.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = (id: string) => {
|
||||||
|
if (confirm('Are you sure you want to delete this waste origin?')) {
|
||||||
|
deleteEntity('wasteOrigins', id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableColumns = [
|
||||||
|
{
|
||||||
|
key: 'type',
|
||||||
|
header: 'Type',
|
||||||
|
render: (wasteOrigin: WasteOrigin) => wasteOrigin.type,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'name',
|
||||||
|
header: 'Name',
|
||||||
|
render: (wasteOrigin: WasteOrigin) => wasteOrigin.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'description',
|
||||||
|
header: 'Description',
|
||||||
|
render: (wasteOrigin: WasteOrigin) => wasteOrigin.description || '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'actions',
|
||||||
|
header: 'Actions',
|
||||||
|
render: (wasteOrigin: WasteOrigin) => (
|
||||||
|
<div className="table-actions">
|
||||||
|
<Button variant="secondary" onClick={() => handleEdit(wasteOrigin)}>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
<Button variant="danger" onClick={() => handleDelete(wasteOrigin.id)}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="waste-origins-config-page">
|
||||||
|
<h1 className="page-title">Waste Origins Configuration</h1>
|
||||||
|
|
||||||
|
<div className="page-content">
|
||||||
|
<Card title={editingId ? 'Edit Waste Origin' : 'Add Waste Origin'} className="form-card">
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="form-row">
|
||||||
|
<Select
|
||||||
|
label="Type *"
|
||||||
|
value={formData.type || 'animals'}
|
||||||
|
onChange={(e) => setFormData({ ...formData, type: e.target.value as WasteOrigin['type'] })}
|
||||||
|
options={originTypeOptions}
|
||||||
|
error={errors.type}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Name *"
|
||||||
|
value={formData.name || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
error={errors.name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
label="Description"
|
||||||
|
value={formData.description || ''}
|
||||||
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="form-actions">
|
||||||
|
<Button type="submit" variant="primary">
|
||||||
|
{editingId ? 'Update' : 'Add'} Waste Origin
|
||||||
|
</Button>
|
||||||
|
{editingId && (
|
||||||
|
<Button type="button" variant="secondary" onClick={resetForm}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="Waste Origins" className="table-card">
|
||||||
|
{wasteOrigins.length === 0 ? (
|
||||||
|
<div className="empty-state">
|
||||||
|
<p>No waste origins configured yet. Add your first waste origin to get started.</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Table columns={tableColumns} data={wasteOrigins} />
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -13,20 +13,84 @@ export interface User extends BaseEntity {
|
|||||||
lastLogin?: string
|
lastLogin?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Waste Origin
|
||||||
|
export interface WasteOrigin extends BaseEntity {
|
||||||
|
type: 'animals' | 'markets' | 'restaurants' | 'other'
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transporter
|
||||||
|
export interface Transporter extends BaseEntity {
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
description?: string
|
||||||
|
contact?: {
|
||||||
|
name: string
|
||||||
|
email?: string
|
||||||
|
phone?: string
|
||||||
|
address?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Waste Regulator Association
|
||||||
|
export interface WasteRegulatorAssociation {
|
||||||
|
regulatorId: string // Reference to NaturalRegulator
|
||||||
|
percentage: number // Percentage of total volume (0-100)
|
||||||
|
}
|
||||||
|
|
||||||
// Waste
|
// Waste
|
||||||
export interface Waste extends BaseEntity {
|
export interface Waste extends BaseEntity {
|
||||||
name: string
|
name: string
|
||||||
originType: 'animals' | 'markets' | 'restaurants' | 'other'
|
originType: 'animals' | 'markets' | 'restaurants' | 'other'
|
||||||
originSubType?: string
|
originSubType?: string
|
||||||
|
wasteOriginId?: string // Reference to WasteOrigin (allows n wastes to 1 origin)
|
||||||
|
regulators?: WasteRegulatorAssociation[] // Associated regulators with volume percentages
|
||||||
originUnitsPer1000m3Methane: number
|
originUnitsPer1000m3Methane: number
|
||||||
bmp: number // Nm³ CH₄/kg VS
|
bmp: number // Nm³ CH₄/kg VS
|
||||||
waterPercentage: number // 0-100
|
waterPercentage: number // 0-100
|
||||||
regulationNeeds: string[]
|
regulationNeeds: string[] // Typical regulation needs (pathogenElimination, odorElimination, etc.)
|
||||||
regulatoryCharacteristics?: {
|
regulatoryCharacteristics?: {
|
||||||
nitrogen?: number
|
// Nutrient Requirements
|
||||||
phosphorus?: number
|
nitrogen?: number // Total Nitrogen (N or NTK)
|
||||||
potassium?: number
|
ammoniacalNitrogen?: number // N-NH₄ - Indicator of rapid effect
|
||||||
carbonNitrogenRatio?: number
|
phosphorus?: number // Total Phosphorus (P)
|
||||||
|
potassium?: number // Total Potassium (K)
|
||||||
|
carbonNitrogenRatio?: number // C:N ratio
|
||||||
|
|
||||||
|
// Heavy Metals and Toxic Elements
|
||||||
|
arsenicElimination?: boolean // Arsenic (As) elimination needed
|
||||||
|
zincElimination?: boolean // Zinc elimination needed
|
||||||
|
aluminumElimination?: boolean // Aluminum elimination needed
|
||||||
|
copperElimination?: boolean // Copper elimination needed
|
||||||
|
heavyMetalsElimination?: boolean // Heavy metals elimination needed
|
||||||
|
|
||||||
|
// Biological Contaminants
|
||||||
|
pathogenElimination?: boolean // Pathogen elimination needed
|
||||||
|
|
||||||
|
// Chemical Parameters
|
||||||
|
lowCNRatioEnrichment?: boolean // Low C:N ratio enrichment needed
|
||||||
|
medicationElimination?: boolean // Medication elimination needed
|
||||||
|
depositElimination?: boolean // Deposit elimination needed
|
||||||
|
odorElimination?: boolean // Odor elimination needed
|
||||||
|
turbidityReduction?: boolean // Turbidity reduction needed
|
||||||
|
sodiumReduction?: boolean // Sodium (Na⁺) reduction needed
|
||||||
|
chlorineReduction?: boolean // Chlorine (Cl⁻) reduction needed
|
||||||
|
electricalConductivityReduction?: boolean // Electrical conductivity reduction needed
|
||||||
|
sulfideElimination?: boolean // Sulfide elimination needed
|
||||||
|
methaneElimination?: boolean // Methane elimination needed
|
||||||
|
co2Elimination?: boolean // CO₂ elimination needed
|
||||||
|
polyphenolElimination?: boolean // Polyphenol elimination needed
|
||||||
|
refractoryFractionsElimination?: boolean // Elimination of refractory structural fractions needed
|
||||||
|
|
||||||
|
// Biological Processes
|
||||||
|
microbiologicalCompetition?: boolean // Microbiological competition needed
|
||||||
|
oilEmulsification?: boolean // Oil emulsification needed
|
||||||
|
|
||||||
|
// pH Regulation
|
||||||
|
acidityReduction?: boolean // Acidity reduction needed
|
||||||
|
phIncrease?: boolean // pH increase needed
|
||||||
|
phReduction?: boolean // pH reduction needed
|
||||||
}
|
}
|
||||||
maxStorageDuration: number // days
|
maxStorageDuration: number // days
|
||||||
}
|
}
|
||||||
@ -36,13 +100,48 @@ export interface NaturalRegulator extends BaseEntity {
|
|||||||
name: string
|
name: string
|
||||||
type: string
|
type: string
|
||||||
regulatoryCharacteristics?: {
|
regulatoryCharacteristics?: {
|
||||||
nitrogen?: number
|
// Nutrient Requirements
|
||||||
phosphorus?: number
|
nitrogen?: number // Total Nitrogen (N or NTK)
|
||||||
potassium?: number
|
ammoniacalNitrogen?: number // N-NH₄ - Indicator of rapid effect
|
||||||
carbonNitrogenRatio?: number
|
phosphorus?: number // Total Phosphorus (P)
|
||||||
phAdjustment?: number // -14 to 14
|
potassium?: number // Total Potassium (K)
|
||||||
metalBinding?: boolean
|
carbonNitrogenRatio?: number // C:N ratio
|
||||||
pathogenReduction?: boolean
|
|
||||||
|
// Heavy Metals and Toxic Elements
|
||||||
|
arsenicElimination?: boolean // Arsenic (As) elimination
|
||||||
|
zincElimination?: boolean // Zinc elimination
|
||||||
|
aluminumElimination?: boolean // Aluminum elimination
|
||||||
|
copperElimination?: boolean // Copper elimination
|
||||||
|
heavyMetalsElimination?: boolean // Heavy metals elimination
|
||||||
|
metalBinding?: boolean // General metal binding capability
|
||||||
|
|
||||||
|
// Biological Contaminants
|
||||||
|
pathogenReduction?: boolean // Pathogen elimination
|
||||||
|
|
||||||
|
// Chemical Parameters
|
||||||
|
lowCNRatioEnrichment?: boolean // Low C:N ratio enrichment
|
||||||
|
medicationElimination?: boolean // Medication elimination
|
||||||
|
depositElimination?: boolean // Deposit elimination
|
||||||
|
odorElimination?: boolean // Odor elimination
|
||||||
|
turbidityReduction?: boolean // Turbidity reduction
|
||||||
|
sodiumReduction?: boolean // Sodium (Na⁺) reduction
|
||||||
|
chlorineReduction?: boolean // Chlorine (Cl⁻) reduction
|
||||||
|
electricalConductivityReduction?: boolean // Electrical conductivity reduction
|
||||||
|
sulfideElimination?: boolean // Sulfide elimination
|
||||||
|
methaneElimination?: boolean // Methane elimination
|
||||||
|
co2Elimination?: boolean // CO₂ elimination
|
||||||
|
polyphenolElimination?: boolean // Polyphenol elimination
|
||||||
|
refractoryFractionsElimination?: boolean // Elimination of refractory structural fractions (lignin, alginates, fucoidans, crystalline cellulose, and insoluble fibers)
|
||||||
|
|
||||||
|
// Biological Processes
|
||||||
|
microbiologicalCompetition?: boolean // Microbiological competition
|
||||||
|
oilEmulsification?: boolean // Oil emulsification
|
||||||
|
|
||||||
|
// pH Regulation
|
||||||
|
acidityReduction?: boolean // Acidity reduction
|
||||||
|
phIncrease?: boolean // pH increase
|
||||||
|
phReduction?: boolean // pH reduction
|
||||||
|
phAdjustment?: number // -14 to 14 - Direct pH adjustment value
|
||||||
}
|
}
|
||||||
applicationConditions: string
|
applicationConditions: string
|
||||||
dosageRequirements: {
|
dosageRequirements: {
|
||||||
@ -246,6 +345,135 @@ export interface Project extends BaseEntity {
|
|||||||
businessPlan?: BusinessPlan
|
businessPlan?: BusinessPlan
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Module Component Types
|
||||||
|
export type ModuleComponentType =
|
||||||
|
| 'firstMethanization' // 1ère méthanisation (obligatoire) 21j
|
||||||
|
| 'waterEvaporation' // Evaporation eau (facultatif) 21j
|
||||||
|
| 'bioremediationEcosystem1' // Biorémidation par écosystemes 1 (facultatif) 21j
|
||||||
|
| 'bioremediationEcosystem2' // Biorémidation par écosystemes 2 (facultatif) 21j
|
||||||
|
| 'bioremediationEcosystem3' // Biorémidation par écosystemes 3 (facultatif) 21j
|
||||||
|
| 'secondMethanization' // 2nd méthanisation (obligatoire sauf si aucune remédiation) 21j
|
||||||
|
| 'waterEvaporationComposting' // Evaporation eau et compostage pour engrais (facultatif) 18j
|
||||||
|
| 'waterUvcTreatment' // Traitement de l'eau uvc (facultatif) 72h
|
||||||
|
| 'waterStorage' // Stockage eau 72h
|
||||||
|
| 'spirulinaWaterStorage' // Spiruline dans le stockage d'eau (facultatif) 21j
|
||||||
|
| 'solarPanels' // Panneaux solaire (facultatif)
|
||||||
|
| 'bitcoinMining' // Minage de bitcoins (facultatif)
|
||||||
|
|
||||||
|
export interface MonthlyValue {
|
||||||
|
january: number
|
||||||
|
february: number
|
||||||
|
march: number
|
||||||
|
april: number
|
||||||
|
may: number
|
||||||
|
june: number
|
||||||
|
july: number
|
||||||
|
august: number
|
||||||
|
september: number
|
||||||
|
october: number
|
||||||
|
november: number
|
||||||
|
december: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Dimensions {
|
||||||
|
length: number // m
|
||||||
|
height: number // m
|
||||||
|
width: number // m
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PowerSpecs {
|
||||||
|
kwh: number // kW.h
|
||||||
|
kw: number // kW équipés
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductionPowerSpecs {
|
||||||
|
kwh: number // kW.h possibles
|
||||||
|
kw: number // kW équipés possibles
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module Component
|
||||||
|
export interface ModuleComponent extends BaseEntity {
|
||||||
|
name: string
|
||||||
|
type: ModuleComponentType
|
||||||
|
isOptional: boolean
|
||||||
|
defaultDuration: number // days or hours
|
||||||
|
durationUnit: 'days' | 'hours'
|
||||||
|
|
||||||
|
// Consumption (per day, by month, at fixed temperature 13°C)
|
||||||
|
waterConsumptionPerDay?: MonthlyValue // L/day or m³/day
|
||||||
|
heatConsumptionPerDay?: MonthlyValue // kJ/day or kWh/day at 13°C
|
||||||
|
powerConsumption?: PowerSpecs // kW.h and kW équipés
|
||||||
|
|
||||||
|
// Capacities
|
||||||
|
totalWetWasteCapacity?: number // t
|
||||||
|
totalDryWasteCapacity?: number // t
|
||||||
|
totalCompostCapacity?: number // t
|
||||||
|
totalWaterCapacity?: number // m³ or L
|
||||||
|
|
||||||
|
// Production
|
||||||
|
methaneProduction?: number // m³/day or m³/cycle
|
||||||
|
addedBiomassProduction?: number // t/day or t/cycle
|
||||||
|
waterProduction?: number // m³/day or L/day
|
||||||
|
heatProduction?: number // kJ/day or kWh/day
|
||||||
|
|
||||||
|
// Immobilization
|
||||||
|
immobilizationDuration?: number // days
|
||||||
|
|
||||||
|
// Waste types
|
||||||
|
wasteTypes?: string[] // waste IDs that can be processed
|
||||||
|
|
||||||
|
// Dimensions
|
||||||
|
dimensions?: Dimensions // L, h, l in meters
|
||||||
|
groundSurface?: number // m² (surface au sol)
|
||||||
|
|
||||||
|
// Needs (per day, by month, at fixed temperature 13°C)
|
||||||
|
heatNeedsPerDay?: MonthlyValue // kJ/day or kWh/day at 13°C
|
||||||
|
coolingNeedsPerDay?: MonthlyValue // kJ/day or kWh/day at 13°C
|
||||||
|
|
||||||
|
// Production power
|
||||||
|
productionPower?: ProductionPowerSpecs // kW.h and kW équipés possibles
|
||||||
|
|
||||||
|
// ETP (Equivalent Temps Plein - Full Time Equivalent)
|
||||||
|
annualExploitationETP?: number // ETP exploitation annuel
|
||||||
|
annualMaintenanceETP?: number // ETP maintenance annuel
|
||||||
|
annualSupervisionETP?: number // ETP supervision annuel
|
||||||
|
|
||||||
|
// Costs (annual)
|
||||||
|
totalMaterialCost?: number // €/year (CAPEX)
|
||||||
|
annualExploitationConsumablesCost?: number // €/year
|
||||||
|
annualMaintenanceConsumablesCost?: number // €/year
|
||||||
|
|
||||||
|
// Lifetime
|
||||||
|
lifetime?: number // years (Durée de vie)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ecosystem Regulator Association
|
||||||
|
export interface EcosystemRegulatorAssociation {
|
||||||
|
regulatorId: string // Reference to NaturalRegulator
|
||||||
|
percentage: number // Percentage of total volume (0-100)
|
||||||
|
role: 'primary' | 'secondary' | 'support' // Role in the ecosystem
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ecosystem
|
||||||
|
export interface Ecosystem extends BaseEntity {
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
// Three primary regulation needs this ecosystem addresses
|
||||||
|
primaryRegulationNeeds: [string, string, string] // Exactly 3 regulation needs
|
||||||
|
// Regulators that compose this ecosystem
|
||||||
|
regulators: EcosystemRegulatorAssociation[]
|
||||||
|
// Waste types that can benefit from this ecosystem (optional, empty means all)
|
||||||
|
compatibleWasteTypes?: string[] // waste IDs
|
||||||
|
// Effectiveness rating for each regulation need (0-100)
|
||||||
|
effectiveness: {
|
||||||
|
[regulationNeed: string]: number // 0-100 percentage
|
||||||
|
}
|
||||||
|
// Application conditions
|
||||||
|
applicationConditions?: string
|
||||||
|
// Estimated treatment duration (days)
|
||||||
|
treatmentDuration?: number
|
||||||
|
}
|
||||||
|
|
||||||
// Storage structure
|
// Storage structure
|
||||||
export interface StorageData {
|
export interface StorageData {
|
||||||
version: string
|
version: string
|
||||||
@ -254,6 +482,10 @@ export interface StorageData {
|
|||||||
wastes: Waste[]
|
wastes: Waste[]
|
||||||
regulators: NaturalRegulator[]
|
regulators: NaturalRegulator[]
|
||||||
services: Service[]
|
services: Service[]
|
||||||
|
wasteOrigins: WasteOrigin[]
|
||||||
|
transporters: Transporter[]
|
||||||
|
moduleComponents: ModuleComponent[] // Added
|
||||||
|
ecosystems: Ecosystem[] // Added
|
||||||
treatmentSites: TreatmentSite[]
|
treatmentSites: TreatmentSite[]
|
||||||
wasteSites: WasteSite[]
|
wasteSites: WasteSite[]
|
||||||
investors: Investor[]
|
investors: Investor[]
|
||||||
|
|||||||
@ -1,16 +1,21 @@
|
|||||||
import { StorageData } from '@/types'
|
import { StorageData, Waste, NaturalRegulator } from '@/types'
|
||||||
import { seedWastes } from '@/data/seedWastes'
|
import wastesSeeds from '../../data/seeds/wastes-seeds.json'
|
||||||
import { seedRegulators } from '@/data/seedRegulators'
|
import regulatorsSeeds from '../../data/seeds/regulators-seeds.json'
|
||||||
import { initializeStorage } from './storage'
|
import { initializeStorage } from './storage'
|
||||||
|
|
||||||
|
// Load seed data from JSON files
|
||||||
export function loadSeedData(): StorageData {
|
export function loadSeedData(): StorageData {
|
||||||
const data = initializeStorage()
|
const data = initializeStorage()
|
||||||
|
|
||||||
// Add seed wastes
|
// Add seed wastes from JSON file
|
||||||
data.wastes = seedWastes
|
if (wastesSeeds && Array.isArray(wastesSeeds.wastes)) {
|
||||||
|
data.wastes = wastesSeeds.wastes as Waste[]
|
||||||
|
}
|
||||||
|
|
||||||
// Add seed regulators
|
// Add seed regulators from JSON file
|
||||||
data.regulators = seedRegulators
|
if (regulatorsSeeds && Array.isArray(regulatorsSeeds.regulators)) {
|
||||||
|
data.regulators = regulatorsSeeds.regulators as NaturalRegulator[]
|
||||||
|
}
|
||||||
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
@ -18,10 +23,12 @@ export function loadSeedData(): StorageData {
|
|||||||
export function addSeedDataToExisting(data: StorageData): StorageData {
|
export function addSeedDataToExisting(data: StorageData): StorageData {
|
||||||
// Only add wastes that don't already exist
|
// Only add wastes that don't already exist
|
||||||
const existingWasteIds = new Set(data.wastes.map(w => w.id))
|
const existingWasteIds = new Set(data.wastes.map(w => w.id))
|
||||||
|
const seedWastes = (wastesSeeds && Array.isArray(wastesSeeds.wastes) ? wastesSeeds.wastes : []) as Waste[]
|
||||||
const newWastes = seedWastes.filter(w => !existingWasteIds.has(w.id))
|
const newWastes = seedWastes.filter(w => !existingWasteIds.has(w.id))
|
||||||
|
|
||||||
// Only add regulators that don't already exist
|
// Only add regulators that don't already exist
|
||||||
const existingRegulatorIds = new Set(data.regulators.map(r => r.id))
|
const existingRegulatorIds = new Set(data.regulators.map(r => r.id))
|
||||||
|
const seedRegulators = (regulatorsSeeds && Array.isArray(regulatorsSeeds.regulators) ? regulatorsSeeds.regulators : []) as NaturalRegulator[]
|
||||||
const newRegulators = seedRegulators.filter(r => !existingRegulatorIds.has(r.id))
|
const newRegulators = seedRegulators.filter(r => !existingRegulatorIds.has(r.id))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,4 +1,11 @@
|
|||||||
import { StorageData } from '@/types'
|
import { StorageData, Waste, NaturalRegulator, Service, WasteOrigin, Transporter, ModuleComponent, Ecosystem } from '@/types'
|
||||||
|
import wastesSeeds from '../../data/seeds/wastes-seeds.json'
|
||||||
|
import regulatorsSeeds from '../../data/seeds/regulators-seeds.json'
|
||||||
|
import servicesSeeds from '../../data/seeds/services-seeds.json'
|
||||||
|
import wasteOriginsSeeds from '../../data/seeds/waste-origins-seeds.json'
|
||||||
|
import transportersSeeds from '../../data/seeds/transporters-seeds.json'
|
||||||
|
import moduleComponentsSeeds from '../../data/seeds/module-components-seeds.json'
|
||||||
|
import ecosystemsSeeds from '../../data/seeds/ecosystems-seeds.json'
|
||||||
|
|
||||||
const STORAGE_KEY = '4nkwaste_simulator_data'
|
const STORAGE_KEY = '4nkwaste_simulator_data'
|
||||||
const USER_KEY = '4nkwaste_simulator_user'
|
const USER_KEY = '4nkwaste_simulator_user'
|
||||||
@ -15,6 +22,10 @@ export function initializeStorage(): StorageData {
|
|||||||
wastes: [],
|
wastes: [],
|
||||||
regulators: [],
|
regulators: [],
|
||||||
services: [],
|
services: [],
|
||||||
|
wasteOrigins: [],
|
||||||
|
transporters: [],
|
||||||
|
moduleComponents: [],
|
||||||
|
ecosystems: [],
|
||||||
treatmentSites: [],
|
treatmentSites: [],
|
||||||
wasteSites: [],
|
wasteSites: [],
|
||||||
investors: [],
|
investors: [],
|
||||||
@ -23,17 +34,111 @@ export function initializeStorage(): StorageData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load data from localStorage
|
// Load seed data from JSON files
|
||||||
|
function loadSeedData(): StorageData {
|
||||||
|
const seedData = initializeStorage()
|
||||||
|
|
||||||
|
// Load wastes from seed file
|
||||||
|
if (wastesSeeds && Array.isArray(wastesSeeds.wastes)) {
|
||||||
|
seedData.wastes = wastesSeeds.wastes as Waste[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load regulators from seed file
|
||||||
|
if (regulatorsSeeds && Array.isArray(regulatorsSeeds.regulators)) {
|
||||||
|
seedData.regulators = regulatorsSeeds.regulators as NaturalRegulator[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load services from seed file
|
||||||
|
if (servicesSeeds && Array.isArray(servicesSeeds.services)) {
|
||||||
|
seedData.services = servicesSeeds.services as Service[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load waste origins from seed file
|
||||||
|
if (wasteOriginsSeeds && Array.isArray(wasteOriginsSeeds.wasteOrigins)) {
|
||||||
|
seedData.wasteOrigins = wasteOriginsSeeds.wasteOrigins as WasteOrigin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load transporters from seed file
|
||||||
|
if (transportersSeeds && Array.isArray(transportersSeeds.transporters)) {
|
||||||
|
seedData.transporters = transportersSeeds.transporters as Transporter[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load module components from seed file
|
||||||
|
if (moduleComponentsSeeds && Array.isArray(moduleComponentsSeeds.moduleComponents)) {
|
||||||
|
seedData.moduleComponents = moduleComponentsSeeds.moduleComponents as ModuleComponent[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load ecosystems from seed file
|
||||||
|
if (ecosystemsSeeds && Array.isArray(ecosystemsSeeds.ecosystems)) {
|
||||||
|
seedData.ecosystems = ecosystemsSeeds.ecosystems as Ecosystem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
return seedData
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge local data over seed data (local data takes precedence)
|
||||||
|
function mergeData(seedData: StorageData, localData: StorageData): StorageData {
|
||||||
|
return {
|
||||||
|
...seedData,
|
||||||
|
...localData,
|
||||||
|
// For arrays, merge by ID (local data overrides seeds)
|
||||||
|
wastes: mergeArraysById(seedData.wastes, localData.wastes),
|
||||||
|
regulators: mergeArraysById(seedData.regulators, localData.regulators),
|
||||||
|
services: mergeArraysById(seedData.services, localData.services),
|
||||||
|
wasteOrigins: mergeArraysById(seedData.wasteOrigins, localData.wasteOrigins),
|
||||||
|
transporters: mergeArraysById(seedData.transporters, localData.transporters),
|
||||||
|
moduleComponents: mergeArraysById(seedData.moduleComponents, localData.moduleComponents),
|
||||||
|
ecosystems: mergeArraysById(seedData.ecosystems, localData.ecosystems),
|
||||||
|
// Keep local data for other arrays
|
||||||
|
users: localData.users || seedData.users,
|
||||||
|
treatmentSites: localData.treatmentSites || seedData.treatmentSites,
|
||||||
|
wasteSites: localData.wasteSites || seedData.wasteSites,
|
||||||
|
investors: localData.investors || seedData.investors,
|
||||||
|
administrativeProcedures: localData.administrativeProcedures || seedData.administrativeProcedures,
|
||||||
|
projects: localData.projects || seedData.projects,
|
||||||
|
// Preserve version and lastModified from local data if exists
|
||||||
|
version: localData.version || seedData.version,
|
||||||
|
lastModified: localData.lastModified || seedData.lastModified,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to merge arrays by ID (local items override seed items with same ID)
|
||||||
|
function mergeArraysById<T extends { id: string }>(seedArray: T[], localArray: T[]): T[] {
|
||||||
|
const localMap = new Map(localArray.map(item => [item.id, item]))
|
||||||
|
const seedMap = new Map(seedArray.map(item => [item.id, item]))
|
||||||
|
|
||||||
|
// Start with all seed items
|
||||||
|
const merged = new Map(seedMap)
|
||||||
|
|
||||||
|
// Override with local items (local takes precedence)
|
||||||
|
localMap.forEach((item, id) => {
|
||||||
|
merged.set(id, item)
|
||||||
|
})
|
||||||
|
|
||||||
|
return Array.from(merged.values())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load data from localStorage, merging with seed data
|
||||||
export function loadStorage(): StorageData {
|
export function loadStorage(): StorageData {
|
||||||
try {
|
try {
|
||||||
const data = localStorage.getItem(STORAGE_KEY)
|
// First load seed data
|
||||||
if (!data) {
|
const seedData = loadSeedData()
|
||||||
return initializeStorage()
|
|
||||||
|
// Then try to load local data
|
||||||
|
const localDataString = localStorage.getItem(STORAGE_KEY)
|
||||||
|
if (!localDataString) {
|
||||||
|
// No local data, return seed data
|
||||||
|
return seedData
|
||||||
}
|
}
|
||||||
return JSON.parse(data) as StorageData
|
|
||||||
|
const localData = JSON.parse(localDataString) as StorageData
|
||||||
|
|
||||||
|
// Merge: local data overrides seed data
|
||||||
|
return mergeData(seedData, localData)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading storage:', error)
|
console.error('Error loading storage:', error)
|
||||||
return initializeStorage()
|
// On error, fallback to seed data
|
||||||
|
return loadSeedData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,6 +172,10 @@ export function importData(jsonString: string): { success: boolean; errors: stri
|
|||||||
if (!Array.isArray(data.wastes)) errors.push('Invalid wastes array')
|
if (!Array.isArray(data.wastes)) errors.push('Invalid wastes array')
|
||||||
if (!Array.isArray(data.regulators)) errors.push('Invalid regulators array')
|
if (!Array.isArray(data.regulators)) errors.push('Invalid regulators array')
|
||||||
if (!Array.isArray(data.services)) errors.push('Invalid services array')
|
if (!Array.isArray(data.services)) errors.push('Invalid services array')
|
||||||
|
if (!Array.isArray(data.wasteOrigins)) errors.push('Invalid wasteOrigins array')
|
||||||
|
if (!Array.isArray(data.transporters)) errors.push('Invalid transporters array')
|
||||||
|
if (!Array.isArray(data.moduleComponents)) errors.push('Invalid moduleComponents array')
|
||||||
|
if (!Array.isArray(data.ecosystems)) errors.push('Invalid ecosystems array')
|
||||||
if (!Array.isArray(data.treatmentSites)) errors.push('Invalid treatmentSites array')
|
if (!Array.isArray(data.treatmentSites)) errors.push('Invalid treatmentSites array')
|
||||||
if (!Array.isArray(data.wasteSites)) errors.push('Invalid wasteSites array')
|
if (!Array.isArray(data.wasteSites)) errors.push('Invalid wasteSites array')
|
||||||
if (!Array.isArray(data.investors)) errors.push('Invalid investors array')
|
if (!Array.isArray(data.investors)) errors.push('Invalid investors array')
|
||||||
|
|||||||
5
src/vite-env.d.ts
vendored
5
src/vite-env.d.ts
vendored
@ -1 +1,6 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.json' {
|
||||||
|
const value: any
|
||||||
|
export default value
|
||||||
|
}
|
||||||
128
translate_seeds.py
Normal file
128
translate_seeds.py
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Temporary script to translate seed files from French to English
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Translation dictionary for common terms
|
||||||
|
translations = {
|
||||||
|
"lisier": "slurry",
|
||||||
|
"vache": "cow",
|
||||||
|
"porc": "pig",
|
||||||
|
"porcelet": "piglet",
|
||||||
|
"poules pondeuses": "laying hens",
|
||||||
|
"poulets de chair": "broiler chickens",
|
||||||
|
"huiles": "oils",
|
||||||
|
"restaurant à fort volume": "high volume restaurant",
|
||||||
|
"cantine scolaire": "school canteen",
|
||||||
|
"ménages": "households",
|
||||||
|
"usine": "plant",
|
||||||
|
"usine de margarine": "margarine plant",
|
||||||
|
"déchets verts": "green waste",
|
||||||
|
"feuilles": "leaves",
|
||||||
|
"tiges tendres": "tender stems",
|
||||||
|
"hectare de production d'herbes": "hectare of herb production",
|
||||||
|
"arbre urbain": "urban tree",
|
||||||
|
"branches": "branches",
|
||||||
|
"arbre élagué": "pruned tree",
|
||||||
|
"hectare d'oliviers": "hectare of olive trees",
|
||||||
|
"grignons d'olive": "olive pomace",
|
||||||
|
"margines": "olive mill wastewater",
|
||||||
|
"boues urbaines": "urban sludge",
|
||||||
|
"station d'épuration urbaine": "urban wastewater treatment plant",
|
||||||
|
"flottats graisseux urbains": "urban fatty floatables",
|
||||||
|
"graisses de station d'épuration": "wastewater treatment plant fats",
|
||||||
|
"sargasses": "sargassum",
|
||||||
|
"laminaires": "laminaria",
|
||||||
|
"gracilaria": "gracilaria",
|
||||||
|
"ulves": "ulva",
|
||||||
|
"bananes mûres": "ripe bananas",
|
||||||
|
"petit marché local": "small local market",
|
||||||
|
"mangues mûres invendues": "unsold ripe mangoes",
|
||||||
|
"usine agroalimentaire": "food processing plant",
|
||||||
|
"légumes frais humides": "fresh wet vegetables",
|
||||||
|
"marché de gros": "wholesale market",
|
||||||
|
"résidus de légumes transformés": "processed vegetable residues",
|
||||||
|
"site industriel agroalimentaire": "food industrial site",
|
||||||
|
"restes de repas cuisinés": "cooked food leftovers",
|
||||||
|
"viande avariée": "spoiled meat",
|
||||||
|
"abattoir": "slaughterhouse",
|
||||||
|
"lait": "milk",
|
||||||
|
"yaourt": "yogurt",
|
||||||
|
"crème périmés": "expired cream",
|
||||||
|
"usine de produits laitiers": "dairy plant",
|
||||||
|
"pain rassis": "stale bread",
|
||||||
|
"boulangerie": "bakery",
|
||||||
|
"poissons avariés": "spoiled fish",
|
||||||
|
"marché aux poissons": "fish market",
|
||||||
|
"biodéchets mixtes": "mixed biowaste",
|
||||||
|
"déchets alimentaires ménagers": "household food waste",
|
||||||
|
}
|
||||||
|
|
||||||
|
def translate_text(text):
|
||||||
|
"""Simple translation function"""
|
||||||
|
if not text:
|
||||||
|
return text
|
||||||
|
|
||||||
|
# Replace common terms
|
||||||
|
result = text
|
||||||
|
for fr, en in translations.items():
|
||||||
|
result = result.replace(fr, en)
|
||||||
|
|
||||||
|
# Translate pH notes
|
||||||
|
result = re.sub(r'pH:\s*([0-9,\.]+)', r'pH: \1', result)
|
||||||
|
result = result.replace('à', 'to')
|
||||||
|
result = result.replace('selon', 'depending on')
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def translate_wastes():
|
||||||
|
"""Translate wastes-seeds.json"""
|
||||||
|
with open('data/seeds/wastes-seeds.json', 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
for waste in data['wastes']:
|
||||||
|
if 'name' in waste:
|
||||||
|
waste['name'] = translate_text(waste['name'])
|
||||||
|
if 'originSubType' in waste:
|
||||||
|
waste['originSubType'] = translate_text(waste['originSubType'])
|
||||||
|
if 'notes' in waste:
|
||||||
|
waste['notes'] = translate_text(waste['notes'])
|
||||||
|
|
||||||
|
with open('data/seeds/wastes-seeds.json', 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
def translate_regulators():
|
||||||
|
"""Translate and clean regulators-seeds.json"""
|
||||||
|
with open('data/seeds/regulators-seeds.json', 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
# Filter out invalid entries
|
||||||
|
valid_regulators = []
|
||||||
|
for reg in data['regulators']:
|
||||||
|
# Skip entries with type "unknown" or invalid names
|
||||||
|
if reg.get('type') == 'unknown':
|
||||||
|
continue
|
||||||
|
if reg.get('name') in ['Logique de symbiose :', 'Effets requis :', 'Cohabitation recommandée :']:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Translate
|
||||||
|
if 'name' in reg:
|
||||||
|
reg['name'] = translate_text(reg['name'])
|
||||||
|
if 'applicationConditions' in reg:
|
||||||
|
reg['applicationConditions'] = translate_text(reg['applicationConditions'])
|
||||||
|
|
||||||
|
valid_regulators.append(reg)
|
||||||
|
|
||||||
|
data['regulators'] = valid_regulators
|
||||||
|
|
||||||
|
with open('data/seeds/regulators-seeds.json', 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
translate_wastes()
|
||||||
|
translate_regulators()
|
||||||
|
print("Translation complete!")
|
||||||
@ -31,6 +31,6 @@
|
|||||||
"@/types/*": ["./src/types/*"]
|
"@/types/*": ["./src/types/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src", "data/seeds/*.json"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user