доработки по корзине и отправке заказа, коризна теперь хранится в локалсторедж, а не в куки, адаптировал корзину под новые товары и модификаторы
This commit is contained in:
gofnnp 2022-12-05 08:37:34 +04:00
parent 801f33623b
commit c7db59110e
9 changed files with 226 additions and 140 deletions

View File

@ -34,25 +34,17 @@
<div formGroupName="deliveryDataForm">
<p сlass="form-row form-row-wide">
<p-dropdown [options]="deliveryTypes" formControlName="deliveryType" placeholder="Доставка"
optionLabel="title" (onChange)="changeDeliveryType($event)" [disabled]="true"></p-dropdown>
optionLabel="title" (onChange)="changeDeliveryType($event)"></p-dropdown>
</p>
<p сlass="form-row form-row-wide">
<label id="deliveryDate">Время выдачи</label>
<p-calendar
formControlName="deliveryDate"
[showTime]="true"
[showSeconds]="false"
[disabled]="true"
[touchUI]="true"
inputId="time"
placeholder="Время выдачи"
dateFormat="dd.mm.yy"
></p-calendar>
<p-calendar formControlName="deliveryDate" [showTime]="true" [showSeconds]="false"
[touchUI]="true" inputId="time" placeholder="Время выдачи" dateFormat="dd.mm.yy"></p-calendar>
</p>
<p сlass="form-row form-row-wide">
<!-- <p-selectButton [options]="paymentMethods" formControlName="paymentMethod" optionLabel="label">
</p-selectButton> -->
*Оплата бонусами
<p-dropdown [options]="paymentMethods" formControlName="paymentMethod" optionLabel="label" placeholder="Тип оплаты">
</p-dropdown>
<!-- *Оплата бонусами -->
</p>
<!-- <p сlass="form-row form-row-last">
<input [maxLength]="255" id="promo-code" pInputText placeholder="Промокод" type="text">
@ -62,10 +54,17 @@
rows="1"></textarea>
</p>
</div>
<button [disabled]="!mainFormGroup.valid" class="elementor-button elementor-button--checkout elementor-size-md"
(click)="submit()">
<span class="elementor-button-text">Оформить заказ</span>
</button>
<div class="bootom-info">
<div class="subtotal">
<span class="products-count">Товаров: {{order.products.length}}</span>
<span class="woocommerce-Price-amount amount"><bdi><span
class="woocommerce-Price-currencySymbol">{{order.products[0].currency_symbol}}</span>{{order.price}}</bdi></span>
</div>
<button
class="elementor-button elementor-button--checkout elementor-size-md">
<span class="elementor-button-text">Оформить заказ</span>
</button>
</div>
<p *ngIf="showMyMessage" style="color: red; font-size: 20px">Такой адрес не найден! Введите правильный адрес</p>
</form>
</div>

View File

@ -25,6 +25,12 @@
-webkit-appearance: none;
appearance: none;
border-radius: 4px;
&.ng-dirty.ng-invalid {
border-color: red;
}
&.ng-invalid.ng-touched {
border-color: red;
}
}
p {
@ -49,22 +55,6 @@
border-radius: 4px;
}
form {
&>button {
background-color: #09467f;
color: #fff;
border-radius: 6px;
width: calc(100% - 66px);
display: flex;
justify-content: center;
align-items: center;
border: none;
height: 40px;
width: 100%;
cursor: pointer;
}
}
.terminal-list-container {
display: flex;
flex-wrap: wrap;
@ -118,4 +108,53 @@
display: block;
font-size: 14px;
}
.bootom-info {
position: fixed;
bottom: 58px;
width: 100%;
display: flex;
padding: 12px 18px;
justify-content: space-between;
left: 0;
align-items: center;
background: #fff;
z-index: 9999;
.subtotal {
font-weight: 600;
font-size: 20px;
display: flex;
flex-direction: column;
.products-count {
font-size: 12px;
font-weight: 400;
}
}
.elementor-button--checkout {
background-color: #09467f;
color: #fff;
border-radius: 6px;
display: flex;
justify-content: center;
align-items: center;
border: none;
padding: 12px;
font-size: 12px;
cursor: pointer;
}
}
@media screen and (min-width: 550px) {
.bootom-info {
bottom: 84px;
width: 500px;
left: 50%;
transform: translate(-50%, 0);
border: solid 1px #dfdfdf;
border-radius: 12px;
}
}
}

