conversion astryx

This commit is contained in:
2026-08-13 11:07:54 +05:30
parent 7c26470c52
commit d935542b11
36 changed files with 3843 additions and 684 deletions

View File

@@ -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>
);