3TE/src/pages/configuration/WasteConfigurationPage.tsx
Nicolas Cantu c7db6590f0 Initial commit: 4NK Waste & Water Simulator
**Motivations :**
* Create a complete simulator for 4NK Waste & Water modular waste treatment infrastructure
* Implement frontend-only application with client-side data persistence
* Provide seed data for wastes and natural regulators from specifications

**Root causes :**
* Need for a simulation tool to configure and manage waste treatment projects
* Requirement for localhost-only access with persistent client-side storage
* Need for initial seed data to bootstrap the application

**Correctifs :**
* Implemented authentication system with AuthContext
* Fixed login/logout functionality with proper state management
* Created placeholder pages for all routes

**Evolutions :**
* Complete application structure with React, TypeScript, and Vite
* Seed data for 9 waste types and 52 natural regulators
* Settings page with import/export and seed data loading functionality
* Configuration pages for wastes and regulators with CRUD operations
* Project management pages structure
* Business plan and yields pages placeholders
* Comprehensive UI/UX design system (dark mode only)
* Navigation system with sidebar and header

**Page affectées :**
* All pages: Login, Dashboard, Waste Configuration, Regulators Configuration, Services Configuration
* Project pages: Project List, Project Configuration, Treatment Sites, Waste Sites, Investors, Administrative Procedures
* Analysis pages: Yields, Business Plan
* Utility pages: Settings, Help
* Components: Layout, Sidebar, Header, base components (Button, Input, Select, Card, Badge, Table)
* Utils: Storage, seed data, formatters, validators, constants
* Types: Complete TypeScript definitions for all entities
2025-12-09 19:09:42 +01:00

239 lines
7.8 KiB
TypeScript

import { useState } from 'react'
import { useStorage } from '@/hooks/useStorage'
import { 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 Badge from '@/components/base/Badge'
import { formatDate } from '@/utils/formatters'
import { validateRequired, validateNumber, validatePercentage } from '@/utils/validators'
import './WasteConfigurationPage.css'
export default function WasteConfigurationPage() {
const { data, addEntity, updateEntity, deleteEntity } = useStorage()
const [editingId, setEditingId] = useState<string | null>(null)
const [formData, setFormData] = useState<Partial<Waste>>({
name: '',
originType: 'animals',
originSubType: '',
originUnitsPer1000m3Methane: 0,
bmp: 0,
waterPercentage: 75,
regulationNeeds: [],
maxStorageDuration: 30,
})
const [errors, setErrors] = useState<Record<string, string>>({})
const wastes = data?.wastes || []
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.bmp = validateNumber(formData.bmp, 0.1, 1.0)
newErrors.waterPercentage = validatePercentage(formData.waterPercentage)
newErrors.originUnitsPer1000m3Methane = validateNumber(formData.originUnitsPer1000m3Methane, 0)
newErrors.maxStorageDuration = validateNumber(formData.maxStorageDuration, 1)
setErrors(newErrors)
if (Object.values(newErrors).some((error) => error !== undefined)) {
return
}
const waste: Waste = {
id: editingId || crypto.randomUUID(),
name: formData.name!,
originType: formData.originType!,
originSubType: formData.originSubType,
originUnitsPer1000m3Methane: formData.originUnitsPer1000m3Methane!,
bmp: formData.bmp!,
waterPercentage: formData.waterPercentage!,
regulationNeeds: formData.regulationNeeds || [],
regulatoryCharacteristics: formData.regulatoryCharacteristics,
maxStorageDuration: formData.maxStorageDuration!,
createdAt: editingId ? wastes.find((w) => w.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
if (editingId) {
updateEntity('wastes', editingId, waste)
} else {
addEntity('wastes', waste)
}
resetForm()
}
const resetForm = () => {
setFormData({
name: '',
originType: 'animals',
originSubType: '',
originUnitsPer1000m3Methane: 0,
bmp: 0,
waterPercentage: 75,
regulationNeeds: [],
maxStorageDuration: 30,
})
setEditingId(null)
setErrors({})
}
const handleEdit = (waste: Waste) => {
setFormData(waste)
setEditingId(waste.id)
}
const handleDelete = (id: string) => {
if (confirm('Are you sure you want to delete this waste type?')) {
deleteEntity('wastes', id)
}
}
const tableColumns = [
{
key: 'name',
header: 'Name',
render: (waste: Waste) => waste.name,
},
{
key: 'originType',
header: 'Origin Type',
render: (waste: Waste) => <Badge variant="info">{waste.originType}</Badge>,
},
{
key: 'bmp',
header: 'BMP (Nm³ CH₄/kg VS)',
render: (waste: Waste) => waste.bmp.toFixed(3),
},
{
key: 'waterPercentage',
header: 'Water %',
render: (waste: Waste) => `${waste.waterPercentage}%`,
},
{
key: 'maxStorageDuration',
header: 'Max Storage (days)',
render: (waste: Waste) => waste.maxStorageDuration,
},
{
key: 'actions',
header: 'Actions',
render: (waste: Waste) => (
<div className="table-actions">
<Button variant="secondary" onClick={() => handleEdit(waste)}>
Edit
</Button>
<Button variant="danger" onClick={() => handleDelete(waste.id)}>
Delete
</Button>
</div>
),
},
]
return (
<div className="waste-config-page">
<h1 className="page-title">Waste Configuration</h1>
<div className="page-content">
<Card title={editingId ? 'Edit Waste Type' : 'Add Waste Type'} 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}
placeholder="Enter waste name"
/>
<Select
label="Origin Type *"
value={formData.originType || 'animals'}
onChange={(e) => setFormData({ ...formData, originType: e.target.value as Waste['originType'] })}
options={originTypeOptions}
/>
</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="BMP (Nm³ CH₄/kg VS) *"
type="number"
step="0.001"
min="0.1"
max="1.0"
value={formData.bmp || ''}
onChange={(e) => setFormData({ ...formData, bmp: parseFloat(e.target.value) || 0 })}
error={errors.bmp}
helpText="Biochemical Methane Potential (0.1 - 1.0)"
/>
<Input
label="Water Percentage (%) *"
type="number"
min="0"
max="100"
value={formData.waterPercentage || ''}
onChange={(e) => setFormData({ ...formData, waterPercentage: parseFloat(e.target.value) || 0 })}
error={errors.waterPercentage}
helpText="Water content percentage (0-100%)"
/>
</div>
<div className="form-row">
<Input
label="Origin Units per 1000m³ Methane *"
type="number"
min="0"
value={formData.originUnitsPer1000m3Methane || ''}
onChange={(e) => setFormData({ ...formData, originUnitsPer1000m3Methane: parseFloat(e.target.value) || 0 })}
error={errors.originUnitsPer1000m3Methane}
/>
<Input
label="Max Storage Duration (days) *"
type="number"
min="1"
value={formData.maxStorageDuration || ''}
onChange={(e) => setFormData({ ...formData, maxStorageDuration: parseInt(e.target.value) || 0 })}
error={errors.maxStorageDuration}
/>
</div>
<div className="form-actions">
<Button type="submit" variant="primary">
{editingId ? 'Update' : 'Add'} Waste Type
</Button>
{editingId && (
<Button type="button" variant="secondary" onClick={resetForm}>
Cancel
</Button>
)}
</div>
</form>
</Card>
<Card title="Waste Types" className="table-card">
<Table columns={tableColumns} data={wastes} emptyMessage="No waste types configured" />
</Card>
</div>
</div>
)
}