feat(F-186): completed feature
This commit is contained in:
95
project/apps/pos/src/components/FreeItemModal.tsx
Normal file
95
project/apps/pos/src/components/FreeItemModal.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user