Compare commits

..

3 Commits

Author SHA1 Message Date
73a522be60 Merge pull request 'dev' (#2) from dev into main
All checks were successful
Build, Publish Docker Image, and Deploy to Kubernetes / build_and_push (push) Successful in 15s
Build, Publish Docker Image, and Deploy to Kubernetes / deploy_to_k8s (push) Successful in 12s
Reviewed-on: #2
2025-06-16 06:58:26 +00:00
Jonas Hinterdorfer
19a433a420 added new design 2025-06-16 08:57:51 +02:00
Jonas Hinterdorfer
f8dd36e91d implemented color 2025-06-16 08:49:07 +02:00
8 changed files with 173 additions and 131 deletions

View File

@ -1,62 +1,62 @@
.cart-overview { .cart-overview {
padding: 15px; padding: 8px 4px;
} }
.cart-summary { .cart-summary {
background-color: #f5f5f5; background-color: #f7f9fc;
border-radius: 8px; border-radius: 6px;
padding: 15px; padding: 12px;
margin-bottom: 20px; margin-bottom: 16px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
} }
.count-info { .count-info {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-bottom: 10px; margin-bottom: 8px;
} }
.food-count, .drink-count { .food-count, .drink-count {
font-weight: 500; font-weight: 500;
font-size: 0.9rem;
} }
.total-price { .total-price {
font-weight: bold; font-weight: bold;
font-size: 1.2rem; font-size: 1.1rem;
text-align: center; text-align: center;
margin-top: 10px; margin-top: 8px;
color: #2c7be5; color: #2c7be5;
} }
.clear-cart-button {
width: 100%;
padding: 12px;
margin-bottom: 15px;
background-color: #ff5252;
color: white;
border: none;
border-radius: 8px;
font-weight: bold;
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 { .cart-actions {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 10px;
margin-bottom: 20px; margin-bottom: 16px;
} }
.calculate-change-button, .clear-cart-button {
width: 100%;
padding: 10px;
border: none;
border-radius: 6px;
font-weight: 600;
font-size: 0.95rem;
cursor: pointer;
transition: background-color 0.2s;
}
.calculate-change-button {
background-color: #2c7be5;
color: white;
}
.clear-cart-button {
background-color: #ff5252;
color: white;
}
.calculate-change-button:active { .calculate-change-button:active {
background-color: #1b5eb5; background-color: #1b5eb5;
} }
@ -68,49 +68,51 @@
.cart-items { .cart-items {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 15px; gap: 8px;
} }
.cart-item { .cart-item {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
background-color: white; background-color: #f7f9fc;
padding: 15px; padding: 12px;
border-radius: 8px; border-radius: 6px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
} }
.item-info { .item-info {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 5px; gap: 3px;
flex: 1; flex: 1;
} }
.item-name { .item-name {
font-weight: 500; font-weight: 500;
font-size: 0.95rem;
} }
.item-option { .item-option {
font-size: 0.9rem; font-size: 0.85rem;
color: #666; color: #666;
} }
.item-price { .item-price {
font-weight: bold; font-weight: bold;
color: #2c7be5; color: #2c7be5;
font-size: 0.9rem;
} }
.item-quantity { .item-quantity {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 8px;
} }
.item-quantity button { .item-quantity button {
width: 30px; width: 26px;
height: 30px; height: 26px;
border: none; border: none;
border-radius: 50%; border-radius: 50%;
background-color: #2c7be5; background-color: #2c7be5;
@ -126,7 +128,8 @@
.empty-cart { .empty-cart {
text-align: center; text-align: center;
padding: 30px; padding: 20px;
color: #666; color: #888;
font-style: italic; font-style: italic;
font-size: 0.9rem;
} }

View File

@ -15,6 +15,9 @@ const ProductDetails: React.FC<ProductDetailsProps> = ({ product, category }) =>
const { addToCart } = useCart(); const { addToCart } = useCart();
const navigate = useNavigate(); const navigate = useNavigate();
// Check if all options have the same price
const hasSamePrice = product.options.every(option => option.price === product.options[0].price);
const handleAddToCart = () => { const handleAddToCart = () => {
addToCart({ addToCart({
productId: product.id, productId: product.id,
@ -26,14 +29,10 @@ const ProductDetails: React.FC<ProductDetailsProps> = ({ product, category }) =>
navigate('/'); navigate('/');
}; };
const incrementQuantity = () => setQuantity(prev => prev + 1);
const decrementQuantity = () => setQuantity(prev => Math.max(1, prev - 1));
// Check if all options have the same price
const hasSamePrice = product.options.every(option => option.price === product.options[0].price);
return ( return (
<div className="product-details"> <div
className="product-details"
>
<h2>{product.name}</h2> <h2>{product.name}</h2>
<div className="options-list"> <div className="options-list">
@ -43,24 +42,24 @@ const ProductDetails: React.FC<ProductDetailsProps> = ({ product, category }) =>
key={option.name} key={option.name}
className={`option-item ${selectedOption.name === option.name ? 'selected' : ''}`} className={`option-item ${selectedOption.name === option.name ? 'selected' : ''}`}
onClick={() => setSelectedOption(option)} onClick={() => setSelectedOption(option)}
style={(option.color || product.color) ?
{ backgroundColor: `${option.color || product.color}60` } : {}}
> >
<span>{option.name}</span> <span>{option.name}</span>
{!hasSamePrice && <span className="option-price">{option.price.toFixed(2)} </span>} {!hasSamePrice && <span className="option-price">{option.price.toFixed(2)} </span>}
</div> </div>
))} ))}
{hasSamePrice && ( {hasSamePrice && (
<div className="uniform-price"> <div className="uniform-price">Preis: {product.options[0].price.toFixed(2)} </div>
Preis: {product.options[0].price.toFixed(2)}
</div>
)} )}
</div> </div>
<div className="quantity-controls"> <div className="quantity-controls">
<h3>Menge:</h3> <h3>Menge:</h3>
<div className="quantity-buttons"> <div className="quantity-buttons">
<button onClick={decrementQuantity}>-</button> <button onClick={() => setQuantity(prev => Math.max(1, prev - 1))}>-</button>
<span>{quantity}</span> <span>{quantity}</span>
<button onClick={incrementQuantity}>+</button> <button onClick={() => setQuantity(prev => prev + 1)}>+</button>
</div> </div>
</div> </div>

View File

@ -1,34 +1,35 @@
.product-list { .product-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 8px;
padding: 10px; padding: 6px;
} }
.product-item { .product-item {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 15px; padding: 14px;
background-color: #fff; background-color: #fff;
border-radius: 8px; border-radius: 6px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
text-decoration: none; text-decoration: none;
color: #333; color: #333;
transition: transform 0.2s, box-shadow 0.2s; transition: transform 0.2s, box-shadow 0.2s;
} }
.product-item:active { .product-item:active {
transform: scale(0.98); transform: scale(0.99);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
} }
.product-name { .product-name {
font-weight: 500; font-weight: 500;
font-size: 1.1rem; font-size: 1rem;
} }
.product-price { .product-price {
font-weight: bold; font-weight: 600;
color: #2c7be5; color: #2c7be5;
font-size: 1.1rem; font-size: 0.95rem;
} }

View File

@ -21,6 +21,7 @@ const ProductList: React.FC<ProductListProps> = ({ products, category }) => {
to={`/product/${category}/${product.id}`} to={`/product/${category}/${product.id}`}
key={product.id} key={product.id}
className="product-item" className="product-item"
style={product.color ? { backgroundColor: `${product.color}80` } : {}}
> >
<div className="product-name">{product.name}</div> <div className="product-name">{product.name}</div>
<div className="product-price">{product.basePrice.toFixed(2)} </div> <div className="product-price">{product.basePrice.toFixed(2)} </div>

View File

@ -4,9 +4,10 @@
"id": "f1", "id": "f1",
"name": "Hendl", "name": "Hendl",
"basePrice": 9.50, "basePrice": 9.50,
"color": "#FFEBCD",
"options": [ "options": [
{"name": "Halbes Hendl", "price": 9.50}, {"name": "Halbes Hendl", "price": 9.50},
{"name": "Ganzes Hendl", "price": 16.00}, {"name": "Ganzes Hendl", "price": 16.00, "color": "#FFDAB9"},
{"name": "Hendl mit Semmel", "price": 11.00} {"name": "Hendl mit Semmel", "price": 11.00}
] ]
}, },
@ -14,16 +15,18 @@
"id": "f2", "id": "f2",
"name": "Schnitzelsemmel", "name": "Schnitzelsemmel",
"basePrice": 5.80, "basePrice": 5.80,
"color": "#FFEFC1",
"options": [ "options": [
{"name": "Klassisch", "price": 5.80}, {"name": "Klassisch", "price": 5.80},
{"name": "Mit Salat", "price": 6.50}, {"name": "Mit Salat", "price": 6.50, "color": "#E8F5E9"},
{"name": "Mit Käse", "price": 6.80} {"name": "Mit Käse", "price": 6.80, "color": "#FFF9C4"}
] ]
}, },
{ {
"id": "f3", "id": "f3",
"name": "Pommes", "name": "Pommes",
"basePrice": 3.50, "basePrice": 3.50,
"color": "#FFD700",
"options": [ "options": [
{"name": "Klein", "price": 3.50}, {"name": "Klein", "price": 3.50},
{"name": "Groß", "price": 4.50}, {"name": "Groß", "price": 4.50},
@ -34,6 +37,7 @@
"id": "f4", "id": "f4",
"name": "Grillwurst", "name": "Grillwurst",
"basePrice": 4.20, "basePrice": 4.20,
"color": "#FFCCCB",
"options": [ "options": [
{"name": "Bratwurst", "price": 4.20}, {"name": "Bratwurst", "price": 4.20},
{"name": "Käsekrainer", "price": 4.50}, {"name": "Käsekrainer", "price": 4.50},
@ -44,6 +48,7 @@
"id": "f5", "id": "f5",
"name": "Leberkässemmel", "name": "Leberkässemmel",
"basePrice": 4.00, "basePrice": 4.00,
"color": "#FFE4E1",
"options": [ "options": [
{"name": "Klassisch", "price": 4.00}, {"name": "Klassisch", "price": 4.00},
{"name": "Mit Gurkerl", "price": 4.30}, {"name": "Mit Gurkerl", "price": 4.30},
@ -54,6 +59,7 @@
"id": "f6", "id": "f6",
"name": "Langos", "name": "Langos",
"basePrice": 4.80, "basePrice": 4.80,
"color": "#DEB887",
"options": [ "options": [
{"name": "Mit Knoblauch", "price": 4.80}, {"name": "Mit Knoblauch", "price": 4.80},
{"name": "Mit Käse", "price": 5.50}, {"name": "Mit Käse", "price": 5.50},
@ -66,16 +72,18 @@
"id": "d1", "id": "d1",
"name": "Bier", "name": "Bier",
"basePrice": 4.20, "basePrice": 4.20,
"color": "#F0E68C",
"options": [ "options": [
{"name": "Krügerl (0,5L)", "price": 4.20}, {"name": "Krügerl (0,5L)", "price": 4.20, "color": "#F5DEB3"},
{"name": "Seidl (0,3L)", "price": 3.50}, {"name": "Seidl (0,3L)", "price": 3.50},
{"name": "Radler (0,5L)", "price": 4.20} {"name": "Radler (0,5L)", "price": 4.20, "color": "#FAFAD2"}
] ]
}, },
{ {
"id": "d2", "id": "d2",
"name": "Cola", "name": "Cola",
"basePrice": 3.50, "basePrice": 3.50,
"color": "#D2B48C",
"options": [ "options": [
{"name": "Cola Rot (0,5L)", "price": 3.50}, {"name": "Cola Rot (0,5L)", "price": 3.50},
{"name": "Cola Weiß (0,5L)", "price": 3.50}, {"name": "Cola Weiß (0,5L)", "price": 3.50},
@ -87,6 +95,7 @@
"id": "d3", "id": "d3",
"name": "Softdrinks", "name": "Softdrinks",
"basePrice": 3.50, "basePrice": 3.50,
"color": "#FFDAB9",
"options": [ "options": [
{"name": "Fanta (0,5L)", "price": 3.50}, {"name": "Fanta (0,5L)", "price": 3.50},
{"name": "Sprite (0,5L)", "price": 3.50}, {"name": "Sprite (0,5L)", "price": 3.50},
@ -98,6 +107,7 @@
"id": "d4", "id": "d4",
"name": "Mineral", "name": "Mineral",
"basePrice": 2.80, "basePrice": 2.80,
"color": "#E0FFFF",
"options": [ "options": [
{"name": "Prickelnd (0,5L)", "price": 2.80}, {"name": "Prickelnd (0,5L)", "price": 2.80},
{"name": "Still (0,5L)", "price": 2.80}, {"name": "Still (0,5L)", "price": 2.80},
@ -109,6 +119,7 @@
"id": "d5", "id": "d5",
"name": "Gespritzt", "name": "Gespritzt",
"basePrice": 3.00, "basePrice": 3.00,
"color": "#FFDAB9",
"options": [ "options": [
{"name": "Apfelsaft g'spritzt", "price": 3.00}, {"name": "Apfelsaft g'spritzt", "price": 3.00},
{"name": "Johannisbeer g'spritzt", "price": 3.20}, {"name": "Johannisbeer g'spritzt", "price": 3.20},
@ -120,6 +131,7 @@
"id": "d6", "id": "d6",
"name": "Spritzer", "name": "Spritzer",
"basePrice": 3.80, "basePrice": 3.80,
"color": "#FFE4E1",
"options": [ "options": [
{"name": "Weißer Spritzer", "price": 3.80}, {"name": "Weißer Spritzer", "price": 3.80},
{"name": "Aperol Spritzer", "price": 4.50}, {"name": "Aperol Spritzer", "price": 4.50},
@ -131,6 +143,7 @@
"id": "d7", "id": "d7",
"name": "Schnaps", "name": "Schnaps",
"basePrice": 3.00, "basePrice": 3.00,
"color": "#E6E6FA",
"options": [ "options": [
{"name": "Marille (2cl)", "price": 3.00}, {"name": "Marille (2cl)", "price": 3.00},
{"name": "Williams (2cl)", "price": 3.00}, {"name": "Williams (2cl)", "price": 3.00},

View File

@ -1,37 +1,61 @@
.home-page { .home-page {
padding-bottom: 20px; display: flex;
flex-direction: column;
min-height: 100vh;
background-color: #f9fafb;
} }
.app-header { .app-header {
background-color: #2c7be5; background-color: #2c7be5;
color: white; color: white;
text-align: center; text-align: center;
padding: 15px; padding: 12px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
} }
.app-header h1 { .app-header h1 {
margin: 0; margin: 0;
font-size: 1.5rem; font-size: 1.4rem;
font-weight: 600;
}
.main-container {
flex: 1;
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px;
max-width: 600px;
margin: 0 auto;
width: 100%;
}
.section-title {
margin: 8px 0;
font-size: 1.2rem;
color: #333;
font-weight: 500;
padding-left: 8px;
border-left: 3px solid #2c7be5;
} }
.category-tabs { .category-tabs {
display: flex; display: flex;
margin: 10px;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
margin-bottom: 4px;
} }
.category-tabs button { .category-tabs button {
flex: 1; flex: 1;
padding: 15px; padding: 12px;
background-color: #f5f5f5; background-color: #f5f5f5;
border: none; border: none;
font-size: 1.1rem; font-size: 1rem;
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s; transition: all 0.2s ease;
} }
.category-tabs button.active { .category-tabs button.active {
@ -39,14 +63,22 @@
color: white; color: white;
} }
.products-section, .cart-section { .products-container {
margin: 20px 0; background-color: white;
border-radius: 10px;
padding: 12px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
} }
.products-section h2, .cart-section h2 { .cart-container {
margin: 0 10px 10px 10px; background-color: white;
font-size: 1.3rem; border-radius: 10px;
color: #333; padding: 12px;
padding-left: 10px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
border-left: 4px solid #2c7be5; }
@media (min-width: 768px) {
.main-container {
max-width: 800px;
}
} }

View File

@ -9,23 +9,11 @@ const HomePage: React.FC = () => {
const [activeTab, setActiveTab] = useState<'food' | 'drinks'>('drinks'); const [activeTab, setActiveTab] = useState<'food' | 'drinks'>('drinks');
useEffect(() => { useEffect(() => {
const loadProducts = async () => { import('../data/products.json')
try { .then(data => {
const data = await import('../data/products.json'); setProductsData(data);
})
// Sort both food and drinks by name .catch(error => console.error('Failed to load products:', error));
const sortedData = {
food: [...data.food].sort((a, b) => a.name.localeCompare(b.name)),
drinks: [...data.drinks].sort((a, b) => a.name.localeCompare(b.name))
};
setProductsData(sortedData);
} catch (error) {
console.error('Failed to load products:', error);
}
};
loadProducts();
}, []); }, []);
return ( return (
@ -34,12 +22,14 @@ const HomePage: React.FC = () => {
<h1>Preis Rechner</h1> <h1>Preis Rechner</h1>
</header> </header>
<div className="main-container">
<div className="products-container">
<div className="category-tabs"> <div className="category-tabs">
<button <button
className={activeTab === 'drinks' ? 'active' : ''} className={activeTab === 'drinks' ? 'active' : ''}
onClick={() => setActiveTab('drinks')} onClick={() => setActiveTab('drinks')}
> >
Trinken Getränke
</button> </button>
<button <button
className={activeTab === 'food' ? 'active' : ''} className={activeTab === 'food' ? 'active' : ''}
@ -47,20 +37,21 @@ const HomePage: React.FC = () => {
> >
Essen Essen
</button> </button>
</div> </div>
<div className="products-section"> <h2 className="section-title">{activeTab === 'food' ? 'Essen' : 'Getränke'}</h2>
<h2>{activeTab === 'food' ? 'Essen' : 'Getränke'}</h2> <ProductList
{activeTab === 'food' && <ProductList products={productsData.food} category="food" />} products={productsData[activeTab]}
{activeTab === 'drinks' && <ProductList products={productsData.drinks} category="drinks" />} category={activeTab}
/>
</div> </div>
<div className="cart-section"> <div className="cart-container">
<h2>Warenkorb</h2> <h2 className="section-title">Warenkorb</h2>
<CartOverview /> <CartOverview />
</div> </div>
</div> </div>
</div>
); );
}; };

View File

@ -1,6 +1,7 @@
export type ProductOption = { export type ProductOption = {
name: string; name: string;
price: number; price: number;
color?: string; // Optional color for this specific option
}; };
export type Product = { export type Product = {
@ -8,6 +9,7 @@ export type Product = {
name: string; name: string;
basePrice: number; basePrice: number;
options: ProductOption[]; options: ProductOption[];
color?: string; // Optional color code for background highlight
}; };
export type ProductsData = { export type ProductsData = {