conversion astryx
This commit is contained in:
@@ -2,6 +2,51 @@ import React, { useState, useMemo, useEffect, useCallback, useRef } from 'react'
|
||||
import { Search, Plus, Minus, Trash2, Receipt, X, Tag, QrCode, CreditCard, PieChart, Barcode, ShoppingCart, User, RefreshCcw, Save, Download, ChevronRight, Zap, Wallet, Smartphone, AlertCircle } from 'lucide-react';
|
||||
import { useFiestaStockStatement, FIESTA_TENANT_ID } from '../services/fiestaQueries';
|
||||
import { num as fnum, str as fstr, type Row } from '../services/fiestaApi';
|
||||
import { useNotify } from './Toasts';
|
||||
import { Dialog } from '@astryxdesign/core/Dialog';
|
||||
|
||||
/**
|
||||
* PHASE 6 — the one modal shell for this screen.
|
||||
*
|
||||
* PosView carried SEVEN hand-rolled `fixed inset-0` overlays, the largest
|
||||
* concentration anywhere in the app. Every one was the same construction: a
|
||||
* blurred backdrop, a white card, an inline `maxWidth`, and a header row with a
|
||||
* close button. None trapped focus, none closed on Escape, and each had to be
|
||||
* dismissed by finding its own X.
|
||||
*
|
||||
* They now share this. Widths are passed per call site so each dialog keeps the
|
||||
* exact size it had (500 / 450 / 400px), and `maxHeight` is preserved where the
|
||||
* original set one — the two that scroll internally would otherwise grow past
|
||||
* the viewport on a long list.
|
||||
*
|
||||
* `print:hidden` is intentionally NOT carried over. It was on the backdrop of
|
||||
* every overlay including the receipt one, which is why the receipt modal had
|
||||
* to fight it back with `print:max-w-none`. Dialog renders in the top layer,
|
||||
* where the browser's own print handling applies.
|
||||
*/
|
||||
function PosModal({
|
||||
onClose,
|
||||
width,
|
||||
maxHeight,
|
||||
children,
|
||||
}: {
|
||||
onClose: () => void;
|
||||
width: number;
|
||||
maxHeight?: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Dialog
|
||||
isOpen
|
||||
onOpenChange={(open) => { if (!open) onClose(); }}
|
||||
width={width}
|
||||
maxHeight={maxHeight}
|
||||
purpose="info"
|
||||
>
|
||||
{children}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
interface PosViewProps {
|
||||
locationid?: number;
|
||||
@@ -24,6 +69,10 @@ interface CartItem {
|
||||
}
|
||||
|
||||
export default function PosView({ locationid, tenantId = FIESTA_TENANT_ID }: PosViewProps) {
|
||||
// PHASE 6 — replaces alert(). A native alert freezes the whole browser
|
||||
// until dismissed; a toast does not, and errors stay until read.
|
||||
const { notifyOk, notifyFail } = useNotify();
|
||||
|
||||
const stockQ = useFiestaStockStatement({ tenantid: tenantId, locationid: locationid ?? 0, pagesize: 200 });
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
@@ -236,7 +285,7 @@ export default function PosView({ locationid, tenantId = FIESTA_TENANT_ID }: Pos
|
||||
if (product) {
|
||||
handleProductScanned(product);
|
||||
} else {
|
||||
alert('Product not found in inventory.');
|
||||
notifyFail('Product not found in inventory.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -733,8 +782,7 @@ export default function PosView({ locationid, tenantId = FIESTA_TENANT_ID }: Pos
|
||||
|
||||
{/* ── MODAL: Manual Product Search ──────────────────────────────── */}
|
||||
{showManualSearch && (
|
||||
<div className="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 flex items-center justify-center p-4 print:hidden">
|
||||
<div className="bg-white rounded-2xl shadow-2xl overflow-hidden flex flex-col max-h-[85vh] animate-in zoom-in-95 duration-200" style={{ width: '100%', maxWidth: '500px' }}>
|
||||
<PosModal onClose={() => setShowManualSearch(false)} width={500} maxHeight="85vh">
|
||||
<div className="flex items-center justify-between px-5 py-4 border-b border-slate-100">
|
||||
<h3 className="font-bold text-slate-900 flex items-center gap-2">
|
||||
<Search className="w-5 h-5 text-indigo-500" /> Product Search
|
||||
@@ -781,14 +829,12 @@ export default function PosView({ locationid, tenantId = FIESTA_TENANT_ID }: Pos
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PosModal>
|
||||
)}
|
||||
|
||||
{/* ── MODAL: Customer CRM ───────────────────────────────────────── */}
|
||||
{showCustomerPrompt && (
|
||||
<div className="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 flex items-center justify-center p-4 print:hidden">
|
||||
<div className="bg-white rounded-2xl shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200" style={{ width: '100%', maxWidth: '400px' }}>
|
||||
<PosModal onClose={closeCustomerPrompt} width={400}>
|
||||
<div className="px-6 pt-6 pb-5">
|
||||
<div className="flex justify-between items-start mb-5">
|
||||
<div className="w-12 h-12 bg-blue-50 rounded-xl flex items-center justify-center">
|
||||
@@ -842,14 +888,12 @@ export default function PosView({ locationid, tenantId = FIESTA_TENANT_ID }: Pos
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PosModal>
|
||||
)}
|
||||
|
||||
{/* ── MODAL: Parked Carts ───────────────────────────────────────── */}
|
||||
{showParkedPrompt && (
|
||||
<div className="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 flex items-center justify-center p-4 print:hidden">
|
||||
<div className="bg-white rounded-2xl shadow-2xl overflow-hidden flex flex-col max-h-[80vh] animate-in zoom-in-95 duration-200" style={{ width: '100%', maxWidth: '450px' }}>
|
||||
<PosModal onClose={() => setShowParkedPrompt(false)} width={450} maxHeight="80vh">
|
||||
<div className="flex items-center justify-between px-5 py-4 border-b border-slate-100">
|
||||
<h3 className="font-bold text-slate-900 flex items-center gap-2">
|
||||
<Download className="w-5 h-5 text-blue-500" /> Parked Sales
|
||||
@@ -883,14 +927,12 @@ export default function PosView({ locationid, tenantId = FIESTA_TENANT_ID }: Pos
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PosModal>
|
||||
)}
|
||||
|
||||
{/* ── MODAL: Discount ───────────────────────────────────────────── */}
|
||||
{discountPromptItem && (
|
||||
<div className="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 flex items-center justify-center p-4 print:hidden">
|
||||
<div className="bg-white rounded-2xl shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200" style={{ width: '100%', maxWidth: '400px' }}>
|
||||
<PosModal onClose={() => setDiscountPromptItem(null)} width={400}>
|
||||
<div className="px-6 pt-6 pb-5">
|
||||
<div className="flex justify-between items-start mb-5">
|
||||
<div className="w-12 h-12 bg-emerald-50 rounded-xl flex items-center justify-center">
|
||||
@@ -938,14 +980,12 @@ export default function PosView({ locationid, tenantId = FIESTA_TENANT_ID }: Pos
|
||||
Confirm Discount
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PosModal>
|
||||
)}
|
||||
|
||||
{/* ── MODAL: Payment ────────────────────────────────────────────── */}
|
||||
{showPayment && (
|
||||
<div className="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 flex items-center justify-center p-4 print:hidden">
|
||||
<div className="bg-white rounded-2xl shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200" style={{ width: '100%', maxWidth: '450px' }}>
|
||||
<PosModal onClose={() => setShowPayment(false)} width={450}>
|
||||
{/* Header */}
|
||||
<div className="bg-white border-b border-slate-200 px-6 py-5 text-slate-900 flex items-start justify-between">
|
||||
<div>
|
||||
@@ -1104,14 +1144,12 @@ export default function PosView({ locationid, tenantId = FIESTA_TENANT_ID }: Pos
|
||||
Confirm {grandTotal < 0 ? 'Refund' : 'Payment'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PosModal>
|
||||
)}
|
||||
|
||||
{/* ── MODAL: Receipt ────────────────────────────────────────────── */}
|
||||
{showReceipt && (
|
||||
<div className="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 flex items-center justify-center p-4 print:static print:bg-white print:block print:p-0">
|
||||
<div className="bg-white rounded-2xl shadow-2xl overflow-hidden print:shadow-none print:max-w-none print:w-[300px]" style={{ width: '100%', maxWidth: '400px' }}>
|
||||
<PosModal onClose={resetPos} width={400}>
|
||||
<div className="px-5 py-4 border-b border-slate-100 flex justify-between items-center bg-slate-50 print:hidden">
|
||||
<h3 className="font-bold text-slate-900 flex items-center gap-2">
|
||||
<Receipt className="w-4 h-4" /> Receipt
|
||||
@@ -1177,14 +1215,12 @@ export default function PosView({ locationid, tenantId = FIESTA_TENANT_ID }: Pos
|
||||
<Receipt className="w-4 h-4" /> Print
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PosModal>
|
||||
)}
|
||||
|
||||
{/* ── MODAL: Daily Report ───────────────────────────────────────── */}
|
||||
{showReport && (
|
||||
<div className="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 flex items-center justify-center p-4 print:hidden">
|
||||
<div className="bg-white rounded-2xl shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200" style={{ width: '100%', maxWidth: '450px' }}>
|
||||
<PosModal onClose={() => setShowReport(false)} width={450}>
|
||||
<div className="flex items-center justify-between px-5 py-4 border-b border-slate-100">
|
||||
<h3 className="font-bold text-slate-900 flex items-center gap-2">
|
||||
<PieChart className="w-5 h-5 text-emerald-500" /> Daily Sales Report
|
||||
@@ -1274,8 +1310,7 @@ export default function PosView({ locationid, tenantId = FIESTA_TENANT_ID }: Pos
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PosModal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user