feat: add POS register screen with full-screen touch-optimized layout
Standalone register at /pos bypassing the admin sidebar layout: - Two-panel layout: product search/grid (60%) + cart/payment (40%) - Product search with barcode scan support (UPC lookup on Enter) - Custom item entry dialog for ad-hoc items - Cart with line items, tax, totals, and remove-item support - Payment dialogs: cash (quick amounts + change calc), card, check - Drawer open/close with balance reconciliation and over/short - Auto-creates pending transaction on first item added - POS link added to admin sidebar nav (module-gated) - Zustand store for POS session state, React Query for server data Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
91
packages/admin/src/components/pos/pos-top-bar.tsx
Normal file
91
packages/admin/src/components/pos/pos-top-bar.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import { Link, useRouter } from '@tanstack/react-router'
|
||||
import { useAuthStore } from '@/stores/auth.store'
|
||||
import { usePOSStore } from '@/stores/pos.store'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { ArrowLeft, LogOut, DollarSign } from 'lucide-react'
|
||||
import type { DrawerSession } from '@/api/pos'
|
||||
import { useState } from 'react'
|
||||
import { POSDrawerDialog } from './pos-drawer-dialog'
|
||||
|
||||
interface POSTopBarProps {
|
||||
locations: { id: string; name: string }[]
|
||||
locationId: string | null
|
||||
onLocationChange: (id: string) => void
|
||||
drawer: DrawerSession | null
|
||||
}
|
||||
|
||||
export function POSTopBar({ locations, locationId, onLocationChange, drawer }: POSTopBarProps) {
|
||||
const router = useRouter()
|
||||
const user = useAuthStore((s) => s.user)
|
||||
const logout = useAuthStore((s) => s.logout)
|
||||
const [drawerDialogOpen, setDrawerDialogOpen] = useState(false)
|
||||
|
||||
const drawerOpen = drawer?.status === 'open'
|
||||
|
||||
function handleLogout() {
|
||||
logout()
|
||||
router.navigate({ to: '/login', replace: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="h-12 border-b border-border bg-card flex items-center justify-between px-3 shrink-0">
|
||||
{/* Left: back + location */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Link to="/" className="flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground">
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Admin</span>
|
||||
</Link>
|
||||
|
||||
{locations.length > 1 ? (
|
||||
<Select value={locationId ?? ''} onValueChange={onLocationChange}>
|
||||
<SelectTrigger className="h-8 w-48 text-sm">
|
||||
<SelectValue placeholder="Select location" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{locations.map((loc) => (
|
||||
<SelectItem key={loc.id} value={loc.id}>{loc.name}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
) : locations.length === 1 ? (
|
||||
<span className="text-sm font-medium">{locations[0].name}</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{/* Center: drawer status */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => setDrawerDialogOpen(true)}
|
||||
>
|
||||
<DollarSign className="h-4 w-4" />
|
||||
{drawerOpen ? (
|
||||
<Badge variant="default" className="text-xs">
|
||||
Drawer Open — ${parseFloat(drawer!.openingBalance).toFixed(2)}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="outline" className="text-xs">Drawer Closed</Badge>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{/* Right: user + logout */}
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">{user?.firstName}</span>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={handleLogout} title="Sign out">
|
||||
<LogOut className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<POSDrawerDialog
|
||||
open={drawerDialogOpen}
|
||||
onOpenChange={setDrawerDialogOpen}
|
||||
drawer={drawer}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user