Initial commit

This commit is contained in:
Nguyen Ha Khue
2023-04-20 12:49:16 +07:00
commit 7fe4d98325
1020 changed files with 99355 additions and 0 deletions
@@ -0,0 +1,45 @@
import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";
import { LoginComponent } from "./login/login.component";
import { ForbiddenComponent } from "./forbidden/forbidden.component";
import { SignupComponent } from "./signup/signup.component";
import { ResetPasswordComponent } from "./reset-password/reset-password.component";
import { SendEmailComponent } from "./send-email/send-email.component";
import { EmailVerifiedComponent } from "./email-verified/email-verified.component";
const routes: Routes = [
// {
// path: "login",
// component: LoginComponent,
// },
// {
// path: "forbidden",
// component: ForbiddenComponent,
// },
// {
// path: "signup",
// component: SignupComponent,
// },
// {
// path: "send-email",
// component: SendEmailComponent,
// },
// {
// path: "reset-password",
// component: ResetPasswordComponent,
// },
// {
// path: "email-verified",
// component: EmailVerifiedComponent,
// },
// {
// path: "email-not-verified",
// component: EmailVerifiedComponent,
// }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class AuthRoutingModule { }
+29
View File
@@ -0,0 +1,29 @@
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { AuthRoutingModule } from "./auth-routing.module";
import { LoginComponent } from "./login/login.component";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
import { CarouselModule } from "ngx-owl-carousel-o";
import { SharedModule } from "../../shared/shared.module";
import { ForbiddenComponent } from "./forbidden/forbidden.component";
import { SignupComponent } from "./signup/signup.component";
import { ResetPasswordComponent } from './reset-password/reset-password.component';
import { SendEmailComponent } from './send-email/send-email.component';
import { EmailVerifiedComponent } from './email-verified/email-verified.component';
import { EmailNotVerifiedComponent } from './email-not-verified/email-not-verified.component';
@NgModule({
declarations: [LoginComponent, SignupComponent, ForbiddenComponent, ResetPasswordComponent, SendEmailComponent, EmailVerifiedComponent, EmailNotVerifiedComponent],
imports: [
CommonModule,
AuthRoutingModule,
ReactiveFormsModule,
CarouselModule,
SharedModule,
NgbModule,
FormsModule],
})
export class AuthModule { }
@@ -0,0 +1,15 @@
<!-- Container-fluid starts-->
<div class="backgound-food d-flex justify-content-center align-items-center">
<div class="forbidden-box d-flex flex-column justify-content-center align-items-center responsive-box">
<img class="forbidden-img" src="../../../../assets/images/dashboard/403-forbidden.png">
<div class="forbidden-text"><span>Email</span> Not Verified</div>
<div class="forbidden-desc mt-1 mb-4">
<div>Tài khoản của bạn chưa được xác minh.</div>
<div>Vui lòng kiểm tra email để kích hoạt tài khoản.</div>
</div>
<div class="forbidden-btn">
<button class="btn btn-primary return-btn" (click)="goToGmail()">Đi tới mail</button>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
@@ -0,0 +1,60 @@
.backgound-food {
background-image: url("../../../../assets/images/background-login.jpg");
width: 100vw;
height: 100vh;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
z-index: -99;
.forbidden-box {
width: 822px;
// height: 536px;
background-color: #fff;
padding: 16px 110px 40px;
border-radius: 10px;
box-shadow: rgb(38, 57, 77) 0px 20px 30px -10px;
.forbidden-img {
max-width: 300px;
}
.forbidden-text {
font-family: 'Sedgwick Ave Display', cursive;
text-transform: uppercase;
font-size: 50px;
span {
color: lightcoral;
font-size: 60px;
}
}
.forbidden-desc {
font-size: 20px;
text-align: center;
font-family: 'Nunito', sans-serif;
}
.forbidden-btn {}
}
}
@media (max-width:768px) {
.backgound-food {
background-image: none;
.responsive-box {
width: 86%;
padding: 16px 20px 40px;
.forbidden-img {
max-width: 248px;
}
.forbidden-text {
text-align: center;
}
}
}
}
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EmailNotVerifiedComponent } from './email-not-verified.component';
describe('EmailNotVerifiedComponent', () => {
let component: EmailNotVerifiedComponent;
let fixture: ComponentFixture<EmailNotVerifiedComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ EmailNotVerifiedComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(EmailNotVerifiedComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,12 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-email-not-verified',
templateUrl: './email-not-verified.component.html',
styleUrls: ['./email-not-verified.component.scss']
})
export class EmailNotVerifiedComponent {
goToGmail() {
window.location.assign("https://mail.google.com/");
}
}
@@ -0,0 +1,16 @@
<!-- Container-fluid starts-->
<div class="backgound-food d-flex justify-content-center align-items-center">
<div class="forbidden-box d-flex flex-column justify-content-center align-items-center responsive-box">
<img class="forbidden-img" src="../../../../assets/images/dashboard/verified-vuong.png">
<div class="forbidden-text"><span>Email</span> Verified</div>
<div class="forbidden-desc mt-1 mb-4">
<div>Tài khoản của bạn đã được xác minh</div>
</div>
<div class="forbidden-btn w-100 d-flex justify-content-around align-items-center ">
<button class="btn btn-primary return-btn" [routerLink]="['/auth', 'login']">Đăng nhập cửa hàng</button>
<a href="https://foodify-fpt.vercel.app/home/login"><button class="btn btn-info return-btn">Đăng nhập
người dùng</button></a>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
@@ -0,0 +1,60 @@
.backgound-food {
background-image: url("../../../../assets/images/background-login.jpg");
width: 100vw;
height: 100vh;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
z-index: -99;
.forbidden-box {
width: 822px;
// height: 536px;
background-color: #fff;
padding: 16px 110px 40px;
border-radius: 10px;
box-shadow: rgb(38, 57, 77) 0px 20px 30px -10px;
.forbidden-img {
max-width: 300px;
}
.forbidden-text {
font-family: 'Sedgwick Ave Display', cursive;
text-transform: uppercase;
font-size: 50px;
span {
color: rgb(128, 240, 186);
font-size: 60px;
}
}
.forbidden-desc {
font-size: 20px;
text-align: center;
font-family: 'Nunito', sans-serif;
}
.forbidden-btn {}
}
}
@media (max-width:768px) {
.backgound-food {
background-image: none;
.responsive-box {
width: 86%;
padding: 16px 20px 40px;
.forbidden-img {
max-width: 248px;
}
.forbidden-text {
text-align: center;
}
}
}
}
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EmailVerifiedComponent } from './email-verified.component';
describe('EmailVerifiedComponent', () => {
let component: EmailVerifiedComponent;
let fixture: ComponentFixture<EmailVerifiedComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ EmailVerifiedComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(EmailVerifiedComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-email-verified',
templateUrl: './email-verified.component.html',
styleUrls: ['./email-verified.component.scss']
})
export class EmailVerifiedComponent {
}
@@ -0,0 +1,15 @@
<!-- Container-fluid starts-->
<div class="backgound-food d-flex justify-content-center align-items-center">
<div class="forbidden-box d-flex flex-column justify-content-center align-items-center responsive-box">
<img class="forbidden-img" src="../../../../assets/images/dashboard/403-forbidden.png">
<div class="forbidden-text"><span>403</span> Forbidden</div>
<div class="forbidden-desc mt-1 mb-4">
<div>Bạn không có quyền truy cập vào trang web này</div>
<div>hoặc tài khoản của bạn đã bị khoá.</div>
</div>
<div class="forbidden-btn">
<button class="btn btn-primary return-btn" [routerLink]="['/auth', 'login']">Quay về</button>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
@@ -0,0 +1,66 @@
.backgound-food {
background-image: url("../../../../assets/images/background-login.jpg");
width: 100vw;
height: 100vh;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
z-index: -99;
.forbidden-box {
width: 822px;
// height: 536px;
background-color: #fff;
padding: 16px 110px 40px;
border-radius: 10px;
box-shadow: rgb(38, 57, 77) 0px 20px 30px -10px;
.forbidden-img {
max-width: 300px;
}
.forbidden-text {
font-family: 'Sedgwick Ave Display', cursive;
text-transform: uppercase;
font-size: 50px;
span {
color: lightcoral;
font-size: 60px;
}
}
.forbidden-desc {
font-size: 20px;
text-align: center;
font-family: 'Nunito', sans-serif;
}
.forbidden-btn {
}
}
}
@media (max-width:768px) {
.backgound-food {
background-image: none;
.responsive-box {
width: 86%;
padding: 16px 20px 40px;
.forbidden-img {
max-width: 248px;
}
.forbidden-text {
text-align: center;
}
}
}
}
@@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-forbidden',
templateUrl: './forbidden.component.html',
styleUrls: ['./forbidden.component.scss']
})
export class ForbiddenComponent {
}
@@ -0,0 +1,207 @@
<div class="page-wrapper">
<div class="authentication-box backgound-food">
<div class="container">
<div class="row">
<div class="col-md-5 p-0 card-left disable-pink">
<div class="card bg-primary">
<div class="svg-icon">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 468.52 468.52"
style="enable-background:new 0 0 468.52 468.52;" xml:space="preserve">
<g>
<path style="fill:#E6E6E6;" d="M468.52,102.595v251.88c0,11.14-7.571,20.52-17.86,23.25c-1.98,0.531-4.06,0.81-6.2,0.81H38.58
c-13.29,0-24.061-10.77-24.061-24.06v-251.88c0-13.29,10.771-24.06,24.061-24.06h405.88c2.14,0,4.22,0.28,6.2,0.81
C460.949,82.075,468.52,91.455,468.52,102.595" />
<path style="fill:#B3B3B3;" d="M468.518,102.591v23.94h-454v-23.94c0-13.29,10.771-24.06,24.06-24.06h405.88
C457.747,78.532,468.518,89.302,468.518,102.591" />
<rect x="42.099" y="141.002" style="fill:#999999;" width="118.901"
height="16.532" />
<polyline style="fill:#FFFFFF;"
points="442.383,222.765 256.652,222.765 256.652,354.496 442.383,354.496 442.383,222.765 " />
<polyline style="fill:#13c9ca;"
points="434.552,329.733 265.552,329.733 265.552,346.265 434.552,346.265 434.552,329.733 " />
<polyline style="fill:#a5a5a5;"
points="286.552,297.744 265.552,297.744 265.552,314.276 286.552,314.276 286.552,297.744 " />
<polyline style="fill:#ffbc58;"
points="350.552,265.754 265.552,265.754 265.552,282.286 350.552,282.286 350.552,265.754 " />
<polyline style="fill:#999999;"
points="411.552,233.765 265.552,233.765 265.552,250.297 411.552,250.297 411.552,233.765 " />
<circle style="fill:#a5a5a5;" cx="47.517" cy="102.532" r="15" />
<circle style="fill:#ffbc58;" cx="87.517" cy="102.532" r="15" />
<path style="fill:#EEEEEE;" d="M463.696,88.139c3.027,4.024,4.822,9.029,4.822,14.453v23.94h-23.321
c0.04,35.899-19.42,70.584-53.444,88.347c-2.102,0.921-4.223,1.779-6.365,2.574l2.774,5.313h54.222v131.731H298.563l7.326,24.038
h138.57c2.14,0,4.22-0.28,6.2-0.81c10.29-2.73,17.86-12.11,17.86-23.25v-251.88C468.52,97.17,466.725,92.164,463.696,88.139
M463.657,88.088c0.012,0.016,0.023,0.031,0.036,0.047C463.681,88.12,463.669,88.103,463.657,88.088 M463.621,88.04
c0.008,0.01,0.015,0.02,0.023,0.03C463.636,88.06,463.629,88.05,463.621,88.04 M463.59,87.999c0.003,0.004,0.006,0.008,0.009,0.012
C463.596,88.007,463.593,88.003,463.59,87.999" />
<path style="fill:#CBCBCB;" d="M444.46,78.535h-11.739c0.396,0.716,0.784,1.437,1.165,2.167c7.655,14.662,11.293,30.36,11.31,45.83
h23.321v-23.94c0-5.424-1.795-10.429-4.822-14.453l0,0c-0.001-0.001-0.002-0.002-0.003-0.003l0,0
c-0.012-0.016-0.023-0.031-0.036-0.047l0,0c-0.005-0.006-0.009-0.012-0.014-0.018l0,0c-0.008-0.01-0.015-0.02-0.023-0.03l0,0
c-0.007-0.01-0.014-0.019-0.022-0.029l0,0c-0.003-0.004-0.006-0.008-0.009-0.012l0,0c-3.187-4.171-7.708-7.269-12.93-8.654
C448.68,78.815,446.6,78.535,444.46,78.535" />
<path style="fill:#FFFFFF;" d="M442.383,222.765h-54.222l5.326,10.202c-0.519,0.271-1.04,0.537-1.561,0.798h19.626v16.532H266.807
l4.711,15.457h79.034v16.532h-73.996l4.711,15.458h5.285v16.532h-0.246l4.711,15.457h143.536v16.532H296.055l2.509,8.231h143.819
V222.765" />
<polyline style="fill:#6EC6EB;"
points="434.552,329.733 291.016,329.733 296.055,346.265 434.552,346.265 434.552,329.733 " />
<polyline style="fill:#FDA589;"
points="286.552,297.744 281.267,297.744 286.306,314.276 286.552,314.276 286.552,297.744 " />
<polyline style="fill:#FBE98E;"
points="350.552,265.754 271.518,265.754 276.556,282.286 350.552,282.286 350.552,265.754 " />
<path style="fill:#EEEEEE;" d="M246.265,126.531h-17.178l29.329,96.234h62.908c-1.465-0.282-2.926-0.591-4.384-0.929l-1.001-0.232
c-24.282-7.625-45.651-24.51-58.362-48.855C249.857,157.962,246.221,142.128,246.265,126.531" />
<path style="fill:#CBCBCB;" d="M258.73,78.535h-44.27l14.628,47.996h17.178c0.028-10.08,1.594-20.062,4.587-29.594
C252.852,90.569,255.489,84.4,258.73,78.535" />
<path style="fill:#FFFFFF;" d="M321.324,222.765h-62.908l7.136,23.415v-12.415h53.983l3.362-10.708
C322.373,222.963,321.848,222.866,321.324,222.765" />
<path style="fill:#BABABA;" d="M411.552,233.765h-19.626c-14.25,7.129-29.421,10.514-44.383,10.514
c-10.173,0-20.251-1.566-29.869-4.586l1.861-5.928h-53.983v12.415l1.255,4.117h144.745V233.765" />
<path style="fill:#CECECE;"
d="M385.387,217.453c-6.196,2.3-12.551,4.073-18.994,5.313h21.767L385.387,217.453" />
<path style="fill:#DBDBDB;"
d="M388.161,222.765h-21.767c-7.428,1.428-14.973,2.147-22.536,2.147
c-6.999,0-14.015-0.616-20.959-1.855l-3.362,10.708h72.391c0.521-0.261,1.042-0.527,1.561-0.798L388.161,222.765" />
<path style="fill:#A4A4A4;" d="M391.926,233.765h-72.391l-1.861,5.928c9.618,3.02,19.696,4.586,29.869,4.586
C362.505,244.279,377.677,240.894,391.926,233.765" />
<path style="fill:#DBDBDB;" d="M345.64,27.261c-15.507,0-31.24,3.637-45.936,11.309c-17.955,9.374-31.855,23.466-40.973,39.961
h173.987c-12.828-23.18-33.641-39.288-57.199-46.682C365.898,28.829,355.817,27.261,345.64,27.261" />
<polyline style="fill:#9F9F9F;"
points="432.719,78.532 258.732,78.532 258.73,78.535 432.721,78.535 432.719,78.532 " />
<path style="fill:#CECECE;"
d="M445.196,126.531H246.265c-0.044,15.597,3.592,31.43,11.312,46.218
c12.711,24.345,34.08,41.23,58.362,48.855l1.001,0.232c1.458,0.338,2.919,0.647,4.384,0.929h45.069
c6.443-1.239,12.797-3.012,18.994-5.313c2.142-0.795,4.263-1.653,6.365-2.574C425.776,197.115,445.237,162.431,445.196,126.531" />
<path style="fill:#B2B2B2;" d="M432.721,78.535H258.73c-3.241,5.865-5.878,12.033-7.878,18.402
c-2.993,9.532-4.559,19.515-4.587,29.594h198.931c-0.017-15.469-3.656-31.168-11.31-45.83
C433.505,79.972,433.117,79.251,432.721,78.535" />
<path style="fill:#DBDBDB;" d="M366.393,222.765h-45.069c0.524,0.101,1.049,0.198,1.574,0.292
c6.945,1.239,13.96,1.855,20.959,1.855C351.42,224.912,358.965,224.193,366.393,222.765" />
<path style="fill:#ffbc58;" d="M345.73,113.225l-5.24,16.69l-24.55,78.18c-24.28-7.62-45.65-24.51-58.36-48.85
c-12.71-24.35-14.35-51.53-6.73-75.81l83.46,26.2L345.73,113.225z" />
<path style="fill:#13c9ca;" d="M393.49,219.465c-7.48,3.9-15.23,6.76-23.08,8.63c-17.7,4.21-35.91,3.38-52.74-1.91l26.47-84.29
l3.32-10.59L393.49,219.465z" />
<path style="fill:#a5a5a5;" d="M391.75,201.375l-46.02-88.15l-11.42-3.59l-83.46-26.2c7.63-24.29,24.51-45.66,48.85-58.37
c2.55-1.33,5.12-2.54,7.73-3.62c22.27-9.33,46.35-9.93,68.09-3.1c24.29,7.62,45.66,24.5,58.37,48.85
C459.3,115.885,440.44,175.955,391.75,201.375z" />
<polyline style="fill:#FFFFFF;"
points="226.383,168.765 40.652,168.765 40.652,354.496 226.383,354.496 226.383,168.765 " />
<polyline style="fill:#2BE0C6;"
points="93.6,254.001 57.602,254.001 57.602,343.996 93.6,343.996 93.6,254.001 " />
<polyline style="fill:#ffbc58;"
points="151.517,224.002 115.518,224.002 115.518,343.996 151.517,343.996 151.517,224.002 " />
<polyline style="fill:#a5a5a5;"
points="209.432,191.004 173.435,191.004 173.435,343.996 209.432,343.996 209.432,191.004 " />
<polyline style="fill:#13c9ca;"
points="242.83,325.765 242.83,454.765 0,454.765 0,325.765 242.83,325.765 " />
<polyline style="fill:#F2F2F2;"
points="15,439.765 15,340.765 227.826,340.765 227.826,439.765 15,439.765 " />
<polyline style="fill:#a5a5a5;"
points="108.064,430.978 87.408,408.6 78.664,420.466 71.9,415.483 86.68,395.426 106.349,416.734
137.239,360.959 160.785,398.8 188.393,349.552 195.722,353.66 161.162,415.307 137.661,377.538 108.064,430.978 " />
<rect x="26" y="357.765" style="fill:#999999;" width="27" height="11" />
<rect x="26" y="384.765" style="fill:#999999;" width="27" height="11" />
<rect x="26" y="411.765" style="fill:#999999;" width="27" height="11" />
<polyline style="fill:#6EC6EB;" points="242.83,325.765 209.432,325.765 173.435,325.765 151.517,325.765 115.518,325.765
93.6,325.765 92.88,325.765 97.451,340.765 227.826,340.765 227.826,439.765 127.619,439.765 132.19,454.765 242.83,454.765
242.83,325.765 " />
<polyline style="fill:#F6F6F6;" points="227.826,340.765 97.451,340.765 115.542,400.135 137.239,360.959 160.785,398.8
188.393,349.552 195.722,353.66 161.162,415.307 137.661,377.538 118.951,411.32 127.619,439.765 227.826,439.765 227.826,340.765
" />
<polyline style="fill:#FDA589;" points="188.393,349.552 160.785,398.8 137.239,360.959 115.542,400.135 118.951,411.32
137.661,377.538 161.162,415.307 195.722,353.66 188.393,349.552 " />
<path style="fill:#CCCCCC;"
d="M393.695,417.909l8.485-20.662l25.251,25.251c1.464,1.464,3.387,2.199,5.303,2.192
c1.923,0,3.84-0.728,5.303-2.192c2.934-2.934,2.927-7.679,0-10.607l-25.251-25.251l20.669-8.492l-78.559-38.792L393.695,417.909z" />
<path style="fill:#808080;" d="M393.695,417.909l8.485-20.662l25.251,25.251c1.464,1.464,3.387,2.199,5.303,2.192
c1.923,0,3.84-0.728,5.303-2.192l-83.142-83.142L393.695,417.909z" />
</g>
</svg>
</div>
<div class="single-item">
<owl-carousel-o [options]="owlcarouselOptions">
<ng-container *ngFor="let data of owlcarousel">
<ng-template carouselSlide class="item">
<div>
<h3>{{data.title}}</h3>
<p>{{data.desc}}</p>
</div>
</ng-template>
</ng-container>
</owl-carousel-o>
</div>
</div>
</div>
<div class="col-md-7 p-0 card-right">
<div class="card tab2-card">
<div style="padding: 46px;" class="card-body">
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs">
<li [ngbNavItem]="1">
<h3 style="color: lightcoral" ngbNavLink>Đăng nhập</h3>
<ng-template ngbNavContent>
<div class="login-img-box d-flex justify-content-center align-items-center">
<img class="login-img"
src="../../../../assets/images/dashboard/Logo web.png">
</div>
<div class="tab-pane fade active show" id="account" role="tabpanel"
aria-labelledby="account-tab">
<form [formGroup]="loginForm" class="form-horizontal auth-form"
novalidate="">
<div class="form-group">
<input required="" name="login[username]" type="email"
class="form-control" placeholder="Email" formControlName="email"
id="exampleInputEmail1">
</div>
<div class="form-group">
<div class="form-control input-password-holder">
<input formControlName="password" class="input-password"
type="{{ showPassword ? 'text' : 'password' }}" #pwc
placeholder="Xác nhận mật khẩu" />
<span class="icon-holder-eye"
(click)="showPassword = !showPassword">
<i
class="{{ showPassword ? 'fa fa-eye-slash' : 'fa fa-eye' }}"></i>
</span>
</div>
<!-- <input required="" name="login[password]" type="password"
class="form-control" placeholder="Password"
formControlName="password"> -->
</div>
<div class="form-button d-flex justify-content-end">
<button class="btn btn-primary w-100 mt-0" type="submit"
(click)="onSignIn()">Đăng nhập</button>
</div>
<div class="form-terms mt-1">
<div class="d-flex justify-content-end">
<!-- <a routerLink="/reset-password" routerLinkActive="active" class="">Quên mật khẩu</a> -->
<a [routerLink]="['/auth/send-email']" routerLinkActive="active"
class="">Quên mật khẩu ?</a>
</div>
</div>
<div class="d-flex justify-content-center align-items-center mt-2">
<p class="mb-0 text-secondary">Trở thành người bán hàng của Foodify?
</p>
<a class="ml-2" [routerLink]="['/auth', 'signup']">Đăng ký ngay</a>
</div>
</form>
</div>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav" class="mt-2"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<ng-template #error_modal>
<div class="modal-body text-center">
<p>Sai tên tài khoản hoặc mật khẩu. Vui lòng kiểm tra lại !</p>
<button type="button" class="btn btn-primary" (click)="closeLayer1()">Tiếp tục</button>
</div>
</ng-template>
@@ -0,0 +1,39 @@
.backgound-food {
background-image: url('../../../../assets/images/background-login.jpg');
width: 100vw;
height: 100vh;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
z-index: -99;
@media (max-width:768px) {
.disable-pink {
display: none;
}
}
.login-img-box {
width: 100%;
padding-bottom: 20px;
.login-img {
max-width: 442px;
}
}
.input-password-holder {
display: flex;
justify-content: space-between;
align-items: center;
.input-password {
border: none;
width: 100%;
}
.icon-holder-eye {
color: #999a9b;
}
}
}
@@ -0,0 +1,109 @@
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { FirebaseService } from "../../../shared/service/firebase.service";
import { Router } from '@angular/router';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
public loginForm: UntypedFormGroup;
public registerForm: UntypedFormGroup;
public active = 1;
// Password
showPassword = false;
//Modal
@ViewChild('error_modal') errorModal: TemplateRef<any>
layer1: BsModalRef;
constructor(private formBuilder: UntypedFormBuilder,
private firebaseAuthService: FirebaseService,
private modalService: BsModalService,
private router: Router) {
this.createLoginForm();
this.createRegisterForm();
}
owlcarousel = [
{
title: "Chào mừng đến với Foodify",
desc: "Foodify - Một trong những ứng dụng đặt đồ ăn tốt nhất hiện nay, được phát triển bởi Pyramide team",
},
{
title: "Foodify - xu hướng mới",
desc: "Với mục đích tạo ra những sản phẩm tốt nhất cho người dùng, chúng mình đã quyết định sử dụng các công nghệ mới nhất vào Foodify",
},
{
title: "Về Pyramide Team",
desc: "Pyramide team là một nhóm sinh viên trường Đại học FPT, team nghiên cứu và phát triển sản phẩm theo công nghệ Angular và Spring Boot",
}
]
owlcarouselOptions = {
loop: true,
items: 1,
dots: true
};
createLoginForm() {
this.loginForm = this.formBuilder.group({
email: [''],
password: [''],
});
}
createRegisterForm() {
this.registerForm = this.formBuilder.group({
email: [''],
password: [''],
confirmPassword: [''],
});
}
ngOnInit() {
}
onSubmit() {
}
onSignUp() {
this.firebaseAuthService.signUp(this.getSignUpEmail, this.getSignUpPassword);
}
onSignIn() {
this.firebaseAuthService.signIn(this.getSignInEmail, this.getSignInPassword).then((result) => {
if (result) {
}
else {
this.layer1 = this.modalService.show(this.errorModal, { class: "modal-sm" });
}
});
}
closeLayer1() {
this.layer1.hide();
}
get getSignUpEmail() {
return this.registerForm.get('email').getRawValue();
}
get getSignUpPassword() {
return this.registerForm.get('password').getRawValue();
}
get getSignInEmail() {
return this.loginForm.get('email').getRawValue();
}
get getSignInPassword() {
return this.loginForm.get('password').getRawValue();
}
}
@@ -0,0 +1,216 @@
<div class="page-wrapper">
<div class="authentication-box backgound-food">
<div class="container">
<div class="row">
<div class="col-md-5 p-0 card-left disable-pink">
<div class="card bg-primary">
<div class="svg-icon">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 468.52 468.52"
style="enable-background: new 0 0 468.52 468.52" xml:space="preserve">
<g>
<path style="fill: #e6e6e6" d="M468.52,102.595v251.88c0,11.14-7.571,20.52-17.86,23.25c-1.98,0.531-4.06,0.81-6.2,0.81H38.58
c-13.29,0-24.061-10.77-24.061-24.06v-251.88c0-13.29,10.771-24.06,24.061-24.06h405.88c2.14,0,4.22,0.28,6.2,0.81
C460.949,82.075,468.52,91.455,468.52,102.595" />
<path style="fill: #b3b3b3" d="M468.518,102.591v23.94h-454v-23.94c0-13.29,10.771-24.06,24.06-24.06h405.88
C457.747,78.532,468.518,89.302,468.518,102.591" />
<rect x="42.099" y="141.002" style="fill: #999999" width="118.901" height="16.532" />
<polyline style="fill: #ffffff"
points="442.383,222.765 256.652,222.765 256.652,354.496 442.383,354.496 442.383,222.765 " />
<polyline style="fill: #13c9ca"
points="434.552,329.733 265.552,329.733 265.552,346.265 434.552,346.265 434.552,329.733 " />
<polyline style="fill: #a5a5a5"
points="286.552,297.744 265.552,297.744 265.552,314.276 286.552,314.276 286.552,297.744 " />
<polyline style="fill: #ffbc58"
points="350.552,265.754 265.552,265.754 265.552,282.286 350.552,282.286 350.552,265.754 " />
<polyline style="fill: #999999"
points="411.552,233.765 265.552,233.765 265.552,250.297 411.552,250.297 411.552,233.765 " />
<circle style="fill: #a5a5a5" cx="47.517" cy="102.532" r="15" />
<circle style="fill: #ffbc58" cx="87.517" cy="102.532" r="15" />
<path style="fill: #eeeeee" d="M463.696,88.139c3.027,4.024,4.822,9.029,4.822,14.453v23.94h-23.321
c0.04,35.899-19.42,70.584-53.444,88.347c-2.102,0.921-4.223,1.779-6.365,2.574l2.774,5.313h54.222v131.731H298.563l7.326,24.038
h138.57c2.14,0,4.22-0.28,6.2-0.81c10.29-2.73,17.86-12.11,17.86-23.25v-251.88C468.52,97.17,466.725,92.164,463.696,88.139
M463.657,88.088c0.012,0.016,0.023,0.031,0.036,0.047C463.681,88.12,463.669,88.103,463.657,88.088 M463.621,88.04
c0.008,0.01,0.015,0.02,0.023,0.03C463.636,88.06,463.629,88.05,463.621,88.04 M463.59,87.999c0.003,0.004,0.006,0.008,0.009,0.012
C463.596,88.007,463.593,88.003,463.59,87.999" />
<path style="fill: #cbcbcb" d="M444.46,78.535h-11.739c0.396,0.716,0.784,1.437,1.165,2.167c7.655,14.662,11.293,30.36,11.31,45.83
h23.321v-23.94c0-5.424-1.795-10.429-4.822-14.453l0,0c-0.001-0.001-0.002-0.002-0.003-0.003l0,0
c-0.012-0.016-0.023-0.031-0.036-0.047l0,0c-0.005-0.006-0.009-0.012-0.014-0.018l0,0c-0.008-0.01-0.015-0.02-0.023-0.03l0,0
c-0.007-0.01-0.014-0.019-0.022-0.029l0,0c-0.003-0.004-0.006-0.008-0.009-0.012l0,0c-3.187-4.171-7.708-7.269-12.93-8.654
C448.68,78.815,446.6,78.535,444.46,78.535" />
<path style="fill: #ffffff" d="M442.383,222.765h-54.222l5.326,10.202c-0.519,0.271-1.04,0.537-1.561,0.798h19.626v16.532H266.807
l4.711,15.457h79.034v16.532h-73.996l4.711,15.458h5.285v16.532h-0.246l4.711,15.457h143.536v16.532H296.055l2.509,8.231h143.819
V222.765" />
<polyline style="fill: #6ec6eb"
points="434.552,329.733 291.016,329.733 296.055,346.265 434.552,346.265 434.552,329.733 " />
<polyline style="fill: #fda589"
points="286.552,297.744 281.267,297.744 286.306,314.276 286.552,314.276 286.552,297.744 " />
<polyline style="fill: #fbe98e"
points="350.552,265.754 271.518,265.754 276.556,282.286 350.552,282.286 350.552,265.754 " />
<path style="fill: #eeeeee" d="M246.265,126.531h-17.178l29.329,96.234h62.908c-1.465-0.282-2.926-0.591-4.384-0.929l-1.001-0.232
c-24.282-7.625-45.651-24.51-58.362-48.855C249.857,157.962,246.221,142.128,246.265,126.531" />
<path style="fill: #cbcbcb" d="M258.73,78.535h-44.27l14.628,47.996h17.178c0.028-10.08,1.594-20.062,4.587-29.594
C252.852,90.569,255.489,84.4,258.73,78.535" />
<path style="fill: #ffffff" d="M321.324,222.765h-62.908l7.136,23.415v-12.415h53.983l3.362-10.708
C322.373,222.963,321.848,222.866,321.324,222.765" />
<path style="fill: #bababa" d="M411.552,233.765h-19.626c-14.25,7.129-29.421,10.514-44.383,10.514
c-10.173,0-20.251-1.566-29.869-4.586l1.861-5.928h-53.983v12.415l1.255,4.117h144.745V233.765" />
<path style="fill: #cecece"
d="M385.387,217.453c-6.196,2.3-12.551,4.073-18.994,5.313h21.767L385.387,217.453" />
<path style="fill: #dbdbdb"
d="M388.161,222.765h-21.767c-7.428,1.428-14.973,2.147-22.536,2.147
c-6.999,0-14.015-0.616-20.959-1.855l-3.362,10.708h72.391c0.521-0.261,1.042-0.527,1.561-0.798L388.161,222.765" />
<path style="fill: #a4a4a4" d="M391.926,233.765h-72.391l-1.861,5.928c9.618,3.02,19.696,4.586,29.869,4.586
C362.505,244.279,377.677,240.894,391.926,233.765" />
<path style="fill: #dbdbdb" d="M345.64,27.261c-15.507,0-31.24,3.637-45.936,11.309c-17.955,9.374-31.855,23.466-40.973,39.961
h173.987c-12.828-23.18-33.641-39.288-57.199-46.682C365.898,28.829,355.817,27.261,345.64,27.261" />
<polyline style="fill: #9f9f9f"
points="432.719,78.532 258.732,78.532 258.73,78.535 432.721,78.535 432.719,78.532 " />
<path style="fill: #cecece"
d="M445.196,126.531H246.265c-0.044,15.597,3.592,31.43,11.312,46.218
c12.711,24.345,34.08,41.23,58.362,48.855l1.001,0.232c1.458,0.338,2.919,0.647,4.384,0.929h45.069
c6.443-1.239,12.797-3.012,18.994-5.313c2.142-0.795,4.263-1.653,6.365-2.574C425.776,197.115,445.237,162.431,445.196,126.531" />
<path style="fill: #b2b2b2" d="M432.721,78.535H258.73c-3.241,5.865-5.878,12.033-7.878,18.402
c-2.993,9.532-4.559,19.515-4.587,29.594h198.931c-0.017-15.469-3.656-31.168-11.31-45.83
C433.505,79.972,433.117,79.251,432.721,78.535" />
<path style="fill: #dbdbdb" d="M366.393,222.765h-45.069c0.524,0.101,1.049,0.198,1.574,0.292
c6.945,1.239,13.96,1.855,20.959,1.855C351.42,224.912,358.965,224.193,366.393,222.765" />
<path style="fill: #ffbc58" d="M345.73,113.225l-5.24,16.69l-24.55,78.18c-24.28-7.62-45.65-24.51-58.36-48.85
c-12.71-24.35-14.35-51.53-6.73-75.81l83.46,26.2L345.73,113.225z" />
<path style="fill: #13c9ca" d="M393.49,219.465c-7.48,3.9-15.23,6.76-23.08,8.63c-17.7,4.21-35.91,3.38-52.74-1.91l26.47-84.29
l3.32-10.59L393.49,219.465z" />
<path style="fill: #a5a5a5" d="M391.75,201.375l-46.02-88.15l-11.42-3.59l-83.46-26.2c7.63-24.29,24.51-45.66,48.85-58.37
c2.55-1.33,5.12-2.54,7.73-3.62c22.27-9.33,46.35-9.93,68.09-3.1c24.29,7.62,45.66,24.5,58.37,48.85
C459.3,115.885,440.44,175.955,391.75,201.375z" />
<polyline style="fill: #ffffff"
points="226.383,168.765 40.652,168.765 40.652,354.496 226.383,354.496 226.383,168.765 " />
<polyline style="fill: #2be0c6"
points="93.6,254.001 57.602,254.001 57.602,343.996 93.6,343.996 93.6,254.001 " />
<polyline style="fill: #ffbc58"
points="151.517,224.002 115.518,224.002 115.518,343.996 151.517,343.996 151.517,224.002 " />
<polyline style="fill: #a5a5a5"
points="209.432,191.004 173.435,191.004 173.435,343.996 209.432,343.996 209.432,191.004 " />
<polyline style="fill: #13c9ca"
points="242.83,325.765 242.83,454.765 0,454.765 0,325.765 242.83,325.765 " />
<polyline style="fill: #f2f2f2"
points="15,439.765 15,340.765 227.826,340.765 227.826,439.765 15,439.765 " />
<polyline style="fill: #a5a5a5"
points="108.064,430.978 87.408,408.6 78.664,420.466 71.9,415.483 86.68,395.426 106.349,416.734
137.239,360.959 160.785,398.8 188.393,349.552 195.722,353.66 161.162,415.307 137.661,377.538 108.064,430.978 " />
<rect x="26" y="357.765" style="fill: #999999" width="27" height="11" />
<rect x="26" y="384.765" style="fill: #999999" width="27" height="11" />
<rect x="26" y="411.765" style="fill: #999999" width="27" height="11" />
<polyline style="fill: #6ec6eb" points="242.83,325.765 209.432,325.765 173.435,325.765 151.517,325.765 115.518,325.765
93.6,325.765 92.88,325.765 97.451,340.765 227.826,340.765 227.826,439.765 127.619,439.765 132.19,454.765 242.83,454.765
242.83,325.765 " />
<polyline style="fill: #f6f6f6" points="227.826,340.765 97.451,340.765 115.542,400.135 137.239,360.959 160.785,398.8
188.393,349.552 195.722,353.66 161.162,415.307 137.661,377.538 118.951,411.32 127.619,439.765 227.826,439.765 227.826,340.765
" />
<polyline style="fill: #fda589" points="188.393,349.552 160.785,398.8 137.239,360.959 115.542,400.135 118.951,411.32
137.661,377.538 161.162,415.307 195.722,353.66 188.393,349.552 " />
<path style="fill: #cccccc"
d="M393.695,417.909l8.485-20.662l25.251,25.251c1.464,1.464,3.387,2.199,5.303,2.192
c1.923,0,3.84-0.728,5.303-2.192c2.934-2.934,2.927-7.679,0-10.607l-25.251-25.251l20.669-8.492l-78.559-38.792L393.695,417.909z" />
<path style="fill: #808080" d="M393.695,417.909l8.485-20.662l25.251,25.251c1.464,1.464,3.387,2.199,5.303,2.192
c1.923,0,3.84-0.728,5.303-2.192l-83.142-83.142L393.695,417.909z" />
</g>
</svg>
</div>
<div class="single-item">
<owl-carousel-o [options]="owlcarouselOptions">
<ng-container *ngFor="let data of owlcarousel">
<ng-template carouselSlide class="item">
<div>
<h3>{{ data.title }}</h3>
<p>{{ data.desc }}</p>
</div>
</ng-template>
</ng-container>
</owl-carousel-o>
</div>
</div>
</div>
<div class="col-md-7 p-0 card-right">
<div class="card tab2-card">
<div class="card-body">
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs">
<li [ngbNavItem]="1">
<h3 style="color: lightcoral" ngbNavLink>Thay đổi mật khẩu mới</h3>
<ng-template ngbNavContent>
<div class="reset-img-box d-flex justify-content-center align-items-center">
<img class="reset-img" src="../../../../assets/images/dashboard/reset-password.png">
</div>
<div class="tab-pane fade active show" id="account" role="tabpanel" aria-labelledby="account-tab">
<form [formGroup]="resetPasswordForm" class="form-horizontal auth-form" novalidate="">
<div class="form-group">
<div class="form-control input-password-holder">
<input formControlName="password" class="input-password"
type="{{ showPassword ? 'text' : 'password' }}" placeholder="Mật khẩu mới" />
<span class="icon-holder-eye" (click)="showPassword = !showPassword">
<i class="{{ showPassword ? 'fa fa-eye-slash' : 'fa fa-eye' }}"></i>
</span>
</div>
<div *ngIf="password?.invalid && (password?.dirty || password?.touched)"
class="alert alert-danger mt-1">
<div *ngIf="password?.errors?.['required']">Vui lòng không được bỏ trống mật khẩu.</div>
<div *ngIf="password?.errors?.['pattern']">
Mật khẩu phải chứa ít nhất 8 ký tự, trong đó gồm một ký tự số, một chữ hoa, một chữ thường
và một ký tự đặc biệt.
<br />
Mật khẩu không được chứa khoảng trắng.
</div>
</div>
</div>
<div class="form-group">
<div class="form-control input-password-holder">
<input formControlName="confirmPassword" class="input-password"
type="{{ showPassword ? 'text' : 'password' }}" #pwc placeholder="Xác nhận mật khẩu" />
<span class="icon-holder-eye" (click)="showPassword = !showPassword">
<i class="{{ showPassword ? 'fa fa-eye-slash' : 'fa fa-eye' }}"></i>
</span>
</div>
<div *ngIf="
confirmPassword?.invalid &&
(confirmPassword?.dirty || confirmPassword?.touched)
" class="alert alert-danger mt-1">
<div *ngIf="confirmPassword?.errors?.['required']">Vui lòng xác nhận mật khẩu.</div>
<div *ngIf="confirmPassword?.errors?.['pattern']">
Mật khẩu phải chứa ít nhất 8 ký tự, trong đó gồm một ký tự số, một chữ hoa, một chữ thường
và một ký tự đặc biệt.
<br />
Mật khẩu không được chứa khoảng trắng.
</div>
<div *ngIf="confirmPassword?.errors?.['confirmedValidator']">
Mật khẩu xác nhận phải giống với mật khẩu đã nhập
</div>
</div>
</div>
<div class="form-button">
<button class="btn btn-primary" type="submit"
(click)="changePassword(changePasswordCompleted)">Thay đổi</button>
</div>
</form>
</div>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav" class="mt-2"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<ng-template #changePasswordCompleted>
<div class="modal-body text-center">
<p>Thay đổi mật khẩu thành công</p>
<button type="button" class="btn btn-primary" (click)="backToLogin()"> Đăng nhập ngay
</button>
</div>
</ng-template>
@@ -0,0 +1,39 @@
.backgound-food {
background-image: url('../../../../assets/images/background-login.jpg');
width: 100vw;
height: 100vh;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
z-index: -99;
@media (max-width:768px) {
.disable-pink {
display: none;
}
}
.reset-img-box {
.reset-img {
max-width: 265px;
padding-bottom: 15px;
}
}
}
.input-password-holder {
display: flex;
justify-content: space-between;
align-items: center;
.input-password {
border: none;
width: 100%;
}
.icon-holder-eye {
color: #999a9b;
}
}
@@ -0,0 +1,140 @@
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { FormControl, FormGroup, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { FirebaseService } from "../../../shared/service/firebase.service";
import { ActivatedRoute, Router } from '@angular/router';
import { Validation } from 'src/app/constants/Validation';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
@Component({
selector: "app-reset-password",
templateUrl: "./reset-password.component.html",
styleUrls: ["./reset-password.component.scss"],
})
export class ResetPasswordComponent {
public resetPasswordForm: FormGroup
public active = 1;
// Password
action: string = '';
oobCode: string = '';
showPassword = false;
//Modal
layer1: BsModalRef;
constructor(
private formBuilder: UntypedFormBuilder,
private activatedRoute: ActivatedRoute,
private firebaseService: FirebaseService,
private modalService: BsModalService,
private router: Router
) {
}
owlcarousel = [
{
title: "Chào mừng đến với Foodify",
desc: "Foodify - Một trong những ứng dụng đặt đồ ăn tốt nhất hiện nay, được phát triển bởi Pyramide team",
},
{
title: "Foodify - xu hướng mới",
desc: "Với mục đích tạo ra những sản phẩm tốt nhất cho người dùng, chúng mình đã quyết định sử dụng các công nghệ mới nhất vào Foodify",
},
{
title: "Về Pyramide Team",
desc: "Pyramide team là một nhóm sinh viên trường Đại học FPT, team nghiên cứu và phát triển sản phẩm theo công nghệ Angular và Spring Boot",
},
];
owlcarouselOptions = {
loop: true,
items: 1,
dots: true,
};
ngOnInit() {
//Get Token from link
this.oobCode = this.activatedRoute.snapshot.queryParamMap.get('oobCode');
this.action = this.activatedRoute.snapshot.queryParamMap.get('mode');
if (this.action == 'verifyEmail') {
this.firebaseService.confirmVerifyEmail(this.oobCode)
this.router.navigate(['/auth/email-verified'])
}
if (this.oobCode) {
this.resetPasswordForm = this.formBuilder.group({
password: new FormControl("", [Validators.required, Validators.pattern(Validation.Regex.Password)]),
confirmPassword: new FormControl("", [Validators.required, Validators.pattern(Validation.Regex.Password)]),
},
{
validator: this.ConfirmedValidator("password", "confirmPassword"),
})
}
else {
this.router.navigate(['/auth/forbidden']);
}
}
//Change password
changePassword(success: TemplateRef<any>) {
if (this.resetPasswordForm.invalid) {
this.resetPasswordForm.markAllAsTouched();
return;
}
this.firebaseService.confirmPasswordReset(this.oobCode, this.password.value);
this.layer1 = this.modalService.show(success, { class: 'modal-sm' })
}
backToLogin() {
this.layer1.hide();
this.router.navigate(['/auth/login']);
}
// Validation for password and confirm password
ConfirmedValidator(controlName: string, matchingControlName: string) {
return (formGroup: FormGroup) => {
const control = formGroup.controls[controlName];
const matchingControl = formGroup.controls[matchingControlName];
if (matchingControl.errors && !matchingControl.errors.confirmedValidator) {
return;
}
if (control.value !== matchingControl.value) {
matchingControl.setErrors({ confirmedValidator: true });
} else {
matchingControl.setErrors(null);
}
};
}
get password() {
return this.resetPasswordForm.get("password");
}
get confirmPassword() {
return this.resetPasswordForm.get("confirmPassword");
}
// onSignUp() {
// this.firebaseAuthService.signUp(this.getSignUpEmail, this.getSignUpPassword);
// }
// onSignIn() {
// this.firebaseAuthService.signIn(this.getSignInEmail, this.getSignInPassword);
// }
// get getSignUpPassword() {
// return this.registerForm.get("password").getRawValue();
// }
// get getSignInEmail() {
// return this.loginForm.get("email").getRawValue();
// }
// get getSignInPassword() {
// return this.loginForm.get("password").getRawValue();
// }
}
@@ -0,0 +1,183 @@
<div class="page-wrapper">
<div class="authentication-box backgound-food">
<div class="container">
<div class="row">
<div class="col-md-5 p-0 card-left disable-pink">
<div class="card bg-primary">
<div class="svg-icon">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 468.52 468.52"
style="enable-background: new 0 0 468.52 468.52" xml:space="preserve">
<g>
<path style="fill: #e6e6e6" d="M468.52,102.595v251.88c0,11.14-7.571,20.52-17.86,23.25c-1.98,0.531-4.06,0.81-6.2,0.81H38.58
c-13.29,0-24.061-10.77-24.061-24.06v-251.88c0-13.29,10.771-24.06,24.061-24.06h405.88c2.14,0,4.22,0.28,6.2,0.81
C460.949,82.075,468.52,91.455,468.52,102.595" />
<path style="fill: #b3b3b3" d="M468.518,102.591v23.94h-454v-23.94c0-13.29,10.771-24.06,24.06-24.06h405.88
C457.747,78.532,468.518,89.302,468.518,102.591" />
<rect x="42.099" y="141.002" style="fill: #999999" width="118.901" height="16.532" />
<polyline style="fill: #ffffff"
points="442.383,222.765 256.652,222.765 256.652,354.496 442.383,354.496 442.383,222.765 " />
<polyline style="fill: #13c9ca"
points="434.552,329.733 265.552,329.733 265.552,346.265 434.552,346.265 434.552,329.733 " />
<polyline style="fill: #a5a5a5"
points="286.552,297.744 265.552,297.744 265.552,314.276 286.552,314.276 286.552,297.744 " />
<polyline style="fill: #ffbc58"
points="350.552,265.754 265.552,265.754 265.552,282.286 350.552,282.286 350.552,265.754 " />
<polyline style="fill: #999999"
points="411.552,233.765 265.552,233.765 265.552,250.297 411.552,250.297 411.552,233.765 " />
<circle style="fill: #a5a5a5" cx="47.517" cy="102.532" r="15" />
<circle style="fill: #ffbc58" cx="87.517" cy="102.532" r="15" />
<path style="fill: #eeeeee" d="M463.696,88.139c3.027,4.024,4.822,9.029,4.822,14.453v23.94h-23.321
c0.04,35.899-19.42,70.584-53.444,88.347c-2.102,0.921-4.223,1.779-6.365,2.574l2.774,5.313h54.222v131.731H298.563l7.326,24.038
h138.57c2.14,0,4.22-0.28,6.2-0.81c10.29-2.73,17.86-12.11,17.86-23.25v-251.88C468.52,97.17,466.725,92.164,463.696,88.139
M463.657,88.088c0.012,0.016,0.023,0.031,0.036,0.047C463.681,88.12,463.669,88.103,463.657,88.088 M463.621,88.04
c0.008,0.01,0.015,0.02,0.023,0.03C463.636,88.06,463.629,88.05,463.621,88.04 M463.59,87.999c0.003,0.004,0.006,0.008,0.009,0.012
C463.596,88.007,463.593,88.003,463.59,87.999" />
<path style="fill: #cbcbcb" d="M444.46,78.535h-11.739c0.396,0.716,0.784,1.437,1.165,2.167c7.655,14.662,11.293,30.36,11.31,45.83
h23.321v-23.94c0-5.424-1.795-10.429-4.822-14.453l0,0c-0.001-0.001-0.002-0.002-0.003-0.003l0,0
c-0.012-0.016-0.023-0.031-0.036-0.047l0,0c-0.005-0.006-0.009-0.012-0.014-0.018l0,0c-0.008-0.01-0.015-0.02-0.023-0.03l0,0
c-0.007-0.01-0.014-0.019-0.022-0.029l0,0c-0.003-0.004-0.006-0.008-0.009-0.012l0,0c-3.187-4.171-7.708-7.269-12.93-8.654
C448.68,78.815,446.6,78.535,444.46,78.535" />
<path style="fill: #ffffff" d="M442.383,222.765h-54.222l5.326,10.202c-0.519,0.271-1.04,0.537-1.561,0.798h19.626v16.532H266.807
l4.711,15.457h79.034v16.532h-73.996l4.711,15.458h5.285v16.532h-0.246l4.711,15.457h143.536v16.532H296.055l2.509,8.231h143.819
V222.765" />
<polyline style="fill: #6ec6eb"
points="434.552,329.733 291.016,329.733 296.055,346.265 434.552,346.265 434.552,329.733 " />
<polyline style="fill: #fda589"
points="286.552,297.744 281.267,297.744 286.306,314.276 286.552,314.276 286.552,297.744 " />
<polyline style="fill: #fbe98e"
points="350.552,265.754 271.518,265.754 276.556,282.286 350.552,282.286 350.552,265.754 " />
<path style="fill: #eeeeee" d="M246.265,126.531h-17.178l29.329,96.234h62.908c-1.465-0.282-2.926-0.591-4.384-0.929l-1.001-0.232
c-24.282-7.625-45.651-24.51-58.362-48.855C249.857,157.962,246.221,142.128,246.265,126.531" />
<path style="fill: #cbcbcb" d="M258.73,78.535h-44.27l14.628,47.996h17.178c0.028-10.08,1.594-20.062,4.587-29.594
C252.852,90.569,255.489,84.4,258.73,78.535" />
<path style="fill: #ffffff" d="M321.324,222.765h-62.908l7.136,23.415v-12.415h53.983l3.362-10.708
C322.373,222.963,321.848,222.866,321.324,222.765" />
<path style="fill: #bababa" d="M411.552,233.765h-19.626c-14.25,7.129-29.421,10.514-44.383,10.514
c-10.173,0-20.251-1.566-29.869-4.586l1.861-5.928h-53.983v12.415l1.255,4.117h144.745V233.765" />
<path style="fill: #cecece"
d="M385.387,217.453c-6.196,2.3-12.551,4.073-18.994,5.313h21.767L385.387,217.453" />
<path style="fill: #dbdbdb"
d="M388.161,222.765h-21.767c-7.428,1.428-14.973,2.147-22.536,2.147
c-6.999,0-14.015-0.616-20.959-1.855l-3.362,10.708h72.391c0.521-0.261,1.042-0.527,1.561-0.798L388.161,222.765" />
<path style="fill: #a4a4a4" d="M391.926,233.765h-72.391l-1.861,5.928c9.618,3.02,19.696,4.586,29.869,4.586
C362.505,244.279,377.677,240.894,391.926,233.765" />
<path style="fill: #dbdbdb" d="M345.64,27.261c-15.507,0-31.24,3.637-45.936,11.309c-17.955,9.374-31.855,23.466-40.973,39.961
h173.987c-12.828-23.18-33.641-39.288-57.199-46.682C365.898,28.829,355.817,27.261,345.64,27.261" />
<polyline style="fill: #9f9f9f"
points="432.719,78.532 258.732,78.532 258.73,78.535 432.721,78.535 432.719,78.532 " />
<path style="fill: #cecece"
d="M445.196,126.531H246.265c-0.044,15.597,3.592,31.43,11.312,46.218
c12.711,24.345,34.08,41.23,58.362,48.855l1.001,0.232c1.458,0.338,2.919,0.647,4.384,0.929h45.069
c6.443-1.239,12.797-3.012,18.994-5.313c2.142-0.795,4.263-1.653,6.365-2.574C425.776,197.115,445.237,162.431,445.196,126.531" />
<path style="fill: #b2b2b2" d="M432.721,78.535H258.73c-3.241,5.865-5.878,12.033-7.878,18.402
c-2.993,9.532-4.559,19.515-4.587,29.594h198.931c-0.017-15.469-3.656-31.168-11.31-45.83
C433.505,79.972,433.117,79.251,432.721,78.535" />
<path style="fill: #dbdbdb" d="M366.393,222.765h-45.069c0.524,0.101,1.049,0.198,1.574,0.292
c6.945,1.239,13.96,1.855,20.959,1.855C351.42,224.912,358.965,224.193,366.393,222.765" />
<path style="fill: #ffbc58" d="M345.73,113.225l-5.24,16.69l-24.55,78.18c-24.28-7.62-45.65-24.51-58.36-48.85
c-12.71-24.35-14.35-51.53-6.73-75.81l83.46,26.2L345.73,113.225z" />
<path style="fill: #13c9ca" d="M393.49,219.465c-7.48,3.9-15.23,6.76-23.08,8.63c-17.7,4.21-35.91,3.38-52.74-1.91l26.47-84.29
l3.32-10.59L393.49,219.465z" />
<path style="fill: #a5a5a5" d="M391.75,201.375l-46.02-88.15l-11.42-3.59l-83.46-26.2c7.63-24.29,24.51-45.66,48.85-58.37
c2.55-1.33,5.12-2.54,7.73-3.62c22.27-9.33,46.35-9.93,68.09-3.1c24.29,7.62,45.66,24.5,58.37,48.85
C459.3,115.885,440.44,175.955,391.75,201.375z" />
<polyline style="fill: #ffffff"
points="226.383,168.765 40.652,168.765 40.652,354.496 226.383,354.496 226.383,168.765 " />
<polyline style="fill: #2be0c6"
points="93.6,254.001 57.602,254.001 57.602,343.996 93.6,343.996 93.6,254.001 " />
<polyline style="fill: #ffbc58"
points="151.517,224.002 115.518,224.002 115.518,343.996 151.517,343.996 151.517,224.002 " />
<polyline style="fill: #a5a5a5"
points="209.432,191.004 173.435,191.004 173.435,343.996 209.432,343.996 209.432,191.004 " />
<polyline style="fill: #13c9ca"
points="242.83,325.765 242.83,454.765 0,454.765 0,325.765 242.83,325.765 " />
<polyline style="fill: #f2f2f2"
points="15,439.765 15,340.765 227.826,340.765 227.826,439.765 15,439.765 " />
<polyline style="fill: #a5a5a5"
points="108.064,430.978 87.408,408.6 78.664,420.466 71.9,415.483 86.68,395.426 106.349,416.734
137.239,360.959 160.785,398.8 188.393,349.552 195.722,353.66 161.162,415.307 137.661,377.538 108.064,430.978 " />
<rect x="26" y="357.765" style="fill: #999999" width="27" height="11" />
<rect x="26" y="384.765" style="fill: #999999" width="27" height="11" />
<rect x="26" y="411.765" style="fill: #999999" width="27" height="11" />
<polyline style="fill: #6ec6eb" points="242.83,325.765 209.432,325.765 173.435,325.765 151.517,325.765 115.518,325.765
93.6,325.765 92.88,325.765 97.451,340.765 227.826,340.765 227.826,439.765 127.619,439.765 132.19,454.765 242.83,454.765
242.83,325.765 " />
<polyline style="fill: #f6f6f6" points="227.826,340.765 97.451,340.765 115.542,400.135 137.239,360.959 160.785,398.8
188.393,349.552 195.722,353.66 161.162,415.307 137.661,377.538 118.951,411.32 127.619,439.765 227.826,439.765 227.826,340.765
" />
<polyline style="fill: #fda589" points="188.393,349.552 160.785,398.8 137.239,360.959 115.542,400.135 118.951,411.32
137.661,377.538 161.162,415.307 195.722,353.66 188.393,349.552 " />
<path style="fill: #cccccc"
d="M393.695,417.909l8.485-20.662l25.251,25.251c1.464,1.464,3.387,2.199,5.303,2.192
c1.923,0,3.84-0.728,5.303-2.192c2.934-2.934,2.927-7.679,0-10.607l-25.251-25.251l20.669-8.492l-78.559-38.792L393.695,417.909z" />
<path style="fill: #808080" d="M393.695,417.909l8.485-20.662l25.251,25.251c1.464,1.464,3.387,2.199,5.303,2.192
c1.923,0,3.84-0.728,5.303-2.192l-83.142-83.142L393.695,417.909z" />
</g>
</svg>
</div>
<div class="single-item">
<owl-carousel-o [options]="owlcarouselOptions">
<ng-container *ngFor="let data of owlcarousel">
<ng-template carouselSlide class="item">
<div>
<h3>{{ data.title }}</h3>
<p>{{ data.desc }}</p>
</div>
</ng-template>
</ng-container>
</owl-carousel-o>
</div>
</div>
</div>
<div class="col-md-7 p-0 card-right">
<div class="card tab2-card">
<div class="card-body">
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs">
<li [ngbNavItem]="1">
<h3 style="color: lightcoral" ngbNavLink>Quên mật khẩu</h3>
<ng-template ngbNavContent>
<div class="email-img-box d-flex justify-content-center align-items-center">
<img class="email-img" src="../../../../assets/images/dashboard/email.png">
</div>
<div class="tab-pane fade active show" id="account" role="tabpanel" aria-labelledby="account-tab">
<form [formGroup]="sendEmailForm" class="form-horizontal auth-form" novalidate="">
<div class="form-group d-flex justify-content-center align-items-center ">
<i class="fa fa-envelope-o" aria-hidden="true"></i>
<input type="email" class="form-control" placeholder="Vui lòng nhập Email"
formControlName="email" />
</div>
<div *ngIf="email?.invalid && (email?.dirty || email?.touched)" class="alert alert-danger">
<div *ngIf="email?.errors?.['required']">Vui lòng không được bỏ trống email</div>
<div *ngIf="email?.errors?.email">Email không hợp lệ, vui lòng nhập lại</div>
</div>
<div class="form-button d-flex justify-content-around">
<button class="btn btn-primary" type="submit" (click)="sendPasswordResetMail()">Tiếp
tục</button>
<button type="button" class="btn btn-danger btn-back" [routerLink]="['/auth', 'login']">Quay
lại</button>
</div>
</form>
</div>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav" class="mt-2"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<ng-template #sendMailCompleted>
<div class="modal-body text-center">
<p>Đã gửi đường dẫn thay đổi mật khẩu đến email của bạn</p>
<p>Vui lòng kiểm tra hòm thư</p>
<button type="button" class="btn btn-primary" (click)="gmail()">Gmail</button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<button type="button" class="btn btn-secondary" (click)="closeLayer1()">Đóng</button>
</div>
</ng-template>
@@ -0,0 +1,38 @@
.backgound-food {
background-image: url("../../../../assets/images/background-login.jpg");
width: 100vw;
height: 100vh;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
z-index: -99;
@media (max-width:768px) {
.disable-pink {
display: none;
}
}
.email-img-box {
.email-img {
max-width: 351px;
padding-bottom: 20px;
}
}
.fa-envelope-o {
font-size: 38px;
margin-right: 12px;
color: #8b8b8b;
}
}
.btn-back {
border-radius: 25px;
margin-top: 20px;
font-weight: 400;
padding: 11px 45px;
}
@@ -0,0 +1,81 @@
import { Component, OnInit, TemplateRef, ViewChild } from "@angular/core";
import { FormControl, FormGroup, UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms";
import { FirebaseService } from "../../../shared/service/firebase.service";
import { Router } from "@angular/router";
import { Validation } from "src/app/constants/Validation";
import { BsModalRef, BsModalService } from "ngx-bootstrap/modal";
@Component({
selector: "app-send-email",
templateUrl: "./send-email.component.html",
styleUrls: ["./send-email.component.scss"],
})
// @ViewChild('sendMailCompleted') sendMailCompleted: TemplateRef<any>;
export class SendEmailComponent {
public sendEmailForm: FormGroup;
public active = 1;
@ViewChild('sendMailCompleted') sendMailCompleted: TemplateRef<any>
constructor(
private formBuilder: UntypedFormBuilder,
private firebaseService: FirebaseService,
private modalService: BsModalService,
private router: Router
) {
}
layer1: BsModalRef;
owlcarousel = [
{
title: "Chào mừng đến với Foodify",
desc: "Foodify - Một trong những ứng dụng đặt đồ ăn tốt nhất hiện nay, được phát triển bởi Pyramide team",
},
{
title: "Foodify - xu hướng mới",
desc: "Với mục đích tạo ra những sản phẩm tốt nhất cho người dùng, chúng mình đã quyết định sử dụng các công nghệ mới nhất vào Foodify",
},
{
title: "Về Pyramide Team",
desc: "Pyramide team là một nhóm sinh viên trường Đại học FPT, team nghiên cứu và phát triển sản phẩm theo công nghệ Angular và Spring Boot",
},
];
owlcarouselOptions = {
loop: true,
items: 1,
dots: true,
};
ngOnInit() {
this.sendEmailForm = this.formBuilder.group({
email: new FormControl("", [Validators.required, Validators.email]),
});
}
sendPasswordResetMail() {
if (this.sendEmailForm.invalid) {
this.sendEmailForm.markAllAsTouched();
return;
}
this.firebaseService.resetPassword(this.email.value);
this.layer1 = this.modalService.show(this.sendMailCompleted, { class: "modal-sm" });
}
gmail() {
this.layer1.hide();
window.location.assign("https://mail.google.com/");
}
closeLayer1() {
this.layer1.hide();
}
get email() {
return this.sendEmailForm.get("email");
}
}
@@ -0,0 +1,388 @@
<!-- Container-fluid starts-->
<div class="backgound-food" [ngClass]="{'change-height': active === 2}">
<div class="d-flex justify-content-center align-items-center">
<div class="form-shop-box">
<div class="d-flex justify-content-center align-items-center ">
<div class="card tab2-card form-shop-item">
<div class="card-header">
<h2 style="color: lightcoral" class="font-weight-bold">Đăng ký shop</h2>
</div>
<div class="card-body tab2-card">
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs">
<li [ngbNavItem]="1">
<a ngbNavLink>Tài khoản</a>
<ng-template ngbNavContent>
<form [formGroup]="addUserForm" class="needs-validation user-add" novalidate>
<h4>Chi tiết tài khoản</h4>
<!-- avatar -->
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom0"><span>*</span> Ảnh đại diện</label>
</div>
<div class="col-xl-8 col-md-7">
<div class="d-flex align-items-center">
<button *ngIf="!userImageChoosen" type="submit" class="btn btn-success"
(click)="chooseImg(chooseUserImage)">
Chọn ảnh
</button>
<button *ngIf="userImageChoosen" type="submit" class="btn btn-secondary"
(click)="chooseImg(chooseUserImage)">
Thay đổi ảnh
</button>
<img class="rounded-circle border border-dark" [src]="avatar" *ngIf="avatar"
style="width: 160px; height: 160px; margin-left: 20px" />
</div>
<!-- <div *ngIf="!userImageChoosen && (addUserForm.get('image')?.dirty || addUserForm.get('image')?.touched)" class="alert alert-danger mt-1">
<div *ngIf="addUserForm.get('image').errors?.['required']">Vui lòng chọn ảnh đại diện.</div>
</div> -->
</div>
</div>
<!-- full name -->
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom0"><span>*</span> Họ và tên</label>
</div>
<div class="col-xl-8 col-md-7">
<input formControlName="fullName" class="form-control" type="text" placeholder="Họ và tên" />
<div *ngIf="userFullName?.invalid && (userFullName?.dirty || userFullName?.touched)"
class="alert alert-danger mt-1">
<div *ngIf="userFullName?.errors?.['required']">Vui lòng không được bỏ trống
tên.</div>
<div *ngIf="userFullName?.errors?.['minlength']">Tên phải có ít nhất 2 kí tự
</div>
</div>
</div>
</div>
<!-- email -->
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom2"><span>*</span> Email</label>
</div>
<div class="col-xl-8 col-md-7">
<input formControlName="email" class="form-control" type="email" placeholder="Email" />
<div *ngIf="userEmail?.invalid && (userEmail?.dirty || userEmail?.touched)"
class="alert alert-danger mt-1">
<div *ngIf="userEmail?.errors?.['required']">Vui lòng không được bỏ trống
email.</div>
<div *ngIf="userEmail?.errors?.email">Email không hợp lệ, vui lòng nhập lại
</div>
</div>
</div>
</div>
<!-- date of birth -->
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom2"><span>*</span> Ngày Sinh</label>
</div>
<div class="col-xl-8 col-md-7">
<input type="date" formControlName="dateOfBirth" class="form-control" placeholder="Ngày sinh" />
<div *ngIf="userDateOfBirth?.invalid && (userDateOfBirth?.dirty || userDateOfBirth?.touched)"
class="alert alert-danger mt-1">
<div *ngIf="userDateOfBirth?.errors?.['required']">Vui lòng không được bỏ
trống ngày sinh.</div>
</div>
</div>
</div>
<!-- phone number -->
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom2"><span>*</span> Số Điện Thoại</label>
</div>
<div class="col-xl-8 col-md-7">
<input formControlName="phoneNumber" class="form-control" type="tel"
placeholder="Số điện thoại" />
<div *ngIf="userPhoneNumber?.invalid && (userPhoneNumber?.dirty || userPhoneNumber?.touched)"
class="alert alert-danger mt-1">
<div *ngIf="userPhoneNumber?.errors?.['required']">Vui lòng không được bỏ
trống số điện thoại.</div>
<div *ngIf="userPhoneNumber?.errors?.['pattern']">Số điện thoại phải có tối
đa 10 số</div>
</div>
</div>
</div>
<!-- identified code -->
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom2"><span>*</span> CMND/CCCD</label>
</div>
<div class="col-xl-8 col-md-7">
<input formControlName="identifiedCode" class="form-control" type="number"
placeholder="CMND/CCCD" />
<div
*ngIf="userIdentifiedCode?.invalid && (userIdentifiedCode?.dirty || userIdentifiedCode?.touched)"
class="alert alert-danger mt-1">
<div *ngIf="userIdentifiedCode?.errors?.['required']">Vui lòng không được bỏ
trống CMND/CCCD.</div>
<div *ngIf="userIdentifiedCode?.errors?.['pattern']">CMND phải có tối đa 9
số hoặc CCCD phải có tối đa 12 số</div>
</div>
</div>
</div>
<hr>
<!-- Address -->
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom2"><span>*</span>Địa chỉ</label>
</div>
<div class="col-xl-8 col-md-7">
<input formControlName="address" class="form-control" type="text" placeholder="Địa chỉ" />
<div *ngIf="userAddress?.invalid && (userAddress?.dirty || userAddress?.touched)"
class="alert alert-danger mt-1">
<div *ngIf="userAddress?.errors?.['required']">Vui lòng không được bỏ
trống địa chỉ</div>
<div *ngIf="userAddress?.errors?.['minlength']">Tên phải có ít nhất 8 kí tự</div>
<!-- <div *ngIf="userAddress?.errors?.['pattern']">CMND phải có tối đa 9
số hoặc CCCD phải có tối đa 12 số</div> -->
</div>
</div>
</div>
</form>
<div class="needs-validation user-add">
<!-- District -->
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom2"><span>*</span> Quận</label>
</div>
<div class="col-xl-8 col-md-7">
<select [(ngModel)]="district" class="form-control" (change)="onDistrictSelected()">
<option [selected]="true" [ngValue]="'none'">--- Chọn quận ---</option>
<option *ngFor="let district of districts" [ngValue]="district.name">{{
district.name }}
</option>
</select>
<div *ngIf="!validDistrict" class="alert alert-danger mt-1">
Vui lòng không được bỏ trống quận
</div>
</div>
</div>
<!-- Wards -->
<div *ngIf="isHaveDistrict" class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom2"><span>*</span> Phường</label>
</div>
<div class="col-xl-8 col-md-7">
<select [(ngModel)]="ward" class="form-control">
<option [ngValue]="'none'">--- Chọn phường ---</option>
<option *ngFor="let ward of wards">{{ ward.name }}
</option>
</select>
<div *ngIf="!validWard" class="alert alert-danger mt-1">
Vui lòng không được bỏ trống phường
</div>
</div>
</div>
</div>
<form [formGroup]="passwordForm" class="needs-validation user-add" novalidate>
<!-- Password -->
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom2"><span>*</span> Mật khẩu</label>
</div>
<div class="col-xl-8 col-md-7">
<div class="form-control input-password-holder">
<input formControlName="password" class="input-password"
type="{{ showPassword ? 'text' : 'password' }}" placeholder="Mật khẩu" />
<span class="icon-holder-eye" (click)="showPassword = !showPassword">
<i class="{{ showPassword ? 'fa fa-eye-slash' : 'fa fa-eye' }}"></i>
</span>
</div>
<div *ngIf="userPassword?.invalid && (userPassword?.dirty || userPassword?.touched)"
class="alert alert-danger mt-1">
<div *ngIf="userPassword?.errors?.['required']">Vui lòng không được bỏ trống mật khẩu</div>
<div *ngIf="userPassword?.errors?.['pattern']">
Mật khẩu phải chứa ít nhất 8 ký tự, trong đó gồm một ký tự số, một
chữ hoa, một chữ thường và một ký
tự đặc biệt.
<br />
Mật khẩu không được chứa khoảng trắng.
</div>
</div>
</div>
</div>
<!-- Confirm Password -->
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom2"><span>*</span>Nhập lại mật khẩu</label>
</div>
<div class="col-xl-8 col-md-7">
<div class="form-control input-password-holder">
<input formControlName="confirmPassword" class="input-password"
type="{{ showPassword ? 'text' : 'password' }}" placeholder="Xác nhận mật khẩu" />
<span class="icon-holder-eye" (click)="showPassword = !showPassword">
<i class="{{ showPassword ? 'fa fa-eye-slash' : 'fa fa-eye' }}"></i>
</span>
</div>
<!-- <input formControlName="confirmPassword" class="form-control" type="text" placeholder="Xác nhận mật khẩu"/> -->
<div
*ngIf="userConfirmPassword?.invalid && (userConfirmPassword?.dirty || userConfirmPassword?.touched)"
class="alert alert-danger mt-1">
<div *ngIf="userConfirmPassword?.errors?.['required']">Vui lòng không được bỏ trống mật khẩu
</div>
<div *ngIf="userConfirmPassword?.errors?.['pattern']">
Mật khẩu phải chứa ít nhất 8 ký tự, trong đó gồm một ký tự số, một
chữ hoa, một chữ thường và một ký
tự đặc biệt.
<br />
Mật khẩu không được chứa khoảng trắng.
</div>
<div *ngIf="userConfirmPassword?.errors?.['confirmedValidator']">
Mật khẩu xác nhận phải giống với mật khẩu đã nhập
</div>
</div>
</div>
</div>
</form>
</ng-template>
</li>
<!-- --------- SHOP ----------- -->
<li [ngbNavItem]="2">
<a ngbNavLink>Shop đăng ký</a>
<ng-template ngbNavContent>
<div class="row product-adding">
<div class="col-xl-5">
<div class="add-product">
<div class="row">
<div class="col-xl-9 xl-50 col-sm-6 col-9">
<img [src]="shopBanner" *ngIf="shopBanner" alt="" class="img-fluid image_zoom_1">
</div>
<!-- <div class="col-xl-3 xl-50 col-sm-6 col-3">
</div> -->
</div>
</div>
</div>
<div class="col-xl-7">
<form [formGroup]="shopForm" class="needs-validation user-add" novalidate="">
<!-- Shop Banner -->
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom0"><span>*</span> Banner shop</label>
</div>
<div class="col-xl-8 col-md-7">
<div class="d-flex align-items-center">
<button *ngIf="!shopImageChoosen" type="submit" class="btn btn-success"
(click)="chooseImg(chooseShopImage)">Chọn
ảnh</button>
<button *ngIf="shopImageChoosen" type="submit" class="btn btn-secondary"
(click)="chooseImg(chooseShopImage)">Thay đổi ảnh</button>
</div>
<!-- <div *ngIf="!shopImageChoosen && (shopForm.get('banner')?.dirty || shopForm.get('banner')?.touched)" class="alert alert-danger mt-1">
<div *ngIf="shopForm.get('banner').errors?.['required']">Vui lòng chọn ảnh banner của shop.</div>
</div> -->
<!-- <div *ngIf="imageUrl?.invalid && (imageUrl?.dirty || imageUrl?.touched)"
class="alert alert-danger mt-1">
<div *ngIf="imageUrl?.errors?.['required']">Vui lòng chọn ảnh đại diện.
</div>
</div> -->
</div>
</div>
<!-- Shop Name -->
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom2"><span>*</span>Tên shop</label>
</div>
<div class="col-xl-8 col-md-7">
<input formControlName="name" class="form-control" type="text" placeholder="Tên shop" />
<div *ngIf="shopName?.invalid && (shopName?.dirty || shopName?.touched)"
class="alert alert-danger mt-1">
<div *ngIf="shopName?.errors?.['required']">Vui lòng không được bỏ trống tên shop</div>
<div *ngIf="shopName?.errors?.['minlength']">Tên shop phải có ít nhất
2 kí tự</div>
</div>
</div>
</div>
<!-- Shop Description -->
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom2"><span>*</span>Mô tả shop</label>
</div>
<div class="col-xl-8 col-md-7">
<textarea formControlName="description" class="form-control" type="text"
placeholder="Mô tả về shop"></textarea>
<div
*ngIf="shopDescription?.invalid && (shopDescription?.dirty || shopDescription?.touched)"
class="alert alert-danger mt-1">
<div *ngIf="shopDescription?.errors?.['required']">Vui lòng không được bỏ
trống phần mô tả shop</div>
<div *ngIf="shopDescription?.errors?.['minlength']">Mô tả phải có ít nhất 8 kí tự</div>
</div>
</div>
</div>
<!-- Is Student Shop -->
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom2"><span>*</span> Loại shop</label>
</div>
<div class="col-xl-8 col-md-7">
<button *ngIf="isStudent" type="button" class="btn btn-secondary" (click)="swap()">Shop sinh
viên
</button>
<button *ngIf="!isStudent" type="button" class="btn btn-primary" (click)="swap()">Shop
thường
</button>
</div>
</div>
</form>
</div>
</div>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav" class="mt-2"></div>
<div class="pull-right">
<button type="button" class="btn btn-danger mr-3" (click)="goBackToLogin()">Quay lại</button>
<button type="submit" class="btn btn-primary" (click)="createUser(createSuccess)">Tạo tài khoản</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
<ng-template #chooseUserImage>
<div class="modal-body text-center">
<p>Vui lòng chọn 1 ảnh</p>
<input class="input-image" type="file" accept="image/*" (change)="onFileSelected($event)" />
</div>
</ng-template>
<ng-template #chooseShopImage>
<div class="modal-body text-center">
<p>Vui lòng chọn 1 ảnh</p>
<input class="input-image" type="file" accept="image/*" (change)="onShopFileSelected($event)" />
</div>
</ng-template>
<ng-template #createSuccess>
<div class="modal-body text-center">
<p>Tạo tài khoản thành công. Vui lòng kiểm tra hòm thư để xác thực tài khoản</p>
<button type="button" class="btn btn-primary" (click)="continue()">Tiếp tục
</button>
</div>
</ng-template>
<ng-template #error_modal>
<div class="modal-body text-center">
<p>{{ errorItem }} đã tồn tại. Vui lòng chọn một {{ errorItem.toLowerCase() }} khác.</p>
<button type="button" class="btn btn-primary" (click)="closeLayer2()">Tiếp tục</button>
</div>
</ng-template>
@@ -0,0 +1,40 @@
.backgound-food {
background-image: url('../../../../assets/images/background-login.jpg');
width: 100vw;
height: 100%;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
z-index: -99;
}
.change-height {
height: 100vh;
}
.form-shop-box {
width: 1300px;
margin: 80px 0;
.form-shop-item {
width: 100%;
}
}
.input-password-holder {
display: flex;
justify-content: space-between;
align-items: center;
.input-password {
border: none;
width: 100%;
}
.icon-holder-eye {
color: #999a9b;
}
}
@@ -0,0 +1,474 @@
import { Component, TemplateRef, ViewChild } from "@angular/core";
import { AngularFireStorage } from "@angular/fire/compat/storage";
import { FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms";
import { Router } from "@angular/router";
import { BsModalRef, BsModalService } from "ngx-bootstrap/modal";
import { EMPTY, finalize, switchMap } from "rxjs";
import { Observable } from "rxjs-compat";
import { Validation } from "src/app/constants/Validation";
import { AuthService } from "src/app/shared/service/auth.service";
import { DistrictService } from "src/app/shared/service/district.service";
import { FirebaseService } from "src/app/shared/service/firebase.service";
import { ShopService } from "src/app/shared/service/shop.service";
import { UserService } from "src/app/shared/service/user.service";
import { Address } from "src/app/shared/tables/address";
import { District } from "src/app/shared/tables/district";
import { Shop } from "src/app/shared/tables/shop";
import { User } from "src/app/shared/tables/user";
import { Ward } from "src/app/shared/tables/ward";
import { environment } from "src/environments/environment";
@Component({
selector: "app-signup",
templateUrl: "./signup.component.html",
styleUrls: ["./signup.component.scss"],
})
export class SignupComponent {
// form
public active = 1;
addUserForm: FormGroup;
passwordForm: FormGroup;
shopForm: FormGroup;
// image
avatar: string;
shopBanner: string;
defaultUserImg = environment.userDefaultImg;
defaultShopImg = environment.shopDefaultImg;
// file
downloadURL: Observable<string>;
userImageFile: File;
shopImageFile: File;
imageLink;
// password
showPassword = false;
// address
isHaveDistrict: boolean;
district: string = "none";
ward: string = "none";
validDistrict = true;
validWard = true;
districts: District[];
wards: Ward[];
userImageChoosen: boolean = false;
shopImageChoosen: boolean = false;
isStudent: boolean = true;
//Modal
errorItem = '';
layer1: BsModalRef;
layer2: BsModalRef;
@ViewChild('error_modal') errorModal: TemplateRef<any>;
constructor(
private formBuilder: FormBuilder,
private authService: AuthService,
private userService: UserService,
private districtService: DistrictService,
private storage: AngularFireStorage,
private shopService: ShopService,
private modalService: BsModalService,
private router: Router,
private firebaseService: FirebaseService
) {
this.createUserForm();
this.createPasswordForm();
this.createShopForm();
}
createUserForm() {
this.addUserForm = this.formBuilder.group({
fullName: new FormControl("", [Validators.required, Validators.minLength(2)]),
email: new FormControl("", [Validators.required, Validators.email]),
dateOfBirth: new FormControl("", [Validators.required]),
phoneNumber: new FormControl("", [Validators.required, Validators.pattern(Validation.Regex.Phone)]),
identifiedCode: new FormControl("", [Validators.required, Validators.pattern(Validation.Regex.IdentifiedCode)]),
address: new FormControl("", [Validators.required, Validators.minLength(8)]),
});
}
createPasswordForm() {
this.passwordForm = this.formBuilder.group(
{
password: new FormControl("", [Validators.required, Validators.pattern(Validation.Regex.Password)]),
confirmPassword: new FormControl("", [Validators.required, Validators.pattern(Validation.Regex.Password)]),
},
{
validator: this.ConfirmedValidator("password", "confirmPassword"),
}
);
}
// Validation for password and confirm password
ConfirmedValidator(controlName: string, matchingControlName: string) {
return (formGroup: FormGroup) => {
const control = formGroup.controls[controlName];
const matchingControl = formGroup.controls[matchingControlName];
if (matchingControl.errors && !matchingControl.errors.confirmedValidator) {
return;
}
if (control.value !== matchingControl.value) {
matchingControl.setErrors({ confirmedValidator: true });
} else {
matchingControl.setErrors(null);
}
};
}
createShopForm() {
this.shopForm = this.formBuilder.group({
name: new FormControl("", [Validators.required, Validators.minLength(2)]),
description: new FormControl("", [Validators.required, Validators.minLength(8)]),
});
}
ngOnInit() {
this.getAllDistrict();
}
/**
* create User from Form
*/
createUser(template: TemplateRef<any>): Promise<void> {
const newUser = new User();
const newAddress = new Address();
const newShop = new Shop();
newUser.fullName = this.userFullName.value;
newUser.email = this.userEmail.value;
newUser.dateOfBirth = this.userDateOfBirth.value;
newUser.phoneNumber = this.userPhoneNumber.value;
newUser.identifiedCode = this.userIdentifiedCode.value;
newUser.defaultAddress = 0;
newUser.isLocked = false;
newUser.roleName = 'ROLE_SHOP';
if (this.district === 'none' || this.ward === 'none') {
this.validDistrict = this.district !== 'none';
this.validWard = this.ward !== 'none';
} else {
// Tiếp tục với quá trình tạo tài khoản
this.validDistrict = true;
this.validWard = true;
}
if (this.addUserForm.invalid || this.passwordForm.invalid || this.shopForm.invalid || this.district == 'none') {
this.addUserForm.markAllAsTouched();
this.passwordForm.markAllAsTouched();
this.shopForm.markAllAsTouched();
return Promise.reject();
}
newShop.name = this.shopName.value
newShop.description = this.shopDescription.value;
newShop.isStudent = this.isStudent;
newShop.isEnabled = true;
newShop.lat = '1';
newShop.lng = '1';
newAddress.address = this.userAddress.value;
newAddress.district = this.district;
if (this.district != "Huyện Hoàng Sa") newAddress.ward = this.ward;
this.authService.checkEmailOrPhoneNumberExist(newUser).subscribe((error) => {
if (error.title == 'emailExist') {
this.errorItem = 'Email';
this.layer2 = this.modalService.show(this.errorModal, { class: 'modal-sm' });
return Promise.reject();
}
else if (error.title == 'phoneNumExist') {
this.errorItem = 'Số điện thoại';
this.layer2 = this.modalService.show(this.errorModal, { class: 'modal-sm' });
return Promise.reject();
}
else {
if (this.userImageChoosen && this.shopImageChoosen) {
this.uploadUserImage(this.userImageFile).then((url) => {
newUser.imageUrl = url
this.authService.SignUpUser(newUser).pipe(
switchMap((user) => {
this.uploadShopImage(this.shopImageFile).then((url) => {
newShop.userId = user.id
newShop.imageUrl = url;
this.authService.SignUpUserAddress(user.id, newAddress).subscribe({
next: () => {
this.firebaseService.signUp(newUser.email, this.userPassword.value);
this.authService.SignUpShop(newShop).subscribe(() => {
this.layer1 = this.modalService.show(template, { class: 'modal-sm' })
})
}
});
})
return EMPTY; // Return an empty observable to prevent nested subscriptions
})
).subscribe({
next: () => { },
error: () => {
this.errorItem = 'CCCD/CMND';
this.layer2 = this.modalService.show(this.errorModal, { class: 'modal-sm' });
}
})
})
}
else if (this.userImageChoosen && !this.shopImageChoosen) {
this.uploadUserImage(this.userImageFile).then((url) => {
newUser.imageUrl = url
this.authService.SignUpUser(newUser).pipe(
switchMap((user) => {
newShop.userId = user.id
newShop.imageUrl = this.defaultShopImg;
this.authService.SignUpUserAddress(user.id, newAddress).subscribe({
next: () => {
this.firebaseService.signUp(newUser.email, this.userPassword.value);
this.authService.SignUpShop(newShop).subscribe(() => {
this.layer1 = this.modalService.show(template, { class: 'modal-sm' })
})
}
});
return EMPTY; // Return an empty observable to prevent nested subscriptions
})
).subscribe({
next: () => { },
error: () => {
this.errorItem = 'CCCD/CMND';
this.layer2 = this.modalService.show(this.errorModal, { class: 'modal-sm' });
}
})
})
}
else if (!this.userImageChoosen && this.shopImageChoosen) {
newUser.imageUrl = this.defaultUserImg;
this.authService.SignUpUser(newUser).pipe(
switchMap((user) => {
this.uploadShopImage(this.shopImageFile).then((url) => {
newShop.userId = user.id
newShop.imageUrl = url;
this.authService.SignUpUserAddress(user.id, newAddress).subscribe({
next: () => {
this.firebaseService.signUp(newUser.email, this.userPassword.value);
this.authService.SignUpShop(newShop).subscribe(() => {
this.layer1 = this.modalService.show(template, { class: 'modal-sm' })
})
}
});
})
return EMPTY; // Return an empty observable to prevent nested subscriptions
})
).subscribe({
next: () => { },
error: () => {
this.errorItem = 'CCCD/CMND';
this.layer2 = this.modalService.show(this.errorModal, { class: 'modal-sm' });
}
})
}
else {
newUser.imageUrl = this.defaultUserImg;
this.authService.SignUpUser(newUser).pipe(
switchMap((user) => {
newShop.userId = user.id
newShop.imageUrl = this.defaultShopImg;
this.authService.SignUpUserAddress(user.id, newAddress).subscribe({
next: () => {
this.firebaseService.signUp(newUser.email, this.userPassword.value);
this.authService.SignUpShop(newShop).subscribe(() => {
this.layer1 = this.modalService.show(template, { class: 'modal-sm' })
})
}
});
return EMPTY; // Return an empty observable to prevent nested subscriptions
})
).subscribe({
next: () => { },
error: () => {
this.errorItem = 'CCCD/CMND';
this.layer2 = this.modalService.show(this.errorModal, { class: 'modal-sm' });
}
})
}
}
})
}
//Districts and Wards
getAllDistrict() {
this.districtService.getAllDistricts().subscribe((distrs) => {
this.districts = distrs;
});
}
onDistrictSelected() {
this.isHaveDistrict = false;
this.districts.forEach((element: District) => {
if (this.district == element.name && this.district != "Huyện Hoàng Sa") {
this.isHaveDistrict = true;
this.wards = element.wards;
}
});
}
swap() {
if (this.isStudent) {
this.isStudent = false;
} else {
this.isStudent = true;
}
}
//Image Selected
onFileSelected(event) {
this.userImageChoosen = true;
this.userImageFile = event.target.files[0];
const reader = new FileReader();
if (event.target.files && event.target.files.length) {
const [file] = event.target.files;
reader.readAsDataURL(file);
reader.onload = () => {
this.avatar = reader.result as string;
localStorage.setItem("image", this.avatar);
};
}
this.layer1.hide();
}
//Shop Image Selected
onShopFileSelected(event) {
this.shopImageChoosen = true;
this.shopImageFile = event.target.files[0];
const reader = new FileReader();
if (event.target.files && event.target.files.length) {
const [file] = event.target.files;
reader.readAsDataURL(file);
reader.onload = () => {
this.shopBanner = reader.result as string;
localStorage.setItem("image", this.shopBanner);
};
}
this.layer1.hide();
}
uploadUserImage(fileUpload: File): Promise<string> {
return new Promise<string>((resolve) => {
let n = Date.now();
const filePath = `UserImages/${n}`;
const fileRef = this.storage.ref(filePath);
const task = this.storage.upload(`UserImages/${n}`, fileUpload);
task
.snapshotChanges()
.pipe(
finalize(() => {
this.downloadURL = fileRef.getDownloadURL();
this.downloadURL.subscribe((url) => {
if (url) {
resolve(url);
}
});
})
)
.subscribe((url) => { });
});
}
uploadShopImage(fileUpload: File): Promise<string> {
return new Promise<string>((resolve) => {
let n = Date.now();
const filePath = `Shops/${n}`;
const fileRef = this.storage.ref(filePath);
const task = this.storage.upload(`Shops/${n}`, fileUpload);
task
.snapshotChanges()
.pipe(
finalize(() => {
this.downloadURL = fileRef.getDownloadURL();
this.downloadURL.subscribe((url) => {
if (url) {
resolve(url);
}
});
})
)
.subscribe((url) => { });
});
}
chooseImg(template: TemplateRef<any>) {
this.layer1 = this.modalService.show(template, { class: "modal-sm" });
}
continue() {
this.layer1.hide();
this.router.navigate(["/vendors/list"]);
}
closeLayer2() {
this.layer2.hide();
}
goBackToLogin() {
this.router.navigate(['/auth/login']);
}
//Getter
get image() {
return this.addUserForm.get("image");
}
get userFullName() {
return this.addUserForm.get("fullName");
}
get userEmail() {
return this.addUserForm.get("email");
}
get userDateOfBirth() {
return this.addUserForm.get("dateOfBirth");
}
get userPhoneNumber() {
return this.addUserForm.get("phoneNumber");
}
get userIdentifiedCode() {
return this.addUserForm.get("identifiedCode");
}
get userAddress() {
return this.addUserForm.get("address");
}
get userDistrict() {
return this.addUserForm.get("district");
}
get userWard() {
return this.addUserForm.get("ward");
}
get userPassword() {
return this.passwordForm.get("password");
}
get userConfirmPassword() {
return this.passwordForm.get("confirmPassword");
}
//Shop
get banner() {
return this.shopForm.get("banner");
}
get shopName() {
return this.shopForm.get("name");
}
get shopDescription() {
return this.shopForm.get("description");
}
}
@@ -0,0 +1,34 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ListCouponComponent } from './list-coupon/list-coupon.component';
import { CreateCouponComponent } from './create-coupon/create-coupon.component';
const routes: Routes = [
{
path: '',
children: [
{
path: 'list-coupons',
component: ListCouponComponent,
data: {
title: "List Coupons",
breadcrumb: "List Coupons"
}
},
{
path: 'create-coupons',
component: CreateCouponComponent,
data: {
title: "Create Coupon",
breadcrumb: "Create Coupons"
}
}
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class CouponsRoutingModule { }
@@ -0,0 +1,21 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { CouponsRoutingModule } from './coupons-routing.module';
import { ListCouponComponent } from './list-coupon/list-coupon.component';
import { CreateCouponComponent } from './create-coupon/create-coupon.component';
import { SharedModule } from 'src/app/shared/shared.module';
@NgModule({
declarations: [ListCouponComponent, CreateCouponComponent],
imports: [
CommonModule,
CouponsRoutingModule,
NgbModule,
ReactiveFormsModule,
SharedModule,
FormsModule
]
})
export class CouponsModule { }
@@ -0,0 +1,184 @@
<!-- Container-fluid starts-->
<div class="container-fluid">
<div class="card">
<div class="card-header">
<h5>Discount Coupon Details</h5>
</div>
<div class="card-body tab2-card">
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs">
<li [ngbNavItem]="1">
<a ngbNavLink>General</a>
<ng-template ngbNavContent>
<form [formGroup]="generalForm" class="needs-validation" novalidate="">
<h4>General</h4>
<div class="row">
<div class="col-sm-12">
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom0"><span>*</span>
Coupan Title</label>
</div>
<div class="col-md-7">
<input class="form-control" formControlName="name"
id="validationCustom0" type="text" required="">
</div>
</div>
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom1"><span>*</span>
Coupan Code</label>
</div>
<div class="col-md-7">
<input class="form-control" formControlName="code"
id="validationCustom1" type="text" required="">
</div>
<div class="valid-feedback">Please Provide a Valid Coupon Code.</div>
</div>
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label>Start Date</label>
</div>
<div class="col-md-7 ">
<div class="input-group input-grp-p">
<input class="datepicker-here form-control digits" ngbDatepicker
formControlName="start_date" #f="ngbDatepicker">
<div class="input-group-append">
<button class="btn calendar" (click)="f.toggle()" type="button"><i
class="fa fa-calendar"></i></button>
</div>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label>End Date</label>
</div>
<div class="col-md-7">
<div class="input-group input-grp-p">
<input class="datepicker-here form-control digits" ngbDatepicker
formControlName="end_date" #c="ngbDatepicker">
<div class="input-group-append">
<button class="btn calendar" (click)="c.toggle()" type="button"><i
class="fa fa-calendar"></i></button>
</div>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label>Free Shipping</label>
</div>
<div class="col-md-7">
<div class="checkbox checkbox-primary">
<input id="checkbox-primary-1" type="checkbox"
formControlName="free_shipping" data-original-title="" title="">
<label for="checkbox-primary-1">Allow Free Shipping</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-xl-3 col-md-4"><label>Quantity</label></div>
<div class="col-md-7">
<input class="form-control" formControlName="quantity" type="number"
required="">
</div>
</div>
<div class="form-group row">
<div class="col-xl-3 col-md-4"><label>Discount Type</label></div>
<div class="col-md-7">
<select class="custom-select form-select" required="">
<option value="">--Select--</option>
<option value="1">Percent</option>
<option value="2">Fixed</option>
</select>
</div>
</div>
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label>Status</label>
</div>
<div class="col-md-7">
<div class="checkbox checkbox-primary">
<input id="checkbox-primary-2" formControlName="status" type="checkbox"
data-original-title="" title="">
<label for="checkbox-primary-2">Enable the Coupon</label>
</div>
</div>
</div>
</div>
</div>
</form>
</ng-template>
</li>
<li [ngbNavItem]="2">
<a ngbNavLink>Restriction</a>
<ng-template ngbNavContent>
<form [formGroup]="restrictionForm" class="needs-validation" novalidate="">
<h4>Restriction</h4>
<div class="form-group row">
<div class="col-xl-3 col-md-4"><label for="validationCustom3">Products</label></div>
<div class="col-md-7"><input class="form-control" formControlName="products" id="validationCustom3"
type="text" required="">
</div>
<div class="valid-feedback">Please Provide a Product Name.</div>
</div>
<div class="form-group row">
<div class="col-xl-3 col-md-4"><label>Category</label></div>
<div class="col-md-7">
<select class="custom-select form-select" formControlName="category" required="">
<option value="">--Select--</option>
<option value="1">Electronics</option>
<option value="2">Clothes</option>
<option value="2">Shoes</option>
<option value="2">Digital</option>
</select>
</div>
</div>
<div class="form-group row">
<div class="col-xl-3 col-md-4"><label for="validationCustom4">Minimum Spend</label></div>
<div class="col-md-7">
<input class="form-control" formControlName="min" id="validationCustom4"
type="number">
</div>
</div>
<div class="form-group row">
<div class="col-xl-3 col-md-4">
<label for="validationCustom5">Maximum Spend</label>
</div>
<div class="col-md-7">
<input class="form-control" formControlName="max" id="validationCustom5"
type="number">
</div>
</div>
</form>
</ng-template>
</li>
<li [ngbNavItem]="3">
<a ngbNavLink>Usage</a>
<ng-template ngbNavContent>
<form [formGroup]="usageForm" class="needs-validation" novalidate="">
<h4>Usage Limits</h4>
<div class="form-group row">
<div class="col-xl-3 col-md-4"><label for="validationCustom6">Per Limit</label></div>
<div class="col-md-7"><input class="form-control" formControlName="limit" id="validationCustom6"
type="number">
</div>
</div>
<div class="form-group row">
<div class="col-xl-3 col-md-4"><label for="validationCustom7">Per Customer</label></div>
<div class="col-md-7"><input class="form-control" formControlName="customer" id="validationCustom7"
type="number">
</div>
</div>
</form>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav" class="mt-2"></div>
<div class="pull-right">
<button type="button" class="btn btn-primary">Save</button>
</div>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
@@ -0,0 +1,61 @@
import { Component, OnInit } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { NgbDateStruct, NgbDate, NgbCalendar, NgbDatepickerConfig } from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'app-create-coupon',
templateUrl: './create-coupon.component.html',
styleUrls: ['./create-coupon.component.scss']
})
export class CreateCouponComponent implements OnInit {
public generalForm: UntypedFormGroup;
public restrictionForm: UntypedFormGroup;
public usageForm: UntypedFormGroup;
public model: NgbDateStruct;
public date: { year: number, month: number };
public modelFooter: NgbDateStruct;
public active = 1;
constructor(private formBuilder: UntypedFormBuilder, private calendar: NgbCalendar) {
this.createGeneralForm();
this.createRestrictionForm();
this.createUsageForm();
}
selectToday() {
this.model = this.calendar.getToday();
}
createGeneralForm() {
this.generalForm = this.formBuilder.group({
name: [''],
code: [''],
start_date: [''],
end_date: [''],
free_shipping: [''],
quantity: [''],
discount_type: [''],
status: [''],
});
}
createRestrictionForm() {
this.restrictionForm = this.formBuilder.group({
products: [''],
category: [''],
min: [''],
max: ['']
})
}
createUsageForm() {
this.usageForm = this.formBuilder.group({
limit: [''],
customer: ['']
})
}
ngOnInit() {
}
}
@@ -0,0 +1,48 @@
<!-- Container-fluid starts-->
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Products Category</h5>
</div>
<div class="card-body">
<div class="custom-datatable">
<button class="btn btn-primary me-1 mb-3" style="float: right;">Delete</button>
<div class="clearfix"></div>
<table class="table table-striped">
<thead>
<tr>
<th scope="col" sortable="title" (sort)="onSort($event)">Title</th>
<th scope="col" sortable="code" (sort)="onSort($event)">Code</th>
<th scope="col" sortable="discount" (sort)="onSort($event)">Discoount</th>
<th scope="col" sortable="status" (sort)="onSort($event)">Status</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of tableItem$ | async ">
<th scope="row">
<input type="checkbox" name="title_select" onclick="onSelect(item.id)">
{{ item.title }}
</th>
<td>
{{item.code}}
</td>
<td>{{item.discount}}</td>
<td><i class="fa fa-circle {{item.status}} f-12"></i></td>
</tr>
</tbody>
</table>
<div class="d-flex justify-content-center p-2">
<ngb-pagination
[collectionSize]="(total$ | async)!" [(page)]="service.page" [pageSize]="service.pageSize">
</ngb-pagination>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
@@ -0,0 +1,51 @@
import {DecimalPipe} from '@angular/common';
import {Component, OnInit, QueryList, ViewChildren} from '@angular/core';
import {Observable} from 'rxjs';
import {NgbdSortableHeader, SortEvent} from 'src/app/shared/directives/NgbdSortableHeader';
import {TableService} from 'src/app/shared/service/table.service';
import {LISTCOUPLEDB, ListCouponsDB} from 'src/app/shared/tables/list-coupon';
@Component({
selector: 'app-list-coupon',
templateUrl: './list-coupon.component.html',
styleUrls: ['./list-coupon.component.scss'],
providers: [TableService, DecimalPipe]
})
export class ListCouponComponent implements OnInit {
public selected = [];
public tableItem$: Observable<ListCouponsDB[]>;
public searchText;
total$: Observable<number>;
constructor(public service: TableService) {
this.tableItem$ = service.tableItem$;
this.total$ = service.total$;
this.service.setUserData(LISTCOUPLEDB);
}
@ViewChildren(NgbdSortableHeader) headers: QueryList<NgbdSortableHeader>;
onSort({column, direction}: SortEvent) {
// resetting other headers
this.headers.forEach((header) => {
if (header.sortable !== column) {
header.direction = '';
}
});
this.service.sortColumn = column;
this.service.sortDirection = direction;
}
onSelect({selected}) {
this.selected.splice(0, this.selected.length);
this.selected.push(...selected);
}
ngOnInit() {
}
}
@@ -0,0 +1,28 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { DashboardComponent } from './dashboard.component';
import { AuthGuard } from 'src/app/shared/guard/auth-guard.service';
const routes: Routes = [
{
path: '',
children: [
{
path: 'default',
component: DashboardComponent,
canActivate: [AuthGuard],
data: {
title: "Trang chủ",
breadcrumb: "Trang chủ"
}
}
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class DashboardRoutingModule { }
@@ -0,0 +1,726 @@
<!-- Container-fluid starts-->
<div class="container-fluid">
<div class="row">
<div class="col-xl-3 col-md-6 xl-50">
<div class="card o-hidden widget-cards">
<div class="bg-warning card-body">
<div class="media static-top-widget row">
<div class="icons-widgets col-4">
<div class="align-self-center text-center">
<app-feather-icons *ngIf="isShop" [icon]="'dollar-sign'"
class="font-warning"></app-feather-icons>
<app-feather-icons *ngIf="!isShop" [icon]="'user'"
class="font-warning"></app-feather-icons>
</div>
</div>
<div *ngIf="isShop" class="media-body col-8"><span class="m-0">Thu nhập 30 ngày qua</span>
<h3 class="mb-0">
<span class="counter" [CountTo]="totalRevenue" [from]="0" [duration]="2">{{ totalRevenue
| currency : "VND"
}}</span>
</h3>
</div>
<div *ngIf="!isShop" class="media-body col-8"><span class="m-0">Người dùng đăng ký mới</span>
<h3 class="mb-0">
<span class="counter" [CountTo]="totalUser" [from]="0" [duration]="2">{{ totalUser
}}</span><small>
người dùng</small>
</h3>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 xl-50">
<div class="card o-hidden widget-cards">
<div class="bg-secondary card-body">
<div class="media static-top-widget row">
<div class="icons-widgets col-4">
<div class="align-self-center text-center">
<app-feather-icons [icon]="'package'" class="font-secondary"></app-feather-icons>
</div>
</div>
<div class="media-body col-8"><span class="m-0">Tổng sản phẩm đã bán</span>
<h3 class="mb-0"><span class="counter" [CountTo]="totalProducts" [from]="0"
[duration]="2">{{ totalProducts }}</span><small> sản phẩm</small></h3>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 xl-50">
<div class="card o-hidden widget-cards">
<div class="bg-primary card-body">
<div class="media static-top-widget row">
<div class="icons-widgets col-4">
<div class="align-self-center text-center">
<app-feather-icons [icon]="'message-square'" class="font-primary"></app-feather-icons>
</div>
</div>
<div class="media-body col-8"><span class="m-0">Số lượt đánh giá sản phẩm</span>
<h3 class="mb-0"><span class="counter" [CountTo]="totalReviewCount" [from]="0"
[duration]="2">{{ totalReviewCount }}</span><small> lượt</small></h3>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 xl-50">
<div class="card o-hidden widget-cards">
<div class="bg-danger card-body">
<div class="media static-top-widget row">
<div class="icons-widgets col-4">
<div class="align-self-center text-center">
<app-feather-icons [icon]="'star'" class="font-danger"></app-feather-icons>
</div>
</div>
<div class="media-body col-8"><span class="m-0">Đánh giá trung bình</span>
<h3 class="mb-0"><span class="counter" [CountTo]="totalAverageRating" [from]="0"
[duration]="2">{{ totalAverageRating }}</span><small> /5</small></h3>
</div>
</div>
</div>
</div>
</div>
<!-- <div class="col-xl-6 xl-100">-->
<!-- <div class="card height-equal">-->
<!-- <div class="card-header">-->
<!-- <h5>Market Value</h5>-->
<!-- </div>-->
<!-- <div class="card-body">-->
<!-- <div class="market-chart">-->
<!-- <x-chartist class="board-chart ct-golden-section" [data]="chart3.data" [type]="chart3.type"-->
<!-- [options]="chart3.options" [events]="chart3.events">-->
<!-- </x-chartist>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<div class="col-xl-12 xl-100">
<div class="card height-equal">
<div class="card-header">
<h5 *ngIf="isShop">Đơn hàng của bạn</h5>
<h5 *ngIf="!isShop">Các đơn hàng gần đây</h5>
</div>
<div class="card-body" *ngIf="orders.length">
<div class=" user-status table-responsive latest-order-table">
<table class="table table-bordernone">
<thead>
<tr>
<th scope="col">Mã đơn hàng</th>
<th scope="col">Tổng giá trị đơn hàng</th>
<th scope="col">Phương thức thanh toán</th>
<th scope="col">Trạng thái đơn hàng</th>
<th scope="col">Địa chỉ</th>
<th scope="col">Shipper</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let order of orders; let i = index">
<td>{{ order?.orderTrackingNumber }}</td>
<td>{{ order?.total | currency : "VND" }}</td>
<td><span [ngClass]="{
badge: true,
'badge-info': order.paymentMethod === 'ZALO PAY',
'badge-success': order.paymentMethod === 'CASH'
}">
{{ order.paymentMethod === "CASH"
? "Tiền mặt"
: order.paymentMethod === "ZALO PAY"
? "Zalo pay"
: ""
}}
</span></td>
<td><span [ngClass]="{
badge: true,
'badge-dark': order.status === 'AWAITING',
'badge-warning': order.status === 'CONFIRMED',
'badge-info': order.status === 'SHIPPING',
'badge-success': order.status === 'COMPLETED',
'badge-primary': order.status === 'CANCELED' || order.status === 'REJECT_DELIVERY'
}">
{{
order.status === "AWAITING"
? "Chờ xác nhận"
: order.status === "CONFIRMED"
? "Đã xác nhận"
: order.status === "SHIPPING"
? "Đang giao hàng"
: order.status === "COMPLETED"
? "Giao thành công"
: order.status === "CANCELED"
? "Đã huỷ đơn"
: order.status === "REJECT_DELIVERY"
? "Không nhận hàng"
: ""
}}
</span></td>
<td>{{ order?.address }}</td>
<td *ngIf="order?.shipper?.user?.fullName != null">{{ order?.shipper?.user?.fullName
}}</td>
<td *ngIf="order?.shipper?.user?.fullName == null"><strong>Đang chờ...</strong></td>
<td>
<a [routerLink]="['/sales', 'detail-order', 'user', order.user.id, 'order', order.id]"
style="cursor: pointer;">
<i style="color: lightseagreen;" class="fa fa-eye f-12"></i>
</a>&nbsp;
</td>
</tr>
<!-- <tr>
<td>2</td>
<td class="digits">$90.00</td>
<td class="font-secondary">Ewallets</td>
<td class="digits">Delivered</td>
</tr>
<tr>
<td>3</td>
<td class="digits">$240.00</td>
<td class="font-warning">Cash</td>
<td class="digits">Delivered</td>
</tr>
<tr>
<td>4</td>
<td class="digits">$120.00</td>
<td class="font-primary">Direct Deposit</td>
<td class="digits">$6523</td>
</tr>
<tr>
<td>5</td>
<td class="digits">$50.00</td>
<td class="font-primary">Bank Transfers</td>
<td class="digits">Delivered</td>
</tr> -->
</tbody>
</table>
<a [routerLink]="['/sales', 'orders']" class="btn btn-primary">Xem tất cả đơn hàng</a>
</div>
</div>
<div class=" user-status table-responsive latest-order-table" *ngIf="!orders.length">
<div class="d-flex justify-content-center p-2">
<img src="../../../assets/images/error/no-order.png" alt="" srcset="">
</div>
<div class="d-flex justify-content-center p-2">
<h4>Bạn chưa có đơn hàng nào</h4>
</div>
</div>
</div>
</div>
<!-- <div class="col-xl-3 col-md-6 xl-50">
<div class="card order-graph sales-carousel">
<div class="card-header">
<h6>Total Sales</h6>
<div class="row">
<div class="col-6">
<div class="small-chartjs">
<canvas baseChart id="small-graph-1" height="600" width="400"
[datasets]="smallLineChartData" [labels]="smallLineChartLabels"
[options]="smallLineChartOptions" [legend]="smallLineChartLegend"
[type]="smallLineChartType" (chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div>
<div class="col-6">
<div class="value-graph">
<h3>42% <span><i class="fa fa-angle-up font-primary"></i></span></h3>
</div>
</div>
</div>
</div>
<div class="card-body">
<div class="d-flex align-items-start">
<div class="media-body">
<span>Sales Last Month</span>
<h2 class="mb-0">9054</h2>
<p>0.25% <span><i class="fa fa-angle-up"></i></span></p>
<h5 class="f-w-600">Gross sales of August</h5>
<p>Lorem Ipsum is simply dummy text of the printing and typesetting</p>
</div>
<div class="bg-primary b-r-8">
<div class="small-box">
<i data-feather="briefcase"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 xl-50">
<div class="card order-graph sales-carousel">
<div class="card-header">
<h6>Total purchase</h6>
<div class="row">
<div class="col-6">
<div class="small-chartjs">
<canvas baseChart id="small-graph-2" height="60vw" width="140vw"
[datasets]="smallLine2ChartData" [labels]="smallLine2ChartLabels"
[options]="smallLine2ChartOptions" [legend]="smallLine2ChartLegend"
[type]="smallLine2ChartType" (chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div>
<div class="col-6">
<div class="value-graph">
<h3>20% <span><i class="fa fa-angle-up font-secondary"></i></span></h3>
</div>
</div>
</div>
</div>
<div class="card-body">
<div class="d-flex align-items-start">
<div class="media-body">
<span>Monthly purchase</span>
<h2 class="mb-0">2154</h2>
<p>0.13% <span><i class="fa fa-angle-up"></i></span></p>
<h5 class="f-w-600">Avg Gross purchase</h5>
<p>Lorem Ipsum is simply dummy text of the printing and typesetting</p>
</div>
<div class="bg-secondary b-r-8">
<div class="small-box">
<i data-feather="credit-card"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 xl-50">
<div class="card order-graph sales-carousel">
<div class="card-header">
<h6>Total cash transaction</h6>
<div class="row">
<div class="col-6">
<div class="small-chartjs">
<canvas baseChart id="small-graph-3" height="60vw" width="140vw"
[datasets]="smallLine3ChartData" [labels]="smallLine3ChartLabels"
[options]="smallLine3ChartOptions" [legend]="smallLine3ChartLegend"
[type]="smallLine3ChartType" (chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div>
<div class="col-6">
<div class="value-graph">
<h3>28% <span><i class="fa fa-angle-up font-warning"></i></span></h3>
</div>
</div>
</div>
</div>
<div class="card-body">
<div class="d-flex align-items-start">
<div class="media-body">
<span>Cash on hand</span>
<h2 class="mb-0">4672</h2>
<p>0.8% <span><i class="fa fa-angle-up"></i></span></p>
<h5 class="f-w-600">Details about cash</h5>
<p>Lorem Ipsum is simply dummy text of the printing and typesetting</p>
</div>
<div class="bg-warning b-r-8">
<div class="small-box">
<i data-feather="shopping-cart"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 xl-50">
<div class="card order-graph sales-carousel">
<div class="card-header">
<h6>Daily Deposits</h6>
<div class="row">
<div class="col-6">
<div class="small-chartjs">
<canvas baseChart id="small-graph-4" height="60vw" width="140vw"
[datasets]="smallLine4ChartData" [labels]="smallLine4ChartLabels"
[options]="smallLine4ChartOptions" [legend]="smallLine4ChartLegend"
[type]="smallLine4ChartType" (chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div>
<div class="col-6">
<div class="value-graph">
<h3>75% <span><i class="fa fa-angle-up font-danger"></i></span></h3>
</div>
</div>
</div>
</div>
<div class="card-body">
<div class="d-flex align-items-start">
<div class="media-body">
<span>Security Deposits</span>
<h2 class="mb-0">0782</h2>
<p>0.25% <span><i class="fa fa-angle-up"></i></span></p>
<h5 class="f-w-600">Gross sales of June</h5>
<p>Lorem Ipsum is simply dummy text of the printing and typesetting</p>
</div>
<div class="bg-danger b-r-8">
<div class="small-box">
<i data-feather="calendar"></i>
</div>
</div>
</div>
</div>
</div>
</div> -->
<!-- <div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Buy / Sell</h5>
</div>
<div class="card-body sell-graph">
<canvas baseChart id="myGraph" [datasets]="lineChartData" [labels]="lineChartLabels"
[options]="lineChartOptions" [legend]="lineChartLegend" [type]="lineChartType"
(chartHover)="chartHovered($event)" (chartClick)="chartClicked($event)"></canvas>
</div>
</div>
</div> -->
<!-- <div class="col-xl-12 xl-100">-->
<!-- <div class="card height-equal">-->
<!-- <div class="card-header">-->
<!-- <h5>Products Cart</h5>-->
<!-- </div>-->
<!-- <div class="card-body">-->
<!-- <div class="user-status table-responsive products-table">-->
<!-- <table class="table table-bordernone mb-0">-->
<!-- <thead>-->
<!-- <tr>-->
<!-- <th scope="col">Details</th>-->
<!-- <th scope="col">Quantity</th>-->
<!-- <th scope="col">Status</th>-->
<!-- <th scope="col">Price</th>-->
<!-- </tr>-->
<!-- </thead>-->
<!-- <tbody>-->
<!-- <tr>-->
<!-- <td>Simply dummy text of the printing</td>-->
<!-- <td class="digits">1</td>-->
<!-- <td class="font-primary">Pending</td>-->
<!-- <td class="digits">$6523</td>-->
<!-- </tr>-->
<!-- <tr>-->
<!-- <td>Long established</td>-->
<!-- <td class="digits">5</td>-->
<!-- <td class="font-secondary">Cancle</td>-->
<!-- <td class="digits">$6523</td>-->
<!-- </tr>-->
<!-- <tr>-->
<!-- <td>sometimes by accident</td>-->
<!-- <td class="digits">10</td>-->
<!-- <td class="font-secondary">Cancle</td>-->
<!-- <td class="digits">$6523</td>-->
<!-- </tr>-->
<!-- <tr>-->
<!-- <td>Classical Latin literature</td>-->
<!-- <td class="digits">9</td>-->
<!-- <td class="font-primary">Return</td>-->
<!-- <td class="digits">$6523</td>-->
<!-- </tr>-->
<!-- <tr>-->
<!-- <td>keep the site on the Internet</td>-->
<!-- <td class="digits">8</td>-->
<!-- <td class="font-primary">Pending</td>-->
<!-- <td class="digits">$6523</td>-->
<!-- </tr>-->
<!-- <tr>-->
<!-- <td>Molestiae consequatur</td>-->
<!-- <td class="digits">3</td>-->
<!-- <td class="font-secondary">Cancle</td>-->
<!-- <td class="digits">$6523</td>-->
<!-- </tr>-->
<!-- <tr>-->
<!-- <td>Pain can procure</td>-->
<!-- <td class="digits">8</td>-->
<!-- <td class="font-primary">Return</td>-->
<!-- <td class="digits">$6523</td>-->
<!-- </tr>-->
<!-- </tbody>-->
<!-- </table>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-xl-6 xl-100">-->
<!-- <div class="card height-equal">-->
<!-- <div class="card-header">-->
<!-- <h5>Empolyee Status</h5>-->
<!-- </div>-->
<!-- <div class="card-body">-->
<!-- <div class="user-status table-responsive products-table">-->
<!-- <table class="table table-bordernone mb-0">-->
<!-- <thead>-->
<!-- <tr>-->
<!-- <th scope="col">Name</th>-->
<!-- <th scope="col">Designation</th>-->
<!-- <th scope="col">Skill Level</th>-->
<!-- <th scope="col">Experience</th>-->
<!-- </tr>-->
<!-- </thead>-->
<!-- <tbody>-->
<!-- <tr>-->
<!-- <td class="bd-t-none u-s-tb">-->
<!-- <div class="align-middle image-sm-size"><img-->
<!-- class="img-radius align-top m-r-15 rounded-circle"-->
<!-- src="assets/images/dashboard/user2.jpg" alt="" data-original-title=""-->
<!-- title="">-->
<!-- <div class="d-inline-block">-->
<!-- <h6>John Deo <span class="text-muted digits">(14+ Online)</span></h6>-->
<!-- </div>-->
<!-- </div>-->
<!-- </td>-->
<!-- <td>Designer</td>-->
<!-- <td>-->
<!-- <div class="progress-showcase">-->
<!-- <div class="progress" style="height: 8px;">-->
<!-- <div class="progress-bar bg-primary" role="progressbar"-->
<!-- style="width: 30%" aria-valuenow="50" aria-valuemin="0"-->
<!-- aria-valuemax="100"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </td>-->
<!-- <td class="digits">2 Year</td>-->
<!-- </tr>-->
<!-- <tr>-->
<!-- <td class="bd-t-none u-s-tb">-->
<!-- <div class="align-middle image-sm-size"><img-->
<!-- class="img-radius align-top m-r-15 rounded-circle"-->
<!-- src="assets/images/dashboard/user1.jpg" alt="" data-original-title=""-->
<!-- title="">-->
<!-- <div class="d-inline-block">-->
<!-- <h6>Holio Mako <span class="text-muted digits">(250+ Online)</span></h6>-->
<!-- </div>-->
<!-- </div>-->
<!-- </td>-->
<!-- <td>Developer</td>-->
<!-- <td>-->
<!-- <div class="progress-showcase">-->
<!-- <div class="progress" style="height: 8px;">-->
<!-- <div class="progress-bar bg-secondary" role="progressbar"-->
<!-- style="width: 70%" aria-valuenow="50" aria-valuemin="0"-->
<!-- aria-valuemax="100"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </td>-->
<!-- <td class="digits">3 Year</td>-->
<!-- </tr>-->
<!-- <tr>-->
<!-- <td class="bd-t-none u-s-tb">-->
<!-- <div class="align-middle image-sm-size"><img-->
<!-- class="img-radius align-top m-r-15 rounded-circle"-->
<!-- src="assets/images/dashboard/man.png" alt="" data-original-title=""-->
<!-- title="">-->
<!-- <div class="d-inline-block">-->
<!-- <h6>Mohsib lara<span class="text-muted digits">(99+ Online)</span></h6>-->
<!-- </div>-->
<!-- </div>-->
<!-- </td>-->
<!-- <td>Tester</td>-->
<!-- <td>-->
<!-- <div class="progress-showcase">-->
<!-- <div class="progress" style="height: 8px;">-->
<!-- <div class="progress-bar bg-primary" role="progressbar"-->
<!-- style="width: 60%" aria-valuenow="50" aria-valuemin="0"-->
<!-- aria-valuemax="100"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </td>-->
<!-- <td class="digits">5 Month</td>-->
<!-- </tr>-->
<!-- <tr>-->
<!-- <td class="bd-t-none u-s-tb">-->
<!-- <div class="align-middle image-sm-size"><img-->
<!-- class="img-radius align-top m-r-15 rounded-circle"-->
<!-- src="assets/images/dashboard/user.png" alt="" data-original-title=""-->
<!-- title="">-->
<!-- <div class="d-inline-block">-->
<!-- <h6>Hileri Soli <span class="text-muted digits">(150+ Online)</span>-->
<!-- </h6>-->
<!-- </div>-->
<!-- </div>-->
<!-- </td>-->
<!-- <td>Designer</td>-->
<!-- <td>-->
<!-- <div class="progress-showcase">-->
<!-- <div class="progress" style="height: 8px;">-->
<!-- <div class="progress-bar bg-secondary" role="progressbar"-->
<!-- style="width: 30%" aria-valuenow="50" aria-valuemin="0"-->
<!-- aria-valuemax="100"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </td>-->
<!-- <td class="digits">3 Month</td>-->
<!-- </tr>-->
<!-- <tr>-->
<!-- <td class="bd-t-none u-s-tb">-->
<!-- <div class="align-middle image-sm-size"><img-->
<!-- class="img-radius align-top m-r-15 rounded-circle"-->
<!-- src="assets/images/dashboard/designer.jpg" alt=""-->
<!-- data-original-title="" title="">-->
<!-- <div class="d-inline-block">-->
<!-- <h6>Pusiz bia <span class="text-muted digits">(14+ Online)</span></h6>-->
<!-- </div>-->
<!-- </div>-->
<!-- </td>-->
<!-- <td>Designer</td>-->
<!-- <td>-->
<!-- <div class="progress-showcase">-->
<!-- <div class="progress" style="height: 8px;">-->
<!-- <div class="progress-bar bg-primary" role="progressbar"-->
<!-- style="width: 90%" aria-valuenow="50" aria-valuemin="0"-->
<!-- aria-valuemax="100"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </td>-->
<!-- <td class="digits">5 Year</td>-->
<!-- </tr>-->
<!-- </tbody>-->
<!-- </table>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Tổng quan đơn hàng</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-xl-6 col-sm-6 xl-50">
<div class="order-graph overflow-hidden">
<h6>Vị trí đặt đơn</h6>
<div class="chart-block chart-vertical-center">
<ngx-charts-pie-chart class="pie-colours-1" [view]="view"
[scheme]="doughnutChartColorScheme" [results]="pieData" [explodeSlices]="false"
[labels]="doughnutChartShowLabels" [arcWidth]=0.50 [doughnut]="true"
[animations]="false" [tooltipDisabled]="doughnutChartTooltip"
[gradient]="doughnutChartGradient">
</ngx-charts-pie-chart>
</div>
<div class="order-graph-bottom">
<div class="media" *ngIf="thanhKheValue > 0">
<div class="order-color-primary"></div>
<div class="media-body">
<h6 class="mb-0">Quận Thanh Khê <span class="pull-right">{{ thanhKheValue
}} đơn hàng</span></h6>
</div>
</div>
<div class="media" *ngIf="sonTraValue > 0">
<div class="order-color-secondary"></div>
<div class="media-body">
<h6 class="mb-0">Quận Sơn Trà <span class="pull-right">{{ sonTraValue
}} đơn hàng</span></h6>
</div>
</div>
<div class="media" *ngIf="camLeValue > 0">
<div class="order-color-danger"></div>
<div class="media-body">
<h6 class="mb-0">Quận Cẩm Lệ<span class="pull-right">{{ camLeValue }} đơn
hàng</span></h6>
</div>
</div>
<div class="media" *ngIf="lienChieuValue > 0">
<div class="order-color-warning"></div>
<div class="media-body">
<h6 class="mb-0">Quận Liên Chiểu<span class="pull-right">{{ lienChieuValue
}} đơn hàng</span></h6>
</div>
</div>
<div class="media" *ngIf="haiChauValue > 0">
<div class="order-color-success" style="background-color: #32CD32;"></div>
<div class="media-body">
<h6 class="mb-0">Quận Hải Châu<span class="pull-right">{{ haiChauValue }}
đơn hàng</span></h6>
</div>
</div>
<div class="media" *ngIf="hoaVangValue > 0">
<div class="order-color-success" style="background-color: mediumpurple;"></div>
<div class="media-body">
<h6 class="mb-0">Huyện Hoà Vang<span class="pull-right">{{ hoaVangValue }}
đơn hàng</span></h6>
</div>
</div>
<div class="media" *ngIf="nguHanhSonValue > 0">
<div class="order-color-success" style="background-color: deepskyblue;"></div>
<div class="media-body">
<h6 class="mb-0">Quận Ngũ Hành Sơn<span class="pull-right">{{
nguHanhSonValue }} đơn hàng</span></h6>
</div>
</div>
<div class="media" *ngIf="hoangSaValue > 0">
<div class="order-color-success" style="background-color: olive;"></div>
<div class="media-body">
<h6 class="mb-0">Huyện Hoàng Sa<span class="pull-right">{{ hoangSaValue }}
đơn hàng</span></h6>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-6 col-sm-6 xl-50">
<div class="order-graph sm-order-space overflow-hidden">
<h6>Top sản phẩm bán chạy</h6>
<div class="peity-chart-dashboard text-center chart-vertical-center">
<ngx-charts-pie-chart *ngIf="!isShop || (isShop && isData)" class="pie-colours-1"
[view]="view" [scheme]="doughnutChartColorScheme" [results]="doughnutData"
[explodeSlices]="false" [labels]="doughnutChartShowLabels" [arcWidth]=0.99
[doughnut]="true" [animations]="false" [tooltipDisabled]="doughnutChartTooltip"
[gradient]="doughnutChartGradient">
</ngx-charts-pie-chart>
</div>
<div class="order-graph-bottom sales-location">
<div class="media" *ngIf="products.length > 0">
<div class="order-shape-primary"></div>
<div class="media-body">
<h6 class="mb-0 me-0">{{ products[0]?.name }} <span class="pull-right">{{
products[0]?.sold }} lượt</span></h6>
</div>
</div>
<div class="media" *ngIf="products.length > 1">
<div class="order-shape-secondary"></div>
<div class="media-body">
<h6 class="mb-0 me-0">{{ products[1]?.name }} <span class="pull-right">{{
products[1]?.sold }} lượt</span></h6>
</div>
</div>
<div class="media" *ngIf="products.length > 2">
<div class="order-shape-danger"></div>
<div class="media-body">
<h6 class="mb-0 me-0">{{ products[2]?.name }} <span class="pull-right">{{
products[2]?.sold }} lượt</span></h6>
</div>
</div>
<div class="media" *ngIf="products.length > 3">
<div class="order-shape-warning"></div>
<div class="media-body">
<h6 class="mb-0 me-0">{{ products[3]?.name }} <span class="pull-right">{{
products[3]?.sold }} lượt</span></h6>
</div>
</div>
<div class="media" *ngIf="products.length > 4">
<div class="order-shape-success"></div>
<div class="media-body">
<h6 class="mb-0 me-0">{{ products[4]?.name }} <span class="pull-right">{{
products[4]?.sold }} lượt</span></h6>
</div>
</div>
</div>
</div>
</div>
<!-- <div class="col-xl-6 xl-100">-->
<!-- <div class="order-graph xl-space">-->
<!-- <h6>Revenue for last month</h6>-->
<!-- <div class="ct-4 flot-chart-container">-->
<!-- <x-chartist [type]="chart5.type" [data]="ch art5.data" [options]="chart5.options">-->
<!-- </x-chartist>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
@@ -0,0 +1,3 @@
th, td {
text-align: center;
}
@@ -0,0 +1,383 @@
import { AfterViewInit, Component, OnInit } from '@angular/core';
import * as chartData from '../../shared/data/chart';
import { doughnutData, pieData } from '../../shared/data/chart';
import { ShopService } from 'src/app/shared/service/shop.service';
import { ProductService } from 'src/app/shared/service/product.service';
import { Product } from 'src/app/shared/tables/product';
import { Order } from 'src/app/shared/tables/order-list';
import { OrderService } from 'src/app/shared/service/order.service';
import { resolve } from 'path';
import { forkJoin } from 'rxjs';
import { UserService } from 'src/app/shared/service/user.service';
import { FirebaseService } from 'src/app/shared/service/firebase.service';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit, AfterViewInit {
//Log-in
isShop: boolean = false;
loggedId: number = Number(localStorage.getItem('user-id'))
loggedRole = localStorage.getItem('user-role');
shopId: number;
//Shop Properties
totalRevenue: number = 0;
totalProducts: number = 0;
totalReviewCount: number = 0;
totalAverageRating: number = 0;
//Orders
orders: Order[] = [];
//Pagination Properties
thePageNumber = 1;
thePageSize = 5;
sortBy = "orderTime";
sortDir = "desc";
theTotalElements = 0;
//District value
thanhKheValue: number;
sonTraValue: number;
camLeValue: number;
lienChieuValue: number;
haiChauValue: number;
hoaVangValue: number;
nguHanhSonValue: number;
hoangSaValue: number;
//Products
products: Product[] = [];
isData: boolean = false;
//Admin
totalUser: number = 0;
day: number = 30;
public doughnutData: Array<Object> = [];
public pieData;
constructor(
private userService: UserService,
private shopService: ShopService,
private productService: ProductService,
private orderService: OrderService,
private firebaseService: FirebaseService
) {
}
ngOnInit() {
if (this.loggedRole != 'ROLE_ADMIN') {
this.isShop = true;
// this.shopId = Number(localStorage.getItem('shop-id'))
const shopIdPromise = new Promise<number>((resolve) => {
const shopId = Number(localStorage.getItem('shop-id'));
resolve(shopId);
})
shopIdPromise.then((shopId) => {
this.shopId = shopId;
this.productService.getProductsByShopIdNoPageable(this.shopId).subscribe((products) => {
let i = 0;
products.forEach(product => {
this.totalReviewCount = this.totalReviewCount + product.reviewCount;
this.totalProducts = this.totalProducts + product.sold;
if (product.averageRating != 0) {
this.totalAverageRating = this.totalAverageRating + product.averageRating;
i++;
}
});
if (i != 0) {
this.totalAverageRating = this.totalAverageRating / i;
}
})
this.productService.getProductsByShopIdAndSort(this.shopId, 0, 5, 'sold', 'desc').subscribe((data) => {
this.products = data.products;
if (this.products.length > 4) {
this.isData = true;
}
this.doughtnutData(data.products);
})
this.orderService.getOrdersByShopId(this.shopId, this.thePageNumber - 1, this.thePageSize, this.sortBy, this.sortDir)
.subscribe(this.processResult());
this.shopService.getShopRevenue(this.shopId, 30).subscribe((res) => {
this.totalRevenue = res;
});
this.valueOfShopDistrict();
})
}
else {
//ADMIN DO HERE
this.userService.countNewUser(this.day).subscribe((res) => {
this.totalUser = res;
})
this.productService.getProductsNoPagination().subscribe((products) => {
let i = 0;
products.forEach(product => {
this.totalReviewCount = this.totalReviewCount + product.reviewCount;
this.totalProducts = this.totalProducts + product.sold;
if (product.averageRating != 0) {
this.totalAverageRating = this.totalAverageRating + product.averageRating;
i++;
}
});
this.totalAverageRating = this.totalAverageRating / i;
})
this.productService.getProductsPaginationAndSort(0, 5, 'sold', 'desc').subscribe((data) => {
this.products = data.products;
this.doughtnutData(data.products)
})
this.orderService
.getOrdersPagination(this.thePageNumber - 1, this.thePageSize, this.sortBy, this.sortDir)
.subscribe(this.processResult());
this.valueOfAdminDistrict();
}
}
processResult() {
return (data: any) => {
this.orders = data.orders;
this.thePageNumber = data.page.pageNo + 1;
this.thePageSize = data.page.pageSize;
this.theTotalElements = data.page.totalElements;
};
}
// doughnut 2
public view = chartData.view;
public doughnutChartColorScheme = chartData.doughnutChartcolorScheme;
public doughnutChartShowLabels = chartData.doughnutChartShowLabels;
public doughnutChartGradient = chartData.doughnutChartGradient;
public doughnutChartTooltip = chartData.doughnutChartTooltip;
public chart5 = chartData.chart5;
// lineChart
public lineChartData = chartData.lineChartData;
public lineChartLabels = chartData.lineChartLabels;
public lineChartOptions = chartData.lineChartOptions;
public lineChartColors = chartData.lineChartColors;
public lineChartLegend = chartData.lineChartLegend;
public lineChartType = chartData.lineChartType;
// lineChart
public smallLineChartData = chartData.smallLineChartData;
public smallLineChartLabels = chartData.smallLineChartLabels;
public smallLineChartOptions = chartData.smallLineChartOptions;
public smallLineChartLegend = chartData.smallLineChartLegend;
public smallLineChartType = chartData.smallLineChartType;
// lineChart
public smallLine2ChartData = chartData.smallLine2ChartData;
public smallLine2ChartLabels = chartData.smallLine2ChartLabels;
public smallLine2ChartOptions = chartData.smallLine2ChartOptions;
public smallLine2ChartLegend = chartData.smallLine2ChartLegend;
public smallLine2ChartType = chartData.smallLine2ChartType;
// lineChart
public smallLine3ChartData = chartData.smallLine3ChartData;
public smallLine3ChartLabels = chartData.smallLine3ChartLabels;
public smallLine3ChartOptions = chartData.smallLine3ChartOptions;
public smallLine3ChartLegend = chartData.smallLine3ChartLegend;
public smallLine3ChartType = chartData.smallLine3ChartType;
// lineChart
public smallLine4ChartData = chartData.smallLine4ChartData;
public smallLine4ChartLabels = chartData.smallLine4ChartLabels;
public smallLine4ChartOptions = chartData.smallLine4ChartOptions;
public smallLine4ChartColors = chartData.smallLine4ChartColors;
public smallLine4ChartLegend = chartData.smallLine4ChartLegend;
public smallLine4ChartType = chartData.smallLine4ChartType;
public chart3 = chartData.chart3;
// events
public chartClicked(e: any): void {
}
public chartHovered(e: any): void {
}
ngAfterViewInit(): void {
}
valueOfAdminDistrict() {
const requests = [
this.orderService.countOrderByDistrict('Thanh Khê'),
this.orderService.countOrderByDistrict('Sơn Trà'),
this.orderService.countOrderByDistrict('Cẩm Lệ'),
this.orderService.countOrderByDistrict('Liên Chiểu'),
this.orderService.countOrderByDistrict('Hải Châu'),
this.orderService.countOrderByDistrict('Hoà Vang'),
this.orderService.countOrderByDistrict('Ngũ Hành Sơn'),
this.orderService.countOrderByDistrict('Hoàng Sa')
];
forkJoin(requests).subscribe(
(results: number[]) => {
this.thanhKheValue = results[0];
this.sonTraValue = results[1];
this.camLeValue = results[2];
this.lienChieuValue = results[3];
this.haiChauValue = results[4];
this.hoaVangValue = results[5];
this.nguHanhSonValue = results[6];
this.hoangSaValue = results[7];
this.pieData = [
{
value: this.thanhKheValue,
name: "Quận Thanh Khê"
},
{
value: this.sonTraValue,
name: "Quận Sơn Trà"
},
{
value: this.camLeValue,
name: "Quận Cẩm Lệ"
},
{
value: this.lienChieuValue,
name: "Quận Liên Chiểu"
},
{
value: this.haiChauValue,
name: "Quận Hải Châu"
},
{
value: this.hoaVangValue,
name: "Huyện Hoà Vang"
},
{
value: this.nguHanhSonValue,
name: "Quận Ngũ Hành Sơn"
},
{
value: this.hoangSaValue,
name: "Huyện Hoàng Sa"
}
]
},
(error) => {
console.error('Error:', error);
}
);
}
valueOfShopDistrict() {
const requests = [
this.orderService.countShopOrderByDistrict(this.shopId, 'Thanh Khê'),
this.orderService.countShopOrderByDistrict(this.shopId, 'Sơn Trà'),
this.orderService.countShopOrderByDistrict(this.shopId, 'Cẩm Lệ'),
this.orderService.countShopOrderByDistrict(this.shopId, 'Liên Chiểu'),
this.orderService.countShopOrderByDistrict(this.shopId, 'Hải Châu'),
this.orderService.countShopOrderByDistrict(this.shopId, 'Hoà Vang'),
this.orderService.countShopOrderByDistrict(this.shopId, 'Ngũ Hành Sơn'),
this.orderService.countShopOrderByDistrict(this.shopId, 'Hoàng Sa')
];
forkJoin(requests).subscribe(
(results: number[]) => {
this.thanhKheValue = results[0];
this.sonTraValue = results[1];
this.camLeValue = results[2];
this.lienChieuValue = results[3];
this.haiChauValue = results[4];
this.hoaVangValue = results[5];
this.nguHanhSonValue = results[6];
this.hoangSaValue = results[7];
this.pieData = [
{
value: this.thanhKheValue,
name: "Quận Thanh Khê"
},
{
value: this.sonTraValue,
name: "Quận Sơn Trà"
},
{
value: this.camLeValue,
name: "Quận Cẩm Lệ"
},
{
value: this.lienChieuValue,
name: "Quận Liên Chiểu"
},
{
value: this.haiChauValue,
name: "Quận Hải Châu"
},
{
value: this.hoaVangValue,
name: "Huyện Hoà Vang"
},
{
value: this.nguHanhSonValue,
name: "Quận Ngũ Hành Sơn"
},
{
value: this.hoangSaValue,
name: "Huyện Hoàng Sa"
}
]
},
(error) => {
console.error('Error:', error);
}
);
}
doughtnutData(products: Product[]) {
// products.forEach((product) => {
// this.doughnutData.push({ value: product.sold, name: product.name });
// })
this.doughnutData = [
{
value: products[0]?.sold,
name: products[0]?.name
},
{
value: products[1]?.sold,
name: products[1]?.name
},
{
value: products[2]?.sold,
name: products[2]?.name
},
{
value: products[3]?.sold,
name: products[3]?.name
},
{
value: products[4]?.sold,
name: products[4]?.name
}
];
}
}
@@ -0,0 +1,26 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DashboardRoutingModule } from './dashboard-routing.module';
import { DashboardComponent } from './dashboard.component';
import { CountToModule } from 'angular-count-to';
import { NgChartsModule } from 'ng2-charts';
import { Ng2GoogleChartsModule } from 'ng2-google-charts';
import { NgxChartsModule } from '@swimlane/ngx-charts';
import { ChartistModule } from 'ng-chartist';
import { SharedModule } from '../../shared/shared.module';
@NgModule({
declarations: [DashboardComponent],
imports: [
CommonModule,
DashboardRoutingModule,
CountToModule,
SharedModule,
NgChartsModule,
Ng2GoogleChartsModule,
NgxChartsModule,
ChartistModule
]
})
export class DashboardModule { }
@@ -0,0 +1,21 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { InvoiceComponent } from './invoice.component';
const routes: Routes = [
{
path: '',
component: InvoiceComponent,
data: {
title: "Invoice",
breadcrumb: "Invoice"
}
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class InvoiceRoutingModule { }
@@ -0,0 +1,57 @@
<!-- Container-fluid starts-->
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Invoice List</h5>
</div>
<div class="card-body">
<div id="batchDelete" class="custom-datatable">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th scope="col" sortable="no">No</th>
<th scope="col" sortable="invoice">Invoice</th>
<th scope="col" sortable="name" >Date</th>
<th scope="col" sortable="name" >Shipping</th>
<th scope="col" sortable="name" >Amount</th>
<th scope="col" sortable="name" >Tax</th>
<th scope="col" sortable="name" >Total</th>
<th scope="col" sortable="name" >Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of tableItem$ | async ">
<td scope="row">
<span>{{item.no}}</span>
</td>
<td>
{{item.invoice}}
</td>
<td>{{item.date}}</td>
<td>{{item.shipping}}</td>
<td>{{item.amount}}</td>
<td>{{item.tax}}</td>
<td>{{item.total}}</td>
<td>
<a href="javascript:void(0)"><i class='fa fa-edit f-12'></i></a>&nbsp;
<a href="javascript:void(0)"><i class="fa fa-trash-o"></i></a>
</td>
</tr>
</tbody>
</table>
<div class="d-flex justify-content-center p-2">
<ngb-pagination
[collectionSize]="(total$ | async)!" [(page)]="service.page" [pageSize]="service.pageSize">
</ngb-pagination>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
@@ -0,0 +1,45 @@
import { DecimalPipe } from '@angular/common';
import { Component, OnInit, QueryList, ViewChildren } from '@angular/core';
import { Observable } from 'rxjs';
import { NgbdSortableHeader, SortEvent } from 'src/app/shared/directives/NgbdSortableHeader';
import { TableService } from 'src/app/shared/service/table.service';
import { InvoiceDB, INVOICEDB } from '../../shared/tables/invoice';
@Component({
selector: 'app-invoice',
templateUrl: './invoice.component.html',
styleUrls: ['./invoice.component.scss'],
providers: [TableService, DecimalPipe]
})
export class InvoiceComponent implements OnInit {
public tableItem$: Observable<InvoiceDB[]>;
public searchText;
total$: Observable<number>;
constructor(public service: TableService) {
this.tableItem$ = service.tableItem$;
this.total$ = service.total$;
this.service.setUserData(INVOICEDB)
}
@ViewChildren(NgbdSortableHeader) headers: QueryList<NgbdSortableHeader>;
onSort({ column, direction }: SortEvent) {
// resetting other headers
this.headers.forEach((header) => {
if (header.sortable !== column) {
header.direction = '';
}
});
this.service.sortColumn = column;
this.service.sortDirection = direction;
}
ngOnInit() {
}
}
@@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { InvoiceRoutingModule } from './invoice-routing.module';
import { InvoiceComponent } from './invoice.component';
import { SharedModule } from 'src/app/shared/shared.module';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
@NgModule({
declarations: [InvoiceComponent],
imports: [
CommonModule,
InvoiceRoutingModule,
SharedModule,
NgbModule
]
})
export class InvoiceModule { }
@@ -0,0 +1,39 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { RatesComponent } from './rates/rates.component';
import { TranslationsComponent } from './translations/translations.component';
import { TaxesComponent } from './taxes/taxes.component';
const routes: Routes = [
{
path: 'translations',
component: TranslationsComponent,
data: {
title: "Translations",
breadcrumb: "Translations"
}
},
{
path: 'currency-rates',
component: RatesComponent,
data: {
title: "Currency Rates",
breadcrumb: "Currency Rates"
}
},
{
path: 'taxes',
component: TaxesComponent,
data: {
title: 'Taxes',
breadcrumb: 'Taxes'
}
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class LocalizationRoutingModule { }
@@ -0,0 +1,21 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LocalizationRoutingModule } from './localization-routing.module';
import { TranslationsComponent } from './translations/translations.component';
import { RatesComponent } from './rates/rates.component';
import { TaxesComponent } from './taxes/taxes.component';
import { SharedModule } from 'src/app/shared/shared.module';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
@NgModule({
declarations: [TranslationsComponent, RatesComponent, TaxesComponent],
imports: [
CommonModule,
LocalizationRoutingModule,
SharedModule,
NgbModule
]
})
export class LocalizationModule { }
@@ -0,0 +1,46 @@
<!-- Container-fluid starts-->
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Currency Details</h5>
</div>
<div class="card-body">
<div id="batchDelete" class="custom-datatable">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th scope="col" sortable="title" (sort)="onSort($event)">Currency Title</th>
<th scope="col" sortable="usd" (sort)="onSort($event)">USD</th>
<th scope="col" sortable="code" (sort)="onSort($event)">Code</th>
<th scope="col" sortable="rate" (sort)="onSort($event)">Exchange Rate</th>
<th scope="col" >Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of tableItem$ | async ">
<td scope="row">
<span>{{item.title}}</span>
</td>
<td>
{{item.usd}}
</td>
<td>{{item.code}}</td>
<td>{{item.rate}}</td>
<td>
<a href="javascript:void(0)"><i class='fa fa-edit f-12'></i></a>&nbsp;
<a href="javascript:void(0)"><i class="fa fa-trash-o"></i></a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
@@ -0,0 +1,46 @@
import { DecimalPipe } from '@angular/common';
import { Component, OnInit, QueryList, ViewChildren } from '@angular/core';
import { Observable } from 'rxjs';
import { NgbdSortableHeader, SortEvent } from 'src/app/shared/directives/NgbdSortableHeader';
import { TableService } from 'src/app/shared/service/table.service';
import { RateDB, RATEDB } from '../../../shared/tables/rate';
@Component({
selector: 'app-rates',
templateUrl: './rates.component.html',
styleUrls: ['./rates.component.scss'],
providers: [TableService, DecimalPipe]
})
export class RatesComponent implements OnInit {
public tableItem$: Observable<RateDB[]>;
public searchText;
total$: Observable<number>;
constructor(public service: TableService) {
this.tableItem$ = service.tableItem$;
this.total$ = service.total$;
this.service.setUserData(RATEDB)
}
@ViewChildren(NgbdSortableHeader) headers: QueryList<NgbdSortableHeader>;
onSort({ column, direction }: SortEvent) {
// resetting other headers
this.headers.forEach((header) => {
if (header.sortable !== column) {
header.direction = '';
}
});
this.service.sortColumn = column;
this.service.sortDirection = direction;
}
ngOnInit() {
}
}
@@ -0,0 +1,46 @@
<!-- Container-fluid starts-->
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Tax Details</h5>
</div>
<div class="card-body">
<div id="batchDelete" class="custom-datatable">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th scope="col" sortable="detail" (sort)="onSort($event)">Currency Title</th>
<th scope="col" sortable="tax_schedule" (sort)="onSort($event)">USD</th>
<th scope="col" sortable="rate" (sort)="onSort($event)" >Code</th>
<th scope="col" sortable="total_amount" (sort)="onSort($event)">Exchange Rate</th>
<th scope="col" (sort)="onSort($event)">Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of tableItem$ | async ">
<td scope="row">
<span>{{item.detail}}</span>
</td>
<td>
{{item.tax_schedule}}
</td>
<td>{{item.rate}}</td>
<td>{{item.total_amount}}</td>
<td>
<a href="javascript:void(0)"><i class='fa fa-edit f-12'></i></a>&nbsp;
<a href="javascript:void(0)"><i class="fa fa-trash-o"></i></a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
@@ -0,0 +1,43 @@
import { DecimalPipe } from '@angular/common';
import { Component, OnInit, QueryList, ViewChildren } from '@angular/core';
import { Observable } from 'rxjs';
import { NgbdSortableHeader, SortEvent } from 'src/app/shared/directives/NgbdSortableHeader';
import { TableService } from 'src/app/shared/service/table.service';
import { TaxesDB, TAXESDB } from '../../../shared/tables/taxes';
@Component({
selector: 'app-taxes',
templateUrl: './taxes.component.html',
styleUrls: ['./taxes.component.scss'],
providers: [TableService, DecimalPipe]
})
export class TaxesComponent implements OnInit {
public tableItem$: Observable<TaxesDB[]>;
public searchText;
total$: Observable<number>;
constructor(public service: TableService) {
this.tableItem$ = service.tableItem$;
this.total$ = service.total$;
this.service.setUserData(TAXESDB)
}
@ViewChildren(NgbdSortableHeader) headers: QueryList<NgbdSortableHeader>;
onSort({ column, direction }: SortEvent) {
// resetting other headers
this.headers.forEach((header) => {
if (header.sortable !== column) {
header.direction = '';
}
});
this.service.sortColumn = column;
this.service.sortDirection = direction;
}
ngOnInit() {
}
}
@@ -0,0 +1,51 @@
<!-- Container-fluid starts-->
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Translations</h5>
</div>
<div class="card-body">
<div id="batchDelete" class="custom-datatable">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th scope="col" sortable="user_key" (sort)="onSort($event)">User Key</th>
<th scope="col" sortable="russian" (sort)="onSort($event)">Russian</th>
<th scope="col" sortable="arabic" (sort)="onSort($event)">Arbic</th>
<th scope="col" sortable="english" (sort)="onSort($event)">English</th>
<th scope="col" >Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of tableItem$ | async ">
<td scope="row">
<span>{{item.user_key}}</span>
</td>
<td>
{{item.russian}}
</td>
<td>{{item.arabic}}</td>
<td>{{item.english}}</td>
<td>
<a href="javascript:void(0)"><i class='fa fa-edit f-12'></i></a>&nbsp;
<a href="javascript:void(0)"><i class="fa fa-trash-o"></i></a>
</td>
</tr>
</tbody>
</table>
<div class="d-flex justify-content-center p-2">
<ngb-pagination
[collectionSize]="(total$ | async)!" [(page)]="service.page" [pageSize]="service.pageSize">
</ngb-pagination>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
@@ -0,0 +1,45 @@
import { DecimalPipe } from '@angular/common';
import { Component, OnInit, QueryList, ViewChildren } from '@angular/core';
import { Observable } from 'rxjs';
import { NgbdSortableHeader, SortEvent } from 'src/app/shared/directives/NgbdSortableHeader';
import { TableService } from 'src/app/shared/service/table.service';
import { TRANSLATIONDB, TranslationsDB } from '../../../shared/tables/translations';
@Component({
selector: 'app-translations',
templateUrl: './translations.component.html',
styleUrls: ['./translations.component.scss'],
providers: [TableService, DecimalPipe]
})
export class TranslationsComponent implements OnInit {
public tableItem$: Observable<TranslationsDB[]>;
public searchText;
total$: Observable<number>;
constructor(public service: TableService) {
this.tableItem$ = service.tableItem$;
this.total$ = service.total$;
this.service.setUserData(TRANSLATIONDB)
}
@ViewChildren(NgbdSortableHeader) headers: QueryList<NgbdSortableHeader>;
onSort({ column, direction }: SortEvent) {
// resetting other headers
this.headers.forEach((header) => {
if (header.sortable !== column) {
header.direction = '';
}
});
this.service.sortColumn = column;
this.service.sortDirection = direction;
}
ngOnInit() {
}
}
@@ -0,0 +1,25 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { MediaComponent } from './media/media.component';
const routes: Routes = [
{
path: '',
children: [
{
path: '',
component: MediaComponent,
data: {
title: "Media",
breadcrumb: "Media"
}
}
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class MediaRoutingModule { }
+21
View File
@@ -0,0 +1,21 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MediaRoutingModule } from './media-routing.module';
import { MediaComponent } from './media/media.component';
import { NgxDropzoneModule } from 'ngx-dropzone';
import { SharedModule } from 'src/app/shared/shared.module';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
@NgModule({
declarations: [MediaComponent],
imports: [
CommonModule,
MediaRoutingModule,
NgxDropzoneModule,
SharedModule,
NgbModule
]
})
export class MediaModule { }
@@ -0,0 +1,67 @@
<!-- Container-fluid starts-->
<div class="container-fluid bulk-cate">
<div class="card ">
<div class="card-header">
<h5>Dropzone Media</h5>
</div>
<div class="card-body">
<div class="dropzone-custom">
<ngx-dropzone class="dropzone-border" (change)="onSelect($event)">
<ngx-dropzone-label>
<div class="dz-message needsclick">
<i class="fa fa-cloud-upload"></i>
<h4 class="mb-0 f-w-600">Drop files here or click to upload.</h4>
</div>
</ngx-dropzone-label>
<ngx-dropzone-preview *ngFor="let f of files" [removable]="true" (removed)="onRemove(f)">
<ngx-dropzone-label>{{ f.name }} ({{ f.type }})</ngx-dropzone-label>
</ngx-dropzone-preview>
</ngx-dropzone>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h5>Media File List</h5>
</div>
<div class="card-body">
<div id="batchDelete" class="category-table custom-datatable media-datatable">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th scope="col" sortable="action">Actions</th>
<th scope="col" sortable="img" (sort)="onSort($event)">Image</th>
<th scope="col" sortable="file_name" (sort)="onSort($event)">File Name</th>
<th scope="col" sortable="url" (sort)="onSort($event)">URL</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of tableItem$ | async ">
<td>
<a href="javascript:void(0)"><i class='fa fa-edit f-12'></i></a>&nbsp;
<a href="javascript:void(0)"><i class="fa fa-trash-o"></i></a>
</td>
<td scope="row">
<img [src]="item.img" class="imgTable" style="width: 20px">
</td>
<td>
{{item.file_name}}
</td>
<td>{{item.url}}</td>
</tr>
</tbody>
</table>
<div class="d-flex justify-content-center p-2 border-top-0">
<ngb-pagination
[collectionSize]="(total$ | async)!" [(page)]="service.page" [pageSize]="service.pageSize">
</ngb-pagination>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
@@ -0,0 +1,55 @@
import { DecimalPipe } from '@angular/common';
import { Component, OnInit, QueryList, ViewChildren } from '@angular/core';
import { DropzoneConfigInterface } from 'ngx-dropzone-wrapper';
import { Observable } from 'rxjs';
import { NgbdSortableHeader, SortEvent } from 'src/app/shared/directives/NgbdSortableHeader';
import { TableService } from 'src/app/shared/service/table.service';
import { MediaDB, MEDIADB } from 'src/app/shared/tables/media';
@Component({
selector: 'app-media',
templateUrl: './media.component.html',
styleUrls: ['./media.component.scss'],
providers: [TableService, DecimalPipe]
})
export class MediaComponent implements OnInit {
public tableItem$: Observable<MediaDB[]>;
public searchText;
total$: Observable<number>;
constructor(public service: TableService) {
this.tableItem$ = service.tableItem$;
this.total$ = service.total$;
this.service.setUserData(MEDIADB)
}
@ViewChildren(NgbdSortableHeader) headers: QueryList<NgbdSortableHeader>;
onSort({ column, direction }: SortEvent) {
// resetting other headers
this.headers.forEach((header) => {
if (header.sortable !== column) {
header.direction = '';
}
});
this.service.sortColumn = column;
this.service.sortDirection = direction;
}
files: File[] = [];
onSelect(event) {
this.files.push(...event.addedFiles);
}
onRemove(event) {
this.files.splice(this.files.indexOf(event), 1);
}
ngOnInit() {
}
}
@@ -0,0 +1,31 @@
<!-- Container-fluid starts-->
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Add Menu</h5>
</div>
<div class="card-body">
<form class="needs-validation">
<div class="form-group row">
<div class="col-xl-3 col-md-4"><label for="validationCustom0"><span>*</span> Menu Name</label></div>
<div class="col-md-8"><input class="form-control" id="validationCustom0" type="text" required=""></div>
</div>
<div clalass="form-group row">
<label class="col-xl-3 col-md-4">Status</label>
<div class="col-xl-9 col-md-8">
<div class="checkbox checkbox-primary">
<input id="checkbox-primary-2" type="checkbox" data-original-title="" title="">
<label for="checkbox-primary-2">Enable the Menu</label>
</div>
</div>
</div>
<button type="button" class="btn btn-primary">Save</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-create-menu',
templateUrl: './create-menu.component.html',
styleUrls: ['./create-menu.component.scss']
})
export class CreateMenuComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
@@ -0,0 +1,45 @@
<!-- Container-fluid starts-->
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Menu Lists</h5>
</div>
<div class="card-body">
<div class="custom-datatable">
<button class="btn btn-primary me-1 mb-3" style="float: right;" >Delete</button>
<div class="clearfix"></div>
<table class="table table-striped">
<thead>
<tr>
<th></th>
<th scope="col" sortable="name" (sort)="onSort($event)">Name</th>
<th scope="col" sortable="status" (sort)="onSort($event)">Status</th>
<th scope="col" sortable="created_on" (sort)="onSort($event)">Created On</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of tableItem$ | async ">
<th> <input type="checkbox" name="title_select" onclick="onSelect(item.id)"></th>
<td scope="row">
{{item.name}}
</td>
<td>
<i class="fa fa-circle {{item.status}} f-12"></i>
</td>
<td>{{item.created_on}}</td>
</tr>
</tbody>
</table>
<div class="d-flex justify-content-center p-2">
<ngb-pagination
[collectionSize]="(total$ | async)!" [(page)]="service.page" [pageSize]="service.pageSize">
</ngb-pagination>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,51 @@
import { DecimalPipe } from '@angular/common';
import { Component, OnInit, QueryList, ViewChildren } from '@angular/core';
import { Observable } from 'rxjs';
import { NgbdSortableHeader, SortEvent } from 'src/app/shared/directives/NgbdSortableHeader';
import { TableService } from 'src/app/shared/service/table.service';
import { ListPagesDB } from 'src/app/shared/tables/list-pages';
import { MENUDB, MenuDB } from 'src/app/shared/tables/menu';
@Component({
selector: 'app-list-menu',
templateUrl: './list-menu.component.html',
styleUrls: ['./list-menu.component.scss'],
providers: [TableService, DecimalPipe]
})
export class ListMenuComponent implements OnInit {
public selected = [];
public tableItem$: Observable<ListPagesDB[]>;
public searchText;
total$: Observable<number>;
constructor(public service: TableService) {
this.tableItem$ = service.tableItem$;
this.total$ = service.total$;
this.service.setUserData(MENUDB)
}
@ViewChildren(NgbdSortableHeader) headers: QueryList<NgbdSortableHeader>;
onSort({ column, direction }: SortEvent) {
// resetting other headers
this.headers.forEach((header) => {
if (header.sortable !== column) {
header.direction = '';
}
});
this.service.sortColumn = column;
this.service.sortDirection = direction;
}
onSelect({ selected }) {
this.selected.splice(0, this.selected.length);
this.selected.push(...selected);
}
ngOnInit() { }
}
@@ -0,0 +1,34 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ListMenuComponent } from './list-menu/list-menu.component';
import { CreateMenuComponent } from './create-menu/create-menu.component';
const routes: Routes = [
{
path: '',
children: [
{
path: 'list-menu',
component: ListMenuComponent,
data: {
title: "Menu Lists",
breadcrumb: "Menu Lists"
}
},
{
path: 'create-menu',
component: CreateMenuComponent,
data: {
title: "Create Menu",
breadcrumb: "Create Menu"
}
}
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class MenusRoutingModule { }
+21
View File
@@ -0,0 +1,21 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
// import { NgxDatatableModule } from '@swimlane/ngx-datatable';
import { MenusRoutingModule } from './menus-routing.module';
import { ListMenuComponent } from './list-menu/list-menu.component';
import { CreateMenuComponent } from './create-menu/create-menu.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { SharedModule } from 'src/app/shared/shared.module';
@NgModule({
declarations: [ListMenuComponent, CreateMenuComponent],
imports: [
CommonModule,
MenusRoutingModule,
NgbModule,
SharedModule
]
})
export class MenusModule { }
@@ -0,0 +1,79 @@
<!-- Container-fluid starts-->
<div class="container-fluid">
<div class="card">
<div class="card-header">
<h5>Add Page</h5>
</div>
<div class="card-body tab2-card">
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs">
<li [ngbNavItem]="1">
<a ngbNavLink>General</a>
<ng-template ngbNavContent>
<form [formGroup]="generalForm" class="needs-validation" novalidate="">
<h4>General</h4>
<div class="form-group row align-items-center">
<div class="col-xl-3 col-md-4"><label for="validationCustom0"><span>*</span> Name</label></div>
<div class="col-xl-8 col-md-7"><input class="form-control" formControlName="name"
id="validationCustom0" type="text" required="">
</div>
</div>
<div class="form-group row editor-label align-items-center">
<div class="col-xl-3 col-md-4"><label>Description</label></div>
<div class="col-xl-8 col-md-7"><textarea class="w-100" rows="4" formControlName="desc"></textarea></div>
</div>
<div class="form-group row">
<div class="col-xl-3 col-md-4"><label>Status</label></div>
<div class="col-xl-8 col-md-7"><div class="checkbox checkbox-primary">
<input id="checkbox-primary-2" formControlName="status" type="checkbox"
data-original-title="" title="">
<label for="checkbox-primary-2">Enable the Page</label>
</div>
</div>
</div>
</form>
</ng-template>
</li>
<li [ngbNavItem]="2">
<a ngbNavLink>SEO</a>
<ng-template ngbNavContent>
<form [formGroup]="seoForm" class="needs-validation" novalidate="">
<h4>SEO</h4>
<div class="form-group row">
<div class="col-xl-3 col-md-4"><label for="validationCustom2">Meta Title</label></div>
<div class="col-xl-8 col-md-7"><input class="form-control" formControlName="title"
id="validationCustom2" type="text" required=""></div>
</div>
<div class="form-group row align-items-center editor-label">
<div class="col-xl-3 col-md-4"><label>Meta Description</label></div>
<div class="col-xl-8 col-md-7"><textarea rows="4" class="w-100" formControlName="meta_desc"></textarea></div>
</div>
</form>
</ng-template>
</li>
<!-- <li [ngbNavItem]="3">
<a ngbNavLink>Usage</a>
<ng-template ngbNavContent>
<form [formGroup]="usageForm" class="needs-validation" novalidate="">
<h4>Usage Limits</h4>
<div class="form-group row">
<label for="validationCustom6" class="col-xl-3 col-md-4">Per Limit</label>
<input class="form-control col-md-7" formControlName="limit" id="validationCustom6"
type="number">
</div>
<div class="form-group row">
<label for="validationCustom7" class="col-xl-3 col-md-4">Per Customer</label>
<input class="form-control col-md-7" formControlName="customer" id="validationCustom7"
type="number">
</div>
</form>
</ng-template>
</li> -->
</ul>
<div [ngbNavOutlet]="nav" class="mt-2"></div>
<div class="pull-right">
<button type="button" class="btn btn-primary">Save</button>
</div>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
@@ -0,0 +1,37 @@
import { Component, OnInit } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
@Component({
selector: 'app-create-page',
templateUrl: './create-page.component.html',
styleUrls: ['./create-page.component.scss']
})
export class CreatePageComponent implements OnInit {
public generalForm: UntypedFormGroup;
public seoForm: UntypedFormGroup;
public active = 1;
constructor(private formBuilder: UntypedFormBuilder) {
this.createGeneralForm();
this.createSeoForm();
}
createGeneralForm() {
this.generalForm = this.formBuilder.group({
name: [''],
desc: [''],
status: ['']
})
}
createSeoForm() {
this.seoForm = this.formBuilder.group({
title: [''],
keyword: [''],
meta_desc: ['']
})
}
ngOnInit() {
}
}
@@ -0,0 +1,43 @@
<!-- Container-fluid starts-->
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Page Details</h5>
</div>
<div class="card-body">
<div class="custom-datatable">
<button class="btn btn-primary me-1 mb-3" style="float: right;">Delete</button>
<div class="clearfix"></div>
<table class="table table-striped">
<thead>
<tr>
<th> </th>
<th scope="col" sortable="name" (sort)="onSort($event)">Name</th>
<th scope="col" sortable="status" (sort)="onSort($event)">Status</th>
<th scope="col" sortable="created_on" (sort)="onSort($event)">Created On</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of tableItem$ | async ">
<th> <input type="checkbox" name="title_select" onclick="onSelect(item.id)"></th>
<th scope="row">
{{ item.name }}</th>
<td><i class="fa fa-circle {{item.status}} f-12"></i></td>
<td>{{item.created_on}}</td>
</tr>
</tbody>
</table>
<div class="d-flex justify-content-center p-2">
<ngb-pagination
[collectionSize]="(total$ | async)!" [(page)]="service.page" [pageSize]="service.pageSize">
</ngb-pagination>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
@@ -0,0 +1,49 @@
import { DecimalPipe } from '@angular/common';
import { Component, OnInit, QueryList, ViewChildren } from '@angular/core';
import { Observable } from 'rxjs';
import { NgbdSortableHeader, SortEvent } from 'src/app/shared/directives/NgbdSortableHeader';
import { TableService } from 'src/app/shared/service/table.service';
import { LISTPAGEDB, ListPagesDB } from 'src/app/shared/tables/list-pages';
@Component({
selector: 'app-list-page',
templateUrl: './list-page.component.html',
styleUrls: ['./list-page.component.scss'],
providers: [TableService, DecimalPipe]
})
export class ListPageComponent implements OnInit {
public selected = [];
public tableItem$: Observable<ListPagesDB[]>;
public searchText;
total$: Observable<number>;
constructor(public service: TableService) {
this.tableItem$ = service.tableItem$;
this.total$ = service.total$;
this.service.setUserData(LISTPAGEDB)
}
@ViewChildren(NgbdSortableHeader) headers: QueryList<NgbdSortableHeader>;
onSort({ column, direction }: SortEvent) {
// resetting other headers
this.headers.forEach((header) => {
if (header.sortable !== column) {
header.direction = '';
}
});
this.service.sortColumn = column;
this.service.sortDirection = direction;
}
onSelect({ selected }) {
this.selected.splice(0, this.selected.length);
this.selected.push(...selected);
}
ngOnInit() {
}
}
@@ -0,0 +1,34 @@
import { NgModule, Component } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ListPageComponent } from './list-page/list-page.component';
import { CreatePageComponent } from './create-page/create-page.component';
const routes: Routes = [
{
path: '',
children: [
{
path: 'list-page',
component: ListPageComponent,
data: {
title: "List Page",
breadcrumb: "List Page"
}
},
{
path: 'create-page',
component: CreatePageComponent,
data: {
title: "Create Page",
breadcrumb: "Create Page"
}
}
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PagesRoutingModule { }
+21
View File
@@ -0,0 +1,21 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { ReactiveFormsModule } from '@angular/forms';
import { PagesRoutingModule } from './pages-routing.module';
import { ListPageComponent } from './list-page/list-page.component';
import { CreatePageComponent } from './create-page/create-page.component';
import { SharedModule } from 'src/app/shared/shared.module';
@NgModule({
declarations: [ListPageComponent, CreatePageComponent],
imports: [
CommonModule,
PagesRoutingModule,
NgbModule,
ReactiveFormsModule,
SharedModule
]
})
export class PagesModule { }
@@ -0,0 +1,108 @@
<div class="container-fluid">
<div class="row product-adding">
<div class="col-xl-6">
<div class="card">
<div class="card-header">
<h5>General</h5>
</div>
<div class="card-body">
<div class="digital-add needs-validation">
<div class="form-group">
<label for="validationCustom01" class="col-form-label pt-0"><span>*</span> Title</label>
<input class="form-control" id="validationCustom01" type="text" required="">
</div>
<div class="form-group">
<label for="validationCustomtitle" class="col-form-label pt-0"><span>*</span> SKU</label>
<input class="form-control" id="validationCustomtitle" type="text" required="">
</div>
<div class="form-group">
<label class="col-form-label"><span>*</span> Categories</label>
<select class="custom-select form-select" required="">
<option value="">--Select--</option>
<option value="1">eBooks</option>
<option value="2">Graphic Design</option>
<option value="3">3D Impact</option>
<option value="4">Application</option>
<option value="5">Websites</option>
</select>
</div>
<div class="form-group">
<label class="col-form-label">Sort Summary</label>
<textarea rows="5" cols="12"></textarea>
</div>
<div class="form-group">
<label for="validationCustom02" class="col-form-label"><span>*</span> Product Price</label>
<input class="form-control" id="validationCustom02" type="text" required="">
</div>
<div class="form-group">
<label class="col-form-label"><span>*</span> Status</label>
<div class="m-checkbox-inline mb-0 custom-radio-ml d-flex radio-animated">
<label class="d-block" for="edo-ani">
<input class="radio_animated" id="edo-ani" type="radio" name="rdo-ani">
Enable
</label>
<label class="d-block" for="edo-ani1">
<input class="radio_animated" id="edo-ani1" type="radio" name="rdo-ani">
Disable
</label>
</div>
</div>
<label class="col-form-label pt-0"> Product Upload</label>
<div class="card-body dropzone-custom p-0">
<ngx-dropzone class="dropzone-border" (change)="onSelect($event)">
<ngx-dropzone-label>
<div class="dz-message needsclick">
<i class="fa fa-cloud-upload"></i>
<h4 class="mb-0 f-w-600">Drop files here or click to upload.</h4>
</div>
</ngx-dropzone-label>
<ngx-dropzone-preview *ngFor="let f of files" [removable]="true" (removed)="onRemove(f)">
<ngx-dropzone-label>{{ f.name }} ({{ f.type }})</ngx-dropzone-label>
</ngx-dropzone-preview>
</ngx-dropzone>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-6">
<div class="card h-auto">
<div class="card-header">
<h5>Add Description</h5>
</div>
<div class="card-body">
<div class="digital-add needs-validation">
<div class="form-group mb-0">
<div class="description-sm">
<ckeditor [editor]="Editor" data="<p>Hello, world!</p>"></ckeditor>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h5>Meta Data</h5>
</div>
<div class="card-body">
<div class="digital-add needs-validation">
<div class="form-group">
<label for="validationCustom05" class="col-form-label pt-0"> Meta Title</label>
<input class="form-control" id="validationCustom05" type="text" required="">
</div>
<div class="form-group">
<label class="col-form-label">Meta Description</label>
<textarea rows="4" cols="12"></textarea>
</div>
<div class="form-group mb-0">
<div class="product-buttons text-end mt-3">
<button type="button" class="btn btn-primary">Add</button>
<button type="button" class="btn btn-light">Discard</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,28 @@
import { Component, OnInit } from '@angular/core';
import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic';
@Component({
selector: 'app-digital-add',
templateUrl: './digital-add.component.html',
styleUrls: ['./digital-add.component.scss']
})
export class DigitalAddComponent implements OnInit {
public Editor = ClassicEditor;
constructor() { }
files: File[] = [];
onSelect(event) {
this.files.push(...event.addedFiles);
}
onRemove(event) {
this.files.splice(this.files.indexOf(event), 1);
}
ngOnInit() {
}
}
@@ -0,0 +1,80 @@
<!-- Container-fluid starts-->
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Digital Products</h5>
</div>
<div class="card-body">
<div class="btn-popup pull-right">
<button type="button" class="btn btn-primary" data-toggle="modal" data-original-title="test"
data-target="#exampleModal" (click)="open(content)">Add Category</button>
<ng-template #content let-modal>
<div class="modal-header">
<h5 class="modal-title f-w-600" id="exampleModalLabel">Add Digital Product</h5>
<button type="button" class="close" aria-label="Close"
(click)="modal.dismiss('Cross click')">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form class="needs-validation">
<div class="form">
<div class="form-group">
<label for="validationCustom01" class="mb-1">Category Name :</label>
<input class="form-control" id="validationCustom01" type="text">
</div>
<div class="form-group mb-0">
<label for="validationCustom02" class="mb-1">Category Image :</label>
<input class="form-control" id="validationCustom02" type="file">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" >Save</button>
<button class="btn btn-secondary" type="button" data-dismiss="modal"
(click)="modal.dismiss('Cross click')">Close</button>
</div>
</ng-template>
</div>
<div id="batchDelete" class="category-table custom-datatable">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col" sortable="img" (sort)="onSort($event)" >Image</th>
<th scope="col" sortable="product_name" (sort)="onSort($event)">Name</th>
<th scope="col" sortable="price" (sort)="onSort($event)" >Price</th>
<th scope="col" sortable="status" (sort)="onSort($event)">Status</th>
<th scope="col" sortable="category" (sort)="onSort($event)" >Category</th>
<th scope="col" sortable="action" (sort)="onSort($event)" >Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let catgory of tableItem$ | async">
<th scope="row">{{ catgory.id }}</th>
<td>
<img [src]="catgory.img" class="imgTable" style="width: 20px">
</td>
<td>{{ catgory.product_name }}</td>
<td>{{ catgory.price }}</td>
<td><i class='fa fa-circle {{catgory.status}} f-12'></i></td>
<td>{{ catgory.category}}</td>
<td>
<a href="javascript:void(0)"><i class='fa fa-edit f-12'></i></a>&nbsp;
<a href="javascript:void(0)"><i class="fa fa-trash-o"></i></a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
@@ -0,0 +1,62 @@
import { Component, OnInit, QueryList, ViewChildren } from '@angular/core';
import { DIGITALCATEGORY, DigitalCategoryDB } from 'src/app/shared/tables/digital-category';
import { NgbModal, ModalDismissReasons } from '@ng-bootstrap/ng-bootstrap';
import { SortEvent } from 'src/app/shared/directives/shorting.directive';
import { NgbdSortableHeader } from "src/app/shared/directives/NgbdSortableHeader";
import { TableService } from 'src/app/shared/service/table.service';
import { Observable } from 'rxjs';
import { DecimalPipe } from '@angular/common';
@Component({
selector: 'app-digital-category',
templateUrl: './digital-category.component.html',
styleUrls: ['./digital-category.component.scss'],
providers: [TableService, DecimalPipe],
})
export class DigitalCategoryComponent implements OnInit {
public closeResult: string;
tableItem$: Observable<DigitalCategoryDB[]>;
public digital_categories = []
constructor(public service: TableService, private modalService: NgbModal) {
this.tableItem$ = service.tableItem$;
this.service.setUserData(DIGITALCATEGORY)
}
@ViewChildren(NgbdSortableHeader) headers: QueryList<NgbdSortableHeader>;
onSort({ column, direction }: SortEvent) {
// resetting other headers
this.headers.forEach((header) => {
if (header.sortable !== column) {
header.direction = '';
}
});
this.service.sortColumn = column;
this.service.sortDirection = direction;
}
open(content) {
this.modalService.open(content, { ariaLabelledBy: 'modal-basic-title' }).result.then((result) => {
this.closeResult = `Closed with: ${result}`;
}, (reason) => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
});
}
private getDismissReason(reason: any): string {
if (reason === ModalDismissReasons.ESC) {
return 'by pressing ESC';
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
return 'by clicking on a backdrop';
} else {
return `with: ${reason}`;
}
}
ngOnInit() {
}
}
@@ -0,0 +1,44 @@
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Product Lists</h5>
</div>
<div class="card-body">
<div id="batchDelete" class="custom-datatable">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col" sortable="img" (sort)="onSort($event)">Image</th>
<th scope="col" sortable="title" (sort)="onSort($event)">Product Title</th>
<th scope="col" sortable="entry_type" (sort)="onSort($event)">Empty Type</th>
<th scope="col" sortable="quantity" (sort)="onSort($event)">Quantity</th>
<th scope="col" sortable="action" (sort)="onSort($event)">Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let catgory of tableItem$ | async ">
<th scope="row">{{ catgory.id }}</th>
<td>
<img [src]="catgory.img" class="imgTable" style="width: 20px">
</td>
<td>{{ catgory.title }}</td>
<td>{{ catgory.entry_type }}</td>
<td>{{ catgory.quantity}}</td>
<td>
<a href="javascript:void(0)"><i class='fa fa-edit f-12'></i></a>&nbsp;
<a href="javascript:void(0)"><i class="fa fa-trash-o"></i></a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,43 @@
import { DecimalPipe } from '@angular/common';
import { Component, OnInit, QueryList, ViewChildren } from '@angular/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { Observable } from 'rxjs';
import { SortEvent } from 'src/app/shared/directives/shorting.directive';
import { NgbdSortableHeader } from "src/app/shared/directives/NgbdSortableHeader";
import { TableService } from 'src/app/shared/service/table.service';
import { DigitalCategoryDB } from 'src/app/shared/tables/digital-category';
import { DIGITALLIST, DigitalListDB } from 'src/app/shared/tables/digital-list';
@Component({
selector: 'app-digital-list',
templateUrl: './digital-list.component.html',
styleUrls: ['./digital-list.component.scss'],
providers: [TableService, DecimalPipe],
})
export class DigitalListComponent implements OnInit {
tableItem$: Observable<DigitalListDB[]>;
public digital_categories = []
constructor(public service: TableService, private modalService: NgbModal) {
this.tableItem$ = service.tableItem$;
this.service.setUserData(DIGITALLIST)
}
@ViewChildren(NgbdSortableHeader) headers: QueryList<NgbdSortableHeader>;
onSort({ column, direction }: SortEvent) {
// resetting other headers
this.headers.forEach((header) => {
if (header.sortable !== column) {
header.direction = '';
}
});
this.service.sortColumn = column;
this.service.sortDirection = direction;
}
ngOnInit() { }
}
@@ -0,0 +1,80 @@
<!-- Container-fluid starts-->
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Digital Sub Category</h5>
</div>
<div class="card-body">
<div class="btn-popup pull-right">
<button type="button" class="btn btn-primary" data-toggle="modal" data-original-title="test"
data-target="#exampleModal" (click)="open(content)">Add Sub Category</button>
<ng-template #content let-modal>
<div class="modal-header">
<h5 class="modal-title f-w-600" id="exampleModalLabel">Add Digital Product</h5>
<button type="button" class="close" aria-label="Close"
(click)="modal.dismiss('Cross click')">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form class="needs-validation">
<div class="form">
<div class="form-group">
<label for="validationCustom01" class="mb-1">Sub Category Name :</label>
<input class="form-control" id="validationCustom01" type="text">
</div>
<div class="form-group mb-0">
<label for="validationCustom02" class="mb-1">Sub Category Image :</label>
<input class="form-control" id="validationCustom02" type="file">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" >Save</button>
<button class="btn btn-secondary" type="button" data-dismiss="modal"
(click)="modal.dismiss('Cross click')">Close</button>
</div>
</ng-template>
</div>
<div id="batchDelete" class="category-table custom-datatable">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col" sortable="img" (sort)="onSort($event)">Image</th>
<th scope="col" sortable="product_name" (sort)="onSort($event)" >Name</th>
<th scope="col" sortable="price" (sort)="onSort($event)">Price</th>
<th scope="col" sortable="status" (sort)="onSort($event)">Status</th>
<th scope="col" sortable="category" (sort)="onSort($event)">Category</th>
<th scope="col" sortable="action" (sort)="onSort($event)">Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let catgory of tableItem$ | async">
<th scope="row">{{ catgory.id }}</th>
<td>
<img [src]="catgory.img" class="imgTable" style="width: 20px">
</td>
<td>{{ catgory.product_name }}</td>
<td>{{ catgory.price }}</td>
<td><i class='fa fa-circle {{catgory.status}} f-12'></i></td>
<td>{{ catgory.category}}</td>
<td>
<a href="javascript:void(0)"><i class='fa fa-edit f-12'></i></a>&nbsp;
<a href="javascript:void(0)"><i class="fa fa-trash-o"></i></a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
@@ -0,0 +1,62 @@
import { Component, OnInit, QueryList, ViewChildren } from '@angular/core';
import { DIGITALSUBCATEGORY, DigitalSubCategoryDB } from 'src/app/shared/tables/digital-sub-category';
import { NgbModal, ModalDismissReasons } from '@ng-bootstrap/ng-bootstrap';
import { SortEvent } from 'src/app/shared/directives/shorting.directive';
import { NgbdSortableHeader } from "src/app/shared/directives/NgbdSortableHeader";
import { DecimalPipe } from '@angular/common';
import { TableService } from 'src/app/shared/service/table.service';
import { Observable } from 'rxjs';
@Component({
selector: 'app-digital-sub-category',
templateUrl: './digital-sub-category.component.html',
styleUrls: ['./digital-sub-category.component.scss'],
providers: [TableService, DecimalPipe],
})
export class DigitalSubCategoryComponent implements OnInit {
public closeResult: string;
tableItem$: Observable<DigitalSubCategoryDB[]>;
public digital_categories = []
constructor(public service: TableService, private modalService: NgbModal) {
this.tableItem$ = service.tableItem$;
this.service.setUserData(DIGITALSUBCATEGORY)
}
open(content) {
this.modalService.open(content, { ariaLabelledBy: 'modal-basic-title' }).result.then((result) => {
this.closeResult = `Closed with: ${result}`;
}, (reason) => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
});
}
private getDismissReason(reason: any): string {
if (reason === ModalDismissReasons.ESC) {
return 'by pressing ESC';
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
return 'by clicking on a backdrop';
} else {
return `with: ${reason}`;
}
}
@ViewChildren(NgbdSortableHeader) headers: QueryList<NgbdSortableHeader>;
onSort({ column, direction }: SortEvent) {
// resetting other headers
this.headers.forEach((header) => {
if (header.sortable !== column) {
header.direction = '';
}
});
this.service.sortColumn = column;
this.service.sortDirection = direction;
}
ngOnInit() {
}
}
@@ -0,0 +1,143 @@
<!-- Container-fluid starts-->
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Add Product</h5>
</div>
<div class="card-body">
<div class="row product-adding">
<div class="col-xl-5">
<div class="add-product">
<div class="row">
<div class="col-xl-9 xl-50 col-sm-6 col-9">
<img src="assets/images/pro3/1.jpg" alt="" class="img-fluid image_zoom_1">
</div>
<div class="col-xl-3 xl-50 col-sm-6 col-3">
<ul class="file-upload-product">
<li *ngFor="let item of url; let i=index">
<div class="box-input-file">
<input class="upload" type="file" (change)="readUrl($event,i)">
<img class="imgUpload" alt="" [src]="item.img">
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="col-xl-7">
<form class="needs-validation" [formGroup]="productForm" novalidate>
<div class="add-product-form">
<div class="form-group row">
<div class="col-xl-3 col-sm-4">
<label for="validationCustom01" class="mb-0">Name</label>
</div>
<div class="col-xl-8 col-sm-7">
<input class="form-control" id="validationCustom01" type="text"
[formControl]="productForm.controls['name']" required>
<div *ngIf="productForm.controls.name.touched && productForm.controls.name.errors?.required"
class="text text-danger">
Title is required.
</div>
</div>
</div>
<div class="form-group row">
<div class="col-xl-3 col-sm-4">
<label for="validationCustom02" class="mb-0">Price</label>
</div>
<div class="col-xl-8 col-sm-7">
<input class="form-control" id="validationCustom02" type="number"
[formControl]="productForm.controls['price']" required="">
<div *ngIf="productForm.controls.price.touched && productForm.controls.price.errors?.required"
class="text text-danger">
Price is required.
</div>
</div>
</div>
<div class="form-group row">
<div class="col-xl-3 col-sm-4">
<label for="validationCustom03" class="mb-0">Category</label>
</div>
<div class="col-xl-8 col-sm-7">
<input class="form-control" id="validationCustom03"
[formControl]="productForm.controls['category']" required="">
<div *ngIf="productForm.controls.category.touched && productForm.controls.category.errors?.required"
class="text text-danger">
Category is required.
</div>
</div>
</div>
<!-- <div class="form-group row">-->
<!-- <div class="col-xl-3 col-sm-4">-->
<!-- <label for="exampleFormControlSelect1" class="mb-0">Select-->
<!-- Size-->
<!-- :</label>-->
<!-- </div>-->
<!-- <div class="col-xl-8 col-sm-7">-->
<!-- <select class="form-control digits" id="exampleFormControlSelect1">-->
<!-- <option>Small</option>-->
<!-- <option>Medium</option>-->
<!-- <option>Large</option>-->
<!-- <option>Extra Large</option>-->
<!-- </select>-->
<!-- </div>-->
<!-- </div>-->
<div class="form-group row">
<div class="col-xl-3 col-sm-4">
<label class="mb-0">Total Products</label>
</div>
<div class="col-xl-8 col-sm-7">
<div class="qty-box1">
<div class="input-group">
<i class="fa fa-minus btnGtr1" style="cursor: pointer;"
(click)=decrement()></i>
<input #productQuantity class="touchspin1 text-center"
name="counter" [value]="counter" type="text">
<i class="fa fa-plus btnLess1" style="cursor: pointer;"
(click)=increment()></i>
</div>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-xl-3 col-sm-4"><label>Add Description</label></div>
<div class="col-xl-8 col-sm-7">
<div class="ps-0 description-sm">
<ckeditor #ckEditor [editor]="Editor" [data]="description"
(change)="onChange($event)"></ckeditor>
</div>
</div>
</div>
</div>
</form>
<div class="form-group row mb-0">
<div class="col-sm-8 offset-xl-3 offset-sm-4">
<div class="product-buttons">
<button type="button" class="btn btn-primary me-1" (click)="onAddNewProduct()">
Add
</button>
<button type="button" class="btn btn-light" (click)="openModal(template)">
Discard
</button>
<ng-template #template>
<div class="modal-body text-center">
<p>Do you want to discard?</p>
<button type="button" class="btn btn-default">Yes
</button>
<button type="button" class="btn btn-primary">No
</button>
</div>
</ng-template>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
@@ -0,0 +1,136 @@
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import { ChangeEvent } from "@ckeditor/ckeditor5-angular";
import { BsModalRef, BsModalService } from "ngx-bootstrap/modal";
import { Product } from "../../../../shared/tables/product";
@Component({
selector: 'app-add-product-category',
templateUrl: './add-product-category.component.html',
styleUrls: ['./add-product-category.component.scss']
})
export class AddProductCategoryComponent {
public productForm: UntypedFormGroup;
public Editor = ClassicEditor;
public counter: number = 1;
public url = [{
img: "assets/images/user.png",
},
{
img: "assets/images/user.png",
},
{
img: "assets/images/user.png",
},
{
img: "assets/images/user.png",
},
{
img: "assets/images/user.png",
}
];
@ViewChild('productQuantity') quantity;
@ViewChild('ckEditor') ckEditor: any;
description: string = '';
modalRef: BsModalRef;
message: string;
constructor(private fb: UntypedFormBuilder,
private modalService: BsModalService) {
this.productForm = this.fb.group({
name: ['', [Validators.required, Validators.pattern('[a-zA-Z][a-zA-Z ]+[a-zA-Z]$')]],
price: ['', [Validators.required, Validators.pattern('[a-zA-Z][a-zA-Z ]+[a-zA-Z]$')]],
category: ['', [Validators.required, Validators.pattern('[a-zA-Z][a-zA-Z ]+[a-zA-Z]$')]],
code: ['', [Validators.required, Validators.pattern('[a-zA-Z][a-zA-Z ]+[a-zA-Z]$')]],
quantity: ['', [Validators.required, Validators.pattern('[a-zA-Z][a-zA-Z ]+[a-zA-Z]$')]],
// size: ['', Validators.required]
});
}
onAddNewProduct() {
// const product = new Product(
// this.productName, this.description, true, 20,
// this.productPrice,
// [this.productCategory], [''], 30);
this.productForm.reset();
}
onChange({ editor }: ChangeEvent) {
let data = editor.getData();
// const data = EDITTORC.instances.Editor.document.getBody().getText();
this.description = data;
}
get productName() {
return this.productForm.get('name').value;
}
get productCategory() {
return this.productForm.get('category').value;
}
get productPrice() {
return this.productForm.get('price').value;
}
get productQuantity() {
return this.productForm.get('quantity').value;
}
// get productName() {
// return this.productForm.get('name').value;
// }
increment() {
this.counter += 1;
}
decrement() {
this.counter -= 1;
}
// FileUpload
readUrl(event: any, i) {
if (event.target.files.length === 0) {
return;
}
// Image upload validation
var mimeType = event.target.files[0].type;
if (mimeType.match(/image\/*/) == null) {
return;
}
// Image upload
var reader = new FileReader();
reader.readAsDataURL(event.target.files[0]);
reader.onload = (_event) => {
this.url[i].img = reader.result.toString();
};
}
openModal(template: TemplateRef<any>) {
this.modalRef = this.modalService.show(template, { class: 'modal-sm' });
}
confirm(): void {
this.message = 'Confirmed!';
this.modalRef.hide();
this.productForm.reset();
// this.ckEditor.instance.setData('');
// this.ckEditor.setData('');
}
decline(): void {
this.message = 'Declined!';
this.modalRef.hide();
}
ngOnInit() {
}
}
@@ -0,0 +1,279 @@
<!-- Container-fluid starts-->
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Thêm sản phẩm</h5>
</div>
<div class="card-body">
<div class="row product-adding">
<div class="col-xl-5">
<div class="add-product">
<div class="row">
<div class="col-xl-9 xl-50 col-sm-6 col-9">
<img src="{{adminImg}}" alt="" class="img-fluid image_zoom_1">
</div>
<div class="col-xl-3 xl-50 col-sm-6 col-3">
<!-- <ul class="file-upload-product">
<li *ngFor="let item of url; let i=index">
<div class="box-input-file">
<input class="upload" type="file" (change)="readUrl($event,i)">
<img class="imgUpload" alt="" [src]="item.img">
</div>
</li>
</ul> -->
</div>
</div>
</div>
</div>
<div class="col-xl-7">
<!-- Form Start -->
<form class="needs-validation" [formGroup]="productForm" novalidate>
<!-- Product Name -->
<div class="add-product-form">
<div class="form-group row">
<div class="col-xl-3 col-sm-4">
<label for="validationCustom01" class="mb-0">Tên sản phẩm</label>
</div>
<div class="col-xl-8 col-sm-7">
<input class="form-control" id="validationCustom01" type="text"
[formControl]="productForm.controls['name']" required>
<!-- <div class="alert alert-danger mt-1" *ngIf="productForm.controls.name.touched && productForm.controls.name.errors?.required"
class="text text-danger">
Tên sản phẩm không được bỏ trống.
</div> -->
<div *ngIf="productName?.invalid && (productName?.dirty || productName?.touched)"
class="alert alert-danger mt-1">
<div *ngIf="productName?.errors?.['required']">Tên sản phẩm không được
bỏ trống.</div>
<div *ngIf="productName?.errors?.['minlength']">Tên sản phẩm phải có ít
nhất 2 kí tự.</div>
</div>
</div>
</div>
<!-- Product Price -->
<div class="form-group row">
<div class="col-xl-3 col-sm-4">
<label for="validationCustom02" class="mb-0">Giá sản phẩm</label>
</div>
<div class="col-xl-8 col-sm-7">
<input class="form-control" id="validationCustom02" type="number"
[formControl]="productForm.controls['price']" required="">
<!-- <div *ngIf="productForm.controls.price.touched && productForm.controls.price.errors?.required"
class="text text-danger">
Giá sản phẩm không được bỏ trống.
</div> -->
<div *ngIf="productPrice?.invalid && (productPrice?.dirty || productPrice?.touched)"
class="alert alert-danger mt-1">
<div *ngIf="productPrice?.errors?.['required']">Giá sản phẩm không được
bỏ trống.</div>
<div *ngIf="productPrice?.errors?.['pattern']">Giá sản phẩm phải là ký
tự số.</div>
</div>
</div>
</div>
<!-- Product Categories -->
<div class="form-group row">
<div class="col-xl-3 col-sm-4">
<label for="validationCustom03" class="mb-0">Loại sản phẩm</label>
</div>
<div class="col-xl-8 col-sm-7" formArrayName="categories">
<div *ngFor="let item of categories.controls; let i = index">
<div formGroupName="{{i}}">
<div class="d-flex mb-2 mt-2">
<input class="form-control" id="validationCustom03"
formControlName="categoryName">
<button type="button" class="btn btn-light ml-2"
(click)="deleteCategory(i)">
Xoá
</button>
</div>
<div *ngIf="categories.controls[i].get('categoryName').invalid &&
(categories.controls[i].get('categoryName').dirty || categories.controls[i].get('categoryName').touched)"
class="alert alert-danger mt-1">
<div
*ngIf="categories.controls[i].get('categoryName')?.errors?.['required']">
Không được để trống loại sản phẩm
</div>
</div>
</div>
</div>
<button type="button" class="btn btn-primary me-1"
(click)="addNewCategory()">
Thêm loại
</button>
</div>
<!-- <div class="col-xl-8 col-sm-7">
<input class="form-control" id="validationCustom03"
[formControl]="productForm.controls['category']" required="">
<div *ngIf="productForm.controls.category.touched && productForm.controls.category.errors?.required"
class="text text-danger">
Loại sản phẩm không được bỏ trống.
</div>
</div> -->
</div>
<!-- Product Images -->
<div class="form-group row">
<div class="col-xl-3 col-sm-4">
<label for="validationCustom03" class="mb-0">Hình ảnh sản phẩm</label>
</div>
<div class="col-xl-8 col-sm-7" formArrayName="images">
<div *ngFor="let item of images.controls; let i = index">
<div formGroupName="{{i}}">
<div class="d-flex mb-2 mt-2">
<input class="form-control" id="image"
formControlName="image_File" type="file" autocomplete="off"
(change)="onFileSelected($event, i)" />
<button type="button" class="btn btn-light ml-2"
(click)="deleteImage(i)">
Xoá
</button>
</div>
<!-- <div
*ngIf="images.controls[i].get('images').invalid &&
(images.controls[i].get('images').dirty || images.controls[i].get('images').touched)" class="alert alert-danger mt-1">
<div *ngIf="images.controls[i].get('images')?.errors?.['required']">
Ảnh không được bỏ trống
</div>
</div> -->
</div>
</div>
<button type="button" class="btn btn-primary me-1" (click)="addNewImage()">
Thêm ảnh
</button>
</div>
</div>
<!-- <div class="form-group row">-->
<!-- <div class="col-xl-3 col-sm-4">-->
<!-- <label for="exampleFormControlSelect1" class="mb-0">Select-->
<!-- Size-->
<!-- :</label>-->
<!-- </div>-->
<!-- <div class="col-xl-8 col-sm-7">-->
<!-- <select class="form-control digits" id="exampleFormControlSelect1">-->
<!-- <option>Small</option>-->
<!-- <option>Medium</option>-->
<!-- <option>Large</option>-->
<!-- <option>Extra Large</option>-->
<!-- </select>-->
<!-- </div>-->
<!-- </div>-->
<!-- Product Description -->
<div class="form-group row">
<div class="col-xl-3 col-sm-4">
<label for="validationCustom02" class="mb-0">Mô tả sản phẩm</label>
</div>
<div class="col-xl-8 col-sm-7">
<textarea class="form-control" id="validationCustom02" type="number"
[formControl]="productForm.controls['descriptions']"
required=""></textarea>
<!-- <div *ngIf="productForm.controls.descriptions.touched && productForm.controls.descriptions.errors?.required"
class="text text-danger">
Mô tả không được bỏ trống.
</div> -->
<div *ngIf="descriptions?.invalid && (descriptions?.dirty || descriptions?.touched)"
class="alert alert-danger mt-1">
<div *ngIf="descriptions?.errors?.['required']">Mô tả sản phẩm không
được bỏ trống.</div>
<div *ngIf="descriptions?.errors?.['minlength']">Mô tả sản phẩm phải có
ít nhất 8 kí tự.</div>
</div>
</div>
</div>
<!-- Product Shop Id -->
<div *ngIf="!isShop" class="form-group row">
<div class="col-xl-3 col-sm-4">
<label for="validationCustom03" class="mb-0">Id shop</label>
</div>
<div class="col-xl-8 col-sm-7">
<input class="form-control" id="validationCustom03"
[formControl]="productForm.controls['shopId']" type="number">
<!-- <div *ngIf="productForm.controls.shopId.touched && productForm.controls.shopId.errors?.required"
class="text text-danger">
Id shop không được bỏ trống.
</div> -->
<div *ngIf="productShopId?.invalid && (productShopId?.dirty || productShopId?.touched)"
class="alert alert-danger mt-1">
<div *ngIf="productShopId?.errors?.['required']">Id shop không được bỏ
trống.</div>
</div>
</div>
</div>
</div>
</form>
<div class="form-group row mb-0">
<div class="col-sm-8 offset-xl-3 offset-sm-4">
<div class="product-buttons">
<button type="button" class="btn btn-primary me-1"
(click)="openModal(add_product)">
Thêm sản phẩm
</button>
<!-- <button type="button" class="btn btn-light" (click)="openModal(add_product)">
Discard
</button> -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
<ng-template #add_product>
<div class="modal-body text-center">
<h5 class="text-dark font-weight-bold mb-4">Bạn chắc chắc muốn thêm sản phẩm này chứ ?</h5>
<div class="modal-btn-option">
<button type="button" class="btn btn-default width-btn" (click)="confirm()">
</button>
<button type="button" class="btn btn-primary width-btn" (click)="decline()">Không
</button>
</div>
</div>
</ng-template>
<ng-template #completedModal>
<div class="modal-body text-center">
<h5 class="text-dark font-weight-bold mb-4">Thêm sản phẩm thành công</h5>
<button type="button" class="btn btn-primary" (click)="completed()">Tiếp
tục
</button>
</div>
</ng-template>
<ng-template #errorModal>
<div class="modal-body text-center">
<h5 class="text-dark font-weight-bold mb-4">Vui lòng thêm ít nhất một {{ item }} vào trong sản phẩm của bạn.
</h5>
<button type="button" class="btn btn-primary" (click)="closeModal()">Tiếp
tục
</button>
</div>
</ng-template>
<ng-template #errorShopModal>
<div class="modal-body text-center">
<h5 class="text-dark font-weight-bold mb-4">Shop bạn thêm vào không tồn tại. Vui lòng chọn một shop khác.</h5>
<button type="button" class="btn btn-primary" (click)="closeModal()">Tiếp
tục
</button>
</div>
</ng-template>
@@ -0,0 +1,11 @@
.modal-btn-option {
width: 100%;
display: flex;
justify-content: space-around;
align-items: center;
padding-bottom: 20px;
.width-btn {
width: 110px;
}
}
@@ -0,0 +1,312 @@
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { FormArray, FormControl, FormGroup, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import { ChangeEvent } from "@ckeditor/ckeditor5-angular";
import { BsModalRef, BsModalService } from "ngx-bootstrap/modal";
import { Product } from "../../../../shared/tables/product";
import { Category } from "../../../../shared/tables/category";
import { ProductService } from 'src/app/shared/service/product.service';
import { environment } from 'src/environments/environment';
import { concatMap, finalize, Observable, switchMap } from 'rxjs';
import { AngularFireStorage } from '@angular/fire/compat/storage';
import { resolve } from 'path';
import { rejects } from 'assert';
import { ProductImageService } from 'src/app/shared/service/product-image.service';
import { ProductImage } from 'src/app/shared/tables/product-image';
import { ToastrService } from 'ngx-toastr';
import { error } from 'console';
@Component({
selector: 'app-add-product',
templateUrl: './add-product.component.html',
styleUrls: ['./add-product.component.scss'],
providers: []
})
export class AddProductComponent implements OnInit {
public adminImage = environment.adminImg;
public productForm: UntypedFormGroup;
public Editor = ClassicEditor;
//Log-in
isShop: boolean = false;
loggedId: number = Number(localStorage.getItem('user-id'))
loggedRole = localStorage.getItem('user-role');
shopId: number;
adminImg = environment.adminImg;
item: string = '';
description: string = '';
message: string;
cats!: FormArray;
imgs!: FormArray;
createdId: number;
imageUrls: string[] = [];
@ViewChild('errorModal') errorModal: TemplateRef<any>
@ViewChild('errorShopModal') errorShopModal: TemplateRef<any>
@ViewChild('completedModal') completedModal: TemplateRef<any>
constructor(
private fb: UntypedFormBuilder,
private productService: ProductService,
private productImageService: ProductImageService,
private modalService: BsModalService,
private storage: AngularFireStorage,
private toastService: ToastrService) {
this.productForm = this.fb.group({
name: new FormControl("", [Validators.required, Validators.minLength(2)]),
price: new FormControl("", [Validators.required, Validators.pattern("^[0-9]*$")]),
shopId: new FormControl("", [Validators.required, Validators.pattern("^[0-9]*$")]),
descriptions: new FormControl("", [Validators.required, Validators.minLength(8)]),
categories: new FormArray([]),
images: new FormArray([])
});
}
ngOnInit() {
if (this.loggedRole != 'ROLE_ADMIN') {
this.isShop = true;
this.shopId = Number(localStorage.getItem('shop-id'))
}
this.productForm.patchValue({
shopId: this.shopId
})
}
onAddNewProduct(): Promise<void> {
return new Promise((resolve) => {
const catArray = this.productForm.get('categories') as FormArray
if (catArray.length == 0) {
this.item = 'thể loại'
this.modalRef.hide();
this.modalRef = this.modalService.show(this.errorModal, { class: 'modal-sm' });
return Promise.reject();
}
const imgArray = this.productForm.get('images') as FormArray
if (imgArray.length == 0) {
this.item = 'ảnh'
this.modalRef.hide();
this.modalRef = this.modalService.show(this.errorModal, { class: 'modal-sm' });
return Promise.reject();
}
const categoryNames: string[] = this.cats.controls.map(control => control.get('categoryName').value);
const uploadFiles: File[] = this.imgs.controls.map(control => control.get('image_File').value);
const product = new Product();
product.name = this.productName.value;
product.description = this.descriptions.value;
product.isEnabled = true;
product.discountPercent = 0;
product.cost = this.productPrice.value;
product.averageRating = 0;
product.reviewCount = 0;
product.categoryNames = categoryNames;
if (this.isShop) {
product.shopId = this.shopId;
}
else {
product.shopId = this.productShopId.value;
}
const uploadPromises = uploadFiles.map((file) => {
return this.uploadImage(file).then((imageUrl: string) => {
this.imageUrls.push(imageUrl)
})
})
this.productService.addProduct(product).subscribe({
next: (product) => {
this.createdId = product.id;
Promise.all(uploadPromises).then(() => {
this.imageUrls.forEach(url => {
const img = new ProductImage();
img.id = 0;
img.imageUrl = url;
img.productId = product.id;
this.productImageService.addProductImage(img, img.productId).subscribe();
});
this.modalRef = this.modalService.show(this.completedModal, { class: 'modal-sm' });
resolve();
})
},
error: () => {
this.modalRef = this.modalService.show(this.errorShopModal, { class: 'modal-sm' });
}
})
// Promise.all(uploadPromises).then(() => {
// this.productService.addProduct(product).pipe(
// switchMap((product) => {
// this.createdId = product.id;
// return this.imageUrls;
// }),
// concatMap((url) => {
// const img = new ProductImage();
// img.id = 0;
// img.imageUrl = url;
// img.productId = this.createdId;
// return this.productImageService.addProductImage(img, img.productId);
// })
// resolve();
// })
})
}
// Category Form Array
addNewCategory() {
this.cats = this.productForm.get('categories') as FormArray;
this.cats.push(this.generateNewCategory());
}
generateNewCategory(): FormGroup {
return new FormGroup({
categoryName: new FormControl('', Validators.required)
});
}
deleteCategory(index: number) {
this.cats = this.productForm.get('categories') as FormArray;
this.cats.removeAt(index);
}
//Image Form Array
addNewImage() {
this.imgs = this.productForm.get('images') as FormArray;
this.imgs.push(this.generateNewImage())
}
generateNewImage(): FormGroup {
return new FormGroup({
image_File: new FormControl('', Validators.required)
})
}
deleteImage(index: number) {
this.imgs = this.productForm.get('images') as FormArray;
this.imgs.removeAt(index)
}
//Getter
get productName() { return this.productForm.get('name') }
get productCategory() {
const numberStr: string = this.productForm.get('category').value;
const idArr: number[] = numberStr.split(",").map(Number);
return idArr;
}
get productPrice() { return this.productForm.get('price') }
get productShopId() { return this.productForm.get('shopId') }
get categories() {
return this.productForm.get('categories') as FormArray;
}
get images() {
return this.productForm.get('images') as FormArray;
}
get descriptions() { return this.productForm.get('descriptions') }
//Modal
modalRef: BsModalRef;
openModal(template: TemplateRef<any>) {
if (this.productForm.invalid) {
this.productForm.markAllAsTouched()
return;
}
this.modalRef = this.modalService.show(template, { class: 'modal-sm' });
}
confirm(): void {
this.modalRef.hide();
this.onAddNewProduct().then(() => {
})
}
decline(): void {
this.modalRef.hide();
}
completed(): void {
this.modalRef.hide();
this.productForm.reset();
}
closeModal() {
this.modalRef.hide();
}
//Image Upload
//File
imageFile: File;
imageUrl: string;
downloadURL: Observable<string>;
onFileSelected(event, index: number) {
this.imageFile = event.target.files[0]
this.imgs = this.productForm.get('images') as FormArray;
this.imgs.at(index).patchValue({ image_File: this.imageFile });
}
uploadImage(fileUpload: File): Promise<string> {
return new Promise<string>((resolve) => {
let n = Date.now();
const filePath = `Products/${n}`;
const fileRef = this.storage.ref(filePath);
const task = this.storage.upload(`Products/${n}`, fileUpload);
task
.snapshotChanges()
.pipe(
finalize(() => {
this.downloadURL = fileRef.getDownloadURL();
this.downloadURL.subscribe(url => {
if (url) {
resolve(url);
}
});
})
)
.subscribe(url => {
}
);
})
}
// // FileUpload
// readUrl(event: any, i) {
// if (event.target.files.length === 0) {
// return;
// }
// // Image upload validation
// var mimeType = event.target.files[0].type;
// if (mimeType.match(/image\/*/) == null) {
// return;
// }
// // Image upload
// var reader = new FileReader();
// reader.readAsDataURL(event.target.files[0]);
// reader.onload = (_event) => {
// this.url[i].img = reader.result.toString();
// };
// }
}
@@ -0,0 +1,57 @@
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Product Lists</h5>
</div>
<div class="card-body">
<div id="batchDelete" class="custom-datatable">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Ảnh</th>
<th scope="col">Tên sản phẩm</th>
<th scope="col">Giá</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let product of products; let i = index ">
<th scope="row">{{ i + 1 }}</th>
<td>
<img [src]="product.images[0].imageUrl" class="imgTable"
style="width: 20px">
</td>
<td>{{ product.name }}</td>
<td>{{ product.cost - product.cost * product.discountPercent / 100 }}</td>
<td>
<a [routerLink]="['/products','product-detail', product.id]"><i
class="fa fa-eye"></i></a>
<!-- <a href="javascript:void(0)"><i class='fa fa-edit f-12'></i></a>&nbsp;
<a href="javascript:void(0)"><i class="fa fa-trash-o"></i></a> -->
</td>
</tr>
</tbody>
</table>
<div class="d-flex justify-content-between p-2">
<ngb-pagination [collectionSize]="theTotalElements" [(page)]="thePageNumber"
[maxSize]="5" [pageSize]="thePageSize" [rotate]="true" [boundaryLinks]="true"
(pageChange)="getProductByCategories()">
</ngb-pagination>
<select class="form-select" style="width: auto" name="pageSize"
(change)="getProductByCategories()" [(ngModel)]="thePageSize">
<option [value]="10" [selected]="true" [ngValue]="10">10 items per page</option>
<option [ngValue]="15">15 items per page</option>
<option [ngValue]="20">20 items per page</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CategoryDetailComponent } from './category-detail.component';
describe('CategoryDetailComponent', () => {
let component: CategoryDetailComponent;
let fixture: ComponentFixture<CategoryDetailComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ CategoryDetailComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(CategoryDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,46 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { CategoryService } from 'src/app/shared/service/category.service';
import { ProductService } from 'src/app/shared/service/product.service';
@Component({
selector: 'app-category-detail',
templateUrl: './category-detail.component.html',
styleUrls: ['./category-detail.component.scss']
})
export class CategoryDetailComponent implements OnInit {
products = [];
categories: number[] = [];
//Pagination Properties
thePageNumber = 1;
thePageSize = 10;
theTotalElements = 0;
constructor(
private categoryService: CategoryService,
private productService: ProductService,
private route: ActivatedRoute) {
}
ngOnInit(): void {
this.getProductByCategories();
}
getProductByCategories() {
const id = +this.route.snapshot.paramMap.get('id')
this.categories.push(id)
this.productService.getProductsByCategoryIds(this.categories, this.thePageNumber - 1, this.thePageSize).subscribe(
this.processResult()
)
}
processResult() {
return (data: any) => {
this.products = data.products;
this.thePageNumber = data.page.pageNo + 1;
this.thePageSize = data.page.pageSize;
this.theTotalElements = data.page.totalElements;
}
}
}
@@ -0,0 +1,131 @@
<!-- Container-fluid starts-->
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Thể loại sản phẩm</h5>
</div>
<div class="card-body">
<!-- <div class="btn-popup pull-right">-->
<!-- <button-->
<!-- type="button" class="btn btn-primary" data-toggle="modal" data-original-title="test"-->
<!-- data-target="#exampleModal" (click)="open(content)">Add Category-->
<!-- </button>-->
<!-- </div>-->
<div id="batchDelete" class="category-table custom-datatable">
<div class="table-responsive">
<!-- <form>
<div class="mb-3 row">
<label for="table-complete-search" class="col-xs-3 col-sm-auto col-form-label">Full text search:</label>
<div class="col-xs-3 col-sm-auto">
<input id="table-complete-search" type="text" class="form-control" name="searchTerm" [(ngModel)]="searchText" autocomplete="off">
</div>
</div>
</form> -->
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th class="d-flex justify-content-center align-items-center" scope="col">
<span>Ảnh</span></th>
<th scope="col">Tên loại</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr *ngFor='let category of categories; let i = index'>
<th scope="row">
<div>{{ i + 1 }}</div>
</th>
<td class="d-flex justify-content-center align-items-center ">
<img [src]="category.imageUrl" class="imgTable" style="width: 20px"
[routerLink]="['/products','category',category.id]">
</td>
<td>{{ category.name }}</td>
<td>
<a style="cursor: pointer;"
[routerLink]="['/products','category',category.id]"><i
style="color: lightseagreen;" class='fa fa-eye f-12'></i></a>&nbsp;
<a style="cursor: pointer;"
(click)="editCategory(productCategoryTemplate, category)"><i
class='fa fa-edit f-12'></i></a>&nbsp;
<a style="cursor: pointer;"
(click)="openModal(deleteCategory, category.id)"><i
class="fa fa-trash-o"></i></a>
</td>
</tr>
</tbody>
</table>
<!-- <div class="d-flex justify-content-center p-2">
<ngb-pagination>
</ngb-pagination>
</div> -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Container-fluid Ends-->
<ng-template #productCategoryTemplate>
<form formGroup="categoryForm">
<div class="modal-header">
<h5 class="modal-title f-w-600" id="exampleModalLabel">Edit Product Category</h5>
<button type="button" class="close" aria-label="Close" (click)="decline()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div>
<div class="form-group">
<label for="validationCustom01" class="mb-1">Tên loại :</label>
<input class="form-control" id="validationCustom01" type="text"
[formControl]="categoryForm.controls['categoryName']" required>
</div>
<div class="form-group mb-0">
<label for="image" class="mb-1">Ảnh :</label>
<input class="form-control" id="image" [formControl]="categoryForm.controls['categoryImage']"
type="file" autocomplete="off" (change)="onFileSelected($event)">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" (click)="onSaveCategory($event, updateSuccess)">Lưu
</button>
<button class="btn btn-secondary" type="button" data-dismiss="modal" (click)="decline()">Đóng
</button>
</div>
</div>
</form>
</ng-template>
<ng-template #deleteCategory>
<div class="modal-body text-center">
<h5 class="text-dark font-weight-bold mb-4">Bạn muốn xoá thể loại này chứ ?<br>
Tất cả những sản phẩm liên quan sẽ bị xoá ?</h5>
<button type="button" class="btn btn-secondary" (click)="confirm(categoryId, deleteSuccess)">
Đồng ý
</button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<button type="button" class="btn btn-primary" (click)="decline()">Không
</button>
</div>
</ng-template>
<ng-template #updateSuccess>
<div class="modal-body text-center">
<h5 class="text-dark font-weight-bold mb-4">Cập nhật thành công</h5>
<button type="button" class="btn btn-primary" (click)="success()">Tiếp tục
</button>
</div>
</ng-template>
<ng-template #deleteSuccess>
<div class="modal-body text-center">
<h5 class="text-dark font-weight-bold mb-4">Cập nhật thành công</h5>
<button type="button" class="btn btn-primary" (click)="success()">Tiếp tục
</button>
</div>
</ng-template>
@@ -0,0 +1,6 @@
@import "ag-grid-community/dist/styles/ag-grid.css";
@import "ag-grid-community/dist/styles/ag-theme-alpine.css";
th, td {
text-align: center;
}
@@ -0,0 +1,138 @@
import { Component, OnInit, TemplateRef } from '@angular/core';
import { DecimalPipe } from '@angular/common';
import { TableService } from 'src/app/shared/service/table.service';
import { CategoryService } from 'src/app/shared/service/category.service';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { FormBuilder, FormGroup } from '@angular/forms';
import { Category } from 'src/app/shared/tables/category';
import { AngularFireStorage } from '@angular/fire/compat/storage';
import { finalize, Observable } from 'rxjs';
import { resolve } from 'path';
import { Router } from '@angular/router';
@Component({
selector: 'app-category',
templateUrl: './category.component.html',
styleUrls: ['./category.component.scss'],
providers: [TableService, DecimalPipe],
})
export class CategoryComponent implements OnInit {
public closeResult: string;
categoryForm: FormGroup;
categories: Category[];
categoryId: number;
//File
imageFile: File;
imageUrl;
downloadURL: Observable<string>;
constructor(
private categoryService: CategoryService,
private modalService: BsModalService,
private formBuilder: FormBuilder,
private storage: AngularFireStorage,
private router: Router) {
this.categoryForm = this.formBuilder.group({
categoryName: [''],
categoryImage: ['']
})
}
ngOnInit(): void {
this.listCategories();
}
listCategories() {
this.categoryService.getCategories().subscribe(
data => {
this.categories = data;
}
)
}
//Modal
modalRef: BsModalRef;
openModal(template: TemplateRef<any>, id: number) {
this.categoryId = id;
this.modalRef = this.modalService.show(template, { class: 'modal-sm' });
}
confirm(categoryId: number, template: TemplateRef<any>): void {
this.categoryService.deleteCategoryById(categoryId).subscribe();
this.modalRef.hide();
this.modalRef = this.modalService.show(template, { class: 'modal-sm' });
}
decline(): void {
this.modalRef.hide();
}
editCategory(template: TemplateRef<any>, category: Category) {
this.categoryId = category.id
this.categoryForm.patchValue({
categoryName: category.name,
categoryImage: category.imageUrl
})
this.modalRef = this.modalService.show(template, { class: 'modal-md' });
}
onSaveCategory(event, template: TemplateRef<any>) {
this.uploadImage().then(() => {
const category = new Category()
category.name = this.categoryName
category.imageUrl = this.imageUrl;
this.categoryService.editCategoryById(this.categoryId, category).subscribe()
this.modalRef.hide();
this.modalRef = this.modalService.show(template, { class: 'modal-md' });
})
}
success(): void {
this.modalRef.hide();
this.listCategories();
this.router.navigate(['/products/category'])
}
onFileSelected(event) {
this.imageFile = event.target.files[0];
}
uploadImage(): Promise<void> {
return new Promise<void>((resolve) => {
let n = Date.now();
const filePath = `Category/${n}`;
const fileRef = this.storage.ref(filePath);
const task = this.storage.upload(`Category/${n}`, this.imageFile);
task
.snapshotChanges()
.pipe(
finalize(() => {
this.downloadURL = fileRef.getDownloadURL();
this.downloadURL.subscribe(url => {
if (url) {
//return url here
this.imageUrl = url;
}
resolve();
});
})
)
.subscribe(url => {
// if (url) {
// }
}
);
})
}
//Getter
get categoryName() { return this.categoryForm.get('categoryName').value }
}

Some files were not shown because too many files have changed in this diff Show More