Implement order creation and cancellation

This commit is contained in:
Rene Vergara 2021-10-27 15:21:55 -05:00
parent af8ee5dc90
commit dce8da391e
19 changed files with 228 additions and 36 deletions

View file

@ -287,7 +287,35 @@ app.post('/api/order', (req, res, next) => {
const order = new ordermodel(req.body.order);
order.save();
res.status(200).json({
message: 'Order added'
message: 'Order added',
order: order
});
});
app.post('/api/lineitem', (req, res, next) => {
console.log('Post /api/lineitem');
ordermodel.findByIdAndUpdate(req.body.order_id, { $push: {lines: req.body.line}}, function(err,docs) {
if (err) {
console.log(err);
} else {
res.status(200).json({
message: 'Item added to order'
});
}
});
});
app.delete('/api/order/:id', (req, res, next) => {
console.log('delete order endpoint', req.params.id);
ordermodel.findByIdAndDelete(req.params.id, function (err, docs) {
if (err) {
console.log(err);
} else {
console.log(docs);
res.status(200).json({
message: 'Order deleted'
});
}
});
});

View file

@ -7,7 +7,7 @@ const orderSchema = mongoose.Schema({
closed: { type: Boolean, required: true, default:false },
lines: [{
qty: {type: Number, required: true, default: 1},
item: { type: String, required: true},
name: { type: String, required: true},
cost: { type: Number, required: true, default: 0}
}]
});

View file

@ -20,6 +20,7 @@ import { ItemCreateComponent } from './items/item-create/item-create.component';
import { ItemDeleteComponent } from './items/item-delete/item-delete.component';
import { ItemAddComponent} from './items/item-add/item-add.component';
import { OrderComponent } from './order/order.component';
import { CancelComponent } from './cancel/cancel.component';
//import { NameDialogComponent } from './namedialog/namedialog.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@ -33,7 +34,8 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
OrderComponent,
ItemCreateComponent,
ItemDeleteComponent,
ItemAddComponent
ItemAddComponent,
CancelComponent
//NameDialogComponent,
],
imports: [
@ -56,6 +58,6 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
],
providers: [],
bootstrap: [AppComponent],
entryComponents: [ItemCreateComponent, ItemDeleteComponent, ItemAddComponent]
entryComponents: [ItemCreateComponent, ItemDeleteComponent, ItemAddComponent, CancelComponent]
})
export class AppModule { }

View file

@ -0,0 +1,3 @@
.text {
font-family: "Roboto-Mono", monospace;
}

View file

@ -0,0 +1,24 @@
<h2 mat-dialog-title class="text">Cancel Order</h2>
<mat-dialog-content>
<p class="text">Are you sure you want to cancel the order?</p>
</mat-dialog-content>
<mat-dialog-actions>
<table cellspacing="0" width="100%">
<tr>
<td>
<button mat-raised-button color="primary" (click)="confirm()">
Yes
</button>
</td>
<td align="right">
<button mat-raised-button (click)="close()">
No
</button>
</td>
</tr>
</table>
</mat-dialog-actions>

View file

@ -0,0 +1,23 @@
import { Inject, Component, OnInit, ViewEncapsulation} from '@angular/core';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog';
@Component({
selector: 'app-cancel',
templateUrl: './cancel.component.html',
styleUrls: ['./cancel.component.css']
})
export class CancelComponent {
constructor(
private dialogRef: MatDialogRef<CancelComponent>
) {}
confirm() {
this.dialogRef.close(true);
}
close() {
this.dialogRef.close(false);
}
}

View file

View file

View file

View file

@ -1,3 +1,7 @@
.text {
font-family: "Roboto Mono", monospace;
}
input[type=number]{
text-align: right;
}

View file

@ -3,7 +3,7 @@
<mat-dialog-content [formGroup]="orderForm">
<p class="text">{{lineItem.name}}</p>
<mat-form-field>
<input matInput placeholder="Quantity" formControlName="qty">
<input matInput type="number" placeholder="Quantity" formControlName="qty">
</mat-form-field>
</mat-dialog-content>

View file

@ -1,3 +1,7 @@
.text {
font-family: 'Roboto-Mono', monospace;
}
input[type=number]{
text-align: right;
}

View file

