699 lines
28 KiB
TypeScript
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>
|
|
)
|
|
}
|