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:
2026-04-24 17:35:22 +03:00
parent 26c4818aa1
commit ee51e52acf
6 changed files with 253 additions and 23 deletions

View File

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

View File

@@ -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}

View File

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