diff --git a/angular/src/app/app.constants.ts b/angular/src/app/app.constants.ts
index 6cc6e78..8aff840 100644
--- a/angular/src/app/app.constants.ts
+++ b/angular/src/app/app.constants.ts
@@ -1,4 +1,4 @@
-import {MainPageCode, OrderStatus, Page, PageCode} from "./interface/data";
+import {MainPageCode, OrderStatus, Page, PageCode, PaymentMethod} from "./interface/data";
export const PageList: Page[] = [
{
@@ -85,4 +85,15 @@ export const orderStatuses: OrderStatus = {
'OnWay': 'В пути',
'Delivered': 'Выполнен',
'Closed': 'Выполнен',
-};
\ No newline at end of file
+};
+
+export const paymentMethods: PaymentMethod[] = [
+ {
+ type: 'Card',
+ label: 'Безналичный расчет'
+ },
+ {
+ type: 'Cash',
+ label: 'Наличными'
+ }
+ ]
\ No newline at end of file
diff --git a/angular/src/app/app.module.ts b/angular/src/app/app.module.ts
index 7a796c1..c5f6c43 100644
--- a/angular/src/app/app.module.ts
+++ b/angular/src/app/app.module.ts
@@ -38,6 +38,10 @@ import { CartComponent } from './pages/cart/cart.component';
import {ListboxModule} from 'primeng/listbox';
import { ProductModalComponent } from './components/product-modal/product-modal.component';
import { CheckboxGroupComponent } from './components/checkbox-group/checkbox-group.component';
+import { TreeSelectModule } from 'primeng/treeselect';
+import { UserDataOrderComponent } from './components/user-data-order/user-data-order.component';
+import {DropdownModule} from "primeng/dropdown";
+import {SelectButtonModule} from 'primeng/selectbutton';
@NgModule({
declarations: [
@@ -58,7 +62,8 @@ import { CheckboxGroupComponent } from './components/checkbox-group/checkbox-gro
ProductsComponent,
CartComponent,
ProductModalComponent,
- CheckboxGroupComponent
+ CheckboxGroupComponent,
+ UserDataOrderComponent
],
imports: [
BrowserModule,
@@ -91,7 +96,10 @@ import { CheckboxGroupComponent } from './components/checkbox-group/checkbox-gro
debug: true
}),
ShareIconsModule,
- ListboxModule
+ ListboxModule,
+ TreeSelectModule,
+ DropdownModule,
+ SelectButtonModule
],
providers: [DialogService, MessageService, MessagingService ],
bootstrap: [AppComponent]
diff --git a/angular/src/app/components/user-data-order/user-data-order.component.html b/angular/src/app/components/user-data-order/user-data-order.component.html
new file mode 100644
index 0000000..a6b0d08
--- /dev/null
+++ b/angular/src/app/components/user-data-order/user-data-order.component.html
@@ -0,0 +1,50 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/angular/src/app/components/user-data-order/user-data-order.component.scss b/angular/src/app/components/user-data-order/user-data-order.component.scss
new file mode 100644
index 0000000..5f6e70e
--- /dev/null
+++ b/angular/src/app/components/user-data-order/user-data-order.component.scss
@@ -0,0 +1,67 @@
+:host {
+ .woocommerce-shipping-fields__field-wrapper {
+ margin-top: 8px;
+ }
+
+
+ .order_form__title {
+ font-weight: 700;
+ font-size: 18px;
+ margin-bottom: 12px;
+ }
+
+ input {
+ width: 100%;
+ color: #000000;
+ border: 1px solid #000000;
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", Segoe UI Symbol;
+ font-size: 1rem;
+ color: #495057;
+ background: #ffffff;
+ padding: 0.5rem 0.75rem;
+ border: 1px solid #ced4da;
+ transition: background-color .15s, border-color .15s, box-shadow .15s;
+ -webkit-appearance: none;
+ appearance: none;
+ border-radius: 4px;
+ }
+
+ p {
+ margin-top: 0;
+ margin-bottom: 1rem;
+ }
+
+ textarea {
+ width: 100%;
+ height: 52px;
+ color: #000000;
+ border: 1px solid #000000;
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", Segoe UI Symbol;
+ font-size: 1rem;
+ color: #495057;
+ background: #ffffff;
+ padding: 0.5rem 0.75rem;
+ border: 1px solid #ced4da;
+ transition: background-color .15s, border-color .15s, box-shadow .15s;
+ -webkit-appearance: none;
+ appearance: none;
+ 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;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/angular/src/app/components/user-data-order/user-data-order.component.spec.ts b/angular/src/app/components/user-data-order/user-data-order.component.spec.ts
new file mode 100644
index 0000000..35e6efe
--- /dev/null
+++ b/angular/src/app/components/user-data-order/user-data-order.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { UserDataOrderComponent } from './user-data-order.component';
+
+describe('UserDataOrderComponent', () => {
+ let component: UserDataOrderComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ UserDataOrderComponent ]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(UserDataOrderComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/angular/src/app/components/user-data-order/user-data-order.component.ts b/angular/src/app/components/user-data-order/user-data-order.component.ts
new file mode 100644
index 0000000..2517133
--- /dev/null
+++ b/angular/src/app/components/user-data-order/user-data-order.component.ts
@@ -0,0 +1,200 @@
+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 { paymentMethods } from "../../app.constants";
+import { OrderService } from "../../services/order.service";
+import { AutocompleteService } from "../../services/autocomplete.service";
+import { StreetValidator } from "../../validators/street.validator";
+import { CartService } from 'src/app/services/cart.service';
+import { environment } from "../../../environments/environment";
+import { MessageService } from "primeng/api";
+import { WpJsonService } from "../../services/wp-json.service";
+import { HttpClientModule } from '@angular/common/http';
+
+
+
+@Component({
+ selector: 'app-user-data-order',
+ templateUrl: './user-data-order.component.html',
+ styleUrls: ['./user-data-order.component.scss']
+})
+export class UserDataOrderComponent implements OnInit {
+
+ @Output() orderSubmitted = new EventEmitter();
+ readonly cities = environment.cities;
+ readonly paymentMethods = paymentMethods;
+ public loading = false;
+ public hasError = false;
+ public mainFormGroup!: FormGroup;
+ public deliveryTypes: DeliveryType[] = [];
+ public minDate!: Date;
+ public new_street!: string | null;
+ public street!: string;
+ public new_house!: string | null;
+ public checkAddress: boolean = true;
+ public showMyMessage: boolean = false;
+
+ public userData: UserData = {
+ first_name: null,
+ last_name: null,
+ street: null,
+ house: null,
+ flat: null,
+ city: this.cities[0],
+ phone: null,
+ };
+ public deliverData: DeliveryData = {
+ deliveryDate: null,
+ deliveryType: null,
+ paymentMethod: paymentMethods[0],
+ comment: '',
+ persons: 1,
+ };
+
+ constructor(
+ private fb: FormBuilder,
+ private orderService: OrderService,
+ private autoCompleteService: AutocompleteService,
+ private streetValidator: StreetValidator,
+ private cartService: CartService,
+ private messageService: MessageService,
+ private wpJsonService: WpJsonService,
+ ) {
+ }
+
+ ngOnInit(): void {
+ this.minDate = new Date();
+ this._createMainForm();
+ }
+
+ changeDeliveryType(event: any) {
+ this.deliverData.deliveryType = event.value;
+ if (this.deliverData.deliveryType?.title) {
+ this.changeValidators(this.deliverData.deliveryType.title)
+ }
+ }
+
+ changeValidators(title: string) {
+ const comment = this.mainFormGroup.controls['deliveryDataForm'].value.comment;
+ const streetValidators = title === 'Доставка' ? [Validators.required, Validators.minLength(2), Validators.maxLength(255),] : []
+ const houseValidators = title === 'Доставка' ? [Validators.required, Validators.maxLength(10),] : []
+ const userDataForm = 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, streetValidators],
+ house: [this.userData.house, houseValidators],
+ flat: [this.userData.flat, []],
+ // city: [this.userData.city, [Validators.required]],
+ });
+ const deliveryDataForm = 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),]],
+ comment: [comment, [Validators.maxLength(255),]],
+ });
+
+ this.mainFormGroup = this.fb.group({
+ userDataForm,
+ deliveryDataForm,
+ });
+ }
+
+ submit(): void {
+ const mainControls = this.mainFormGroup.controls;
+ if (this.mainFormGroup.invalid) {
+ Object.keys(mainControls).forEach(groupName => {
+ const childGroupControls = (mainControls[groupName] as FormGroup).controls;
+ Object.keys(mainControls).forEach(controlName => {
+ childGroupControls[controlName].markAsTouched();
+ });
+ });
+ return;
+ }
+ this.submitOrder();
+ }
+
+ submitOrder(): void {
+ this.loading = true;
+ const userData: UserData = this.mainFormGroup.controls['userDataForm'].value;
+ userData.phone = this.userData.phone;
+ this.orderService.setUserData(userData);
+ this.orderService.setDeliveryData(this.mainFormGroup.controls['deliveryDataForm'].value);
+ this.orderService.submit().subscribe({
+ next: (_) => {
+ this.loading = false;
+ this.cartService.clearCart();
+ this.orderSubmitted.next();
+ },
+ error: () => {
+ this.loading = false;
+ this.hasError = true;
+ }
+ })
+ }
+
+ private async _createMainForm(): Promise {
+ try {
+ this.loading = true;
+ const userDataForm = await this._createUserDataForm();
+ const deliveryDataForm = await this._createDeliveryDataForm();
+ this.mainFormGroup = this.fb.group({
+ userDataForm,
+ deliveryDataForm,
+ });
+ this.loading = false;
+ }
+ catch (e) {
+ console.error('Erroe: ', e);
+
+ this.messageService.add({
+ severity: 'error',
+ summary: 'Произошла ошибка',
+ })
+ }
+ }
+
+ private async _createUserDataForm(): Promise {
+ const order = await this.orderService.getOrder(true);
+ this.userData = Object.assign({}, this.userData, order.userData);
+ this.userData.city = this.cities[0];
+ this.userData.phone = order.phone;
+ // await this.autoCompleteService.setCity(this.userData.city);
+ 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*$')]],
+ flat: [this.userData.flat, []],
+ // city: [this.userData.city, [Validators.required]],
+ });
+ }
+
+ private async _createDeliveryDataForm(): Promise {
+ this.deliveryTypes = [
+ {
+ "cost": 100,
+ "title": "Доставка",
+ "id": 11,
+ "type": "delivery"
+ },
+ {
+ "cost": 0,
+ "title": "Самовывоз",
+ "id": 16,
+ "type": "self_delivery"
+ }
+ ];
+ this.deliverData.deliveryType = this.deliveryTypes[0];
+ 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),]],
+ comment: [this.deliverData.comment, [Validators.maxLength(255),]],
+ });
+ }
+}
+
diff --git a/angular/src/app/interface/data.ts b/angular/src/app/interface/data.ts
index 6de7464..5f9d104 100644
--- a/angular/src/app/interface/data.ts
+++ b/angular/src/app/interface/data.ts
@@ -1,3 +1,4 @@
+import { CartProduct } from "../models/cart-product";
export enum PageCode {
@@ -79,6 +80,7 @@ export interface DeliveryType {
cost: number;
title: string;
id: number;
+ type: string;
}
export interface AcceptedOrder {
@@ -111,7 +113,7 @@ export interface Product {
description: string;
stock_status: string;
currency_symbol: string;
- modifier_data: Modifier[];
+ modifier_data: CartModifier[];
short_description: string;
guid: string;
groupId: string;
@@ -129,7 +131,7 @@ export interface AllData {
export interface Group {
id: string;
- name: string;
+ label: string;
}
export interface ModifiersGroup {
@@ -147,6 +149,7 @@ export interface Modifier {
id: string,
name: string,
groupId: string,
+ price?: number,
restrictions: {
minQuantity: number,
maxQuantity: number,
@@ -155,6 +158,16 @@ export interface Modifier {
}
}
+export interface CartModifier {
+ id: string;
+ name: string;
+ options: Modifier[];
+}
+
+export interface Cart {
+ products: CartProduct[];
+}
+
// export interface Modifier {
// id: number;
// name: string;
@@ -170,9 +183,13 @@ export interface Modifier {
export interface Option {
id: number;
name: string;
- price: string;
- prechecked: string;
- active?: boolean;
+ groupId: string;
+ restrictions: {
+ minQuantity: number,
+ maxQuantity: number,
+ freeQuantity: number,
+ byDefault: number
+ }
}
export interface OrderProduct {
diff --git a/angular/src/app/models/cart-product.ts b/angular/src/app/models/cart-product.ts
new file mode 100644
index 0000000..33365c2
--- /dev/null
+++ b/angular/src/app/models/cart-product.ts
@@ -0,0 +1,44 @@
+import {CartModifier, Modifier, ModifiersGroup, Option} from "../interface/data";
+import { v4 as uuidv4 } from 'uuid';
+
+export class CartProduct {
+
+
+ constructor(id: string, name: string, modifiers: ModifiersGroup[] = [], options: Modifier[], amount: number = 1) {
+ this.id = id;
+ this.guid = uuidv4();
+ this.amount = amount;
+ this.name = name;
+ this.modifiers = modifiers.map(modifier => ({name: modifier.name, id: modifier.id, options: []}));
+ }
+
+ id: string;
+ guid: string;
+ amount: number;
+ name: string;
+ modifiers: CartModifier[];
+
+
+ increment(): void{
+ this.amount++;
+ }
+
+ 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)
+ }
+ }
+ }
+}
diff --git a/angular/src/app/models/order-product.ts b/angular/src/app/models/order-product.ts
index c71bbda..73174fa 100644
--- a/angular/src/app/models/order-product.ts
+++ b/angular/src/app/models/order-product.ts
@@ -1,4 +1,4 @@
-import {Modifier, Product} from "../interface/data";
+import {CartModifier, Modifier, Product} from "../interface/data";
export class OrderProduct implements Product{
@@ -29,7 +29,7 @@ export class OrderProduct implements Product{
public id: string;
public image_gallery: string[];
public image: string;
- public modifier_data: Modifier[];
+ public modifier_data: CartModifier[];
public name: string;
public price: number;
public stock_status: string;
@@ -39,29 +39,27 @@ export class OrderProduct implements Product{
get finalPrice(): number{
- // const modifiersPrice = this.modifier_data.reduce((previousValue, currentValue) => {
- // return previousValue + currentValue.options.reduce((previousOptionValue, currentOptionValue) => {
- // return previousOptionValue + Number(currentOptionValue.price);
- // }, 0);
- // }, 0);
- // return (Number(this.price) + modifiersPrice) * this.amount;
- return 1
- new Date()
+ const modifiersPrice = this.modifier_data.reduce((previousValue, currentValue) => {
+ return previousValue + currentValue.options.reduce((previousOptionValue, currentOptionValue) => {
+ return previousOptionValue + Number(currentOptionValue.price ? currentOptionValue.price : 0);
+ }, 0);
+ }, 0);
+ return (Number(this.price) + modifiersPrice) * this.amount;
}
toJson(){
return {
id: this.id,
- amount: this.amount,
- name: this.name,
- modifiers: this.modifier_data?.map(modifier => {
+ amount: this.amount * this.price,
+ price: this.price,
+ options: this.modifier_data?.map((modifier) => {
return {
- id: modifier.id,
- // options: modifier.options,
+ option: modifier.name,
+ variant: modifier.options[0]?.name || null
}
}),
+ quantity: this.amount,
+ name: this.name,
}
}
-
-
}
diff --git a/angular/src/app/models/order.ts b/angular/src/app/models/order.ts
index 5d14cad..7134b48 100644
--- a/angular/src/app/models/order.ts
+++ b/angular/src/app/models/order.ts
@@ -2,6 +2,7 @@ import {DeliveryData, UserData} from "../interface/data";
import {OrderProduct} from "./order-product";
import * as moment from 'moment';
import { CookiesService } from "../services/cookies.service";
+import { environment } from "src/environments/environment";
export interface OrderInfo {
products: OrderProduct[];
@@ -36,33 +37,23 @@ export class Order {
toJson(): any {
const date = moment(this.deliveryData?.deliveryDate ?? Date.now());
return {
- items: this.products.map(product => {
- return product.toJson();
- }),
- user_data: {
- phone: this.phone,
- ...this.userData
- },
- payment_method: this.deliveryData?.paymentMethod.type,
- delivery_time: date.format('HH:mm'),
- delivery_date: date.format("YYYY-MM-DD"),
- delivery_instance_id: this.deliveryData?.deliveryType?.id,
+ formname: "Cart",
+ paymentsystem: this.deliveryData?.paymentMethod.type,
+ phone: this.phone,
persons: 1,
- payments: [
- {
- type: this.deliveryData?.paymentMethod.type,
- summ: this.price,
- },
- {
- type: "crm4retail",
- summ: 0,
- payload: {
- id: "c07a10d8-ba7e-43b0-92aa-ae470060bc7d"
- }
- }
- ],
- comment: this.deliveryData?.comment,
- token: this.token
+ name: "31",
+ payment: {
+ delivery_price: 100,
+ products: this.products.map(product => {
+ return product.toJson();
+ }),
+ delivery_fio: this.userData?.first_name,
+ subtotal: this.price,
+ delivery_comment: this.deliveryData?.comment,
+ delivery: this.deliveryData?.deliveryType?.type,
+ delivery_address: `${environment.cities[0]}, ул ${this.userData?.street}, ${this.userData?.house}`,
+ amount: this.price + 100
+ },
}
}
}
diff --git a/angular/src/app/pages/account/account.component.html b/angular/src/app/pages/account/account.component.html
index b835cd8..2e22bf7 100644
--- a/angular/src/app/pages/account/account.component.html
+++ b/angular/src/app/pages/account/account.component.html
@@ -7,12 +7,14 @@
{{page.name}}
diff --git a/angular/src/app/pages/account/account.component.scss b/angular/src/app/pages/account/account.component.scss
index 28dba4a..39d1598 100644
--- a/angular/src/app/pages/account/account.component.scss
+++ b/angular/src/app/pages/account/account.component.scss
@@ -2,6 +2,7 @@
.woocommerce {
min-height: calc(100vh - 39px);
padding: 20px 18px;
+ position: relative;
&.auth-page {
display: flex;
@@ -13,6 +14,7 @@
max-width: 600px;
height: 50px;
margin: -20px auto 0 auto;
+
ul {
display: flex;
justify-content: space-between;
@@ -20,16 +22,36 @@
align-items: center;
font-size: 14px;
padding: 0 16px;
+
li {
width: 100%;
text-align: center;
border-right: solid #e1e1e1 1px;
padding: 8px 0;
cursor: pointer;
+
&.is-active {
background: #0d457e;
color: #fff;
}
+
+ &.cart {
+ position: relative;
+
+ &::before {
+ content: attr(data-counter);
+ color: #fff;
+ position: absolute;
+ right: 3px;
+ top: 1px;
+ background: #D7120B;
+ border-radius: 50px;
+ min-width: 1.2rem;
+ line-height: 1.2rem;
+ font-size: .8rem;
+ text-align: center;
+ }
+ }
}
}
}
@@ -118,6 +140,8 @@
.version {
opacity: 0.5;
+ position: absolute;
+ bottom: 12px;
}
}
}
\ No newline at end of file
diff --git a/angular/src/app/pages/account/account.component.ts b/angular/src/app/pages/account/account.component.ts
index 87de779..cd5e468 100644
--- a/angular/src/app/pages/account/account.component.ts
+++ b/angular/src/app/pages/account/account.component.ts
@@ -10,6 +10,7 @@ import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog';
import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service';
import { MessageService } from 'primeng/api';
import { lastValueFrom } from 'rxjs';
+import { CartService } from 'src/app/services/cart.service';
@Component({
selector: 'app-account',
@@ -27,7 +28,8 @@ export class AccountComponent implements OnInit {
private route: ActivatedRoute,
private dialogService: DialogService,
private jsonRpcService: JsonrpcService,
- private messageService: MessageService
+ private messageService: MessageService,
+ private cartService: CartService,
) { }
public currentPage!: Page;
@@ -42,7 +44,8 @@ export class AccountComponent implements OnInit {
readonly MainPageCode = MainPageCode;
readonly mainPageList = PageListMain;
- public currentPageMain: Page = this.mainPageList[0];
+ public currentPageMain: Page = this.mainPageList[environment.production ? 0 : 1];
+ public cartCount = 0;
ngOnInit(): void {
if (!this.getToken()) {
@@ -62,6 +65,13 @@ export class AccountComponent implements OnInit {
this.currentPage = currentPage;
}
});
+ this.cartCount = this.cartService.cartCount;
+ this.cartService.cartCount$.subscribe({
+ next: (count) => {
+ this.cartCount = count;
+ document.querySelectorAll('.cart')[0].setAttribute("data-counter", this.cartCount.toString())
+ }
+ });
}
document.body.classList.add(
'woocommerce-account',
diff --git a/angular/src/app/pages/account/bonus-program/bonus-program.component.scss b/angular/src/app/pages/account/bonus-program/bonus-program.component.scss
index 4cdd421..c8c07e9 100644
--- a/angular/src/app/pages/account/bonus-program/bonus-program.component.scss
+++ b/angular/src/app/pages/account/bonus-program/bonus-program.component.scss
@@ -8,6 +8,9 @@
font-weight: 700;
font-size: 20px;
line-height: 24px;
+ max-width: 400px;
+ margin: 0 auto;
+ width: 100%;
}
&>p {
diff --git a/angular/src/app/pages/cart/cart.component.html b/angular/src/app/pages/cart/cart.component.html
index 6bdabc8..b78e36b 100644
--- a/angular/src/app/pages/cart/cart.component.html
+++ b/angular/src/app/pages/cart/cart.component.html
@@ -1 +1,77 @@
-cart works!
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/angular/src/app/pages/cart/cart.component.scss b/angular/src/app/pages/cart/cart.component.scss
index e69de29..0a81787 100644
--- a/angular/src/app/pages/cart/cart.component.scss
+++ b/angular/src/app/pages/cart/cart.component.scss
@@ -0,0 +1,196 @@
+:host {
+ .cart {
+ margin-top: 16px;
+ margin-bottom: 100px;
+ }
+
+ .elementor-menu-cart {
+ &__product {
+ grid-template-columns: 71px auto;
+ grid-template-rows: var(--price-quantity-position--grid-template-rows, auto auto);
+ position: relative;
+ display: grid;
+ padding-bottom: 20px;
+ padding-right: 30px;
+
+ .variation {
+ display: grid;
+ grid-template-columns: max-content auto;
+ margin: 10px 8px;
+ color: var(--product-variations-color, #373a3c);
+
+ dt {
+ grid-column-start: 1;
+ font-weight: 700;
+ }
+
+ dd {
+ grid-column-start: 2;
+ -webkit-margin-start: 5px;
+ margin-inline-start: 5px;
+ margin-bottom: 0.5rem;
+ margin-left: 6px;
+ }
+ }
+ }
+
+ &__product-image {
+ grid-row-start: 1;
+ grid-row-end: 3;
+ width: 100%;
+
+ img {
+ border-radius: 6px;
+ }
+ }
+
+ &__product-name {
+ grid-column-start: 2;
+ grid-column-end: 3;
+ margin: 0;
+ font-size: 14px;
+ padding-left: 20px;
+ }
+
+ &__product-price {
+ font-size: 14px;
+ padding-left: 20px;
+ grid-column-start: 2;
+ 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;
+ }
+
+ &__product-remove {
+ color: #818a91;
+ width: var(--remove-item-button-size, 22px);
+ height: var(--remove-item-button-size, 22px);
+ border-radius: var(--remove-item-button-size, 22px);
+ border: 1px solid var(--remove-item-button-color, #d4d4d4);
+ text-align: center;
+ overflow: hidden;
+ position: absolute;
+ top: 0px;
+ right: 0;
+ bottom: 20px;
+ -webkit-transition: .3s;
+ -o-transition: .3s;
+ transition: .3s;
+
+ &::before,
+ &::after {
+ content: "";
+ position: absolute;
+ height: 1px;
+ width: 50%;
+ top: 50%;
+ left: 25%;
+ margin-top: -1px;
+ background: var(--remove-item-button-color, #d4d4d4);
+ z-index: 1;
+ -webkit-transition: .3s;
+ -o-transition: .3s;
+ transition: .3s;
+ }
+
+ &::before {
+ -webkit-transform: rotate(45deg);
+ -ms-transform: rotate(45deg);
+ transform: rotate(45deg);
+ }
+
+ &::after {
+ -webkit-transform: rotate(-45deg);
+ -ms-transform: rotate(-45deg);
+ transform: rotate(-45deg);
+ }
+
+ &>a {
+ display: block;
+ z-index: 2;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ opacity: 0;
+ position: absolute;
+ }
+ }
+
+ &__bottom-info {
+ position: fixed;
+ width: 100%;
+ left: 0;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ bottom: 0;
+ padding: 18px;
+ background: #fff;
+ border-top: solid #d9d9d9 1px;
+ z-index: 3;
+ }
+
+ &__subtotal {
+ font-weight: 600;
+ }
+
+ &__footer-buttons {
+ a {
+ padding: 12px;
+ display: block;
+ width: fit-content;
+ background: #09467f;
+ border-radius: 4px;
+ text-decoration: none;
+ color: #fff;
+ font-size: 12px;
+ }
+ }
+ }
+
+ .product-thumbnail {
+ background: #eee;
+ border-radius: 9px;
+ height: 70px;
+ }
+
+ .product-change-amount {
+ width: 50px;
+ height: 30px;
+ margin-top: 4px;
+ border-radius: 5px;
+ display: flex;
+ border: solid #cbcbcb 1px;
+ color: #525252;
+ cursor: pointer;
+ user-select: none;
+
+ &__symbol {
+ width: 50%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border-right: solid #cbcbcb 1px;
+ }
+ }
+
+ .cart-product {
+ &__supplements {
+ position: absolute;
+ right: 0;
+ bottom: 26px;
+ padding: 8px;
+ background: #f9b004;
+ color: #fff;
+ border-radius: 4px;
+ cursor: pointer;
+ }
+ }
+
+ .woocommerce-mini-cart__empty-message {
+ text-align: center;
+ margin-top: 16px;
+ }
+}
\ No newline at end of file
diff --git a/angular/src/app/pages/cart/cart.component.ts b/angular/src/app/pages/cart/cart.component.ts
index a449d1f..b3f3465 100644
--- a/angular/src/app/pages/cart/cart.component.ts
+++ b/angular/src/app/pages/cart/cart.component.ts
@@ -1,4 +1,8 @@
import { Component, OnInit } from '@angular/core';
+import { Order } from 'src/app/models/order';
+import { OrderProduct } from 'src/app/models/order-product';
+import { CartService, ProductAmountAction } from 'src/app/services/cart.service';
+import { OrderService } from 'src/app/services/order.service';
@Component({
selector: 'app-cart',
@@ -6,10 +10,52 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./cart.component.scss']
})
export class CartComponent implements OnInit {
+ public loading = false;
+ public orderConfirmed = false;
+ public order!: Order;
+ public price!: number;
- constructor() { }
+ constructor(
+ private orderService: OrderService,
+ private cartService: CartService
+ ) { }
ngOnInit(): void {
+ this.loadCart()
+ }
+
+ async loadCart(): Promise {
+ this.loading = true;
+ this.order = await this.orderService.getOrder(true);
+ if (this.order) this.price = this.order.price
+ this.loading = false;
+ }
+
+ removeFromCart(event: Event, guid: string): void{
+ event.preventDefault();
+ this.orderService.removeFromCart(guid);
+ }
+
+ confirmOrder(event: Event): void{
+ event.preventDefault();
+ this.orderConfirmed = true;
+ // this.confirm.emit();
+ }
+
+ setAmount(product: OrderProduct, method: 'plus' | 'minus') {
+ if (method === 'plus') {
+ this.cartService.changeAmountProduct(product.guid, ProductAmountAction.increment)
+ product.amount++
+ this.price = this.price + Number(product.price);
+ } else if (method === 'minus' && product.amount > 1) {
+ this.cartService.changeAmountProduct(product.guid, ProductAmountAction.decrement)
+ product.amount--
+ this.price = this.price - Number(product.price);
+ }
+ }
+
+ orderSubmitted() {
+
}
}
diff --git a/angular/src/app/services/autocomplete.service.ts b/angular/src/app/services/autocomplete.service.ts
new file mode 100644
index 0000000..bb9b8ac
--- /dev/null
+++ b/angular/src/app/services/autocomplete.service.ts
@@ -0,0 +1,77 @@
+import {Injectable} from '@angular/core';
+import {HttpClient, HttpHeaders} from "@angular/common/http";
+import {map} from "rxjs/operators";
+import {lastValueFrom, Observable} from "rxjs";
+
+enum CompleteType {
+ city,
+ street,
+}
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AutocompleteService {
+
+ private city!: string;
+ private cityId!: string;
+ private query?: string
+ private streets: string[] = [];
+
+ constructor(private http: HttpClient) {
+ }
+
+ async setCity(city: string | null): Promise {
+ if (city && this.city != city) {
+ this.city = city;
+ let headers = new HttpHeaders();
+ const cityData = await lastValueFrom(
+ this._request(`query=${city}&contentType=city`)
+ .pipe(
+ map(
+ (res: any) => res.result.filter(
+ (city: any) => city.id != 'Free'
+ )[0]
+ ),
+ )
+ );
+ this.cityId = cityData.id;
+ return true;
+ }
+ return false;
+ }
+
+ async queryStreet(query: string, city: string | null = null): Promise {
+ let headers = new HttpHeaders();
+ headers = headers.set('Content-Type', 'application/json');
+ let newCityId = await this.setCity(city);
+ if (!this.cityId) {
+ return [];
+ }
+ if (!this.query || this.query !== query || newCityId) {
+ this.query = query;
+ this.streets = await lastValueFrom(
+ this._request(`query=${query}&offset=0&limit=20&cityId=${this.cityId}&contentType=street`)
+ .pipe(
+ map(
+ (res: any) => res.result
+ .filter(
+ (street: any) => street.id != 'Free'
+ )
+ .map(
+ (street: any) => street.name
+ )
+ )
+ )
+ );
+ }
+ return this.streets;
+ }
+
+ //jsonp запрос для обхода cors кладра
+ _request(params: String): Observable {
+ const src = '//kladr-api.ru/api.php?';
+ return this.http.jsonp(src + params, 'callback');
+ };
+
+}
diff --git a/angular/src/app/services/cart.service.ts b/angular/src/app/services/cart.service.ts
new file mode 100644
index 0000000..5d407ac
--- /dev/null
+++ b/angular/src/app/services/cart.service.ts
@@ -0,0 +1,111 @@
+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';
+
+export enum ProductAmountAction {
+ increment,
+ decrement,
+}
+
+@Injectable({
+ providedIn: 'root'
+})
+export class CartService {
+
+ constructor(
+ private cookieService: CookiesService,
+ private wpJsonService: WpJsonService,
+ ) { }
+
+ private cart!: Cart;
+
+ public cartCount$ = new Subject();
+
+
+ getCart(){
+ return this._getCartProducts();
+ }
+
+
+ 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 ++;
+ }
+ else {
+ cart.products.push(product);
+ this.cartCount$.next(cart.products.length);
+ }
+ this.cookieService.setCookie('cart', JSON.stringify(cart));
+ }
+
+ removeFromCart(guid: string): void{
+ const cart = this._getCartProducts();
+ if(!cart.products){
+ return;
+ }
+ cart.products = cart.products.filter((value) => value.guid !== guid);
+ this.cookieService.setCookie('cart', JSON.stringify(cart));
+ this.cartCount$.next(cart.products.length);
+ }
+
+ 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));
+ }
+
+ changeAmountProduct(productTempId: string,action: ProductAmountAction): void{
+ const cart = this._getCartProducts();
+ if(!cart.products){
+ return;
+ }
+ const product: CartProduct | undefined = cart.products.find((value) => value.guid === productTempId);
+ if(product && action === ProductAmountAction.increment){
+ product.amount++
+ // product.increment();
+ }
+ else if(product && action === ProductAmountAction.decrement){
+ product.amount--
+ // product.decrement();
+ }
+ this.cookieService.setCookie('cart', JSON.stringify(cart));
+ this.cartCount$.next(cart.products.length);
+ }
+
+ clearCart(){
+ this.cart = {products: []};
+ this.cookieService.setCookie('cart', JSON.stringify(this.cart));
+ this.cartCount$.next(0);
+ }
+
+ _getCartProducts(): Cart{
+ if(this.cart){
+ return this.cart;
+ }
+
+ const cartJson = this.cookieService.getItem('cart');
+ this.cart = cartJson ? JSON.parse(cartJson) : {products: []};
+ return this.cart;
+ }
+
+ get cartCount(): number{
+ return this._getCartProducts().products.length;
+ }
+}
diff --git a/angular/src/app/services/order.service.ts b/angular/src/app/services/order.service.ts
new file mode 100644
index 0000000..a9b8eb9
--- /dev/null
+++ b/angular/src/app/services/order.service.ts
@@ -0,0 +1,113 @@
+import {Injectable} from '@angular/core';
+import {CartService} from "./cart.service";
+import {WpJsonService} from "./wp-json.service";
+import {forkJoin, lastValueFrom, Observable, tap} from "rxjs";
+import {Cart, DeliveryData, DeliveryType, Modifier, Product, UserData} from "../interface/data";
+import {Order} from "../models/order";
+import {OrderProduct} from "../models/order-product";
+import {JsonrpcService, RpcService} from "./jsonrpc.service";
+import {CookiesService} from "./cookies.service";
+import {MessageService} from "primeng/api";
+import {map} from "rxjs/operators";
+import { cloneDeep } from 'lodash';
+import { environment } from 'src/environments/environment';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class OrderService {
+
+ private order!: Order;
+
+ constructor(
+ private cartService: CartService,
+ private wpJsonService: WpJsonService,
+ private jsonRpcService: JsonrpcService,
+ private cookiesService: CookiesService,
+ private messageService: MessageService,
+ ) {
+ }
+
+ async getDeliveryTypes(): Promise {
+ return await lastValueFrom(this.wpJsonService.getDeliveryTypes());
+ }
+
+
+ async getOrder(refresh = false): Promise {
+ if (!this.order || refresh) {
+ const cart = this.cartService.getCart();
+
+ if (cart.products.length) {
+ const products = await this.getProducts(cart);
+ const additionalInfo = this.jsonRpcService.rpc({
+ method: 'getAdditionalInfo',
+ params: []
+ }, RpcService.authService, true);
+
+ const tokenData = this.jsonRpcService.rpc({
+ method: 'getTokenData',
+ params: [this.cookiesService.getItem('token')],
+ }, RpcService.authService, true);
+
+ const info = await lastValueFrom(forkJoin([additionalInfo, tokenData, products]));
+ const token = this.cookiesService.getItem('token')
+ this.order = new Order({products: products, userData: info[0]?.data, phone: info[1].data?.mobile_number, token: token});
+ }
+ }
+ return this.order;
+ }
+
+ async getProducts(cart: Cart): Promise {
+ const allData = await lastValueFrom(this.wpJsonService.getAllData())
+ const products: OrderProduct[] = []
+ for (let i = 0; i < cart.products.length; i++) {
+ const productSub = allData.products.find((product: any) => product.id === cart.products[i].id)
+ const product = Object.assign(cloneDeep(cart.products[i]), {
+ category_id: 0,
+ price: productSub.price,
+ currency_symbol: '₽',
+ description: '',
+ short_description: '',
+ image_gallery: [],
+ image: productSub.image,
+ modifier_data: cart.products[i].modifiers,
+ stock_status: 'instock',
+ groupId: productSub.groupId,
+ modifiers_group: productSub.modifiers_group
+ })
+ const orderProduct: OrderProduct = new OrderProduct(product, cart.products[i].guid, cart.products[i].amount)
+ products.push(orderProduct)
+ }
+ return products
+ }
+
+ removeFromCart(productGuid: string): void {
+ this.order.products = this.order.products.filter(value => value.guid !== productGuid);
+ this.cartService.removeFromCart(productGuid);
+ }
+
+ setUserData(userData: UserData): void {
+ this.order.userData = userData;
+ }
+
+ setDeliveryData(deliveryData: DeliveryData): void {
+ this.order.deliveryData = deliveryData;
+ }
+
+ submit(): Observable {
+ return this.wpJsonService.createOrder(this.order.toJson(), environment.webhookItRetail).pipe(
+ tap({
+ next: (_) => {
+ this.jsonRpcService.rpc({
+ method: 'updateAdditionalInfo',
+ params: [this.order.userData, this.order.deliveryData]
+ }, RpcService.authService, true).subscribe();
+ this.messageService.add({
+ severity:'success',
+ summary: 'Заказ создан',
+ });
+ },
+ }),
+ );
+ }
+}
diff --git a/angular/src/app/services/wp-json.service.ts b/angular/src/app/services/wp-json.service.ts
index 97dd18e..3d29f48 100644
--- a/angular/src/app/services/wp-json.service.ts
+++ b/angular/src/app/services/wp-json.service.ts
@@ -30,8 +30,8 @@ export class WpJsonService {
return this._request('orders/delivery-types', 'GET');
}
- createOrder(order: any){
- return this._request('orders', 'POST', order);
+ createOrder(order: any, url: string){
+ return this._request('', 'POST', order);
}
getOrders(): Observable{
@@ -46,7 +46,7 @@ export class WpJsonService {
return this._request('static/nomen_1eb3fb56-3c4c-43b7-9a04-ce532ab7548f.json', 'GET')
}
- _request(path: string, method: string, body?: any, auth = false): Observable {
+ _request(path: string, method: string, body?: any, auth = false, baseUrl = null): Observable {
const token = decodeURI(this.cookiesService.getItem('token') ?? '');
let headers = new HttpHeaders();
headers = headers.set('Content-Type', 'application/json');
@@ -60,8 +60,10 @@ export class WpJsonService {
body: this.body,
};
- const url = environment.production ? window.location.origin + '/' : this.api
-
+ let url = environment.production ? window.location.origin + '/' : this.api
+ if (baseUrl) {
+ url = baseUrl
+ }
return this.http
.request( method, url + path + urlToken, options);
}
diff --git a/angular/src/app/validators/street.validator.ts b/angular/src/app/validators/street.validator.ts
new file mode 100644
index 0000000..e074f02
--- /dev/null
+++ b/angular/src/app/validators/street.validator.ts
@@ -0,0 +1,25 @@
+import { Injectable } from '@angular/core';
+import {AbstractControl, ValidationErrors, AsyncValidator} from '@angular/forms';
+import {AutocompleteService} from "../services/autocomplete.service";
+
+
+
+@Injectable({ providedIn: 'root' })
+export class StreetValidator implements AsyncValidator {
+ constructor(private autocompleteService: AutocompleteService) {}
+
+ async validate(
+ control: AbstractControl
+ ): Promise {
+ try{
+ const streets = await this.autocompleteService.queryStreet(control.value);
+ if(streets.includes(control.value)){
+ return null;
+ }
+ return { validStreet: false }
+ }
+ catch (e){
+ return { validStreet: false }
+ }
+ }
+}
diff --git a/angular/src/environments/environment.prod.ts b/angular/src/environments/environment.prod.ts
index 3aaa6e8..e04bd00 100644
--- a/angular/src/environments/environment.prod.ts
+++ b/angular/src/environments/environment.prod.ts
@@ -20,5 +20,7 @@ export const environment = {
version: packageJson.version,
appleWalletEndpoint: 'https://apple-push-notifications.it-retail.tech/apns/api',
appleWalletSecret: 'Token F5mbzEERAznGKVbB6l',
- clientName: 'Sakura'
+ webhookItRetail: 'https://webhook.it-retail.tech/handlers/tillda/1eb3fb56-3c4c-43b7-9a04-ce532ab7548f',
+ clientName: 'Sakura',
+ cities: ['Менделеевск'],
}
diff --git a/angular/src/environments/environment.ts b/angular/src/environments/environment.ts
index 4548360..0a45e0f 100644
--- a/angular/src/environments/environment.ts
+++ b/angular/src/environments/environment.ts
@@ -4,7 +4,7 @@ export const environment = {
production: false,
appAuthEndpoint: 'https://auth.crm4retail.ru/tnt',
appBonusEndpoint: 'https://customerapi2.mi.crm4retail.ru/json.rpc/',
- appWPEndpoint: './assets/',
+ appWPEndpoint: './',
hasBonusProgram: true,
systemId: 'g6zyv8tj53w28ov7cl',
defaultUrl: 'http://192.168.0.179:4200',
@@ -20,5 +20,7 @@ export const environment = {
version: packageJson.version,
appleWalletEndpoint: 'http://192.168.0.179:4200/apns/api',
appleWalletSecret: 'Token F5mbzEERAznGKVbB6l',
- clientName: 'Sakura'
+ webhookItRetail: 'https://webhook.it-retail.tech/handlers/tillda/1eb3fb56-3c4c-43b7-9a04-ce532ab7548f',
+ clientName: 'Sakura',
+ cities: ['Менделеевск'],
};
diff --git a/angular/src/styles.scss b/angular/src/styles.scss
index bac2ba7..32163cf 100644
--- a/angular/src/styles.scss
+++ b/angular/src/styles.scss
@@ -22,6 +22,29 @@ table{border-collapse:collapse;border-spacing:0}
border-color: red;
}
+.p-dropdown {
+ width: 100%;
+ height: 39px;
+}
+
+.p-selectbutton {
+ display: flex;
+ &>.p-button{
+ flex: 1;
+ font-size: 12px;
+ }
+}
+
+.p-selectbutton .p-button.p-highlight {
+ background: #f9b004;
+ border-color: #f9b004;
+}
+
+.p-selectbutton .p-button.p-highlight:hover {
+ background: #f9b004;
+ border-color: #f9b004;
+}
+
mark {
padding: 4px;
background: #009688;
@@ -64,3 +87,16 @@ button {
input::-webkit-date-and-time-value {
text-align: left;
}
+
+.p-treeselect {
+ min-width: 180px;
+}
+
+.p-tree .p-tree-container .p-treenode {
+ font-size: 12px;
+ padding: 0;
+}
+
+.p-tree .p-tree-container .p-treenode .p-treenode-content {
+ padding: 0;
+}