**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
259 lines
8.6 KiB
TypeScript
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>
|
|
)
|
|
}
|