96 lines
3.0 KiB
TypeScript
96 lines
3.0 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
|
|
interface FreeItemModalProps {
|
|
onAdd: (item: { name: string; unitPriceCents: number }) => void;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export default function FreeItemModal({ onAdd, onClose }: FreeItemModalProps) {
|
|
const [name, setName] = useState('');
|
|
const [price, setPrice] = useState('');
|
|
const [error, setError] = useState('');
|
|
|
|
const submit = (event: React.FormEvent) => {
|
|
event.preventDefault();
|
|
const normalized = price.trim().replace(',', '.');
|
|
const unitPriceCents = /^\d+(?:\.\d{1,2})?$/.test(normalized)
|
|
? Math.round(Number(normalized) * 100)
|
|
: 0;
|
|
if (!name.trim() || unitPriceCents <= 0) {
|
|
setError('Indica un nombre y un precio positivo');
|
|
return;
|
|
}
|
|
onAdd({ name: name.trim(), unitPriceCents });
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="free-item-title"
|
|
>
|
|
<form
|
|
onSubmit={submit}
|
|
className="w-full max-w-md space-y-5 rounded-2xl bg-white p-6 shadow-2xl"
|
|
>
|
|
<div className="flex items-start justify-between gap-4">
|
|
<div>
|
|
<p className="text-sm font-semibold text-[#2D6A4F]">Sin producto de almacén</p>
|
|
<h2 id="free-item-title" className="text-2xl font-bold text-gray-900">
|
|
Artículo libre
|
|
</h2>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
className="min-h-12 min-w-12 rounded-xl bg-gray-100 text-xl"
|
|
aria-label="Cerrar"
|
|
>
|
|
✕
|
|
</button>
|
|
</div>
|
|
<label className="block text-sm font-semibold text-gray-700">
|
|
Nombre o servicio
|
|
<input
|
|
value={name}
|
|
onChange={(event) => {
|
|
setName(event.target.value);
|
|
setError('');
|
|
}}
|
|
required
|
|
maxLength={200}
|
|
autoFocus
|
|
className="mt-1 min-h-12 w-full rounded-xl border-2 border-gray-200 px-4 outline-none focus:border-[#2D6A4F]"
|
|
placeholder="Ej. Asesoría nutricional"
|
|
/>
|
|
</label>
|
|
<label className="block text-sm font-semibold text-gray-700">
|
|
Precio (€)
|
|
<input
|
|
value={price}
|
|
onChange={(event) => {
|
|
setPrice(event.target.value);
|
|
setError('');
|
|
}}
|
|
required
|
|
inputMode="decimal"
|
|
className="mt-1 min-h-12 w-full rounded-xl border-2 border-gray-200 px-4 text-xl font-bold outline-none focus:border-[#2D6A4F]"
|
|
placeholder="0,00"
|
|
/>
|
|
</label>
|
|
{error && (
|
|
<p className="text-sm font-medium text-red-600" aria-live="polite">
|
|
{error}
|
|
</p>
|
|
)}
|
|
<button className="min-h-14 w-full rounded-xl bg-[#2D6A4F] font-bold text-white">
|
|
Añadir al ticket
|
|
</button>
|
|
</form>
|
|
</div>
|
|
);
|
|
}
|