Files
GreenHomeUI/src/components/alert-settings/RuleFormCard.tsx
alireza 9ec7187e07
Some checks failed
Deploy MyApp on Same Server / build-and-deploy (push) Has been cancelled
fix: prevent number input reset to 0 when clearing value in rule form
Avoid converting empty string to 0 in value1 and value2 number inputs, which caused unexpected behavior in some browsers when users cleared the field.
2026-05-31 22:38:38 +03:30

148 lines
8.1 KiB
TypeScript

'use client'
import { Trash2 } from 'lucide-react'
import { SENSOR_TYPES, COMPARISON_TYPES, SensorType, ComparisonType } from './constants'
import { getSensorIcon, getSensorLabel, getSensorUnit, getComparisonLabel } from './utils'
import { CreateAlertRuleRequest } from '@/lib/api'
type RuleFormCardProps = {
rule: CreateAlertRuleRequest
index: number
totalRules: number
onUpdate: (index: number, updates: Partial<CreateAlertRuleRequest>) => void
onRemove: (index: number) => void
}
export function RuleFormCard({ rule, index, totalRules, onUpdate, onRemove }: RuleFormCardProps) {
const needsMax = COMPARISON_TYPES.find(c => c.value === rule.comparisonType)?.needsMax
const SensorIcon = getSensorIcon(rule.sensorType)
return (
<div className="bg-gradient-to-br from-orange-50 to-red-50 border border-orange-200 rounded-xl p-3 relative shadow-sm">
{/* Header - Compact */}
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<span className="text-xs font-bold text-orange-700 bg-white/80 px-2 py-0.5 rounded-full shadow-sm">
{totalRules === 1 ? '⚡ شرط' : `⚡ شرط ${index + 1}`}
</span>
{/* Compact summary of current selection */}
<span className="text-xs text-gray-500 hidden sm:inline">
<SensorIcon className="w-3 h-3 inline ml-1" />
{getSensorLabel(rule.sensorType)}
{' | '}
{getComparisonLabel(rule.comparisonType)}
{' | '}
{rule.value1}{needsMax ? ` - ${rule.value2 ?? '?'}` : ''} {getSensorUnit(rule.sensorType)}
</span>
</div>
{totalRules > 1 && (
<button
type="button"
onClick={() => onRemove(index)}
className="p-1.5 text-red-500 hover:bg-red-100 rounded-lg transition-colors"
title="حذف این شرط"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
)}
</div>
{/* Compact row: Sensor | Comparison | Value */}
<div className="flex flex-col sm:flex-row gap-2">
{/* Sensor Type - Compact Select */}
<div className="flex-1 min-w-0">
<select
value={rule.sensorType}
onChange={(e) => onUpdate(index, { sensorType: Number(e.target.value) as SensorType })}
className="w-full appearance-none px-3 py-2 pr-8 bg-white border border-orange-200 rounded-lg text-sm font-medium text-gray-700 focus:ring-2 focus:ring-orange-500 focus:border-orange-500 cursor-pointer"
style={{
backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`,
backgroundPosition: 'left 0.5rem center',
backgroundRepeat: 'no-repeat',
backgroundSize: '1.25rem'
}}
>
{SENSOR_TYPES.map(sensor => (
<option key={sensor.value} value={sensor.value}>
{sensor.label} ({sensor.unit})
</option>
))}
</select>
</div>
{/* Comparison Type - Compact Select */}
<div className="flex-1 min-w-0">
<select
value={rule.comparisonType}
onChange={(e) => onUpdate(index, { comparisonType: Number(e.target.value) as ComparisonType })}
className="w-full appearance-none px-3 py-2 pr-8 bg-white border border-orange-200 rounded-lg text-sm font-medium text-gray-700 focus:ring-2 focus:ring-orange-500 focus:border-orange-500 cursor-pointer"
style={{
backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`,
backgroundPosition: 'left 0.5rem center',
backgroundRepeat: 'no-repeat',
backgroundSize: '1.25rem'
}}
>
{COMPARISON_TYPES.map(comp => (
<option key={comp.value} value={comp.value}>
{comp.label}
</option>
))}
</select>
</div>
{/* Threshold Values - Compact Inputs */}
<div className="flex gap-1.5 items-center flex-1">
<div className="relative flex-1 min-w-0">
<input
type="number"
step="0.01"
value={rule.value1}
onChange={(e) => {
const val = e.target.value
// Only update if value is not empty; otherwise keep previous value
// to avoid Number('') → 0 breaking the input in some browsers
if (val !== '') {
onUpdate(index, { value1: Number(val) })
}
}}
className="w-full px-3 py-2 pl-10 bg-white border border-orange-200 rounded-lg text-sm font-medium focus:ring-2 focus:ring-orange-500 focus:border-orange-500"
placeholder={needsMax ? 'از' : 'مقدار'}
required
/>
<span className="absolute left-2 top-1/2 -translate-y-1/2 text-xs text-gray-400 font-medium">
{getSensorUnit(rule.sensorType)}
</span>
</div>
{needsMax && (
<>
<span className="text-gray-400 text-sm flex-shrink-0">-</span>
<div className="relative flex-1 min-w-0">
<input
type="number"
step="0.01"
value={rule.value2 ?? ''}
onChange={(e) => {
const val = e.target.value
// Only update if value is not empty; otherwise keep previous value
// to avoid Number('') → 0 breaking the input in some browsers
if (val !== '') {
onUpdate(index, { value2: Number(val) })
}
}}
className="w-full px-3 py-2 pl-10 bg-white border border-orange-200 rounded-lg text-sm font-medium focus:ring-2 focus:ring-orange-500 focus:border-orange-500"
placeholder="تا"
required
/>
<span className="absolute left-2 top-1/2 -translate-y-1/2 text-xs text-gray-400 font-medium">
{getSensorUnit(rule.sensorType)}
</span>
</div>
</>
)}
</div>
</div>
</div>
)
}