Waiter PWA: UX polish — table names, category colours, print ack, PIN fix
- TableCard: show table.label (display name) instead of internal number - TableListPage: zone filter rows 50% taller; table cards capped at 132px max-height so single-table zones don't stretch; grid aligns to top - ProductPicker: category tabs use their configured colour (inactive=35% opacity); new View All button opens a full-screen category tile modal - AddItemsPage: show per-printer print acknowledgement after sending order; print failures keep items as drafts and show a clear error screen - PinPad: reduced to 4 dots/digits with auto-submit on 4th digit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,10 @@ export default function PinPad({ onSubmit, loading }) {
|
||||
const [pin, setPin] = useState('')
|
||||
|
||||
function press(digit) {
|
||||
if (pin.length < 8) setPin(p => p + digit)
|
||||
if (pin.length >= 4) return
|
||||
const next = pin + digit
|
||||
setPin(next)
|
||||
if (next.length === 4 && !loading) onSubmit(next)
|
||||
}
|
||||
|
||||
function backspace() {
|
||||
@@ -15,7 +18,7 @@ export default function PinPad({ onSubmit, loading }) {
|
||||
if (pin.length > 0 && !loading) onSubmit(pin)
|
||||
}
|
||||
|
||||
const dots = Array.from({ length: 8 }, (_, i) => (
|
||||
const dots = Array.from({ length: 4 }, (_, i) => (
|
||||
<span key={i} style={{ fontSize: 20, color: i < pin.length ? '#f59e0b' : '#334155' }}>●</span>
|
||||
))
|
||||
|
||||
|
||||
@@ -1,24 +1,58 @@
|
||||
import { useState } from 'react'
|
||||
import ItemOptionsModal from './ItemOptionsModal'
|
||||
|
||||
function hexToRgba(hex, alpha) {
|
||||
if (!hex) return null
|
||||
const h = hex.replace('#', '')
|
||||
const r = parseInt(h.substring(0, 2), 16)
|
||||
const g = parseInt(h.substring(2, 4), 16)
|
||||
const b = parseInt(h.substring(4, 6), 16)
|
||||
return `rgba(${r},${g},${b},${alpha})`
|
||||
}
|
||||
|
||||
export default function ProductPicker({ categories, products, onAdd }) {
|
||||
const [activeCat, setActiveCat] = useState(categories[0]?.id ?? null)
|
||||
const [selectedProduct, setSelectedProduct] = useState(null)
|
||||
const [viewAllOpen, setViewAllOpen] = useState(false)
|
||||
|
||||
const filtered = products.filter(p => p.category_id === activeCat)
|
||||
|
||||
function selectCategory(id) {
|
||||
setActiveCat(id)
|
||||
setViewAllOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="product-picker">
|
||||
<div className="category-tabs">
|
||||
{categories.map(cat => (
|
||||
<button
|
||||
key={cat.id}
|
||||
className={`cat-tab ${activeCat === cat.id ? 'cat-tab--active' : ''}`}
|
||||
onClick={() => setActiveCat(cat.id)}
|
||||
>
|
||||
{cat.name}
|
||||
</button>
|
||||
))}
|
||||
{/* View All button — always first */}
|
||||
<button
|
||||
className="cat-tab cat-tab--viewall"
|
||||
onClick={() => setViewAllOpen(true)}
|
||||
title="Εμφάνιση όλων"
|
||||
>
|
||||
⊞
|
||||
</button>
|
||||
|
||||
{categories.map(cat => {
|
||||
const isActive = activeCat === cat.id
|
||||
const bg = cat.color
|
||||
? isActive ? cat.color : hexToRgba(cat.color, 0.35)
|
||||
: isActive ? 'var(--accent)' : 'var(--bg3)'
|
||||
const color = cat.color
|
||||
? isActive ? '#fff' : 'rgba(255,255,255,0.65)'
|
||||
: isActive ? '#1c1400' : 'var(--muted)'
|
||||
return (
|
||||
<button
|
||||
key={cat.id}
|
||||
className="cat-tab"
|
||||
style={{ background: bg, color, border: isActive && cat.color ? `2px solid ${cat.color}` : undefined }}
|
||||
onClick={() => setActiveCat(cat.id)}
|
||||
>
|
||||
{cat.name}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="product-grid">
|
||||
@@ -35,6 +69,39 @@ export default function ProductPicker({ categories, products, onAdd }) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* View All modal */}
|
||||
{viewAllOpen && (
|
||||
<div className="modal-overlay" onClick={() => setViewAllOpen(false)}>
|
||||
<div
|
||||
className="cat-all-modal"
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
<div className="cat-all-modal__header">
|
||||
<span className="cat-all-modal__title">Κατηγορίες</span>
|
||||
<button className="icon-btn" onClick={() => setViewAllOpen(false)}>✕</button>
|
||||
</div>
|
||||
<div className="cat-all-grid">
|
||||
{categories.map(cat => {
|
||||
const isActive = activeCat === cat.id
|
||||
const bg = cat.color || 'var(--bg3)'
|
||||
const overlay = isActive ? 'rgba(255,255,255,0.18)' : 'rgba(0,0,0,0.35)'
|
||||
return (
|
||||
<button
|
||||
key={cat.id}
|
||||
className={`cat-all-tile ${isActive ? 'cat-all-tile--active' : ''}`}
|
||||
style={{ background: bg, boxShadow: isActive ? `0 0 0 3px #fff` : undefined }}
|
||||
onClick={() => selectCategory(cat.id)}
|
||||
>
|
||||
<span className="cat-all-tile__overlay" style={{ background: overlay }} />
|
||||
<span className="cat-all-tile__name">{cat.name}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedProduct && (
|
||||
<ItemOptionsModal
|
||||
product={selectedProduct}
|
||||
|
||||
@@ -13,10 +13,11 @@ export default function TableCard({ table, order, currentUserId, onClick }) {
|
||||
cardClass = 'table-card table-card--active'
|
||||
}
|
||||
|
||||
const displayName = table.label || `T${table.number}`
|
||||
|
||||
return (
|
||||
<button className={cardClass} onClick={onClick}>
|
||||
<span className="table-card__number">{table.number}</span>
|
||||
{table.name && <span className="table-card__name">{table.name}</span>}
|
||||
<span className="table-card__number">{displayName}</span>
|
||||
<span className="table-card__status">{statusLabel}</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user