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>
73 lines
2.1 KiB
TypeScript
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>
|
|
)
|
|
}
|