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 RegulatorsConfigurationPage from './pages/configuration/RegulatorsConfigurationPage'
|
||||
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 ProjectConfigurationPage from './pages/projects/ProjectConfigurationPage'
|
||||
import TreatmentSitesPage from './pages/projects/TreatmentSitesPage'
|
||||
@ -36,6 +40,10 @@ function App() {
|
||||
<Route path="/configuration/waste" element={<WasteConfigurationPage />} />
|
||||
<Route path="/configuration/regulators" element={<RegulatorsConfigurationPage />} />
|
||||
<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/new" element={<ProjectConfigurationPage />} />
|
||||
<Route path="/projects/:id" element={<ProjectConfigurationPage />} />
|
||||
|
||||
@ -24,6 +24,22 @@ export default function Sidebar() {
|
||||
<span className="sidebar-icon">💼</span>
|
||||
<span className="sidebar-label">Services</span>
|
||||
</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 className="sidebar-section">
|
||||
|
||||
@ -12,6 +12,14 @@ export default function DashboardPage() {
|
||||
const wastes = data?.wastes || []
|
||||
const services = data?.services || []
|
||||
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 (
|
||||
<div className="dashboard">
|
||||
@ -36,43 +44,41 @@ export default function DashboardPage() {
|
||||
<div className="dashboard-metric-value">{treatmentSites.length}</div>
|
||||
<div className="dashboard-metric-label">Treatment Sites</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="dashboard-section">
|
||||
<h2 className="dashboard-section-title">Recent Projects</h2>
|
||||
{projects.length === 0 ? (
|
||||
<div className="dashboard-empty">
|
||||
<p>No projects yet. Create your first project to get started.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="dashboard-projects">
|
||||
{projects.slice(0, 5).map((project) => (
|
||||
<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>
|
||||
|
||||
<div className="dashboard-section">
|
||||
<h2 className="dashboard-section-title">Quick Actions</h2>
|
||||
<div className="dashboard-actions">
|
||||
<a href="/projects/new" className="dashboard-action-button">
|
||||
Create New Project
|
||||
</a>
|
||||
<a href="/configuration/waste" className="dashboard-action-button">
|
||||
Configure Waste Types
|
||||
</a>
|
||||
<a href="/yields" className="dashboard-action-button">
|
||||
View Yields
|
||||
</a>
|
||||
<a href="/business-plan" className="dashboard-action-button">
|
||||
Business Plan
|
||||
</a>
|
||||
<div className="dashboard-metric-card">
|
||||
<div className="dashboard-metric-value">{regulators.length}</div>
|
||||
<div className="dashboard-metric-label">Regulators</div>
|
||||
</div>
|
||||
<div className="dashboard-metric-card">
|
||||
<div className="dashboard-metric-value">{services.length}</div>
|
||||
<div className="dashboard-metric-label">Services</div>
|
||||
</div>
|
||||
<div className="dashboard-metric-card">
|
||||
<div className="dashboard-metric-value">{wasteOrigins.length}</div>
|
||||
<div className="dashboard-metric-label">Waste Origins</div>
|
||||
</div>
|
||||
<div className="dashboard-metric-card">
|
||||
<div className="dashboard-metric-value">{transporters.length}</div>
|
||||
<div className="dashboard-metric-label">Transporters</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 className="dashboard-metric-card">
|
||||
<div className="dashboard-metric-value">{investors.length}</div>
|
||||
<div className="dashboard-metric-label">Investors</div>
|
||||
</div>
|
||||
<div className="dashboard-metric-card">
|
||||
<div className="dashboard-metric-value">{administrativeProcedures.length}</div>
|
||||
<div className="dashboard-metric-label">Administrative Procedures</div>
|
||||
</div>
|
||||
<div className="dashboard-metric-card">
|
||||
<div className="dashboard-metric-value">{moduleComponents.length}</div>
|
||||
<div className="dashboard-metric-label">Module Components</div>
|
||||
</div>
|
||||
<div className="dashboard-metric-card">
|
||||
<div className="dashboard-metric-value">{ecosystems.length}</div>
|
||||
<div className="dashboard-metric-label">Ecosystems</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;
|
||||
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'
|
||||
|
||||
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 [formData, setFormData] = useState<Partial<NaturalRegulator>>({
|
||||
name: '',
|
||||
@ -20,10 +20,36 @@ export default function RegulatorsConfigurationPage() {
|
||||
max: 0,
|
||||
unit: 'kg/t',
|
||||
},
|
||||
regulatoryCharacteristics: {},
|
||||
})
|
||||
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) => {
|
||||
e.preventDefault()
|
||||
@ -45,7 +71,7 @@ export default function RegulatorsConfigurationPage() {
|
||||
id: editingId || crypto.randomUUID(),
|
||||
name: formData.name!,
|
||||
type: formData.type!,
|
||||
regulatoryCharacteristics: formData.regulatoryCharacteristics,
|
||||
regulatoryCharacteristics: formData.regulatoryCharacteristics || {},
|
||||
applicationConditions: formData.applicationConditions!,
|
||||
dosageRequirements: formData.dosageRequirements!,
|
||||
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,
|
||||
unit: 'kg/t',
|
||||
},
|
||||
regulatoryCharacteristics: {},
|
||||
})
|
||||
setEditingId(null)
|
||||
setErrors({})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@ -91,18 +128,21 @@ export default function RegulatorsConfigurationPage() {
|
||||
{
|
||||
key: 'name',
|
||||
header: 'Name',
|
||||
render: (regulator: NaturalRegulator) => regulator.name,
|
||||
render: (regulator: NaturalRegulator) => regulator.name || '-',
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
header: 'Type',
|
||||
render: (regulator: NaturalRegulator) => regulator.type,
|
||||
render: (regulator: NaturalRegulator) => regulator.type || '-',
|
||||
},
|
||||
{
|
||||
key: 'dosage',
|
||||
header: 'Dosage',
|
||||
render: (regulator: NaturalRegulator) =>
|
||||
`${regulator.dosageRequirements.min} - ${regulator.dosageRequirements.max} ${regulator.dosageRequirements.unit}`,
|
||||
render: (regulator: NaturalRegulator) => {
|
||||
const dosage = regulator.dosageRequirements
|
||||
if (!dosage) return '-'
|
||||
return `${dosage.min} - ${dosage.max} ${dosage.unit}`
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
@ -182,6 +222,276 @@ export default function RegulatorsConfigurationPage() {
|
||||
}
|
||||
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 className="form-actions">
|
||||
@ -198,9 +508,15 @@ export default function RegulatorsConfigurationPage() {
|
||||
</Card>
|
||||
|
||||
<Card title="Natural Regulators" className="table-card">
|
||||
<Table columns={tableColumns} data={regulators} emptyMessage="No regulators configured" />
|
||||
{regulators.length > 0 ? (
|
||||
<Table columns={tableColumns} data={regulators} emptyMessage="No regulators configured" />
|
||||
) : (
|
||||
<div className="table-empty">
|
||||
<p>No regulators configured</p>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</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;
|
||||
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 { useStorage } from '@/hooks/useStorage'
|
||||
import { Waste } from '@/types'
|
||||
import { Waste, WasteRegulatorAssociation, NaturalRegulator } from '@/types'
|
||||
import Card from '@/components/base/Card'
|
||||
import Button from '@/components/base/Button'
|
||||
import Input from '@/components/base/Input'
|
||||
@ -27,6 +27,8 @@ export default function WasteConfigurationPage() {
|
||||
const [errors, setErrors] = useState<Record<string, string>>({})
|
||||
|
||||
const wastes = data?.wastes || []
|
||||
const wasteOrigins = data?.wasteOrigins || []
|
||||
const regulators = data?.regulators || []
|
||||
|
||||
const originTypeOptions = [
|
||||
{ value: 'animals', label: 'Animals' },
|
||||
@ -35,6 +37,16 @@ export default function WasteConfigurationPage() {
|
||||
{ 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) => {
|
||||
e.preventDefault()
|
||||
|
||||
@ -56,11 +68,13 @@ export default function WasteConfigurationPage() {
|
||||
name: formData.name!,
|
||||
originType: formData.originType!,
|
||||
originSubType: formData.originSubType,
|
||||
wasteOriginId: formData.wasteOriginId,
|
||||
originUnitsPer1000m3Methane: formData.originUnitsPer1000m3Methane!,
|
||||
bmp: formData.bmp!,
|
||||
waterPercentage: formData.waterPercentage!,
|
||||
regulationNeeds: formData.regulationNeeds || [],
|
||||
regulatoryCharacteristics: formData.regulatoryCharacteristics,
|
||||
regulators: formData.regulators || [],
|
||||
maxStorageDuration: formData.maxStorageDuration!,
|
||||
createdAt: editingId ? wastes.find((w) => w.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
@ -80,10 +94,12 @@ export default function WasteConfigurationPage() {
|
||||
name: '',
|
||||
originType: 'animals',
|
||||
originSubType: '',
|
||||
wasteOriginId: undefined,
|
||||
originUnitsPer1000m3Methane: 0,
|
||||
bmp: 0,
|
||||
waterPercentage: 75,
|
||||
regulationNeeds: [],
|
||||
regulators: [],
|
||||
maxStorageDuration: 30,
|
||||
})
|
||||
setEditingId(null)
|
||||
@ -166,12 +182,24 @@ export default function WasteConfigurationPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
label="Origin Sub Type"
|
||||
value={formData.originSubType || ''}
|
||||
onChange={(e) => setFormData({ ...formData, originSubType: e.target.value })}
|
||||
placeholder="Enter sub type (optional)"
|
||||
/>
|
||||
<div className="form-row">
|
||||
<Input
|
||||
label="Origin Sub Type"
|
||||
value={formData.originSubType || ''}
|
||||
onChange={(e) => setFormData({ ...formData, originSubType: e.target.value })}
|
||||
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">
|
||||
<Input
|
||||
@ -216,6 +244,348 @@ export default function WasteConfigurationPage() {
|
||||
/>
|
||||
</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">
|
||||
<Button type="submit" variant="primary">
|
||||
{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
|
||||
}
|
||||
|
||||
// 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
|
||||
export interface Waste extends BaseEntity {
|
||||
name: string
|
||||
originType: 'animals' | 'markets' | 'restaurants' | 'other'
|
||||
originSubType?: string
|
||||
wasteOriginId?: string // Reference to WasteOrigin (allows n wastes to 1 origin)
|
||||
regulators?: WasteRegulatorAssociation[] // Associated regulators with volume percentages
|
||||
originUnitsPer1000m3Methane: number
|
||||
bmp: number // Nm³ CH₄/kg VS
|
||||
waterPercentage: number // 0-100
|
||||
regulationNeeds: string[]
|
||||
regulationNeeds: string[] // Typical regulation needs (pathogenElimination, odorElimination, etc.)
|
||||
regulatoryCharacteristics?: {
|
||||
nitrogen?: number
|
||||
phosphorus?: number
|
||||
potassium?: number
|
||||
carbonNitrogenRatio?: number
|
||||
// Nutrient Requirements
|
||||
nitrogen?: number // Total Nitrogen (N or NTK)
|
||||
ammoniacalNitrogen?: number // N-NH₄ - Indicator of rapid effect
|
||||
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
|
||||
}
|
||||
@ -36,13 +100,48 @@ export interface NaturalRegulator extends BaseEntity {
|
||||
name: string
|
||||
type: string
|
||||
regulatoryCharacteristics?: {
|
||||
nitrogen?: number
|
||||
phosphorus?: number
|
||||
potassium?: number
|
||||
carbonNitrogenRatio?: number
|
||||
phAdjustment?: number // -14 to 14
|
||||
metalBinding?: boolean
|
||||
pathogenReduction?: boolean
|
||||
// Nutrient Requirements
|
||||
nitrogen?: number // Total Nitrogen (N or NTK)
|
||||
ammoniacalNitrogen?: number // N-NH₄ - Indicator of rapid effect
|
||||
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
|
||||
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
|
||||
dosageRequirements: {
|
||||
@ -246,6 +345,135 @@ export interface Project extends BaseEntity {
|
||||
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
|
||||
export interface StorageData {
|
||||
version: string
|
||||
@ -254,6 +482,10 @@ export interface StorageData {
|
||||
wastes: Waste[]
|
||||
regulators: NaturalRegulator[]
|
||||
services: Service[]
|
||||
wasteOrigins: WasteOrigin[]
|
||||
transporters: Transporter[]
|
||||
moduleComponents: ModuleComponent[] // Added
|
||||
ecosystems: Ecosystem[] // Added
|
||||
treatmentSites: TreatmentSite[]
|
||||
wasteSites: WasteSite[]
|
||||
investors: Investor[]
|
||||
|
||||
@ -1,16 +1,21 @@
|
||||
import { StorageData } from '@/types'
|
||||
import { seedWastes } from '@/data/seedWastes'
|
||||
import { seedRegulators } from '@/data/seedRegulators'
|
||||
import { StorageData, Waste, NaturalRegulator } from '@/types'
|
||||
import wastesSeeds from '../../data/seeds/wastes-seeds.json'
|
||||
import regulatorsSeeds from '../../data/seeds/regulators-seeds.json'
|
||||
import { initializeStorage } from './storage'
|
||||
|
||||
// Load seed data from JSON files
|
||||
export function loadSeedData(): StorageData {
|
||||
const data = initializeStorage()
|
||||
|
||||
// Add seed wastes
|
||||
data.wastes = seedWastes
|
||||
// Add seed wastes from JSON file
|
||||
if (wastesSeeds && Array.isArray(wastesSeeds.wastes)) {
|
||||
data.wastes = wastesSeeds.wastes as Waste[]
|
||||
}
|
||||
|
||||
// Add seed regulators
|
||||
data.regulators = seedRegulators
|
||||
// Add seed regulators from JSON file
|
||||
if (regulatorsSeeds && Array.isArray(regulatorsSeeds.regulators)) {
|
||||
data.regulators = regulatorsSeeds.regulators as NaturalRegulator[]
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
@ -18,10 +23,12 @@ export function loadSeedData(): StorageData {
|
||||
export function addSeedDataToExisting(data: StorageData): StorageData {
|
||||
// Only add wastes that don't already exist
|
||||
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))
|
||||
|
||||
// Only add regulators that don't already exist
|
||||
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))
|
||||
|
||||
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 USER_KEY = '4nkwaste_simulator_user'
|
||||
@ -15,6 +22,10 @@ export function initializeStorage(): StorageData {
|
||||
wastes: [],
|
||||
regulators: [],
|
||||
services: [],
|
||||
wasteOrigins: [],
|
||||
transporters: [],
|
||||
moduleComponents: [],
|
||||
ecosystems: [],
|
||||
treatmentSites: [],
|
||||
wasteSites: [],
|
||||
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 {
|
||||
try {
|
||||
const data = localStorage.getItem(STORAGE_KEY)
|
||||
if (!data) {
|
||||
return initializeStorage()
|
||||
// First load seed data
|
||||
const seedData = loadSeedData()
|
||||
|
||||
// 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) {
|
||||
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.regulators)) errors.push('Invalid regulators 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.wasteSites)) errors.push('Invalid wasteSites 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" />
|
||||
|
||||
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/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": ["src", "data/seeds/*.json"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user