Merge pull request 'dev' (#1) from dev into main
All checks were successful
Build, Publish Docker Image, and Deploy to Kubernetes / build_and_push (push) Successful in 18s
Build, Publish Docker Image, and Deploy to Kubernetes / deploy_to_k8s (push) Successful in 17s

Reviewed-on: #1
This commit is contained in:
jonas 2025-06-16 05:58:32 +00:00
commit e390f78926
9 changed files with 329 additions and 16 deletions

BIN
public/Scheunenfest.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

View File

@ -7,7 +7,7 @@
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
content="Ein Rechner für Kellner vom Scheunenfest"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
@ -24,7 +24,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<title>Kellner Rechner</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@ -1,6 +1,6 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"short_name": "Scheunenfestrechner",
"name": "Kellnerrechner für das Scheunenfest",
"icons": [
{
"src": "favicon.ico",

View File

@ -2,6 +2,7 @@ import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import HomePage from './pages/HomePage';
import ProductDetailsPage from './pages/ProductDetailsPage';
import ChangeCalculatorPage from './pages/ChangeCalculatorPage';
import { CartProvider } from './contexts/CartContext';
import './App.css';
@ -13,6 +14,7 @@ function App() {
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/product/:category/:productId" element={<ProductDetailsPage />} />
<Route path="/change-calculator" element={<ChangeCalculatorPage />} />
</Routes>
</div>
</Router>

View File

@ -40,6 +40,26 @@
cursor: pointer;
transition: background-color 0.2s;
}
.calculate-change-button {
width: 100%;
padding: 12px;
background-color: #2c7be5;
color: white;
border: none;
border-radius: 8px;
font-weight: bold;
cursor: pointer;
transition: background-color 0.2s;
}
.cart-actions {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 20px;
}
.calculate-change-button:active {
background-color: #1b5eb5;
}
.clear-cart-button:active {
background-color: #e53935;

View File

@ -1,8 +1,10 @@
import React from 'react';
import { useCart } from '../contexts/CartContext';
import { useNavigate } from 'react-router-dom';
import './CartOverview.css';
const CartOverview: React.FC = () => {
const navigate = useNavigate(); // <-- Move here
const {
cartItems,
incrementQuantity,
@ -27,9 +29,21 @@ const CartOverview: React.FC = () => {
<div className="total-price">Gesamtpreis: {getTotalPrice().toFixed(2)} </div>
</div>
<button className="clear-cart-button" onClick={clearCart}>
Warenkorb löschen
</button>
<div className="cart-actions">
<button
className="calculate-change-button"
onClick={() => navigate('/change-calculator')}
>
Restgeld berechnen
</button>
<button
className="clear-cart-button"
onClick={clearCart}
>
Warenkorb leeren
</button>
</div>
<div className="cart-items">
{cartItems.map((item) => (

View File

@ -0,0 +1,120 @@
.change-calculator-page {
height: 100vh;
display: flex;
flex-direction: column;
}
.calculator-container {
padding: 15px;
flex: 1;
display: flex;
flex-direction: column;
gap: 20px;
}
.totals-section {
background-color: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.total-item {
display: flex;
justify-content: space-between;
padding: 8px 0;
font-size: 1.1rem;
border-bottom: 1px solid #eee;
}
.total-item:last-child {
border-bottom: none;
}
.total-item.highlight {
font-weight: bold;
font-size: 1.2rem;
color: #2c7be5;
}
.denominations-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.denomination-button {
background-color: #2c7be5;
color: white;
border: none;
border-radius: 8px;
padding: 15px 5px;
font-size: 1.1rem;
font-weight: 500;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: background-color 0.2s;
}
.denomination-button:active {
background-color: #1a68d1;
}
.selected-denominations {
background-color: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.selected-denominations h3 {
margin-top: 0;
margin-bottom: 15px;
font-size: 1.1rem;
color: #333;
padding-left: 10px;
border-left: 4px solid #2c7be5;
}
.selected-denominations ul {
list-style: none;
padding: 0;
margin: 0;
}
.denomination-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #eee;
}
.denomination-item:last-child {
border-bottom: none;
}
.item-controls {
display: flex;
gap: 5px;
}
.item-controls button {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f5f5;
border: 1px solid #ddd;
border-radius: 4px;
}
.reset-button {
width: 100%;
padding: 10px;
margin-top: 15px;
background-color: #f5f5f5;
border: 1px solid #ddd;
border-radius: 4px;
font-weight: 500;
}

View File

@ -0,0 +1,147 @@
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useCart } from '../contexts/CartContext';
import './ChangeCalculatorPage.css';
interface MoneyItem {
value: number;
label: string;
count: number;
}
const ChangeCalculatorPage: React.FC = () => {
const navigate = useNavigate();
const { getTotalPrice, clearCart } = useCart();
const cartTotal = getTotalPrice();
const initialDenominations: MoneyItem[] = [
{ value: 0.5, label: '0,50 €', count: 0 },
{ value: 1, label: '1 €', count: 0 },
{ value: 2, label: '2 €', count: 0 },
{ value: 5, label: '5 €', count: 0 },
{ value: 10, label: '10 €', count: 0 },
{ value: 20, label: '20 €', count: 0 },
{ value: 50, label: '50 €', count: 0 },
{ value: 100, label: '100 €', count: 0 },
{ value: 200, label: '200 €', count: 0 },
];
const [moneyItems, setMoneyItems] = useState<MoneyItem[]>(initialDenominations);
const [customerTotal, setCustomerTotal] = useState<number>(0);
const [changeAmount, setChangeAmount] = useState<number>(0);
// Calculate totals whenever money items change
useEffect(() => {
const total = moneyItems.reduce((sum, item) => sum + item.value * item.count, 0);
setCustomerTotal(total);
setChangeAmount(Math.max(0, total - cartTotal));
}, [moneyItems, cartTotal]);
const handleDenominationClick = (index: number) => {
const updatedItems = [...moneyItems];
updatedItems[index].count += 1;
setMoneyItems(updatedItems);
};
const resetDenomination = (index: number) => {
const updatedItems = [...moneyItems];
updatedItems[index].count = 0;
setMoneyItems(updatedItems);
};
const decreaseDenomination = (index: number) => {
if (moneyItems[index].count > 0) {
const updatedItems = [...moneyItems];
updatedItems[index].count -= 1;
setMoneyItems(updatedItems);
}
};
const resetAllDenominations = () => {
setMoneyItems(initialDenominations.map(item => ({ ...item, count: 0 })));
};
const handleDoneClick = () => {
clearCart();
navigate('/');
};
return (
<div className="change-calculator-page">
<header className="details-header">
<button className="back-button" onClick={() => navigate('/')}>
&larr; Zurück
</button>
<h1>Restgeld</h1>
<button className="done-button" onClick={handleDoneClick}>
Fertig
</button>
</header>
<div className="calculator-container">
<div className="totals-section">
<div className="total-item">
<span>Warenkorb:</span>
<span>{cartTotal.toFixed(2)} </span>
</div>
<div className="total-item">
<span>Erhalten:</span>
<span>{customerTotal.toFixed(2)} </span>
</div>
<div className="total-item highlight">
<span>Restgeld:</span>
<span>{changeAmount.toFixed(2)} </span>
</div>
</div>
<div className="denominations-grid">
{moneyItems.map((item, index) => (
<button
key={item.value}
className="denomination-button"
onClick={() => handleDenominationClick(index)}
>
{item.label}
</button>
))}
</div>
<div className="selected-denominations">
<h3>Ausgewählte Münzen/Scheine</h3>
{moneyItems.some(item => item.count > 0) ? (
<ul>
{moneyItems
.filter(item => item.count > 0)
.map((item, index) => (
<li key={item.value} className="denomination-item">
<span>{item.count}x {item.label}</span>
<div className="item-controls">
<button onClick={() => decreaseDenomination(
moneyItems.findIndex(i => i.value === item.value)
)}>-</button>
<button onClick={() => resetDenomination(
moneyItems.findIndex(i => i.value === item.value)
)}>×</button>
</div>
</li>
))}
</ul>
) : (
<p>Keine Auswahl</p>
)}
{moneyItems.some(item => item.count > 0) && (
<button
className="reset-button"
onClick={resetAllDenominations}
>
Alle zurücksetzen
</button>
)}
</div>
</div>
</div>
);
};
export default ChangeCalculatorPage;

View File

@ -25,6 +25,16 @@
left: 15px;
}
.done-button {
background: none;
border: none;
color: white;
font-size: 1.2rem;
cursor: pointer;
padding: 0;
position: absolute;
right: 15px;
}
.details-header h1 {
flex: 1;
text-align: center;