feat(POS-FIX-11): completed feature

This commit is contained in:
chattie
2026-08-24 08:13:43 +02:00
parent 579e0a18eb
commit d103b634aa
13 changed files with 207 additions and 13 deletions

View File

@@ -123,6 +123,8 @@ export default function RegisterPage() {
const [recoveringSaleId, setRecoveringSaleId] = useState<string | null>(null);
// POS-FIX-9: merge dialog when recovering with cart items
const [mergePendingSale, setMergePendingSale] = useState<PosPendingSale | null>(null);
// POS-FIX-11: park with name dialog
const [pendingParkName, setPendingParkName] = useState<string | null>(null);
// POS-FIX-3/POS-FIX-5: close session
const [showCloseSession, setShowCloseSession] = useState(false);
const [closingActualCash, setClosingActualCash] = useState('');
@@ -481,6 +483,20 @@ export default function RegisterPage() {
setError('Carrito vacío');
return;
}
// POS-FIX-11: if no customer, ask for a name
if (!customer) {
setPendingParkName('');
return;
}
await doParkSale();
};
// POS-FIX-11: actual parking with optional name
const doParkSale = async (name?: string) => {
if (!config?.session) {
setError('No hay sesión abierta');
return;
}
setProcessing(true);
setError('');
try {
@@ -505,8 +521,10 @@ export default function RegisterPage() {
),
payments: [],
...(customer ? { customerId: customer.id } : {}),
...(name ? { posLabel: name } : {}),
});
setRestPaymentFor(null);
setPendingParkName(null);
void loadPendingSales();
// Refresh the pending panel so the new parked sale appears
setLoadingPending(true);
@@ -866,14 +884,19 @@ export default function RegisterPage() {
<p className="text-xs text-gray-500">Sin ventas pendientes.</p>
) : (
<ul className="flex flex-1 flex-col gap-2 overflow-y-auto">
{pendingSales.map((sale) => (
{pendingSales.map((sale) => {
const saleDate = new Date(sale.createdAt);
const dateStr = saleDate.toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit', year: '2-digit' });
const timeStr = saleDate.toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' });
const displayName = sale.posLabel || sale.receiptNumber || sale.id.slice(0, 8);
return (
<li
key={sale.id}
className="rounded-xl border border-amber-200 bg-white p-3 text-sm shadow-sm"
>
<p className="font-semibold text-gray-800">{sale.receiptNumber ?? sale.id.slice(0, 8)}</p>
<p className="text-xs text-gray-500">
Total {formatPrice(sale.totalCents)} · pendiente {formatPrice(sale.outstandingCents)}
<p className="font-semibold text-gray-800">{displayName}</p>
<p className="text-xs text-gray-400">
📅 {dateStr} {timeStr} · Total {formatPrice(sale.totalCents)} · pendiente {formatPrice(sale.outstandingCents)}
</p>
<div className="mt-2 flex gap-1">
<button
@@ -894,7 +917,8 @@ export default function RegisterPage() {
</button>
</div>
</li>
))}
);
})}
</ul>
)}
</aside>
@@ -1484,6 +1508,53 @@ export default function RegisterPage() {
</div>
)}
{/* POS-FIX-11: name dialog when parking without customer */}
{pendingParkName !== null && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
<div className="w-full max-w-xs rounded-2xl bg-white p-6 shadow-xl">
<h2 className="mb-2 text-lg font-bold text-gray-900">Nombre para identificar</h2>
<p className="mb-4 text-sm text-gray-500">
¿A nombre de quién es este ticket pendiente?
</p>
<input
type="text"
value={pendingParkName}
onChange={(e) => setPendingParkName(e.target.value)}
placeholder="Ej: María García"
maxLength={100}
className="mb-4 w-full rounded-xl border border-gray-300 px-3 py-2 text-sm outline-none focus:border-[#2D6A4F]"
autoFocus
onKeyDown={(e) => {
if (e.key === 'Enter' && pendingParkName.trim()) {
void doParkSale(pendingParkName.trim());
}
}}
/>
<div className="flex gap-2">
<button
type="button"
onClick={() => setPendingParkName(null)}
className="flex-1 rounded-xl border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
>
Cancelar
</button>
<button
type="button"
onClick={() => {
if (pendingParkName.trim()) {
void doParkSale(pendingParkName.trim());
}
}}
disabled={!pendingParkName.trim()}
className="flex-1 rounded-xl bg-[#2D6A4F] px-4 py-2 text-sm font-bold text-white disabled:opacity-50"
>
Guardar
</button>
</div>
</div>
</div>
)}
{/* POS-FIX-8: delete confirmation for pending sale */}
{deleteConfirmFor && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">

View File

@@ -87,6 +87,7 @@ export interface PosPendingSale {
createdAt: string;
cashierEmail?: string | null;
receiptNumber?: string | null;
posLabel?: string | null;
}
export interface RecoveredOrderItem {