3TE/src/pages/configuration/RegulationCharacteristicsConfigurationPage.tsx
Nicolas Cantu 5c7137f3d2 Add administrative procedures to module components and centralized regulation characteristics configuration
**Motivations :**
* Allow associating administrative procedures to module components with status tracking
* Centralize regulation characteristics configuration for better data consistency
* Link regulation characteristics to wastes, regulators, ecosystems, and module components

**Root causes :**
* Need to track administrative procedures per module component
* Regulation characteristics were hardcoded in multiple places, causing inconsistency
* No centralized way to manage and reference regulation characteristics

**Correctifs :**
* Added ModuleComponentProcedureAssociation interface with procedureId, status, and notes
* Created RegulationCharacteristic entity with name, code, category, description, unit, isBoolean, minValue, maxValue
* Added regulationCharacteristicIds field to Waste, NaturalRegulator, Ecosystem, and ModuleComponent
* Updated all configuration pages to use regulation characteristics from centralized configuration
* Created RegulationCharacteristicsConfigurationPage for managing characteristics
* Added seeds for regulation characteristics (31 characteristics covering all categories)
* Added seeds for companies (4NK Water & Waste default company)

**Evolutions :**
* Module components can now have associated administrative procedures with status (toDo, done, na)
* Regulation characteristics are now centralized and can be referenced by multiple entities
* All regulation needs and characteristics are now managed through a single configuration page
* Business plans can be added to all entities (already implemented, documented in data_schemas.md)
* Updated data_schemas.md with complete documentation of all entities, relations, and validation rules

**Page affectées :**
* src/pages/configuration/ModuleComponentsConfigurationPage.tsx - Added administrative procedures section
* src/pages/configuration/RegulationCharacteristicsConfigurationPage.tsx - New page for managing characteristics
* src/pages/configuration/WasteConfigurationPage.tsx - Updated to use regulation characteristics
* src/pages/configuration/RegulatorsConfigurationPage.tsx - Updated to use regulation characteristics
* src/pages/configuration/EcosystemsConfigurationPage.tsx - Updated to use regulation characteristics
* src/types/index.ts - Added new interfaces and fields
* src/utils/storage.ts - Added regulation characteristics and companies to storage
* data_schemas.md - Complete documentation update
* data/seeds/regulation-characteristics-seeds.json - New seed file
* data/seeds/companies-seeds.json - New seed file
2025-12-10 08:27:52 +01:00

259 lines
8.6 KiB
TypeScript