@ -2,6 +2,12 @@
font-family: 'Roboto-Mono', monospace;
}
.text {
font-family: 'Roboto-Mono', monospace;
}
.small {
font-size: 75%;
}
.icons {
font-family: 'Material Icons';
}
@ -11,7 +17,7 @@
}
img.icon{
margin-bottom: -1px;
margin-bottom: -3px;
}
div.card{

View file

@ -1,20 +1,18 @@
<div *ngIf="items.length > 0">
<div class="card" *ngFor="let item of itemsUpdate | async">
<mat-card>
<mat-card-title class="card">
<table cellspacing="0" width="100%">
<table cellspacing="0" width="100%" class="text">
<tr>
<td>{{item.name}}</td>
<td align="right">
<p class="price">{{item.cost | currency: 'USD'}}</p>
<p class="price"><img class="icon" src="/assets/zec.png" width="12px" />{{(item.cost/price) | number: '1.0-6'}}</p>
<p class="price"><img class="icon" src="/assets/zec-roboto.png" width="10px" />{{(item.cost/price) | number: '1.0-6'}}</p>
</td>
</tr>
</table>
</mat-card-title>
<mat-card-subtitle class="card">
<p>{{item.description}}</p>
</mat-card-subtitle>
<div align="center">
<p class="text small">{{item.description}}</p>
</div>
<mat-card-actions>
<table cellspacing="0" width="100%">
<tr>

View file

@ -1,3 +1,7 @@
.text {
font-family: 'Roboto Mono', monospace;
font-family: "Roboto-Mono", monospace;
}
img.icon{
margin-bottom: -3px;
}

View file

@ -1,13 +1,41 @@
<p *ngIf="order.address.length == 0">No open order!</p>
<mat-card class="text" *ngIf="order.address.length > 0">
<table>
<tr>
<th>Qty.</th>
<th>Items</th>
</tr>
<tr *ngFor="let item of order.lines">
<td>{{item.qty}}</td>
<td>{{item.name}}</td>
</tr>
</table>
<div align="center">
<mat-card-title>
{{order._id}}
<table cellspacing="0" width="100%">
<tr>
<td>Order Total:</td>
<td align="right">
<p class="text">{{total | currency: 'USD'}}</p>
<p class="text"><img class="icon" src="/assets/zec-roboto.png" width="14px" />{{(total/price) | number: '1.0-6'}}</p>
</td>
</tr>
</table>
</mat-card-title>
<table>
<tr>
<th>Qty.</th>
<th>Items</th>
</tr>
<tr *ngFor="let item of order.lines">
<td align="right">{{item.qty}}</td>
<td>{{item.name}}</td>
</tr>
</table>
</div>
<mat-card-actions>
<table cellspacing="0" width="100%">
<tr>
<td>
<button mat-raised-button class="text" (click)="cancelOrder()">Cancel</button>
</td>
<td align="right">
<button mat-raised-button class="text" color="primary">Checkout</button>
</td>
</tr>
</table>
</mat-card-actions>
</mat-card>

View file

@ -1,9 +1,11 @@
import { Component, OnInit } from '@angular/core';
import { MatDialog, MatDialogConfig} from '@angular/material/dialog';
import { Observable } from 'rxjs';
import { Order } from './order.model';
import { FullnodeService } from '../fullnode.service';
import { UserService } from '../user.service';
import { OrderService } from './order.service';
import { CancelComponent } from '../cancel/cancel.component';
@Component({
selector: 'app-order',
@ -14,20 +16,50 @@ import { OrderService } from './order.service';
export class OrderComponent implements OnInit{
public order: Order = {address: '', session: '', timestamp: '', closed: false, lines: [{qty: 1, name: '', cost: 0}]};
public price: number = 1;
public total: number = 0;
public orderUpdate: Observable<Order>;
public priceUpdate: Observable<number>;
public totalUpdate: Observable<number>;
constructor(
public fullnodeService: FullnodeService,
public orderService: OrderService
public orderService: OrderService,
private dialog: MatDialog
) {
this.priceUpdate = fullnodeService.priceUpdate;
this.priceUpdate.subscribe((price) => {
this.price = price;
});
this.orderUpdate = orderService.orderUpdate;
this.orderUpdate.subscribe((order) => {
this.order = order;
});
this.totalUpdate = orderService.totalUpdate;
this.totalUpdate.subscribe((total) => {
this.total = total;
});
}
ngOnInit() {
}
cancelOrder() {
const dialogConfig = new MatDialogConfig();
dialogConfig.disableClose = true;
dialogConfig.autoFocus = true;
const dialogRef = this.dialog.open(CancelComponent, dialogConfig);
dialogRef.afterClosed().subscribe((val) => {
if (val) {
console.log('Canceling');
this.orderService.cancelOrder(this.order._id!).subscribe((response) => {
this.orderService.getOrder();
});;
} else {
console.log('Returning to page');
}
this.orderService.getOrder();
});
}
}

View file

@ -9,7 +9,8 @@ import { LineItem} from '../items/lineitem.model';
@Injectable({providedIn: 'root'})
export class OrderService {
private dataStore: { user: User, order: Order } = {
private dataStore: { total:number, user: User, order: Order } = {
total: 0,
user:{
address: '',
session: '',
@ -31,6 +32,8 @@ export class OrderService {
};
private _orderUpdated: BehaviorSubject<Order> = new BehaviorSubject(this.dataStore.order);
public readonly orderUpdate: Observable<Order> = this._orderUpdated.asObservable();
private _totalUpdated: BehaviorSubject<number> = new BehaviorSubject(this.dataStore.total);
public readonly totalUpdate: Observable<number> = this._totalUpdated.asObservable();
public userUpdate: Observable<User>;
constructor(
@ -41,11 +44,12 @@ export class OrderService {
this.userUpdate.subscribe((user) => {
this.dataStore.user = user;
//console.log('OS: const', user);
this.getOrder(this.dataStore.user.session);
this.getOrder();
});
}
getOrder(session: string) {
getOrder() {
var session = this.dataStore.user.session;
const params = new HttpParams().append('session', session);
let obs = this.http.get<{message: string, order: any}>('http://localhost:3000/api/order', { headers:{}, params:params, observe: 'response'});
@ -53,6 +57,11 @@ export class OrderService {
if (OrderDataResponse.status == 200) {
this.dataStore.order = OrderDataResponse.body!.order;
this._orderUpdated.next(Object.assign({}, this.dataStore).order);
this.dataStore.total = 0;
for(var line of this.dataStore.order.lines) {
this.dataStore.total += line.qty * line.cost;
}
this._totalUpdated.next(Object.assign({}, this.dataStore).total);
} else {
console.log('No order found');
}
@ -62,30 +71,57 @@ export class OrderService {
}
addToOrder(lineItem: LineItem) {
if (this.dataStore.order.address.length == 0) {
console.log('No open order, creating...', lineItem);
this.createOrder();
if(this.dataStore.order._id != null) {
let obs = this.http.post<{message: string}>('http://localhost:3000/api/lineitem', { order_id: this.dataStore.order._id, line: lineItem });
obs.subscribe((orderData) => {
this.getOrder();
});
} else {
console.log('Open order, adding...', lineItem);
this.createOrder(lineItem);
}
}
createOrder() {
createOrder(lineItem: LineItem) {
var order:Order = {
address: this.dataStore.user.address,
session: this.dataStore.user.session,
closed: false,
lines: []
};
let obs = this.http.post<{message: string}>('http://localhost:3000/api/order', {order: order});
let obs = this.http.post<{message: string, order: Order}>('http://localhost:3000/api/order', {order: order});
obs.subscribe((orderData) => {
console.log('Create order', orderData);
this.getOrder(this.dataStore.user.session);
this.dataStore.order = orderData.order;
this._orderUpdated.next(Object.assign({}, this.dataStore).order);
this.addToOrder(lineItem);
});
return obs;
}
cancelOrder(id: string) {
let obs = this.http.delete<{message: string}>('http://localhost:3000/api/order/'+id);
obs.subscribe((OrderResponse) => {
console.log('Order deleted');
//this.getOrder();
this.dataStore.order = {
address: '',
session: '',
timestamp: '',
closed: false,
lines: [
{
qty: 1,
name: '',
cost:0
}
]
};
this._orderUpdated.next(Object.assign({}, this.dataStore).order);
});
return obs;
}
}

BIN
src/assets/zec-roboto.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 B