Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,423 changes: 4,271 additions & 152 deletions typescript/package-lock.json

Large diffs are not rendered by default.

17 changes: 15 additions & 2 deletions typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,34 @@
"scripts": {
"test": "mocha --require ts-node/register --recursive test/**/*.test.ts",
"watch:test": "npm run test -- --watch-extensions ts --watch",
"compile": "tsc"
"compile": "tsc",
"start": "webpack serve --mode development --open",
"build": "webpack --mode production"
},
"author": "Johan Martinsson",
"license": "ISC",
"devDependencies": {
"@types/chai": "^4.1.7",
"@types/mocha": "^10.0.6",
"@types/react": "^19.2.8",
"@types/react-dom": "^19.2.3",
"approvals": "^6.2.4",
"chai": "^4.4.1",
"mocha": "^10.3.0"
"css-loader": "^7.1.2",
"html-webpack-plugin": "^5.6.5",
"mocha": "^10.3.0",
"style-loader": "^4.0.0",
"ts-loader": "^9.5.4",
"webpack": "^5.104.1",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.3"
},
"dependencies": {
"@types/lodash": "^4.14.119",
"@types/node": "^20.11.25",
"lodash": "^4.17.19",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"ts-node": "^10.9.2",
"typescript": "^5.4.2"
}
Expand Down
11 changes: 11 additions & 0 deletions typescript/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Supermarket Receipt</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
20 changes: 20 additions & 0 deletions typescript/src/ui/InMemoryCatalog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { SupermarketCatalog } from '../model/SupermarketCatalog';
import { Product } from '../model/Product';

export class InMemoryCatalog implements SupermarketCatalog {
private products: Map<string, Product> = new Map();
private prices: Map<string, number> = new Map();

addProduct(product: Product, price: number): void {
this.products.set(product.name, product);
this.prices.set(product.name, price);
}

getUnitPrice(product: Product): number {
return this.prices.get(product.name) || 0;
}

getAllProducts(): Product[] {
return Array.from(this.products.values());
}
}
282 changes: 282 additions & 0 deletions typescript/src/ui/components/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}

body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #f5f5f5;
}

.app {
min-height: 100vh;
}

.app-header {
background-color: #2c3e50;
color: white;
padding: 1.5rem;
text-align: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.app-header h1 {
margin: 0;
font-size: 2rem;
}

.app-content {
display: flex;
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
gap: 2rem;
flex-wrap: wrap;
}

.left-panel {
flex: 1;
min-width: 300px;
}

.right-panel {
flex: 1;
min-width: 300px;
}

/* Product Catalog */
.product-catalog {
background: white;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

.product-catalog h2 {
margin-bottom: 1rem;
color: #2c3e50;
}

.special-offers-info {
background-color: #fff3cd;
border: 1px solid #ffc107;
border-radius: 6px;
padding: 1rem;
margin-bottom: 1.5rem;
}

.special-offers-info h3 {
color: #856404;
margin-bottom: 0.5rem;
font-size: 1rem;
}

.special-offers-info ul {
list-style: none;
padding: 0;
}

.special-offers-info li {
padding: 0.25rem 0;
color: #856404;
font-size: 0.9rem;
}

.products-list {
display: flex;
flex-direction: column;
gap: 1rem;
}

.product-item {
border: 1px solid #e0e0e0;
border-radius: 6px;
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
transition: box-shadow 0.2s;
}

.product-item:hover {
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}

.product-info h3 {
margin-bottom: 0.5rem;
color: #2c3e50;
font-size: 1.1rem;
}

.product-info .price {
color: #27ae60;
font-weight: bold;
font-size: 1.1rem;
}

.product-actions {
display: flex;
gap: 0.5rem;
align-items: center;
}

.quantity-input {
width: 80px;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}

.add-button {
background-color: #3498db;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.2s;
}

.add-button:hover {
background-color: #2980b9;
}

/* Cart */
.cart {
background: white;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
margin-bottom: 2rem;
}

.cart h2 {
margin-bottom: 1rem;
color: #2c3e50;
}

.empty-cart {
text-align: center;
color: #95a5a6;
padding: 2rem;
}

.cart-items {
margin-bottom: 1rem;
}

.cart-item {
padding: 1rem;
border-bottom: 1px solid #ecf0f1;
}

.cart-item:last-child {
border-bottom: none;
}

.cart-item-name {
font-weight: bold;
margin-bottom: 0.5rem;
color: #2c3e50;
}

.cart-item-details {
display: flex;
justify-content: space-between;
color: #7f8c8d;
font-size: 0.9rem;
}

.cart-item-total {
font-weight: bold;
color: #2c3e50;
}

.cart-subtotal {
padding: 1rem;
border-top: 2px solid #ecf0f1;
text-align: right;
font-size: 1.1rem;
}

.cart-actions {
display: flex;
gap: 1rem;
margin-top: 1rem;
}

.checkout-button,
.clear-button {
flex: 1;
padding: 0.75rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
font-weight: bold;
transition: background-color 0.2s;
}

.checkout-button {
background-color: #27ae60;
color: white;
}

.checkout-button:hover {
background-color: #229954;
}

.clear-button {
background-color: #e74c3c;
color: white;
}

.clear-button:hover {
background-color: #c0392b;
}

/* Receipt */
.receipt-display {
background: white;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

.receipt-display h2 {
margin-bottom: 1rem;
color: #2c3e50;
}

.receipt-content {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 1.5rem;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
white-space: pre;
overflow-x: auto;
line-height: 1.6;
}

@media (max-width: 768px) {
.app-content {
flex-direction: column;
}

.product-actions {
flex-direction: column;
}

.quantity-input {
width: 100%;
}
}
Loading