# Restaurant POS Application
This is a minimal yet fully functional restaurant POS application built with React. It includes features like item grid with category filtering, table selection, and a cart system, along with a payment modal that supports multiple payment types.
## Dir Structure:
public
└── favicon.svg
src
├── components
│ ├── Cart.jsx
│ ├── ItemGrid.jsx
│ └── TableSelection.jsx
├── App.css
├── App.jsx
├── data.js
└── index.jsx
.gitignore
.replit
README.md
generated-icon.png
index.html
package-lock.json
package.json
tsconfig.json
vite.config.js
Copy
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React + TypeScript + Replit</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.jsx"></script>
</body>
</html>
[React](https://reactjs.org/) is a popular JavaScript library for building user interfaces.
[Vite](https://vitejs.dev/) is a blazing fast frontend build tool that includes features like Hot Module Reloading (HMR), optimized builds, and TypeScript support out of the box.
Using the two in conjunction is one of the fastest ways to build a web app.
{
"name": "react-javascript",
"version": "1.0.0",
"type": "module",
"description": "React TypeScript on Replit, using Vite bundler",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@vitejs/plugin-react": "^4.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^5.2.2",
"vite": "^5.0.0"
}
}
JavaScript
Copy
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
import { useState, useEffect } from 'react'
import './App.css'
import ItemGrid from './components/ItemGrid'
import TableSelection from './components/TableSelection'
import Cart from './components/Cart'
import { categories, items, tables } from './data'
export default function App() {
const [selectedTable, setSelectedTable] = useState(null)
const [tableOrders, setTableOrders] = useState({})
const [activeOrderId, setActiveOrderId] = useState(null)
useEffect(() => {
const savedOrders = localStorage.getItem('tableOrders')
if (savedOrders) {
setTableOrders(JSON.parse(savedOrders))
}
}, [])
useEffect(() => {
localStorage.setItem('tableOrders', JSON.stringify(tableOrders))
}, [tableOrders])
const addToCart = (item) => {
if (!selectedTable) return
const tableId = selectedTable.id
const currentOrders = tableOrders[tableId] || {}
const orderId = activeOrderId || Date.now()
setTableOrders(prev => ({
...prev,
[tableId]: {
...currentOrders,
[orderId]: [...(currentOrders[orderId] || []), {...item, id: Date.now()}]
}
}))
if (!activeOrderId) {
setActiveOrderId(orderId)
}
}
const removeFromCart = (itemId) => {
if (!selectedTable || !activeOrderId) return
const tableId = selectedTable.id
setTableOrders(prev => ({
...prev,
[tableId]: {
...prev[tableId],
[activeOrderId]: prev[tableId][activeOrderId].filter(item => item.id !== itemId)
}
}))
}
const clearCart = () => {
if (!selectedTable || !activeOrderId) return
const tableId = selectedTable.id
setTableOrders(prev => {
const newOrders = {...prev}
delete newOrders[tableId][activeOrderId]
return newOrders
})
setActiveOrderId(null)
}
const createNewOrder = () => {
if (!selectedTable) return
setActiveOrderId(Date.now())
}
const switchOrder = (orderId) => {
setActiveOrderId(orderId)
}
const currentItems = selectedTable && activeOrderId
? tableOrders[selectedTable.id]?.[activeOrderId] || []
: []
const currentTableOrders = selectedTable
? Object.keys(tableOrders[selectedTable.id] || {})
: []
return (
<main className="pos-container">
<section className="items-section">
<ItemGrid
items={items}
categories={categories}
selectedCategory={'all'}
setSelectedCategory={() => {}}
addToCart={addToCart}
/>
</section>
<section className="tables-section">
<TableSelection
tables={tables}
selectedTable={selectedTable}
setSelectedTable={setSelectedTable}
/>
</section>
<section className="cart-section">
<Cart
items={currentItems}
removeFromCart={removeFromCart}
selectedTable={selectedTable}
clearCart={clearCart}
orders={currentTableOrders}
activeOrderId={activeOrderId}
onSwitchOrder={switchOrder}
onNewOrder={createNewOrder}
/>
</section>
</main>
)
}
/* CSS Reset */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
line-height: 1.5;
}
/* Main Layout */
.pos-container {
display: grid;
grid-template-columns: 2fr 1fr 1fr;
gap: 20px;
padding: 20px;
height: 100vh;
background: #f5f5f5;
}
.items-section,
.tables-section,
.cart-section {
background: #ffffff;
border-radius: 6px;
padding: 24px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
overflow-y: auto;
}
button {
padding: 8px 16px;
border: none;
border-radius: 8px;
cursor: pointer;
background: #007AFF;
color: white;
font-weight: 500;
transition: all 0.2s ease;
font-size: 14px;
}
button:hover {
background: #0063CC;
transform: translateY(-1px);
}
.category-filter {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.items-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 15px;
}
.item-card {
background: #f5f5f7;
padding: 16px;
border-radius: 6px;
text-align: center;
transition: transform 0.2s ease;
}
.item-card:hover {
transform: translateY(-2px);
}
.item-card h3 {
margin-bottom: 8px;
font-size: 16px;
}
.tables-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.table-card {
padding: 16px;
background: #f5f5f7;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
}
.table-card.selected {
background: #E3F2FD;
border-color: #007AFF;
transform: translateY(-2px);
}
.cart-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #eee;
}
.cart-total {
margin-top: 20px;
font-weight: bold;
text-align: right;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: white;
padding: 24px;
border-radius: 6px;
width: 90%;
max-width: 500px;
}
.payment-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
margin: 20px 0;
}
.payment-input {
width: 100%;
padding: 10px;
border: 1px solid #e1e1e1;
border-radius: 6px;
font-size: 14px;
background: #f5f5f7;
}
.payment-summary {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
margin: 20px 0;
}
.payment-label {
font-size: 14px;
color: #666;
margin-bottom: 4px;
}
.pay-button {
width: 100%;
padding: 12px;
margin-top: 20px;
background: #007AFF;
color: white;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: 500;
}
.search-input {
width: 100%;
padding: 10px;
border: 1px solid #e1e1e1;
border-radius: 6px;
margin-bottom: 15px;
font-size: 14px;
background: #f5f5f7;
transition: all 0.2s ease;
}
.search-input:focus {
outline: none;
border-color: #007AFF;
background: white;
}
.room-select {
width: 100%;
padding: 10px;
border: 1px solid #e1e1e1;
border-radius: 6px;
margin-bottom: 15px;
font-size: 14px;
background: #f5f5f7;
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23007AFF%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 8px auto;
}
.room-select:focus {
outline: none;
border-color: #007AFF;
background-color: white;
}
.table-controls {
margin-bottom: 20px;
}
.order-controls {
display: flex;
gap: 8px;
margin-bottom: 20px;
overflow-x: auto;
padding-bottom: 10px;
}
.order-controls button {
flex-shrink: 0;
padding: 6px 12px;
font-size: 13px;
}
.cart-empty {
text-align: center;
color: #666;
margin-top: 20px;
}
import { useState } from 'react'
export default function ItemGrid({ items, categories, selectedCategory, setSelectedCategory, addToCart }) {
const [searchQuery, setSearchQuery] = useState('')
const filteredItems = items
.filter(item => selectedCategory === 'all' || item.category === selectedCategory)
.filter(item => item.name.toLowerCase().includes(searchQuery.toLowerCase()))
return (
<div>
<input
type="search"
placeholder="Search items..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="search-input"
/>
<div className="category-filter">
<button
onClick={() => setSelectedCategory('all')}
style={{ background: selectedCategory === 'all' ? '#357abd' : '#4a90e2' }}
>
All
</button>
{categories.map(category => (
<button
key={category}
onClick={() => setSelectedCategory(category)}
style={{ background: selectedCategory === category ? '#357abd' : '#4a90e2' }}
>
{category}
</button>
))}
</div>
<div className="items-grid">
{filteredItems.map(item => (
<div key={item.id} className="item-card">
<h3>{item.name}</h3>
<p>${item.price.toFixed(2)}</p>
<button onClick={() => addToCart(item)}>Add to Cart</button>
</div>
))}
</div>
</div>
)
}
import { useState } from 'react'
export default function TableSelection({ tables, selectedTable, setSelectedTable }) {
const [searchQuery, setSearchQuery] = useState('')
const [selectedRoom, setSelectedRoom] = useState('all')
const rooms = ['all', ...new Set(tables.map(table => table.room))]
const filteredTables = tables
.filter(table => selectedRoom === 'all' || table.room === selectedRoom)
.filter(table => table.number.toString().includes(searchQuery))
return (
<div>
<h2>Tables</h2>
<div className="table-controls">
<input
type="search"
placeholder="Search table number..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="search-input"
/>
<select
value={selectedRoom}
onChange={(e) => setSelectedRoom(e.target.value)}
className="room-select"
>
{rooms.map(room => (
<option key={room} value={room}>
{room === 'all' ? 'All Rooms' : room}
</option>
))}
</select>
</div>
<div className="tables-grid">
{filteredTables.map(table => (
<div
key={table.id}
className={`table-card ${selectedTable?.id === table.id ? 'selected' : ''}`}
onClick={() => setSelectedTable(table)}
>
<h3>Table {table.number}</h3>
<p>Seats: {table.seats}</p>
<p>Room: {table.room}</p>
</div>
))}
</div>
</div>
)
}
import { useState } from 'react'
export default function Cart({
items,
removeFromCart,
selectedTable,
clearCart,
orders,
activeOrderId,
onSwitchOrder,
onNewOrder
}) {
const [showPayModal, setShowPayModal] = useState(false)
const [payments, setPayments] = useState([])
const [currentPayment, setCurrentPayment] = useState({ method: 'cash', amount: 0 })
const total = items.reduce((sum, item) => sum + item.price, 0)
const totalPaid = payments.reduce((sum, payment) => sum + payment.amount, 0)
const remaining = total - totalPaid
const handleAddPayment = () => {
if (currentPayment.amount > 0) {
setPayments([...payments, currentPayment])
const nextMethod = payments.length === 0 ? 'card' : (payments.length === 1 ? 'upi' : 'cash');
setCurrentPayment({ method: nextMethod, amount: Math.max(0, remaining - currentPayment.amount) });
}
};
const handlePay = () => {
if (totalPaid >= total) {
clearCart()
setPayments([])
setShowPayModal(false)
}
}
const handlePaymentChange = (method, amount) => {
setCurrentPayment({ method, amount: parseFloat(amount) || 0 });
}
if (!selectedTable) {
return <div className="cart-empty">Select a table to start an order</div>
}
return (
<div>
<h2>Table {selectedTable.number}</h2>
<div className="order-controls">
<button onClick={onNewOrder}>New Order</button>
{orders.map(orderId => (
<button
key={orderId}
onClick={() => onSwitchOrder(orderId)}
style={{
background: activeOrderId === orderId ? '#0063CC' : '#f5f5f7',
color: activeOrderId === orderId ? 'white' : '#333',
margin: '5px'
}}
>
Order #{orderId.toString().slice(-4)}
</button>
))}
</div>
{items.map(item => (
<div key={item.id} className="cart-item">
<span>{item.name}</span>
<div>
<span>${item.price.toFixed(2)}</span>
<button
onClick={() => removeFromCart(item.id)}
style={{ marginLeft: '10px', background: '#dc3545' }}
>
×
</button>
</div>
</div>
))}
<div className="cart-total">
Total: ${total.toFixed(2)}
</div>
{items.length > 0 && (
<button
onClick={() => setShowPayModal(true)}
style={{ width: '100%', marginTop: '10px' }}
>
Pay
</button>
)}
{showPayModal && (
<div className="modal">
<div className="modal-content">
<h3>Payment</h3>
<p>Total: ${total.toFixed(2)}</p>
<p>Remaining: ${remaining.toFixed(2)}</p>
<div className="payment-grid">
<div>
<div className="payment-label">Cash</div>
<input
type="number"
className="payment-input"
value={currentPayment.method === 'cash' ? currentPayment.amount : ''}
onChange={(e) => handlePaymentChange('cash', e.target.value)}
placeholder="0"
/>
</div>
<div>
<div className="payment-label">Card</div>
<input
type="number"
className="payment-input"
value={currentPayment.method === 'card' ? currentPayment.amount : ''}
onChange={(e) => handlePaymentChange('card', e.target.value)}
placeholder="0"
/>
</div>
<div>
<div className="payment-label">UPI</div>
<input
type="number"
className="payment-input"
value={currentPayment.method === 'upi' ? currentPayment.amount : ''}
onChange={(e) => handlePaymentChange('upi', e.target.value)}
placeholder="0"
/>
</div>
</div>
<div className="payment-summary">
<div>
<div className="payment-label">Total</div>
<input
type="number"
className="payment-input"
value={total}
disabled
/>
</div>
<div>
<div className="payment-label">Change</div>
<input
type="number"
className="payment-input"
value={Math.max(0, totalPaid - total)}
disabled
/>
</div>
</div>
{payments.length > 0 && (
<div className="payments-list">
{payments.map((payment, index) => (
<div key={index} className="payment-item">
<span>{payment.method}</span>
<span>${payment.amount.toFixed(2)}</span>
</div>
))}
</div>
)}
<div className="modal-actions">
<button onClick={() => setShowPayModal(false)}>Cancel</button>
<button
onClick={handlePay}
disabled={totalPaid < total}
style={{
background: totalPaid < total ? '#ccc' : '#007AFF',
marginLeft: '10px'
}}
>
Complete Payment
</button>
</div>
</div>
</div>
)}
</div>
)
}
export const categories = ['Food', 'Drinks', 'Desserts', 'Appetizers', 'Specials']
export const items = [
{ id: 1, name: 'Burger', price: 12.99, category: 'Food' },
{ id: 2, name: 'Pizza', price: 15.99, category: 'Food' },
{ id: 3, name: 'Salad', price: 8.99, category: 'Food' },
{ id: 4, name: 'Soda', price: 2.99, category: 'Drinks' },
{ id: 5, name: 'Coffee', price: 3.99, category: 'Drinks' },
{ id: 6, name: 'Ice Cream', price: 5.99, category: 'Desserts' },
{ id: 7, name: 'Cake', price: 6.99, category: 'Desserts' },
{ id: 8, name: 'Pasta', price: 13.99, category: 'Food' },
{ id: 9, name: 'Tea', price: 2.99, category: 'Drinks' },
{ id: 10, name: 'Steak', price: 25.99, category: 'Food' },
{ id: 11, name: 'Fish & Chips', price: 16.99, category: 'Food' },
{ id: 12, name: 'Wings', price: 10.99, category: 'Appetizers' },
{ id: 13, name: 'Nachos', price: 9.99, category: 'Appetizers' },
{ id: 14, name: 'Smoothie', price: 4.99, category: 'Drinks' },
{ id: 15, name: 'Tiramisu', price: 7.99, category: 'Desserts' },
{ id: 16, name: 'Chicken Curry', price: 14.99, category: 'Food' },
{ id: 17, name: 'Mojito', price: 8.99, category: 'Drinks' },
{ id: 18, name: 'Cheese Plate', price: 12.99, category: 'Appetizers' },
{ id: 19, name: 'Chef Special', price: 29.99, category: 'Specials' },
{ id: 20, name: 'Seasonal Soup', price: 6.99, category: 'Appetizers' }
]
export const tables = [
{ id: 1, number: 1, seats: 2, room: 'Main Hall' },
{ id: 2, number: 2, seats: 4, room: 'Main Hall' },
{ id: 3, number: 3, seats: 4, room: 'Main Hall' },
{ id: 4, number: 4, seats: 6, room: 'Main Hall' },
{ id: 5, number: 5, seats: 2, room: 'Main Hall' },
{ id: 6, number: 6, seats: 8, room: 'Main Hall' },
{ id: 7, number: 7, seats: 2, room: 'Terrace' },
{ id: 8, number: 8, seats: 4, room: 'Terrace' },
{ id: 9, number: 9, seats: 6, room: 'Terrace' },
{ id: 10, number: 10, seats: 4, room: 'Terrace' },
{ id: 11, number: 11, seats: 2, room: 'Private Room' },
{ id: 12, number: 12, seats: 8, room: 'Private Room' },
{ id: 13, number: 14, seats: 4, room: 'Bar Area' },
{ id: 14, number: 15, seats: 4, room: 'Bar Area' },
{ id: 15, number: 16, seats: 2, room: 'Bar Area' }
]
This markdown file compiles all the components of your application along with the corresponding structure for restaurant pos in react.