523 lines
20 KiB
TypeScript
523 lines
20 KiB
TypeScript
import { useState } from 'react'
|
|
import { useStorage } from '@/hooks/useStorage'
|
|
import { NaturalRegulator } from '@/types'
|
|
import Card from '@/components/base/Card'
|
|
import Button from '@/components/base/Button'
|
|
import Input from '@/components/base/Input'
|
|
import Table from '@/components/base/Table'
|
|
import { validateRequired, validateNumber } from '@/utils/validators'
|
|
import './RegulatorsConfigurationPage.css'
|
|
|
|
export default function RegulatorsConfigurationPage() {
|
|
const { data, addEntity, updateEntity, deleteEntity, loading } = useStorage()
|
|
const [editingId, setEditingId] = useState<string | null>(null)
|
|
const [formData, setFormData] = useState<Partial<NaturalRegulator>>({
|
|
name: '',
|
|
type: '',
|
|
applicationConditions: '',
|
|
dosageRequirements: {
|
|
min: 0,
|
|
max: 0,
|
|
unit: 'kg/t',
|
|
},
|
|
regulatoryCharacteristics: {},
|
|
})
|
|
const [errors, setErrors] = useState<Record<string, string>>({})
|
|
|
|
if (loading || !data) {
|
|
return (
|
|
<div className="regulators-config-page">
|
|
<div className="page-loading">Loading...</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Safely normalize regulators to ensure all required fields exist
|
|
const regulators: NaturalRegulator[] = (data.regulators || [])
|
|
.filter(reg => reg && reg.id && reg.name) // Filter out invalid entries
|
|
.map(reg => ({
|
|
id: reg.id,
|
|
name: reg.name || '',
|
|
type: reg.type || '',
|
|
applicationConditions: reg.applicationConditions || '',
|
|
dosageRequirements: reg.dosageRequirements || {
|
|
min: 0,
|
|
max: 0,
|
|
unit: 'kg/t' as const,
|
|
},
|
|
regulatoryCharacteristics: reg.regulatoryCharacteristics || {},
|
|
createdAt: reg.createdAt || new Date().toISOString(),
|
|
updatedAt: reg.updatedAt,
|
|
notes: reg.notes,
|
|
}))
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
|
|
const newErrors: Record<string, string> = {}
|
|
newErrors.name = validateRequired(formData.name)
|
|
newErrors.type = validateRequired(formData.type)
|
|
newErrors.applicationConditions = validateRequired(formData.applicationConditions)
|
|
newErrors.dosageMin = validateNumber(formData.dosageRequirements?.min, 0)
|
|
newErrors.dosageMax = validateNumber(formData.dosageRequirements?.max, formData.dosageRequirements?.min)
|
|
|
|
setErrors(newErrors)
|
|
|
|
if (Object.values(newErrors).some((error) => error !== undefined)) {
|
|
return
|
|
}
|
|
|
|
const regulator: NaturalRegulator = {
|
|
id: editingId || crypto.randomUUID(),
|
|
name: formData.name!,
|
|
type: formData.type!,
|
|
regulatoryCharacteristics: formData.regulatoryCharacteristics || {},
|
|
applicationConditions: formData.applicationConditions!,
|
|
dosageRequirements: formData.dosageRequirements!,
|
|
createdAt: editingId ? regulators.find((r) => r.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
}
|
|
|
|
if (editingId) {
|
|
updateEntity('regulators', editingId, regulator)
|
|
} else {
|
|
addEntity('regulators', regulator)
|
|
}
|
|
|
|
resetForm()
|
|
}
|
|
|
|
const resetForm = () => {
|
|
setFormData({
|
|
name: '',
|
|
type: '',
|
|
applicationConditions: '',
|
|
dosageRequirements: {
|
|
min: 0,
|
|
max: 0,
|
|
unit: 'kg/t',
|
|
},
|
|
regulatoryCharacteristics: {},
|
|
})
|
|
setEditingId(null)
|
|
setErrors({})
|
|
}
|
|
|
|
const handleEdit = (regulator: NaturalRegulator) => {
|
|
setFormData({
|
|
name: regulator.name || '',
|
|
type: regulator.type || '',
|
|
applicationConditions: regulator.applicationConditions || '',
|
|
dosageRequirements: regulator.dosageRequirements || {
|
|
min: 0,
|
|
max: 0,
|
|
unit: 'kg/t',
|
|
},
|
|
regulatoryCharacteristics: regulator.regulatoryCharacteristics || {},
|
|
})
|
|
setEditingId(regulator.id)
|
|
}
|
|
|
|
const handleDelete = (id: string) => {
|
|
if (confirm('Are you sure you want to delete this regulator?')) {
|
|
deleteEntity('regulators', id)
|
|
}
|
|
}
|
|
|
|
const tableColumns = [
|
|
{
|
|
key: 'name',
|
|
header: 'Name',
|
|
render: (regulator: NaturalRegulator) => regulator.name || '-',
|
|
},
|
|
{
|
|
key: 'type',
|
|
header: 'Type',
|
|
render: (regulator: NaturalRegulator) => regulator.type || '-',
|
|
},
|
|
{
|
|
key: 'dosage',
|
|
header: 'Dosage',
|
|
render: (regulator: NaturalRegulator) => {
|
|
const dosage = regulator.dosageRequirements
|
|
if (!dosage) return '-'
|
|
return `${dosage.min} - ${dosage.max} ${dosage.unit}`
|
|
},
|
|
},
|
|
{
|
|
key: 'actions',
|
|
header: 'Actions',
|
|
render: (regulator: NaturalRegulator) => (
|
|
<div className="table-actions">
|
|
<Button variant="secondary" onClick={() => handleEdit(regulator)}>
|
|
Edit
|
|
</Button>
|
|
<Button variant="danger" onClick={() => handleDelete(regulator.id)}>
|
|
Delete
|
|
</Button>
|
|
</div>
|
|
),
|
|
},
|
|
]
|
|
|
|
return (
|
|
<div className="regulators-config-page">
|
|
<h1 className="page-title">Natural Regulators Configuration</h1>
|
|
|
|
<div className="page-content">
|
|
<Card title={editingId ? 'Edit Regulator' : 'Add Regulator'} 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="Type *"
|
|
value={formData.type || ''}
|
|
onChange={(e) => setFormData({ ...formData, type: e.target.value })}
|
|
error={errors.type}
|
|
/>
|
|
</div>
|
|
|
|
<Input
|
|
label="Application Conditions *"
|
|
value={formData.applicationConditions || ''}
|
|
onChange={(e) => setFormData({ ...formData, applicationConditions: e.target.value })}
|
|
error={errors.applicationConditions}
|
|
/>
|
|
|
|
<div className="form-row">
|
|
<Input
|
|
label="Dosage Min *"
|
|
type="number"
|
|
min="0"
|
|
value={formData.dosageRequirements?.min || ''}
|
|
onChange={(e) =>
|
|
setFormData({
|
|
...formData,
|
|
dosageRequirements: {
|
|
...formData.dosageRequirements!,
|
|
min: parseFloat(e.target.value) || 0,
|
|
},
|
|
})
|
|
}
|
|
error={errors.dosageMin}
|
|
/>
|
|
<Input
|
|
label="Dosage Max *"
|
|
type="number"
|
|
min={formData.dosageRequirements?.min || 0}
|
|
value={formData.dosageRequirements?.max || ''}
|
|
onChange={(e) =>
|
|
setFormData({
|
|
...formData,
|
|
dosageRequirements: {
|
|
...formData.dosageRequirements!,
|
|
max: parseFloat(e.target.value) || 0,
|
|
},
|
|
})
|
|
}
|
|
error={errors.dosageMax}
|
|
/>
|
|
<Select
|
|
label="Dosage Unit *"
|
|
value={formData.dosageRequirements?.unit || 'kg/t'}
|
|
onChange={(e) =>
|
|
setFormData({
|
|
...formData,
|
|
dosageRequirements: {
|
|
...formData.dosageRequirements!,
|
|
unit: e.target.value as 'kg/t' | 'L/t' | '%',
|
|
},
|
|
})
|
|
}
|
|
options={[
|
|
{ value: 'kg/t', label: 'kg/t' },
|
|
{ value: 'L/t', label: 'L/t' },
|
|
{ value: '%', label: '%' },
|
|
]}
|
|
/>
|
|
</div>
|
|
|
|
{/* Regulatory Characteristics Section */}
|
|
<div className="form-section">
|
|
<h3 className="form-section-title">Regulatory Characteristics (Typical)</h3>
|
|
|
|
{/* Nutrient Requirements */}
|
|
<div className="form-subsection">
|
|
<h4 className="form-subsection-title">Nutrient Requirements</h4>
|
|
<div className="form-row">
|
|
<Input
|
|
label="Nitrogen (N or NTK)"
|
|
type="number"
|
|
step="0.01"
|
|
min="0"
|
|
value={formData.regulatoryCharacteristics?.nitrogen || ''}
|
|
onChange={(e) => setFormData({
|
|
...formData,
|
|
regulatoryCharacteristics: {
|
|
...(formData.regulatoryCharacteristics || {}),
|
|
nitrogen: e.target.value ? parseFloat(e.target.value) : undefined,
|
|
},
|
|
})}
|
|
/>
|
|
<Input
|
|
label="Ammoniacal Nitrogen (N-NH₄)"
|
|
type="number"
|
|
step="0.01"
|
|
min="0"
|
|
value={formData.regulatoryCharacteristics?.ammoniacalNitrogen || ''}
|
|
onChange={(e) => setFormData({
|
|
...formData,
|
|
regulatoryCharacteristics: {
|
|
...(formData.regulatoryCharacteristics || {}),
|
|
ammoniacalNitrogen: e.target.value ? parseFloat(e.target.value) : undefined,
|
|
},
|
|
})}
|
|
/>
|
|
</div>
|
|
<div className="form-row">
|
|
<Input
|
|
label="Phosphorus (P)"
|
|
type="number"
|
|
step="0.01"
|
|
min="0"
|
|
value={formData.regulatoryCharacteristics?.phosphorus || ''}
|
|
onChange={(e) => setFormData({
|
|
...formData,
|
|
regulatoryCharacteristics: {
|
|
...(formData.regulatoryCharacteristics || {}),
|
|
phosphorus: e.target.value ? parseFloat(e.target.value) : undefined,
|
|
},
|
|
})}
|
|
/>
|
|
<Input
|
|
label="Potassium (K)"
|
|
type="number"
|
|
step="0.01"
|
|
min="0"
|
|
value={formData.regulatoryCharacteristics?.potassium || ''}
|
|
onChange={(e) => setFormData({
|
|
...formData,
|
|
regulatoryCharacteristics: {
|
|
...(formData.regulatoryCharacteristics || {}),
|
|
potassium: e.target.value ? parseFloat(e.target.value) : undefined,
|
|
},
|
|
})}
|
|
/>
|
|
</div>
|
|
<div className="form-row">
|
|
<Input
|
|
label="Carbon:Nitrogen Ratio (C:N)"
|
|
type="number"
|
|
step="0.1"
|
|
min="0"
|
|
value={formData.regulatoryCharacteristics?.carbonNitrogenRatio || ''}
|
|
onChange={(e) => setFormData({
|
|
...formData,
|
|
regulatoryCharacteristics: {
|
|
...(formData.regulatoryCharacteristics || {}),
|
|
carbonNitrogenRatio: e.target.value ? parseFloat(e.target.value) : undefined,
|
|
},
|
|
})}
|
|
/>
|
|
<Input
|
|
label="pH Adjustment (-14 to 14)"
|
|
type="number"
|
|
step="0.1"
|
|
min="-14"
|
|
max="14"
|
|
value={formData.regulatoryCharacteristics?.phAdjustment || ''}
|
|
onChange={(e) => setFormData({
|
|
...formData,
|
|
regulatoryCharacteristics: {
|
|
...(formData.regulatoryCharacteristics || {}),
|
|
phAdjustment: e.target.value ? parseFloat(e.target.value) : undefined,
|
|
},
|
|
})}
|
|
helpText="Negative values reduce pH, positive values increase pH"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Heavy Metals and Toxic Elements */}
|
|
<div className="form-subsection">
|
|
<h4 className="form-subsection-title">Heavy Metals and Toxic Elements (Capabilities)</h4>
|
|
<div className="checkbox-grid">
|
|
{[
|
|
{ key: 'arsenicElimination', label: 'Arsenic (As) Elimination' },
|
|
{ key: 'zincElimination', label: 'Zinc Elimination' },
|
|
{ key: 'aluminumElimination', label: 'Aluminum Elimination' },
|
|
{ key: 'copperElimination', label: 'Copper Elimination' },
|
|
{ key: 'heavyMetalsElimination', label: 'Heavy Metals Elimination' },
|
|
{ key: 'metalBinding', label: 'Metal Binding' },
|
|
].map((item) => (
|
|
<label key={item.key} className="checkbox-item">
|
|
<input
|
|
type="checkbox"
|
|
checked={Boolean((formData.regulatoryCharacteristics?.[item.key as keyof NonNullable<typeof formData.regulatoryCharacteristics>] as boolean))}
|
|
onChange={(e) => setFormData({
|
|
...formData,
|
|
regulatoryCharacteristics: {
|
|
...(formData.regulatoryCharacteristics || {}),
|
|
[item.key]: e.target.checked,
|
|
},
|
|
})}
|
|
/>
|
|
<span>{item.label}</span>
|
|
</label>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Biological Contaminants */}
|
|
<div className="form-subsection">
|
|
<h4 className="form-subsection-title">Biological Contaminants (Capabilities)</h4>
|
|
<div className="checkbox-grid">
|
|
<label className="checkbox-item">
|
|
<input
|
|
type="checkbox"
|
|
checked={formData.regulatoryCharacteristics?.pathogenReduction || false}
|
|
onChange={(e) => setFormData({
|
|
...formData,
|
|
regulatoryCharacteristics: {
|
|
...(formData.regulatoryCharacteristics || {}),
|
|
pathogenReduction: e.target.checked,
|
|
},
|
|
})}
|
|
/>
|
|
<span>Pathogen Reduction</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Chemical Parameters */}
|
|
<div className="form-subsection">
|
|
<h4 className="form-subsection-title">Chemical Parameters (Capabilities)</h4>
|
|
<div className="checkbox-grid">
|
|
{[
|
|
{ key: 'lowCNRatioEnrichment', label: 'Low C:N Ratio Enrichment' },
|
|
{ key: 'medicationElimination', label: 'Medication Elimination' },
|
|
{ key: 'depositElimination', label: 'Deposit Elimination' },
|
|
{ key: 'odorElimination', label: 'Odor Elimination' },
|
|
{ key: 'turbidityReduction', label: 'Turbidity Reduction' },
|
|
{ key: 'sodiumReduction', label: 'Sodium (Na⁺) Reduction' },
|
|
{ key: 'chlorineReduction', label: 'Chlorine (Cl⁻) Reduction' },
|
|
{ key: 'electricalConductivityReduction', label: 'Electrical Conductivity Reduction' },
|
|
{ key: 'sulfideElimination', label: 'Sulfide Elimination' },
|
|
{ key: 'methaneElimination', label: 'Methane Elimination' },
|
|
{ key: 'co2Elimination', label: 'CO₂ Elimination' },
|
|
{ key: 'polyphenolElimination', label: 'Polyphenol Elimination' },
|
|
{ key: 'refractoryFractionsElimination', label: 'Refractory Structural Fractions Elimination' },
|
|
].map((item) => {
|
|
const key = item.key as keyof NonNullable<typeof formData.regulatoryCharacteristics>
|
|
return (
|
|
<label key={item.key} className="checkbox-item">
|
|
<input
|
|
type="checkbox"
|
|
checked={Boolean(formData.regulatoryCharacteristics?.[key])}
|
|
onChange={(e) => setFormData({
|
|
...formData,
|
|
regulatoryCharacteristics: {
|
|
...(formData.regulatoryCharacteristics || {}),
|
|
[item.key]: e.target.checked,
|
|
},
|
|
})}
|
|
/>
|
|
<span>{item.label}</span>
|
|
</label>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Biological Processes */}
|
|
<div className="form-subsection">
|
|
<h4 className="form-subsection-title">Biological Processes (Capabilities)</h4>
|
|
<div className="checkbox-grid">
|
|
{[
|
|
{ key: 'microbiologicalCompetition', label: 'Microbiological Competition' },
|
|
{ key: 'oilEmulsification', label: 'Oil Emulsification' },
|
|
].map((item) => {
|
|
const key = item.key as keyof NonNullable<typeof formData.regulatoryCharacteristics>
|
|
return (
|
|
<label key={item.key} className="checkbox-item">
|
|
<input
|
|
type="checkbox"
|
|
checked={Boolean(formData.regulatoryCharacteristics?.[key])}
|
|
onChange={(e) => setFormData({
|
|
...formData,
|
|
regulatoryCharacteristics: {
|
|
...(formData.regulatoryCharacteristics || {}),
|
|
[item.key]: e.target.checked,
|
|
},
|
|
})}
|
|
/>
|
|
<span>{item.label}</span>
|
|
</label>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
{/* pH Regulation */}
|
|
<div className="form-subsection">
|
|
<h4 className="form-subsection-title">pH Regulation (Capabilities)</h4>
|
|
<div className="checkbox-grid">
|
|
{[
|
|
{ key: 'acidityReduction', label: 'Acidity Reduction' },
|
|
{ key: 'phIncrease', label: 'pH Increase' },
|
|
{ key: 'phReduction', label: 'pH Reduction' },
|
|
].map((item) => {
|
|
const key = item.key as keyof NonNullable<typeof formData.regulatoryCharacteristics>
|
|
return (
|
|
<label key={item.key} className="checkbox-item">
|
|
<input
|
|
type="checkbox"
|
|
checked={Boolean(formData.regulatoryCharacteristics?.[key])}
|
|
onChange={(e) => setFormData({
|
|
...formData,
|
|
regulatoryCharacteristics: {
|
|
...(formData.regulatoryCharacteristics || {}),
|
|
[item.key]: e.target.checked,
|
|
},
|
|
})}
|
|
/>
|
|
<span>{item.label}</span>
|
|
</label>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="form-actions">
|
|
<Button type="submit" variant="primary">
|
|
{editingId ? 'Update' : 'Add'} Regulator
|
|
</Button>
|
|
{editingId && (
|
|
<Button type="button" variant="secondary" onClick={resetForm}>
|
|
Cancel
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</form>
|
|
</Card>
|
|
|
|
<Card title="Natural Regulators" className="table-card">
|
|
{regulators.length > 0 ? (
|
|
<Table columns={tableColumns} data={regulators} emptyMessage="No regulators configured" />
|
|
) : (
|
|
<div className="table-empty">
|
|
<p>No regulators configured</p>
|
|
</div>
|
|
)}
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|