3TE/src/pages/configuration/ModuleComponentsConfigurationPage.tsx

699 lines
28 KiB
TypeScript

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>
)
}