Files
sam-react-prod/src/components/items/DynamicItemForm/fields/UnitValueField.tsx

129 lines
3.8 KiB
TypeScript
Raw Normal View History

/**
* +
* Input() + Select()
*
* properties: { units, defaultUnit, precision }
* : { value: number, unit: string }
*/
'use client';
import { useCallback } from 'react';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import type { DynamicFieldRendererProps, UnitValueConfig } from '../types';
interface UnitValueData {
value: number | null;
unit: string;
}
function parseUnitValue(raw: unknown, defaultUnit: string): UnitValueData {
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
const obj = raw as Record<string, unknown>;
return {
value: obj.value !== null && obj.value !== undefined ? Number(obj.value) : null,
unit: typeof obj.unit === 'string' ? obj.unit : defaultUnit,
};
}
// 숫자만 들어온 경우
if (typeof raw === 'number') {
return { value: raw, unit: defaultUnit };
}
return { value: null, unit: defaultUnit };
}
export function UnitValueField({
field,
value,
onChange,
error,
disabled,
}: DynamicFieldRendererProps) {
const fieldKey = field.field_key || `field_${field.id}`;
const config = (field.properties || {}) as UnitValueConfig;
const units = config.units || [];
const defaultUnit = config.defaultUnit || (units.length > 0 ? units[0].value : '');
const precision = config.precision;
const data = parseUnitValue(value, defaultUnit);
const handleValueChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const raw = e.target.value;
if (raw === '' || raw === '-') {
onChange({ value: null, unit: data.unit });
return;
}
const num = parseFloat(raw);
if (!isNaN(num)) {
const final = precision !== undefined
? Math.round(num * Math.pow(10, precision)) / Math.pow(10, precision)
: num;
onChange({ value: final, unit: data.unit });
}
}, [data.unit, precision, onChange]);
const handleUnitChange = useCallback((newUnit: string) => {
onChange({ value: data.value, unit: newUnit });
}, [data.value, onChange]);
return (
<div>
<Label htmlFor={fieldKey}>
{field.field_name}
{field.is_required && <span className="text-red-500"> *</span>}
</Label>
<div className="flex gap-2">
<Input
id={fieldKey}
type="number"
placeholder={field.placeholder || '값 입력'}
value={data.value !== null ? String(data.value) : ''}
onChange={handleValueChange}
disabled={disabled}
step={precision !== undefined ? Math.pow(10, -precision) : 'any'}
className={`flex-1 ${error ? 'border-red-500' : ''}`}
/>
{units.length > 0 ? (
<Select
key={`${fieldKey}-unit-${data.unit}`}
value={data.unit}
onValueChange={handleUnitChange}
disabled={disabled}
>
<SelectTrigger className="w-24 shrink-0">
<SelectValue placeholder="단위" />
</SelectTrigger>
<SelectContent>
{units.map((u) => (
<SelectItem key={u.value} value={u.value}>
{u.label}
</SelectItem>
))}
</SelectContent>
</Select>
) : (
<span className="flex items-center text-sm text-muted-foreground px-2">
{data.unit || '-'}
</span>
)}
</div>
{error && (
<p className="text-xs text-red-500 mt-1">{error}</p>
)}
{!error && field.description && (
<p className="text-xs text-muted-foreground mt-1">
* {field.description}
</p>
)}
</div>
);
}