Replace submodules with full folder contents
This commit is contained in:
@ -0,0 +1,173 @@
|
||||
<div class="vending-container" *ngIf="!error">
|
||||
<div class="header">
|
||||
<button class="logout-button" (click)="logout()">Logout</button>
|
||||
<button class="cart-button" (click)="toggleCart()">
|
||||
<span class="cart-icon">🛒</span>
|
||||
<span class="cart-count" *ngIf="cart.length > 0">{{ cart.length }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Vending Machine Grid -->
|
||||
@for (row of ['A', 'B', 'C', 'D', 'E', 'F']; track row) {
|
||||
<div class="vending-row">
|
||||
<div class="row-title">
|
||||
<span>Row {{ row }}</span>
|
||||
<div class="scroll-hint">
|
||||
<span>Scroll</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slots-container">
|
||||
@for (col of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; track col) {
|
||||
<div class="slot-card" [ngClass]="{'disabled': !getSlot(row, col)?.enabled}">
|
||||
<div class="slot-header">
|
||||
<span class="slot-name">{{ row + col }}</span>
|
||||
</div>
|
||||
<div class="slot-body">
|
||||
@if (getSlot(row, col)?.product_id) {
|
||||
<div class="product-image-container">
|
||||
<img *ngIf="getSlot(row, col)?.product_image"
|
||||
[src]="'http://localhost:5001/' + getSlot(row, col)?.product_image"
|
||||
[attr.alt]="getSlot(row, col)?.product_name" class="product-image">
|
||||
</div>
|
||||
|
||||
<div class="product-details">
|
||||
<p class="product-name">{{ getSlot(row, col)?.product_name }}</p>
|
||||
|
||||
<!-- Price and Quantity Selector -->
|
||||
<div class="price-quantity-row">
|
||||
<p class="product-price">{{ getSlot(row, col)?.price }} ₹</p>
|
||||
|
||||
<div class="quantity-selector"
|
||||
*ngIf="getSlot(row, col)?.enabled && getSlot(row, col)?.units && getSlot(row, col)!.units > 0">
|
||||
<button class="quantity-btn minus-btn" (click)="decrementQuantity(row, col)"
|
||||
[disabled]="selectedQuantities[row + col] <= 1">
|
||||
<span class="minus-icon">-</span>
|
||||
</button>
|
||||
|
||||
<span class="quantity-display">{{ selectedQuantities[row + col] || 1 }}</span>
|
||||
|
||||
<button class="quantity-btn plus-btn" (click)="incrementQuantity(row, col)"
|
||||
[disabled]="selectedQuantities[row + col] >= getSlot(row, col)!.units">
|
||||
<span class="plus-icon">+</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="product-actions">
|
||||
<button class="add-to-cart-btn"
|
||||
[disabled]="!getSlot(row, col)?.enabled || !getSlot(row, col)?.units || getSlot(row, col)!.units <= 0"
|
||||
(click)="addToCart(getSlot(row, col)!, row + col)">
|
||||
Add to Cart
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="empty-slot">
|
||||
<p class="add-product-text">No Product Assigned</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="scroll-shadows"></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Shopping Cart Sidebar -->
|
||||
<div class="shopping-cart" [ngClass]="{'open': cartOpen}">
|
||||
<div class="cart-header">
|
||||
<h2>Shopping Cart</h2>
|
||||
<button class="close-cart" (click)="toggleCart()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="cart-items">
|
||||
@if (cart.length === 0) {
|
||||
<p class="empty-cart-message">Your cart is empty</p>
|
||||
} @else {
|
||||
@for (item of cart; track item; let i = $index) {
|
||||
<div class="cart-item">
|
||||
<div class="cart-item-image">
|
||||
<img *ngIf="item.productImage" [src]="'http://localhost:5001/' + item.productImage"
|
||||
[attr.alt]="item.productName">
|
||||
</div>
|
||||
<div class="cart-item-details">
|
||||
<p class="cart-item-name">{{ item.productName }}</p>
|
||||
<div class="cart-item-controls">
|
||||
<button class="quantity-btn" (click)="updateCartItemQuantity(item, item.quantity - 1)"
|
||||
[disabled]="item.quantity <= 1">-</button>
|
||||
<span class="quantity">{{ item.quantity }}</span>
|
||||
<button class="quantity-btn" (click)="updateCartItemQuantity(item, item.quantity + 1)"
|
||||
[disabled]="item.quantity >= (getSlot(item.slotId[0], +item.slotId.substring(1))?.units || 0)">+</button>
|
||||
</div>
|
||||
<p class="cart-item-price">₹{{ item.price.toFixed(2) }} × {{ item.quantity }} = ₹{{ item.totalPrice.toFixed(2) }}</p>
|
||||
</div>
|
||||
<button class="remove-item" (click)="removeFromCart(i)">×</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="cart-footer" *ngIf="cart.length > 0">
|
||||
<div class="cart-total">
|
||||
<span>Total:</span>
|
||||
<span class="total-price">₹{{ totalCartPrice.toFixed(2) }}</span>
|
||||
</div>
|
||||
<button class="checkout-btn" (click)="proceedToPayment()">
|
||||
Proceed to Payment
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cart Overlay -->
|
||||
<div class="cart-overlay" *ngIf="cartOpen" (click)="toggleCart()"></div>
|
||||
|
||||
<!-- Payment Confirmation Dialog -->
|
||||
<div class="payment-dialog-overlay" *ngIf="showPaymentDialog">
|
||||
<div class="payment-dialog">
|
||||
<div class="dialog-header">
|
||||
<h2>Confirm Payment</h2>
|
||||
<button class="close-dialog" (click)="closePaymentDialog()" [disabled]="processingTransaction">×</button>
|
||||
</div>
|
||||
|
||||
<div class="dialog-body">
|
||||
<div class="order-summary">
|
||||
<h3>Order Summary</h3>
|
||||
<div class="summary-items">
|
||||
@for (item of paymentDialogData?.cart; track item) {
|
||||
<div class="summary-item">
|
||||
<div class="summary-item-left">
|
||||
<span class="item-name">{{ item.productName }}</span>
|
||||
<span class="item-quantity">Qty: {{ item.quantity }}</span>
|
||||
</div>
|
||||
<span class="item-price">₹{{ item.totalPrice.toFixed(2) }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="summary-total">
|
||||
<span class="total-label">Total Amount:</span>
|
||||
<span class="total-amount">₹{{ formattedTotalAmount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialog-footer">
|
||||
<button
|
||||
class="dialog-btn cancel-btn"
|
||||
(click)="handlePaymentCancel()"
|
||||
[disabled]="processingTransaction">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="dialog-btn paid-btn"
|
||||
(click)="handlePaymentPaid()"
|
||||
[disabled]="processingTransaction">
|
||||
{{ processingTransaction ? 'Processing...' : 'Paid' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="error" class="error">{{ error }}</div>
|
||||
<p *ngIf="loading">Loading...</p>
|
||||
@ -0,0 +1,777 @@
|
||||
//machine slot.scss
|
||||
.vending-container {
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.logout-button, .cart-button {
|
||||
padding: 8px 16px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #0069d9;
|
||||
}
|
||||
}
|
||||
|
||||
.cart-button {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.cart-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.cart-count {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -8px;
|
||||
background-color: red;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.vending-row {
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.row-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
background: #e9ecef;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.scroll-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.slots-container {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
padding: 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.slot-card {
|
||||
min-width: 180px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.slot-card.disabled {
|
||||
background: #f9f9f9;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.slot-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.slot-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.status-text.enabled {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.status-text.disabled {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.slot-body {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.product-image-container {
|
||||
margin-bottom: 10px;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
max-width: 100px;
|
||||
max-height: 100px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.product-details {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-weight: bold;
|
||||
margin: 0 0 5px;
|
||||
}
|
||||
|
||||
.product-stats {
|
||||
margin: 0 0 10px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.units-display {
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.product-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.quantity-selector {
|
||||
margin-bottom: 5px;
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
}
|
||||
|
||||
.add-to-cart-btn {
|
||||
padding: 5px 10px;
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: #6c757d;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.65;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-slot {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.add-product-text {
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.scroll-shadows {
|
||||
height: 10px;
|
||||
background: linear-gradient(to right, rgba(0,0,0,0.1), transparent);
|
||||
}
|
||||
|
||||
/* Shopping Cart Styles */
|
||||
.shopping-cart {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: -400px;
|
||||
width: 350px;
|
||||
height: 100%;
|
||||
background-color: white;
|
||||
box-shadow: -2px 0 5px rgba(0,0,0,0.2);
|
||||
transition: right 0.3s ease-in-out;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.shopping-cart.open {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.cart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.close-cart {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: #6c757d;
|
||||
|
||||
&:hover {
|
||||
color: #343a40;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cart-items {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.empty-cart-message {
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.cart-item {
|
||||
display: flex;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.cart-item-image {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.cart-item-details {
|
||||
flex: 1;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.cart-item-name {
|
||||
font-weight: bold;
|
||||
margin: 0 0 5px;
|
||||
}
|
||||
|
||||
.cart-item-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.quantity-btn {
|
||||
background-color: #e9ecef;
|
||||
border: 1px solid #ddd;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.quantity {
|
||||
margin: 0 10px;
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cart-item-price {
|
||||
margin: 0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.remove-item {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 18px;
|
||||
color: #dc3545;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #c82333;
|
||||
}
|
||||
}
|
||||
|
||||
.cart-footer {
|
||||
padding: 15px;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.cart-total {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.total-price {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.checkout-btn {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
|
||||
&:hover {
|
||||
background-color: #0069d9;
|
||||
}
|
||||
}
|
||||
|
||||
.cart-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
z-index: 999;
|
||||
}
|
||||
// Add/Update these styles in your machine-slots.component.scss file
|
||||
|
||||
.price-quantity-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
flex-shrink: 0; // Prevents price from shrinking
|
||||
}
|
||||
|
||||
// Update your existing quantity-selector styles
|
||||
.quantity-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px; // Reduced gap for tighter layout
|
||||
font-family: Arial, sans-serif;
|
||||
flex-shrink: 0; // Prevents quantity selector from shrinking
|
||||
}
|
||||
|
||||
.quantity-btn {
|
||||
width: 28px; // Slightly smaller for better fit
|
||||
height: 28px;
|
||||
border: 2px solid #ddd;
|
||||
background-color: white;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 16px; // Slightly smaller font
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: #f5f5f5;
|
||||
border-color: #bbb;
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: #f9f9f9;
|
||||
border-color: #e5e5e5;
|
||||
color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.quantity-display {
|
||||
font-size: 14px; // Smaller font for compact layout
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
padding: 0 6px; // Reduced padding
|
||||
}
|
||||
|
||||
.minus-icon,
|
||||
.plus-icon {
|
||||
line-height: 1;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
// Make sure the product-actions only contains the button now
|
||||
.product-actions {
|
||||
margin-top: 8px;
|
||||
|
||||
.add-to-cart-btn {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: #bdc3c7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
PAYMENT DIALOG
|
||||
============================================ */
|
||||
|
||||
.payment-dialog-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 99999;
|
||||
padding: 20px;
|
||||
animation: fadeIn 0.2s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.payment-dialog {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
|
||||
animation: slideUp 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.close-dialog {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 32px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
|
||||
&:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-body {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.order-summary {
|
||||
h3 {
|
||||
margin: 0 0 16px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.summary-items {
|
||||
border-top: 1px solid #e0e0e0;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.summary-item-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
font-size: 15px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.item-quantity {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.item-price {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.summary-total {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 0 0;
|
||||
margin-top: 12px;
|
||||
border-top: 2px solid #333;
|
||||
|
||||
.total-label {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.total-amount {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #4CAF50;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 20px 24px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.dialog-btn {
|
||||
flex: 1;
|
||||
padding: 14px 24px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
}
|
||||
|
||||
.paid-btn {
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: #45a049;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark mode support */
|
||||
.dark .payment-dialog {
|
||||
background: #1e293b;
|
||||
|
||||
.dialog-header {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
||||
|
||||
h2 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.close-dialog {
|
||||
color: #999;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.summary-items {
|
||||
border-top-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.05);
|
||||
|
||||
.item-name {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.item-quantity {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.item-price {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.summary-total {
|
||||
border-top-color: rgba(255, 255, 255, 0.2);
|
||||
|
||||
.total-label {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
border-top-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 575px) {
|
||||
.payment-dialog {
|
||||
max-width: 95vw;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
padding: 16px 20px;
|
||||
|
||||
h2 {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.summary-total {
|
||||
.total-label {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.total-amount {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
.dialog-btn {
|
||||
padding: 12px 20px;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MachineSlotsComponent } from './machine-slots.component';
|
||||
|
||||
describe('MachineSlotsComponent', () => {
|
||||
let component: MachineSlotsComponent;
|
||||
let fixture: ComponentFixture<MachineSlotsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MachineSlotsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MachineSlotsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,627 @@
|
||||
// src/app/machine-slots/machine-slots.component.ts
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
|
||||
interface Slot {
|
||||
slot_name: string; // row_id (A-F)
|
||||
column: number;
|
||||
enabled: boolean;
|
||||
product_id: string | null;
|
||||
units: number;
|
||||
price: string;
|
||||
product_name: string;
|
||||
product_image: string | null;
|
||||
}
|
||||
|
||||
interface CartItem {
|
||||
slotId: string;
|
||||
productId: string;
|
||||
productName: string;
|
||||
productImage: string | null;
|
||||
quantity: number;
|
||||
price: number;
|
||||
totalPrice: number;
|
||||
}
|
||||
|
||||
interface PaymentDialogData {
|
||||
cart: CartItem[];
|
||||
totalAmount: number;
|
||||
machineId: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-machine-slots',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule],
|
||||
templateUrl: './machine-slots.component.html',
|
||||
styleUrls: ['./machine-slots.component.scss']
|
||||
})
|
||||
export class MachineSlotsComponent implements OnInit {
|
||||
machineId: string | null = null;
|
||||
slots: Slot[] = [];
|
||||
loading = false;
|
||||
error = '';
|
||||
cartOpen = false;
|
||||
cart: CartItem[] = [];
|
||||
selectedQuantities: { [key: string]: number } = {};
|
||||
totalCartPrice = 0;
|
||||
|
||||
// Payment dialog properties
|
||||
showPaymentDialog = false;
|
||||
paymentDialogData: PaymentDialogData | null = null;
|
||||
processingTransaction = false;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private http: HttpClient,
|
||||
private authService: AuthService,
|
||||
private router: Router
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.machineId = this.route.snapshot.paramMap.get('id') || this.authService.getLoggedInMachineId();
|
||||
console.log('Using machineId:', this.machineId);
|
||||
|
||||
if (this.machineId) {
|
||||
this.loadMachineSlots();
|
||||
} else {
|
||||
this.error = 'Machine ID not found';
|
||||
}
|
||||
}
|
||||
|
||||
loadMachineSlots() {
|
||||
this.loading = true;
|
||||
this.http.get<Slot[]>(`${environment.apiUrl}/machine-slots/${this.machineId}`).subscribe(
|
||||
(data) => {
|
||||
this.slots = data;
|
||||
this.slots.forEach(slot => {
|
||||
if (slot.product_id) {
|
||||
this.selectedQuantities[`${slot.slot_name}${slot.column}`] = 1;
|
||||
}
|
||||
});
|
||||
this.loading = false;
|
||||
},
|
||||
(error) => {
|
||||
this.error = 'Failed to load machine slots';
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getSlot(row: string, column: number): Slot | undefined {
|
||||
return this.slots.find(s => s.slot_name === row && s.column === column);
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.authService.logout();
|
||||
this.router.navigate(['/login']);
|
||||
}
|
||||
|
||||
toggleCart() {
|
||||
this.cartOpen = !this.cartOpen;
|
||||
}
|
||||
|
||||
addToCart(slot: Slot, slotId: string) {
|
||||
if (!slot.enabled || !slot.product_id || slot.units <= 0) return;
|
||||
|
||||
const quantity = this.selectedQuantities[slotId];
|
||||
if (!quantity || quantity <= 0 || quantity > slot.units) return;
|
||||
|
||||
const existingItemIndex = this.cart.findIndex(item => item.slotId === slotId);
|
||||
|
||||
if (existingItemIndex !== -1) {
|
||||
this.cart[existingItemIndex].quantity = quantity;
|
||||
this.cart[existingItemIndex].totalPrice = quantity * parseFloat(slot.price);
|
||||
} else {
|
||||
this.cart.push({
|
||||
slotId,
|
||||
productId: slot.product_id,
|
||||
productName: slot.product_name,
|
||||
productImage: slot.product_image,
|
||||
quantity,
|
||||
price: parseFloat(slot.price),
|
||||
totalPrice: quantity * parseFloat(slot.price)
|
||||
});
|
||||
}
|
||||
|
||||
this.calculateTotalCartPrice();
|
||||
this.debugCart();
|
||||
}
|
||||
|
||||
removeFromCart(index: number) {
|
||||
this.cart.splice(index, 1);
|
||||
this.calculateTotalCartPrice();
|
||||
}
|
||||
|
||||
updateCartItemQuantity(item: CartItem, quantity: number) {
|
||||
if (quantity > 0) {
|
||||
const slot = this.slots.find(s => s.slot_name + s.column === item.slotId);
|
||||
if (slot && quantity <= slot.units) {
|
||||
item.quantity = quantity;
|
||||
item.totalPrice = quantity * item.price;
|
||||
this.calculateTotalCartPrice();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
calculateTotalCartPrice() {
|
||||
this.totalCartPrice = this.cart.reduce((total, item) => total + item.totalPrice, 0);
|
||||
}
|
||||
|
||||
incrementQuantity(row: string, col: number): void {
|
||||
const slot = this.getSlot(row, col);
|
||||
if (!slot || !slot.units) return;
|
||||
|
||||
const key = `${row}${col}`;
|
||||
const currentQuantity = this.selectedQuantities[key] || 1;
|
||||
|
||||
if (currentQuantity < slot.units) {
|
||||
this.selectedQuantities[key] = currentQuantity + 1;
|
||||
}
|
||||
}
|
||||
|
||||
decrementQuantity(row: string, col: number): void {
|
||||
const key = `${row}${col}`;
|
||||
const currentQuantity = this.selectedQuantities[key] || 1;
|
||||
|
||||
if (currentQuantity > 1) {
|
||||
this.selectedQuantities[key] = currentQuantity - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Simple payment dialog approach
|
||||
proceedToPayment() {
|
||||
console.log('=== PROCEED TO PAYMENT CLICKED ===');
|
||||
console.log('Machine ID:', this.machineId);
|
||||
console.log('Cart:', this.cart);
|
||||
console.log('Cart length:', this.cart.length);
|
||||
|
||||
if (!this.machineId || this.cart.length === 0) {
|
||||
console.log('BLOCKED: Missing machine ID or empty cart');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Opening payment dialog...');
|
||||
this.paymentDialogData = {
|
||||
cart: [...this.cart],
|
||||
totalAmount: this.totalCartPrice,
|
||||
machineId: this.machineId
|
||||
};
|
||||
|
||||
console.log('Payment dialog data:', this.paymentDialogData);
|
||||
this.showPaymentDialog = true;
|
||||
console.log('showPaymentDialog set to:', this.showPaymentDialog);
|
||||
}
|
||||
|
||||
closePaymentDialog() {
|
||||
this.showPaymentDialog = false;
|
||||
this.paymentDialogData = null;
|
||||
}
|
||||
|
||||
handlePaymentPaid() {
|
||||
if (!this.paymentDialogData || this.processingTransaction) return;
|
||||
|
||||
this.processingTransaction = true;
|
||||
|
||||
// Prepare inventory update data
|
||||
const inventoryUpdates = this.paymentDialogData.cart.map(item => ({
|
||||
slotId: item.slotId,
|
||||
quantityDispensed: item.quantity
|
||||
}));
|
||||
|
||||
const updateData = {
|
||||
machineId: this.machineId,
|
||||
inventoryUpdates: inventoryUpdates
|
||||
};
|
||||
|
||||
// STEP 1: Update inventory FIRST
|
||||
this.http.post(`${environment.apiUrl}/machine-slots/update-inventory`, updateData).subscribe({
|
||||
next: (inventoryResponse: any) => {
|
||||
console.log('✓ Server inventory updated successfully:', inventoryResponse);
|
||||
|
||||
// STEP 2: Now create transactions (only if inventory update succeeded)
|
||||
const transactionData = this.paymentDialogData!.cart.map(item => ({
|
||||
machine_id: this.paymentDialogData!.machineId,
|
||||
product_name: item.productName,
|
||||
quantity: item.quantity,
|
||||
amount: item.totalPrice,
|
||||
payment_type: 'cash',
|
||||
status: 'Success'
|
||||
}));
|
||||
|
||||
this.http.post(`${environment.apiUrl}/transactions/bulk-create`, {
|
||||
transactions: transactionData
|
||||
}).subscribe({
|
||||
next: (transactionResponse: any) => {
|
||||
console.log('✓ Transactions created successfully:', transactionResponse);
|
||||
|
||||
// STEP 3: Update local inventory
|
||||
this.updateLocalInventory(this.paymentDialogData!.cart);
|
||||
|
||||
// STEP 4: Clear cart and close dialog
|
||||
this.clearCart();
|
||||
this.closePaymentDialog();
|
||||
|
||||
// STEP 5: Show success message
|
||||
alert('Payment successful! Items dispensed.');
|
||||
|
||||
// STEP 6: Reload slots to sync with server
|
||||
this.loadMachineSlots();
|
||||
|
||||
this.processingTransaction = false;
|
||||
},
|
||||
error: (transactionError) => {
|
||||
console.error('✗ Error creating transactions:', transactionError);
|
||||
this.error = 'Inventory updated but transaction recording failed';
|
||||
this.processingTransaction = false;
|
||||
|
||||
// Still reload to show updated inventory
|
||||
this.loadMachineSlots();
|
||||
}
|
||||
});
|
||||
},
|
||||
error: (inventoryError) => {
|
||||
console.error('✗ Failed to update server inventory:', inventoryError);
|
||||
this.error = 'Failed to update inventory. Payment not processed.';
|
||||
this.processingTransaction = false;
|
||||
|
||||
// Don't create transactions if inventory update failed
|
||||
// This prevents selling items that couldn't be deducted from inventory
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handlePaymentCancel() {
|
||||
this.closePaymentDialog();
|
||||
}
|
||||
|
||||
private updateLocalInventory(paidCart: CartItem[]) {
|
||||
paidCart.forEach(cartItem => {
|
||||
const slot = this.slots.find(s =>
|
||||
s.slot_name + s.column === cartItem.slotId
|
||||
);
|
||||
|
||||
if (slot && slot.units >= cartItem.quantity) {
|
||||
slot.units -= cartItem.quantity;
|
||||
|
||||
if (slot.units <= 0) {
|
||||
slot.enabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update selectedQuantities
|
||||
this.slots.forEach(slot => {
|
||||
if (slot.product_id && slot.units > 0) {
|
||||
this.selectedQuantities[`${slot.slot_name}${slot.column}`] = 1;
|
||||
} else {
|
||||
delete this.selectedQuantities[`${slot.slot_name}${slot.column}`];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private showSuccessMessage(message: string) {
|
||||
alert(message);
|
||||
}
|
||||
|
||||
clearCart() {
|
||||
this.cart = [];
|
||||
this.totalCartPrice = 0;
|
||||
}
|
||||
|
||||
generateQuantityOptions(slot: Slot): number[] {
|
||||
const options: number[] = [];
|
||||
for (let i = 1; i <= slot.units; i++) options.push(i);
|
||||
return options;
|
||||
}
|
||||
|
||||
getUnitStatusClass(units: number): string {
|
||||
if (units <= 0) return 'empty';
|
||||
if (units <= 3) return 'low-stock';
|
||||
return '';
|
||||
}
|
||||
|
||||
get formattedTotalAmount(): string {
|
||||
return this.paymentDialogData?.totalAmount !== undefined
|
||||
? this.paymentDialogData.totalAmount.toFixed(2)
|
||||
: '0.00';
|
||||
}
|
||||
|
||||
debugCart() {
|
||||
console.log('Cart state:');
|
||||
console.log('- Length:', this.cart.length);
|
||||
console.log('- Items:', this.cart);
|
||||
console.log('- Total:', this.totalCartPrice);
|
||||
console.log('- Machine ID:', this.machineId);
|
||||
}
|
||||
|
||||
|
||||
/* ========================================
|
||||
PAYU PAYMENT GATEWAY CODE (HIDDEN FOR NOW)
|
||||
========================================
|
||||
|
||||
// Uncomment this section when you want to use PayU payment gateway
|
||||
|
||||
interface PayUResponse {
|
||||
key: string;
|
||||
txnid: string;
|
||||
amount: string;
|
||||
productinfo: string;
|
||||
firstname: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
surl: string;
|
||||
furl: string;
|
||||
hash: string;
|
||||
service_provider?: string;
|
||||
udf1?: string;
|
||||
udf2?: string;
|
||||
udf3?: string;
|
||||
udf4?: string;
|
||||
udf5?: string;
|
||||
}
|
||||
|
||||
showingPaymentMessage = false;
|
||||
paymentSuccessMessage = false;
|
||||
paymentFailureMessage = false;
|
||||
|
||||
proceedToPaymentWithPayU() {
|
||||
if (!this.machineId || this.cart.length === 0 || this.processingTransaction) return;
|
||||
|
||||
this.processingTransaction = true;
|
||||
this.error = '';
|
||||
|
||||
const dispensingInstructions = this.cart.map(item => ({
|
||||
slotId: item.slotId,
|
||||
rowId: item.slotId[0],
|
||||
column: parseInt(item.slotId.substring(1)),
|
||||
quantity: item.quantity
|
||||
}));
|
||||
|
||||
const orderData = {
|
||||
machineId: this.machineId,
|
||||
dispensingInstructions,
|
||||
totalAmount: this.totalCartPrice.toFixed(2),
|
||||
productInfo: this.cart.map(item => `${item.productName} x ${item.quantity}`).join(', '),
|
||||
cartData: JSON.stringify(this.cart),
|
||||
userDetails: {
|
||||
firstname: 'Customer',
|
||||
email: 'customer@example.com',
|
||||
phone: '1234567890'
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
sessionStorage.setItem('pendingCart', JSON.stringify(this.cart));
|
||||
sessionStorage.setItem('pendingMachineId', this.machineId);
|
||||
}
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
};
|
||||
|
||||
this.http.post<PayUResponse>(`${environment.apiUrl}/create-payu-order`, orderData, { headers }).subscribe({
|
||||
next: (payuData) => {
|
||||
if (!payuData || !payuData.key || !payuData.hash || !payuData.amount) {
|
||||
this.error = 'Invalid payment data received. Please try again.';
|
||||
this.processingTransaction = false;
|
||||
return;
|
||||
}
|
||||
this.redirectToPayU(payuData);
|
||||
},
|
||||
error: (error) => {
|
||||
if (error.status === 0) {
|
||||
this.error = 'Unable to connect to payment service. Please check your connection.';
|
||||
} else if (error.status === 404) {
|
||||
this.error = 'Payment service not found. Please contact support.';
|
||||
} else if (error.status >= 500) {
|
||||
this.error = 'Payment service is temporarily unavailable. Please try again.';
|
||||
} else {
|
||||
this.error = error.error?.error || 'There was a problem creating the payment order. Please try again.';
|
||||
}
|
||||
this.processingTransaction = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private redirectToPayU(payuData: PayUResponse) {
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = environment.payuUrl || 'https://test.payu.in/_payment';
|
||||
form.target = '_blank';
|
||||
form.style.display = 'none';
|
||||
|
||||
const fields = [
|
||||
{ name: 'key', value: payuData.key || '' },
|
||||
{ name: 'txnid', value: payuData.txnid || '' },
|
||||
{ name: 'amount', value: payuData.amount || '' },
|
||||
{ name: 'productinfo', value: payuData.productinfo || '' },
|
||||
{ name: 'firstname', value: payuData.firstname || '' },
|
||||
{ name: 'email', value: payuData.email || '' },
|
||||
{ name: 'phone', value: payuData.phone || '' },
|
||||
{ name: 'surl', value: payuData.surl || '' },
|
||||
{ name: 'furl', value: payuData.furl || '' },
|
||||
{ name: 'hash', value: typeof payuData.hash === 'string' ? payuData.hash : String(payuData.hash) },
|
||||
{ name: 'udf1', value: payuData.udf1 || this.machineId || '' },
|
||||
{ name: 'udf2', value: payuData.udf2 || JSON.stringify(this.cart.map(item => ({
|
||||
slotId: item.slotId,
|
||||
quantity: item.quantity,
|
||||
productName: item.productName
|
||||
})))
|
||||
},
|
||||
{ name: 'udf3', value: payuData.udf3 || '' },
|
||||
{ name: 'udf4', value: payuData.udf4 || '' },
|
||||
{ name: 'udf5', value: payuData.udf5 || '' }
|
||||
];
|
||||
|
||||
if (payuData.service_provider) {
|
||||
fields.push({ name: 'service_provider', value: payuData.service_provider });
|
||||
}
|
||||
|
||||
fields.forEach(field => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = field.name;
|
||||
input.value = field.value;
|
||||
form.appendChild(input);
|
||||
});
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
|
||||
setTimeout(() => {
|
||||
if (document.body.contains(form)) {
|
||||
document.body.removeChild(form);
|
||||
}
|
||||
this.processingTransaction = false;
|
||||
}, 100);
|
||||
|
||||
this.showPaymentTabMessage();
|
||||
}
|
||||
|
||||
private showPaymentTabMessage() {
|
||||
this.showingPaymentMessage = true;
|
||||
setTimeout(() => {
|
||||
this.showingPaymentMessage = false;
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
private checkForPaymentSuccess() {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const paymentStatus = urlParams.get('status');
|
||||
const txnid = urlParams.get('txnid');
|
||||
|
||||
if (paymentStatus === 'success' && txnid) {
|
||||
this.handlePaymentSuccess();
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.delete('status');
|
||||
url.searchParams.delete('txnid');
|
||||
url.searchParams.delete('amount');
|
||||
window.history.replaceState({}, document.title, url.toString());
|
||||
} else if (paymentStatus === 'failure') {
|
||||
this.showPaymentFailureMessage();
|
||||
}
|
||||
}
|
||||
|
||||
handlePaymentSuccess() {
|
||||
if (typeof window !== 'undefined') {
|
||||
const pendingCart = sessionStorage.getItem('pendingCart');
|
||||
const pendingMachineId = sessionStorage.getItem('pendingMachineId');
|
||||
|
||||
if (pendingCart && pendingMachineId && pendingMachineId === this.machineId) {
|
||||
try {
|
||||
const cartToProcess: CartItem[] = JSON.parse(pendingCart);
|
||||
this.processSuccessfulPayment(cartToProcess);
|
||||
} catch (e) {
|
||||
console.error('Error parsing pending cart for payment success:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private processSuccessfulPayment(paidCart: CartItem[]) {
|
||||
paidCart.forEach(cartItem => {
|
||||
const slot = this.slots.find(s =>
|
||||
s.slot_name + s.column === cartItem.slotId
|
||||
);
|
||||
|
||||
if (slot && slot.units >= cartItem.quantity) {
|
||||
slot.units -= cartItem.quantity;
|
||||
if (slot.units <= 0) {
|
||||
slot.enabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.clearCart();
|
||||
|
||||
this.slots.forEach(slot => {
|
||||
if (slot.product_id && slot.units > 0) {
|
||||
this.selectedQuantities[`${slot.slot_name}${slot.column}`] = 1;
|
||||
} else {
|
||||
delete this.selectedQuantities[`${slot.slot_name}${slot.column}`];
|
||||
}
|
||||
});
|
||||
|
||||
this.updateServerInventory(paidCart);
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
sessionStorage.removeItem('pendingCart');
|
||||
sessionStorage.removeItem('pendingMachineId');
|
||||
}
|
||||
|
||||
this.showPaymentSuccessMessage();
|
||||
}
|
||||
|
||||
private updateServerInventory(paidCart: CartItem[]) {
|
||||
const inventoryUpdates = paidCart.map(item => ({
|
||||
slotId: item.slotId,
|
||||
rowId: item.slotId[0],
|
||||
column: parseInt(item.slotId.substring(1)),
|
||||
quantityDispensed: item.quantity
|
||||
}));
|
||||
|
||||
const updateData = {
|
||||
machineId: this.machineId,
|
||||
inventoryUpdates: inventoryUpdates
|
||||
};
|
||||
|
||||
this.http.post(`${environment.apiUrl}/update-inventory`, updateData).subscribe({
|
||||
next: (response) => {
|
||||
console.log('Server inventory updated successfully:', response);
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Failed to update server inventory:', error);
|
||||
this.loadMachineSlots();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private showPaymentSuccessMessage() {
|
||||
this.paymentSuccessMessage = true;
|
||||
setTimeout(() => {
|
||||
this.paymentSuccessMessage = false;
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
private showPaymentFailureMessage() {
|
||||
this.paymentFailureMessage = true;
|
||||
setTimeout(() => {
|
||||
this.paymentFailureMessage = false;
|
||||
}, 8000);
|
||||
}
|
||||
|
||||
restoreCartFromSession() {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const pendingCart = sessionStorage.getItem('pendingCart');
|
||||
const pendingMachineId = sessionStorage.getItem('pendingMachineId');
|
||||
|
||||
if (pendingCart && pendingMachineId && pendingMachineId === this.machineId) {
|
||||
try {
|
||||
this.cart = JSON.parse(pendingCart);
|
||||
this.calculateTotalCartPrice();
|
||||
|
||||
this.cart.forEach(item => {
|
||||
this.selectedQuantities[item.slotId] = item.quantity;
|
||||
});
|
||||
|
||||
sessionStorage.removeItem('pendingCart');
|
||||
sessionStorage.removeItem('pendingMachineId');
|
||||
} catch (e) {
|
||||
console.error('Error parsing pending cart:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
======================================== */
|
||||
}
|
||||
Reference in New Issue
Block a user