diff --git a/angular/src/app/app.constants.ts b/angular/src/app/app.constants.ts index eb42cd9..7bf7127 100644 --- a/angular/src/app/app.constants.ts +++ b/angular/src/app/app.constants.ts @@ -27,7 +27,7 @@ export const PageListWithBonus: Page[] = [ name: 'Ваша карта лояльности', description: '', resName: 'bonus-program', - onSideBar: true, + onSideBar: false, }, { code: PageCode.Orders, diff --git a/angular/src/app/app.module.ts b/angular/src/app/app.module.ts index a6b3eea..be38951 100644 --- a/angular/src/app/app.module.ts +++ b/angular/src/app/app.module.ts @@ -56,6 +56,10 @@ import { ChangeQuantityComponent } from './components/change-quantity/change-qua import { MenuComponent } from './components/menu/menu.component'; import { SidebarModule } from 'primeng/sidebar'; import {RippleModule} from 'primeng/ripple'; +import {MatTabsModule} from '@angular/material/tabs'; +import { ModifierComponent } from './components/modifier/modifier.component'; +import { OptionComponent } from './components/option/option.component'; +import { ChangeQuantityOptionDirective } from './directives/change-quantity-option.directive'; const routes: Routes = [ { path: '', redirectTo: 'products', pathMatch: 'full' }, @@ -88,7 +92,10 @@ const routes: Routes = [ UserDataOrderComponent, InfoComponent, ChangeQuantityComponent, - MenuComponent + MenuComponent, + ModifierComponent, + OptionComponent, + ChangeQuantityOptionDirective ], imports: [ BrowserModule, @@ -127,7 +134,8 @@ const routes: Routes = [ PaginatorModule, InputTextModule, SidebarModule, - RippleModule + RippleModule, + MatTabsModule ], providers: [DialogService, MessageService, MessagingService ], bootstrap: [AppComponent] diff --git a/angular/src/app/components/change-quantity/change-quantity.component.scss b/angular/src/app/components/change-quantity/change-quantity.component.scss index 36b7259..aa90499 100644 --- a/angular/src/app/components/change-quantity/change-quantity.component.scss +++ b/angular/src/app/components/change-quantity/change-quantity.component.scss @@ -1,12 +1,14 @@ :host { .change-quantity { border: 1px solid #dfdee2; - border-radius: 4px; + border-radius: 1.125rem; display: flex; align-items: center; justify-content: space-between; - height: 28px; - margin: 4px 16px 4px 0; + margin: 0; + height: 100%; + min-width: 100px; + padding: 0 8px; button { background: #ffffff; diff --git a/angular/src/app/components/exit/exit.component.scss b/angular/src/app/components/exit/exit.component.scss index a8d444c..409fada 100644 --- a/angular/src/app/components/exit/exit.component.scss +++ b/angular/src/app/components/exit/exit.component.scss @@ -2,7 +2,7 @@ button { margin-right: 1rem; padding: 4px 21px; margin-top: 8px; - background-color: #09467f; + background-color: var(--orange-main); color: #fff; border-radius: 3px; border: none; diff --git a/angular/src/app/components/footer-buttons/footer-buttons.component.scss b/angular/src/app/components/footer-buttons/footer-buttons.component.scss index a027c25..f36f0cc 100644 --- a/angular/src/app/components/footer-buttons/footer-buttons.component.scss +++ b/angular/src/app/components/footer-buttons/footer-buttons.component.scss @@ -15,7 +15,7 @@ align-items: center; width: 60px; height: 60px; - background: #09467f; + background: var(--orange-main); border: solid #fff 1px !important; color: #fff; border-radius: 100%; diff --git a/angular/src/app/components/modifier/modifier.component.html b/angular/src/app/components/modifier/modifier.component.html new file mode 100644 index 0000000..98c8128 --- /dev/null +++ b/angular/src/app/components/modifier/modifier.component.html @@ -0,0 +1,12 @@ +
+ +

{{ modifier.name }}

+
+ +
+
diff --git a/angular/src/app/components/modifier/modifier.component.scss b/angular/src/app/components/modifier/modifier.component.scss new file mode 100644 index 0000000..318f5d0 --- /dev/null +++ b/angular/src/app/components/modifier/modifier.component.scss @@ -0,0 +1,15 @@ +:host { + .modifier-container { + margin-bottom: 32px; + &__options { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 12px; + } + + h3 { + font-weight: 500; + } + } +} \ No newline at end of file diff --git a/angular/src/app/components/modifier/modifier.component.spec.ts b/angular/src/app/components/modifier/modifier.component.spec.ts new file mode 100644 index 0000000..b30fc0f --- /dev/null +++ b/angular/src/app/components/modifier/modifier.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ModifierComponent } from './modifier.component'; + +describe('ModifierComponent', () => { + let component: ModifierComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ModifierComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ModifierComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/angular/src/app/components/modifier/modifier.component.ts b/angular/src/app/components/modifier/modifier.component.ts new file mode 100644 index 0000000..4b641d7 --- /dev/null +++ b/angular/src/app/components/modifier/modifier.component.ts @@ -0,0 +1,19 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { CartModifier, Modifier } from 'src/app/interface/data'; +import { CartProduct } from 'src/app/models/cart-product'; + +@Component({ + selector: 'app-modifier[modifier][product]', + templateUrl: './modifier.component.html', + styleUrls: ['./modifier.component.scss'] +}) +export class ModifierComponent implements OnInit { + @Input() product!: CartProduct + @Input() modifier!: CartModifier + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/angular/src/app/components/navbar/navbar.component.scss b/angular/src/app/components/navbar/navbar.component.scss index 6ba4bcf..9e1a7d4 100644 --- a/angular/src/app/components/navbar/navbar.component.scss +++ b/angular/src/app/components/navbar/navbar.component.scss @@ -3,7 +3,7 @@ padding: 0 54px; width: 100%; height: 54px; - // background: #09467f; + // background: var(--orange-main); color: #fff; display: flex; align-items: center; diff --git a/angular/src/app/components/option/option.component.html b/angular/src/app/components/option/option.component.html new file mode 100644 index 0000000..4ac0676 --- /dev/null +++ b/angular/src/app/components/option/option.component.html @@ -0,0 +1,17 @@ +
+ {{ option.name }} +

{{ option.name }}

+
diff --git a/angular/src/app/components/option/option.component.scss b/angular/src/app/components/option/option.component.scss new file mode 100644 index 0000000..bbde19e --- /dev/null +++ b/angular/src/app/components/option/option.component.scss @@ -0,0 +1,67 @@ +:host { + width: 100%; + max-width: 170px; + .option-container { + align-items: center; + border: 1px solid #d8d8d8; + border-radius: 1.125rem; + display: flex; + flex-direction: column; + gap: 8px; + padding: 0; + padding-bottom: 12px; + position: relative; + -webkit-user-select: none; + user-select: none; + width: 100%; + height: 100%; + overflow: hidden; + text-align: center; + cursor: pointer; + + &.selected { + border-color: var(--orange-main); + } + + &[quantity] { + &::before { + content: attr(quantity); + display: block; + width: 35px; + height: 35px; + background-color: var(--orange-main); + position: absolute; + border-radius: 100%; + display: flex; + justify-content: center; + align-items: center; + color: #fff; + right: 10px; + top: 6px; + opacity: 0.8; + } + } + + &[quantity="0"] { + &::before { + display: none; + } + } + + img { + width: 100%; + object-fit: cover; + } + + h4 { + font-size: 14px; + margin: 0 10px; + } + } +} + +@media screen and (max-width: 500px) { + :host { + max-width: calc(50% - 6px); + } +} \ No newline at end of file diff --git a/angular/src/app/components/option/option.component.spec.ts b/angular/src/app/components/option/option.component.spec.ts new file mode 100644 index 0000000..3441a96 --- /dev/null +++ b/angular/src/app/components/option/option.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { OptionComponent } from './option.component'; + +describe('OptionComponent', () => { + let component: OptionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ OptionComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(OptionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/angular/src/app/components/option/option.component.ts b/angular/src/app/components/option/option.component.ts new file mode 100644 index 0000000..540a3df --- /dev/null +++ b/angular/src/app/components/option/option.component.ts @@ -0,0 +1,20 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { CartModifier, Modifier } from 'src/app/interface/data'; +import { CartProduct } from 'src/app/models/cart-product'; + +@Component({ + selector: 'app-option[option][modifier][product]', + templateUrl: './option.component.html', + styleUrls: ['./option.component.scss'] +}) +export class OptionComponent implements OnInit { + @Input() option!: Modifier + @Input() modifier!: CartModifier; + @Input() product!: CartProduct; + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/angular/src/app/components/product-modal/product-modal.component.html b/angular/src/app/components/product-modal/product-modal.component.html index 6734afd..1b5ce23 100644 --- a/angular/src/app/components/product-modal/product-modal.component.html +++ b/angular/src/app/components/product-modal/product-modal.component.html @@ -1,10 +1,20 @@
- {{product.name}} - {{product.name}} +
+ {{ product.name }} +

Lorem ipsum dolor sit amet consectetur adipisicing elit. Quasi quas libero consequatur dolorum itaque dolor deserunt aut facilis debitis!

+
-
- -
+ + + {{ index + 1 }} из {{ cartProduct.modifiers.length }} + +
- +
diff --git a/angular/src/app/components/product-modal/product-modal.component.scss b/angular/src/app/components/product-modal/product-modal.component.scss index 7ac6c09..b447ea8 100644 --- a/angular/src/app/components/product-modal/product-modal.component.scss +++ b/angular/src/app/components/product-modal/product-modal.component.scss @@ -1,100 +1,140 @@ :host { - .product-modal { - position: relative; - padding-bottom: 60px; + .product-modal { + position: relative; + padding-bottom: 39px; + display: flex; + flex-direction: row; + gap: 32px; - &>img { - width: 100%; - border-radius: 1.125rem; - margin-top: 16px; - } - - &__footer { - width: 100%; - height: auto; - display: flex; - position: absolute; - bottom: 0; - padding-top: 8px; - border-top: solid #d1d1d1 1px; - flex-direction: row; - justify-content: space-between; - align-items: center; - } - - &__footer-price { - font-size: 18px; - font-weight: 600; - } - - &__add-to-cart { - padding: 8px 26px; - background: #C43F00; - border: none; - border-radius: 1.125rem; - color: #fff; - } - - &__modifier { - border: 1px solid rgba(128, 128, 128, 0.23); - margin: 0.5em 0; - padding-bottom: 6px; - border-radius: 1.125rem; - - a { - width: 100%; - display: block; - padding: 0.75em; - border-radius: 0.15em; - transition: background .3s ease; - } - - .options-container { - transition: opacity .5s ease; - padding-left: 16px; - display: none; - opacity: 0; - - &.isShow { - display: block; - opacity: 1; - } - } - - &.no-valid { - border-color: #cc0000; - } - } - - &__modifier-icon { - float: right; - - &::before, - &::after { - display: inline-block; - font-size: 14px; - transition: transform .5s ease; - } - - &::before { - content: "\\"; - transform: rotate(346deg); - } - - &::after { - content: "/"; - transform: rotate(14deg); - } - - &.isShow { - &::before { - transform: rotate(76deg); - } - - &::after { - transform: rotate(104deg); - } - } - } + &__information-container { + max-width: calc(50% - 16px); + img { + width: 100%; + border-radius: 1.125rem; + height: fit-content; + object-fit: cover; + } } -} \ No newline at end of file + + &__description { + margin-top: 8px; + font-size: 14px; + } + + // & > img { + // width: 50%; + // border-radius: 1.125rem; + // height: fit-content; + // object-fit: cover; + // } + + &__footer { + width: 100%; + height: auto; + display: flex; + position: absolute; + bottom: -14px; + padding-top: 16px; + border-top: solid #d1d1d1 1px; + flex-direction: row; + justify-content: space-between; + align-items: center; + } + + &__footer-price { + font-size: 18px; + font-weight: 600; + } + + &__footer-buttons { + display: flex; + flex-direction: row; + gap: 8px; + } + + &__add-to-cart { + padding: 8px 26px; + background: var(--orange-main); + border: none; + border-radius: 1.125rem; + color: #fff; + } + + &__modifier { + border: 1px solid rgba(128, 128, 128, 0.23); + margin: 0.5em 0; + padding-bottom: 6px; + border-radius: 1.125rem; + + a { + width: 100%; + display: block; + padding: 0.75em; + border-radius: 0.15em; + transition: background 0.3s ease; + } + + .options-container { + transition: opacity 0.5s ease; + padding-left: 16px; + display: none; + opacity: 0; + + &.isShow { + display: block; + opacity: 1; + } + } + + &.no-valid { + border-color: #cc0000; + } + } + + &__modifiers-container { + height: 400px; + overflow-y: auto; + } + + &__modifier-icon { + float: right; + + &::before, + &::after { + display: inline-block; + font-size: 14px; + transition: transform 0.5s ease; + } + + &::before { + content: "\\"; + transform: rotate(346deg); + } + + &::after { + content: "/"; + transform: rotate(14deg); + } + + &.isShow { + &::before { + transform: rotate(76deg); + } + + &::after { + transform: rotate(104deg); + } + } + } + } + + @media screen and (max-width: 1070px) { + .product-modal { + flex-direction: column; + + &__information-container { + max-width: 100%; + } + } + } +} diff --git a/angular/src/app/components/product-modal/product-modal.component.ts b/angular/src/app/components/product-modal/product-modal.component.ts index d2e54af..b531452 100644 --- a/angular/src/app/components/product-modal/product-modal.component.ts +++ b/angular/src/app/components/product-modal/product-modal.component.ts @@ -32,7 +32,7 @@ export class ProductModalComponent implements OnInit { this.product = this.config.data.product this.modifiersGroups = this.config.data.modifiersGroups this.modifiers = this.config.data.modifiers - this.cartProduct = new CartProduct(this.product.id, this.product.name, this.modifiersFilter(), this.modifiers); + this.cartProduct = new CartProduct(this.product.id, this.product.name, this.modifiersFilter(), this.modifiers, this.product.price); } modifiersFilter(): ModifiersGroup[] { @@ -43,25 +43,6 @@ export class ProductModalComponent implements OnInit { return this.modifiers.filter((modifier) => modifier.groupId === modifierGroup.id) } - changeQuantity(event: any) { - const value: ChangeValue = event.value - const modifierGroup: CartModifier = event.modifierGroup - const option: Modifier = event.option - - const modifGroup = this.cartProduct.modifiers.find((modifGroup) => modifGroup.idLocal === modifierGroup.idLocal) - const modifier = modifGroup?.options.find((modifier) => modifier.idLocal == option.idLocal) - - if (!modifier) return - if (!modifier.quantity && modifier.quantity !== 0) modifier.quantity = modifier.restrictions.byDefault - if (value.type === 'minus') { - modifier.quantity -= value.variableQuantity - } else { - modifier.quantity += value.variableQuantity - } - } - - - selectedOptions(modifier: ModifiersGroup): Modifier[] { const cartModifier = this.cartProduct.modifiers.find(cartModifier => cartModifier.id === modifier.id) if (modifier.restrictions.maxQuantity === 1 && modifier.restrictions.minQuantity === 1) { @@ -103,6 +84,15 @@ export class ProductModalComponent implements OnInit { element.setAttribute('isShow', 'true') } + changeProductAmount({type, variableQuantity}: ChangeValue) { + if (type === 'plus') { + this.cartProduct.increment() + } else { + this.cartProduct.decrement() + } + + } + addToCart(event: Event) { if (event) { event.preventDefault() 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 index d1eddc4..012640d 100644 --- 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 @@ -68,7 +68,7 @@ flex-direction: column; align-items: center; gap: 8px; - border: solid #09467f 1px; + border: solid var(--orange-main) 1px; border-radius: 16px; overflow: hidden; padding-bottom: 16px; @@ -137,7 +137,7 @@ } .elementor-button--checkout { - background-color: #09467f; + background-color: var(--orange-main); color: #fff; border-radius: 6px; display: flex; diff --git a/angular/src/app/directives/change-quantity-option.directive.spec.ts b/angular/src/app/directives/change-quantity-option.directive.spec.ts new file mode 100644 index 0000000..6127623 --- /dev/null +++ b/angular/src/app/directives/change-quantity-option.directive.spec.ts @@ -0,0 +1,8 @@ +import { ChangeQuantityOptionDirective } from './change-quantity-option.directive'; + +describe('ChangeQuantityOptionDirective', () => { + it('should create an instance', () => { + const directive = new ChangeQuantityOptionDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/angular/src/app/directives/change-quantity-option.directive.ts b/angular/src/app/directives/change-quantity-option.directive.ts new file mode 100644 index 0000000..a9518bf --- /dev/null +++ b/angular/src/app/directives/change-quantity-option.directive.ts @@ -0,0 +1,65 @@ +import { + Directive, + ElementRef, + HostListener, + Input, + OnInit, +} from '@angular/core'; +import { CartModifier, Modifier } from '../interface/data'; +import { CartProduct } from '../models/cart-product'; + +@Directive({ + selector: '[appChangeQuantityOption]', +}) +export class ChangeQuantityOptionDirective implements OnInit { + @Input() option!: Modifier; + @Input() modifier!: CartModifier; + @Input() product!: CartProduct; + + constructor(private el: ElementRef) {} + + ngOnInit(): void { + this.option.quantity = this.option.restrictions.byDefault; + + if (!this.modifier.lastChangeOption && this.option.restrictions.byDefault) { + this.modifier.lastChangeOption = this.option.idLocal; + } + } + + @HostListener('click') onOptionClick() { + this.changeQuantity({ option: this.option, modifier: this.modifier }); + } + + changeQuantity({ + option, + modifier, + }: { + option: Modifier; + modifier: CartModifier; + }): void { + if (!option.quantity && option.quantity !== 0) { + return; + } + const quantity = option.quantity; + const allQuantity = modifier.allQuantity; + const maxOptionQ = option.restrictions.maxQuantity; + const minOptionQ = option.restrictions.minQuantity; + const maxModifierQ = modifier.restrictions.maxQuantity; + const minModifierQ = modifier.restrictions.minQuantity; + const plusCondition = + (quantity < maxOptionQ && allQuantity < maxModifierQ) || + (maxOptionQ === 0 && (allQuantity < maxModifierQ || maxModifierQ === 0)); + + if (!plusCondition && quantity > minOptionQ && allQuantity > minModifierQ) { + this.product.decrementOption(modifier.idLocal, option.idLocal); + } + if (allQuantity === maxModifierQ && modifier.lastChangeOption) { + this.product.decrementOption(modifier.idLocal, modifier.lastChangeOption); + this.changeQuantity({ option, modifier }); + } + if (plusCondition) { + this.product.incrementOption(modifier.idLocal, option.idLocal); + modifier.lastChangeOption = option.idLocal; + } + } +} diff --git a/angular/src/app/interface/data.ts b/angular/src/app/interface/data.ts index 48ae207..93731f2 100644 --- a/angular/src/app/interface/data.ts +++ b/angular/src/app/interface/data.ts @@ -157,6 +157,7 @@ export interface Modifier { groupId: string, price?: number, quantity?: number, + image?: string; restrictions: { minQuantity: number, maxQuantity: number, @@ -166,6 +167,7 @@ export interface Modifier { } export interface CartModifier { + lastChangeOption?: string; id: string; idLocal: string; name: string; diff --git a/angular/src/app/models/cart-product.ts b/angular/src/app/models/cart-product.ts index 68885e3..f59bf0e 100644 --- a/angular/src/app/models/cart-product.ts +++ b/angular/src/app/models/cart-product.ts @@ -1,14 +1,15 @@ -import { CartModifier, Modifier, ModifiersGroup, Option } from "../interface/data"; +import { CartModifier, Modifier, ModifiersGroup } from "../interface/data"; import { v4 as uuidv4 } from 'uuid'; export class CartProduct { - constructor(id: string, name: string, modifiers: ModifiersGroup[] = [], options: Modifier[], amount: number = 1) { + constructor(id: string, name: string, modifiers: ModifiersGroup[] = [], options: Modifier[], price: number, amount: number = 1) { this.id = id; this.guid = uuidv4(); this.amount = amount; this.name = name; + this.price = price * amount this.modifiers = modifiers.map(modifier => ({ name: modifier.name, id: modifier.id, @@ -33,6 +34,7 @@ export class CartProduct { guid: string; amount: number; name: string; + price: number; modifiers: CartModifier[]; increment(): void { @@ -45,6 +47,29 @@ export class CartProduct { } } + incrementOption(modifierId: string, optionId: string): void { + const modifier = this.modifiers.find((modifier) => modifier.idLocal === modifierId) + const option = modifier?.options.find((option) => option.idLocal === optionId) + if (!option?.quantity && option?.quantity !== 0) return + option.quantity++ + } + + decrementOption(modifierId: string, optionId: string): void { + const modifier = this.modifiers.find((modifier) => modifier.idLocal === modifierId) + const option = modifier?.options.find((option) => option.idLocal === optionId) + if (!option?.quantity && option?.quantity !== 0) return + option.quantity-- + } + + get finalPrice(): number{ + const modifiersPrice = this.modifiers.reduce((previousValue, currentValue) => { + return previousValue + currentValue.options.reduce((previousOptionValue, currentOptionValue) => { + return previousOptionValue + Number(currentOptionValue.price ? currentOptionValue.price * (currentOptionValue.quantity ?? 0) : 0); + }, 0); + }, 0); + return (Number(this.price) + modifiersPrice) * this.amount; + } + // addOption(modifier: ModifiersGroup, option: Modifier): void { // const productModifier = this.modifiers.find(value => value.id === modifier.id); // if (productModifier) { diff --git a/angular/src/app/pages/account/account.component.html b/angular/src/app/pages/account/account.component.html index 887238d..e204b93 100644 --- a/angular/src/app/pages/account/account.component.html +++ b/angular/src/app/pages/account/account.component.html @@ -6,7 +6,11 @@ >