Files
lunarfront-app/packages/admin/src/components/pos/pos-register.tsx
ryan bd3a25aa1c 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>
2026-04-04 19:29:37 +00:00

73 lines
2.1 KiB
TypeScript

import { useEffect } from 'react'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { queryOptions } from '@tanstack/react-query'
import { api } from '@/lib/api-client'
import { usePOSStore } from '@/stores/pos.store'
import { currentDrawerOptions, transactionOptions } from '@/api/pos'
import { POSTopBar } from './pos-top-bar'
import { POSItemPanel } from './pos-item-panel'
import { POSCartPanel } from './pos-cart-panel'
interface Location {
id: string
name: string
}
function locationsOptions() {
return queryOptions({
queryKey: ['locations'],
queryFn: () => api.get<{ data: Location[] }>('/v1/locations'),
})
}
export function POSRegister() {
const { locationId, setLocation, currentTransactionId, drawerSessionId, setDrawerSession } = usePOSStore()
const queryClient = useQueryClient()
// Fetch locations
const { data: locationsData } = useQuery(locationsOptions())
const locations = locationsData?.data ?? []
// Auto-select first location
useEffect(() => {
if (!locationId && locations.length > 0) {
setLocation(locations[0].id)
}
}, [locationId, locations, setLocation])
// Fetch current drawer for selected location
const { data: drawer } = useQuery({
...currentDrawerOptions(locationId),
retry: false,
})
// Sync drawer session ID
useEffect(() => {
if (drawer?.id && drawer.status === 'open') {
setDrawerSession(drawer.id)
}
}, [drawer, setDrawerSession])
// Fetch current transaction
const { data: transaction } = useQuery(transactionOptions(currentTransactionId))
return (
<div className="flex flex-col h-full">
<POSTopBar
locations={locations}
locationId={locationId}
onLocationChange={setLocation}
drawer={drawer ?? null}
/>
<div className="flex flex-1 min-h-0">
<div className="w-[60%] border-r border-border overflow-hidden">
<POSItemPanel transaction={transaction ?? null} />
</div>
<div className="w-[40%] overflow-hidden">
<POSCartPanel transaction={transaction ?? null} />
</div>
</div>
</div>
)
}