import { useState } from 'react'
import { useStorage } from '@/hooks/useStorage'
import { RegulationCharacteristic } 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 './RegulationCharacteristicsConfigurationPage.css'
export default function RegulationCharacteristicsConfigurationPage() {
const { data, addEntity, updateEntity, deleteEntity } = useStorage()
const [editingId, setEditingId] = useState<string | null>(null)
const [formData, setFormData] = useState<Partial<RegulationCharacteristic>>({
name: '',
code: '',
category: 'other',
description: '',
unit: '',
isBoolean: true,
minValue: undefined,
maxValue: undefined,
})
const [errors, setErrors] = useState<Record<string, string>>({})
const characteristics = data?.regulationCharacteristics || []
const categoryOptions = [
{ value: 'nutrient', label: 'Nutrient' },
{ value: 'heavyMetal', label: 'Heavy Metal' },
{ value: 'biological', label: 'Biological' },
{ value: 'chemical', label: 'Chemical' },
{ value: 'biologicalProcess', label: 'Biological Process' },
{ value: 'ph', label: 'pH' },
{ value: 'other', label: 'Other' },
]
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
const newErrors: Record<string, string> = {}
newErrors.name = validateRequired(formData.name)
newErrors.code = validateRequired(formData.code)
// Check for duplicate code
const existingWithCode = characteristics.find((c) => c.code === formData.code && c.id !== editingId)
if (existingWithCode) {
newErrors.code = 'A characteristic with this code already exists'
}
setErrors(newErrors)
if (Object.values(newErrors).some((error) => error !== undefined)) {
return
}
const characteristic: RegulationCharacteristic = {
id: editingId || crypto.randomUUID(),
name: formData.name!,
code: formData.code!,
category: formData.category!,
description: formData.description,
unit: formData.unit,
isBoolean: formData.isBoolean ?? true,
minValue: formData.minValue,
maxValue: formData.maxValue,
createdAt: editingId ? characteristics.find((c) => c.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
if (editingId) {
updateEntity('regulationCharacteristics', editingId, characteristic)
} else {
addEntity('regulationCharacteristics', characteristic)
}
resetForm()
}
const resetForm = () => {
setFormData({
name: '',
code: '',
category: 'other',
description: '',
unit: '',
isBoolean: true,
minValue: undefined,
maxValue: undefined,
})
setEditingId(null)
setErrors({})
}
const handleEdit = (characteristic: RegulationCharacteristic) => {
setFormData(characteristic)
setEditingId(characteristic.id)
}
const handleDelete = (id: string) => {
if (confirm('Are you sure you want to delete this regulation characteristic?')) {
deleteEntity('regulationCharacteristics', id)
}
}
const tableColumns = [
{
key: 'name',
header: 'Name',
render: (char: RegulationCharacteristic) => char.name,
},
{
key: 'code',
header: 'Code',
render: (char: RegulationCharacteristic) => <code>{char.code}</code>,
},
{
key: 'category',
header: 'Category',
render: (char: RegulationCharacteristic) => {
const categoryLabel = categoryOptions.find((opt) => opt.value === char.category)?.label || char.category
return categoryLabel
},
},
{
key: 'type',
header: 'Type',
render: (char: RegulationCharacteristic) => (char.isBoolean ? 'Boolean' : 'Numeric'),
},
{
key: 'unit',
header: 'Unit',
render: (char: RegulationCharacteristic) => char.unit || '-',
},
{
key: 'range',
header: 'Range',
render: (char: RegulationCharacteristic) => {
if (char.isBoolean) return '-'
if (char.minValue !== undefined && char.maxValue !== undefined) {
return `${char.minValue} - ${char.maxValue}`
}
if (char.minValue !== undefined) return `${char.minValue}`
if (char.maxValue !== undefined) return `${char.maxValue}`
return '-'
},
},
{
key: 'actions',
header: 'Actions',
render: (char: RegulationCharacteristic) => (
<div className="table-actions">
<Button variant="secondary" onClick={() => handleEdit(char)}>
Edit
</Button>
<Button variant="danger" onClick={() => handleDelete(char.id)}>
Delete
</Button>
</div>
),
},
]
return (
<div className="regulation-characteristics-config-page">
<h1 className="page-title">Regulation Characteristics Configuration</h1>
<div className="page-content">
<Card title={editingId ? 'Edit Regulation Characteristic' : 'Add Regulation Characteristic'} 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="Code *"
value={formData.code || ''}
onChange={(e) => setFormData({ ...formData, code: e.target.value })}
error={errors.code}
helpText="Unique identifier (e.g., 'pathogenElimination', 'heavyMetalsElimination')"
/>
</div>
<div className="form-row">
<Select
label="Category *"
value={formData.category || 'other'}
onChange={(e) => setFormData({ ...formData, category: e.target.value as RegulationCharacteristic['category'] })}
options={categoryOptions}
/>
<Select
label="Type *"
value={formData.isBoolean ? 'boolean' : 'numeric'}
onChange={(e) => setFormData({ ...formData, isBoolean: e.target.value === 'boolean' })}
options={[
{ value: 'boolean', label: 'Boolean (Capability/Need)' },
{ value: 'numeric', label: 'Numeric (Value)' },
]}
/>
</div>
<Input
label="Description"
value={formData.description || ''}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
multiline
rows={3}
/>
{!formData.isBoolean && (
<div className="form-row">
<Input
label="Unit"
value={formData.unit || ''}
onChange={(e) => setFormData({ ...formData, unit: e.target.value })}
helpText="Unit of measurement (e.g., 'kg/t', '%', 'pH')"
/>
<Input
label="Min Value"
type="number"
step="0.01"
value={formData.minValue !== undefined ? formData.minValue : ''}
onChange={(e) => setFormData({ ...formData, minValue: e.target.value ? parseFloat(e.target.value) : undefined })}
/>
<Input
label="Max Value"
type="number"
step="0.01"
value={formData.maxValue !== undefined ? formData.maxValue : ''}
onChange={(e) => setFormData({ ...formData, maxValue: e.target.value ? parseFloat(e.target.value) : undefined })}
/>
</div>
)}
<div className="form-actions">
<Button type="submit" variant="primary">
{editingId ? 'Update' : 'Add'} Characteristic
</Button>
{editingId && (
<Button type="button" variant="secondary" onClick={resetForm}>
Cancel
</Button>
)}
</div>
</form>
</Card>
<Card title="Regulation Characteristics" className="table-card">
<Table columns={tableColumns} data={characteristics} emptyMessage="No regulation characteristics configured" />
</Card>
</div>
</div>
)
}