View File

@ -1,6 +1,6 @@
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { DeliveryData, DeliveryType, UserData } from 'src/app/interface/data';
import { DeliveryData, DeliveryType, PaymentMethod, UserData } from 'src/app/interface/data';
import { paymentMethods } from "../../app.constants";
import { OrderService } from "../../services/order.service";
import { AutocompleteService } from "../../services/autocomplete.service";
@ -12,6 +12,10 @@ import { WpJsonService } from "../../services/wp-json.service";
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { CookiesService } from 'src/app/services/cookies.service';
import moment from 'moment';
import { Order } from 'src/app/models/order';
import { Store } from '@ngrx/store';
import * as fromConfig from '../../state/config/config.reducer'
import { lastValueFrom } from 'rxjs';
@ -24,7 +28,7 @@ export class UserDataOrderComponent implements OnInit {
@Output() orderSubmitted = new EventEmitter<void>();
readonly cities = environment.cities;
readonly paymentMethods = paymentMethods;
public paymentMethods!: PaymentMethod[];
public loading = false;
public hasError = false;
public mainFormGroup!: FormGroup;
@ -34,6 +38,7 @@ export class UserDataOrderComponent implements OnInit {
public new_house!: string | null;
public checkAddress: boolean = true;
public showMyMessage: boolean = false;
public order!: Order;
public userData: UserData = {
first_name: null,
@ -45,15 +50,18 @@ export class UserDataOrderComponent implements OnInit {
phone: null,
};
public deliverData: DeliveryData = {
deliveryDate: moment().add(1, 'hours').toDate(),
deliveryDate: null,
deliveryType: null,
paymentMethod: paymentMethods[0],
paymentMethod: null,
comment: '',
persons: 1
};
public terminalList!: any;
public selectedTerminal!: any;
checkoutConfig$ = this.store.select(fromConfig.selectCheckout);
checkoutConfig!: any;
constructor(
private fb: FormBuilder,
private orderService: OrderService,
@ -64,14 +72,22 @@ export class UserDataOrderComponent implements OnInit {
private wpJsonService: WpJsonService,
private http: HttpClient,
private cookiesService: CookiesService,
) {
}
private store: Store
) { }
ngOnInit(): void {
async ngOnInit() {
this._createMainForm();
this.getTerminalList();
this.selectedTerminal = JSON.parse(this.cookiesService.getItem('selectedTerminal') || '')
this.checkoutConfig$.subscribe({
next: (value: any) => {
this.checkoutConfig = value
}
})
this.deliverData.deliveryDate = moment().add(this.checkoutConfig.timeDelivery.changeTime.defaultValue, 'minutes').toDate()
this.paymentMethods = this.checkoutConfig.payments.values
this.deliverData.paymentMethod = this.paymentMethods[this.checkoutConfig.payments.default]
}
getTerminalList() {
@ -81,7 +97,7 @@ export class UserDataOrderComponent implements OnInit {
},
error: (err) => {
console.error(err);
}
})
}
@ -125,21 +141,26 @@ export class UserDataOrderComponent implements OnInit {
if (this.mainFormGroup.invalid) {
Object.keys(mainControls).forEach(groupName => {
const childGroupControls = (mainControls[groupName] as FormGroup).controls;
Object.keys(mainControls).forEach(controlName => {
Object.keys(childGroupControls).forEach(controlName => {
childGroupControls[controlName].markAsTouched();
});
});
this.messageService.add({
severity: 'error',
summary: 'Заполните обязательные поля',
})
return;
}
this.submitOrder();
}
submitOrder(): void {
async submitOrder() {
this.loading = true;
const userData: UserData = this.mainFormGroup.controls['userDataForm'].value;
const userData: UserData = this.mainFormGroup.controls['userDataForm'].getRawValue();
userData.phone = this.userData.phone;
this.orderService.setUserData(userData);
this.orderService.setDeliveryData(this.mainFormGroup.controls['deliveryDataForm'].value);
this.orderService.setDeliveryData(this.mainFormGroup.controls['deliveryDataForm'].getRawValue());
this.orderService.submit().subscribe({
next: (_) => {
this.loading = false;
@ -175,43 +196,31 @@ export class UserDataOrderComponent implements OnInit {
}
private async _createUserDataForm(): Promise<FormGroup> {
const order = await this.orderService.getOrder(true);
this.userData = Object.assign({}, this.userData, order.userData);
this.order = await this.orderService.getOrder(true);
this.userData = Object.assign({}, this.userData, this.order.userData);
this.userData.city = this.cities[0];
this.userData.phone = order.phone;
this.userData.phone = this.order.phone;
// await this.autoCompleteService.setCity(this.userData.city);
const isSelfDelivery = this.deliverData.deliveryType?.type === "self_delivery"
return this.fb.group({
phone: [this.userData.phone],
first_name: [this.userData.first_name, [Validators.required, Validators.minLength(2), Validators.maxLength(255),]],
// last_name: [this.userData.last_name, [Validators.required, Validators.minLength(2), Validators.maxLength(255),]],
street: [this.userData.street, [Validators.required, Validators.minLength(2), Validators.maxLength(255),]],
house: [this.userData.house, [Validators.required, Validators.maxLength(10), Validators.pattern('^\\d+[-|\\d]+\\d+$|^\\d*$')]],
street: [{ value: this.userData.street, disabled: isSelfDelivery }, isSelfDelivery ?? [Validators.required, Validators.minLength(2), Validators.maxLength(255),]],
house: [{ value: this.userData.house, disabled: isSelfDelivery }, isSelfDelivery ?? [Validators.required, Validators.maxLength(10), Validators.pattern('^\\d+[-|\\d]+\\d+$|^\\d*$')]],
flat: [this.userData.flat, []],
// city: [this.userData.city, [Validators.required]],
});
}
private async _createDeliveryDataForm(): Promise<FormGroup> {
this.deliveryTypes = [
{
"cost": 100,
"title": "Доставка",
"id": 11,
"type": "delivery"
},
{
"cost": 0,
"title": "Самовывоз",
"id": 16,
"type": "self_delivery"
}
];
this.deliverData.deliveryType = this.deliveryTypes[1];
this.deliveryTypes = this.checkoutConfig.delivery.values;
this.deliverData.deliveryType = this.deliveryTypes[this.checkoutConfig.delivery.default];
return this.fb.group({
deliveryDate: [this.deliverData.deliveryDate, []],
deliveryType: [this.deliverData.deliveryType, [Validators.required]],
paymentMethod: [this.deliverData.paymentMethod, [Validators.required]],
// persons: [this.deliverData.persons, [Validators.required, Validators.minLength(2), Validators.maxLength(255),]],
deliveryDate: [{ value: this.deliverData.deliveryDate, disabled: this.checkoutConfig.timeDelivery.changeTime.disabled }, []],
deliveryType: [{ value: this.deliverData.deliveryType, disabled: this.checkoutConfig.delivery.disabled }, [Validators.required]],
paymentMethod: [{ value: this.deliverData.paymentMethod, disabled: this.checkoutConfig.payments.disabled }, [Validators.required]],
persons: [this.deliverData.persons, [Validators.required, Validators.minLength(2), Validators.maxLength(255),]],
comment: [this.deliverData.comment, [Validators.maxLength(255),]]
});
}

View File

@ -1,4 +1,4 @@
import {CartModifier, Modifier, ModifiersGroup, Option} from "../interface/data";
import { CartModifier, Modifier, ModifiersGroup, Option } from "../interface/data";
import { v4 as uuidv4 } from 'uuid';
export class CartProduct {
@ -9,7 +9,24 @@ export class CartProduct {
this.guid = uuidv4();
this.amount = amount;
this.name = name;
this.modifiers = modifiers.map(modifier => ({name: modifier.name, id: modifier.id, options: []}));
this.modifiers = modifiers.map(modifier => ({
name: modifier.name,
id: modifier.id,
idLocal: uuidv4(),
options: JSON.parse(JSON.stringify(options)).filter((option: any) => option.groupId === modifier.id),
restrictions: modifier.restrictions,
get allQuantity() {
return this.options.reduce((a: any, b: any) => a + (b['quantity'] || 0), 0)
}
}));
this.modifiers.forEach((modifier) => {
modifier.options.forEach((option) => {
option.idLocal = uuidv4()
if (!option.quantity && option.quantity !== 0) {
option.quantity = option.restrictions.byDefault
}
})
})
}
id: string;
@ -18,27 +35,26 @@ export class CartProduct {
name: string;
modifiers: CartModifier[];
increment(): void{
increment(): void {
this.amount++;
}
decrement(): void{
if (this.amount > 0){
decrement(): void {
if (this.amount > 0) {
this.amount--;
}
}
addOption(modifier: ModifiersGroup, option: Modifier): void{
const productModifier = this.modifiers.find(value => value.id === modifier.id);
if (productModifier){
const optionIndex = productModifier.options.findIndex(value => value.id === option.id);
if(optionIndex === -1){
productModifier.options.push(option);
}
else {
productModifier.options.splice(optionIndex, 1)
}
}
}
// addOption(modifier: ModifiersGroup, option: Modifier): void {
// const productModifier = this.modifiers.find(value => value.id === modifier.id);
// if (productModifier) {
// const optionIndex = productModifier.options.findIndex(value => value.id === option.id);
// if (optionIndex === -1) {
// productModifier.options.push(option);
// }
// else {
// productModifier.options.splice(optionIndex, 1)
// }
// }
// }
}

View File

@ -41,7 +41,7 @@ export class OrderProduct implements Product{
get finalPrice(): number{
const modifiersPrice = this.modifier_data.reduce<number>((previousValue, currentValue) => {
return previousValue + currentValue.options.reduce<number>((previousOptionValue, currentOptionValue) => {
return previousOptionValue + Number(currentOptionValue.price ? currentOptionValue.price : 0);
return previousOptionValue + Number(currentOptionValue.price ? currentOptionValue.price * (currentOptionValue.quantity ?? 0) : 0);
}, 0);
}, 0);
return (Number(this.price) + modifiersPrice) * this.amount;
@ -55,9 +55,14 @@ export class OrderProduct implements Product{
options: this.modifier_data?.map((modifier) => {
return {
option: modifier.name,
variant: modifier.options[0]?.name || null
variants: modifier.options.map((option) => ({
variant: option.name,
id: option.id,
quantity: option.quantity
})).filter((option) => option.quantity) || null,
id: modifier.id
}
}),
}).filter((modifier) => modifier.variants.length),
quantity: this.amount,
name: this.name,
}

View File

@ -38,7 +38,7 @@ export class Order {
const date = moment(this.deliveryData?.deliveryDate ?? Date.now());
return {
formname: "Cart",
paymentsystem: this.deliveryData?.paymentMethod.type,
paymentsystem: this.deliveryData?.paymentMethod?.type,
phone: this.phone,
persons: 1,
name: "31",
@ -51,7 +51,7 @@ export class Order {
subtotal: this.price,
delivery_comment: this.deliveryData?.comment,
delivery: this.deliveryData?.deliveryType?.type,
delivery_address: `${environment.cities[0]}, ул ${this.userData?.street}, ${this.userData?.house}`,
delivery_address: this.deliveryData?.deliveryType?.type === "self_delivery" ? null : `${environment.cities[0]}, ул ${this.userData?.street}, ${this.userData?.house}`,
amount: this.price + 100
},
}

View File

@ -17,9 +17,9 @@
<dl *ngFor="let modifier of product.modifier_data" class="variation"
[ngStyle]="{margin: !modifier.options.length && 0}">
<ng-container *ngFor="let option of modifier.options">
<dt style="max-width: 160px;" class="variation-">{{option.name}}:</dt>
<dd style="display: flex; align-items: flex-end; margin-bottom: 0;" class="variation-">
<p>{{product.currency_symbol}}{{option.price ?? 0}}</p>
<dt *ngIf="option.quantity" style="max-width: 160px;" class="variation-">{{option.name}}:</dt>
<dd *ngIf="option.quantity" style="display: flex; align-items: flex-end; margin-bottom: 0;" class="variation-">
<p>{{option.quantity}} × {{product.currency_symbol}}{{(option.price ?? 0) * option.quantity}}</p>
</dd>
</ng-container>
</dl>
@ -27,7 +27,7 @@
<div class="elementor-menu-cart__product-price product-price" data-title="Price">
<span class="quantity">
<span class="product-quantity">{{product.amount}} ×</span>
<span class="product-quantity">{{product.amount}} × </span>
<span class="woocommerce-Price-amount amount">
<bdi>
<span class="woocommerce-Price-currencySymbol">{{product.currency_symbol}}</span>
@ -58,7 +58,7 @@
</div>
<div *ngIf="order.products.length != 0" class="elementor-menu-cart__bottom-info">
<div class="elementor-menu-cart__subtotal">
<strong>К оплате: </strong>
<span class="products-count">Товаров: {{order.products.length}}</span>
<span class="woocommerce-Price-amount amount"><bdi><span
class="woocommerce-Price-currencySymbol">{{order.products[0].currency_symbol}}</span>{{order.price}}</bdi></span>
</div>
@ -80,10 +80,10 @@
</div>
<div class="grid p-fluid">
<div class="col-6">
<button type="button" pButton (click)="onConfirm()" label="Да" class="p-button-success">Да</button>
<button type="button" pButton (click)="onConfirm()" label="Да" class="p-button-success"></button>
</div>
<div class="col-6">
<button type="button" pButton (click)="onReject()" label="Нет" class="p-button-secondary">Нет</button>
<button type="button" pButton (click)="onReject()" label="Нет" class="p-button-secondary"></button>
</div>
</div>
</div>

View File

@ -36,7 +36,7 @@
}
}
}
&__product-image {
grid-row-start: 1;
grid-row-end: 3;
@ -44,6 +44,7 @@
img {
border-radius: 6px;
object-fit: cover;
}
}
@ -62,7 +63,7 @@
grid-column-end: 3;
-ms-flex-item-align: var(--price-quantity-position--align-self, end);
align-self: var(--price-quantity-position--align-self, end);
font-weight: 300;
font-weight: 400;
}
&__product-remove {
@ -126,16 +127,23 @@
left: 0;
display: flex;
justify-content: space-between;
align-items: flex-start;
bottom: 0;
align-items: center;
bottom: 58px;
padding: 18px;
background: #fff;
border-top: solid #d9d9d9 1px;
z-index: 3;
}
&__subtotal {
font-weight: 600;
font-size: 20px;
display: flex;
flex-direction: column;
& .products-count {
font-size: 12px;
font-weight: 400;
}
}
&__footer-buttons {
@ -212,4 +220,15 @@
margin: 32px auto;
display: block;
}
@media screen and (min-width: 550px) {
.elementor-menu-cart__bottom-info {
bottom: 84px;
width: 500px;
left: 50%;
transform: translate(-50%, 0);
border: solid 1px #dfdfdf;
border-radius: 12px;
}
}
}

View File

@ -1,9 +1,9 @@
import {Injectable} from '@angular/core';
import {CookiesService} from "./cookies.service";
import {Cart} from "../interface/data";
import {isEqual} from 'lodash/fp';
import {CartProduct} from "../models/cart-product";
import {Subject} from "rxjs";
import { Injectable } from '@angular/core';
import { CookiesService } from "./cookies.service";
import { Cart } from "../interface/data";
import { isEqual } from 'lodash/fp';
import { CartProduct } from "../models/cart-product";
import { Subject } from "rxjs";
import { update } from 'lodash';
import { WpJsonService } from './wp-json.service';
@ -20,7 +20,7 @@ export class CartService {
constructor(
private cookieService: CookiesService,
private wpJsonService: WpJsonService,
) { }
) { }
private cart!: Cart;
@ -29,65 +29,64 @@ export class CartService {
public selectedTerminal$ = new Subject<Object>();
getCart(){
getCart() {
return this._getCartProducts();
}
addToCart(product: CartProduct): void{
addToCart(product: CartProduct): void {
const cart = this._getCartProducts();
cart.products = cart.products ?? [];
const sameProduct = cart.products.find((value) => value.id === product.id && isEqual(value.modifiers, product.modifiers));
if(sameProduct){
sameProduct.amount ++;
if (sameProduct) {
sameProduct.amount++;
}
else {
cart.products.push(product);
this.cartCount$.next(cart.products.length);
}
this.cookieService.setCookie('cart', JSON.stringify(cart));
localStorage.setItem('cart', JSON.stringify(cart));
}
removeFromCart(guid: string): void{
removeFromCart(guid: string): void {
const cart = this._getCartProducts();
if(!cart.products){
if (!cart.products) {
return;
}
cart.products = cart.products.filter((value) => value.guid !== guid);
this.cookieService.setCookie('cart', JSON.stringify(cart));
localStorage.setItem('cart', JSON.stringify(cart));
this.cartCount$.next(cart.products.length);
}
updateProductFromCart(product: CartProduct): void{
updateProductFromCart(product: CartProduct): void {
// const cart = this._getCartProducts();
// if(!cart.products){
// return;
// }
// const updateProduct = cart.products.find((value) => Number(value.id) === product.id)
// if (updateProduct) {
// updateProduct.modifiers = JSON.parse(JSON.stringify(product.modifiers))
// }
// this.cookieService.setCookie('cart', JSON.stringify(cart));
// localStorage.setItem('cart', JSON.stringify(cart));
}
changeAmountProduct(productTempId: string,action: ProductAmountAction): void{
changeAmountProduct(productTempId: string, action: ProductAmountAction): void {
const cart = this._getCartProducts();
if(!cart.products){
if (!cart.products) {
return;
}
const product: CartProduct | undefined = cart.products.find((value) => value.guid === productTempId);
if(product && action === ProductAmountAction.increment){
if (product && action === ProductAmountAction.increment) {
product.amount++
// product.increment();
}
else if(product && action === ProductAmountAction.decrement){
else if (product && action === ProductAmountAction.decrement) {
product.amount--
// product.decrement();
}
this.cookieService.setCookie('cart', JSON.stringify(cart));
localStorage.setItem('cart', JSON.stringify(cart));
this.cartCount$.next(cart.products.length);
}
@ -96,23 +95,23 @@ export class CartService {
this.selectedTerminal$.next(terminal)
}
clearCart(){
this.cart = {products: []};
this.cookieService.setCookie('cart', JSON.stringify(this.cart));
clearCart() {
this.cart = { products: [] };
localStorage.setItem('cart', JSON.stringify(this.cart));
this.cartCount$.next(0);
}
_getCartProducts(): Cart{
if(this.cart){
_getCartProducts(): Cart {
if (this.cart) {
return this.cart;
}
const cartJson = this.cookieService.getItem('cart');
this.cart = cartJson ? JSON.parse(cartJson) : {products: []};
const cartJson = localStorage.getItem('cart');
this.cart = cartJson ? JSON.parse(cartJson) : { products: [] };
return this.cart;
}
get cartCount(): number{
get cartCount(): number {
return this._getCartProducts().products.length;
}
}