Browse Source

24th Feb

devBranch
Benjamin Arhen 3 years ago
parent
commit
9051e3c6ae
  1. 11
      package-lock.json
  2. 1
      package.json
  3. 1
      src/app/app.component.ts
  4. 5
      src/app/app.module.ts
  5. 26
      src/app/app.resolvers.ts
  6. 3
      src/app/app.routing.ts
  7. 21
      src/app/core/auth/auth.interceptor.ts
  8. 32
      src/app/core/auth/auth.service.ts
  9. 18
      src/app/core/user/user.service.ts
  10. 2
      src/app/layout/common/goldcoin/goldcoin.component.html
  11. 79
      src/app/layout/common/goldcoin/goldcoin.component.ts
  12. 136
      src/app/layout/common/goldcoin/goldcoin.service.ts
  13. 29
      src/app/layout/common/user/user.component.html
  14. 54
      src/app/layout/common/user/user.component.ts
  15. 3
      src/app/layout/common/user/user.module.ts
  16. 25
      src/app/models/businessModel.ts
  17. 21
      src/app/models/couponsModel.ts
  18. 40
      src/app/models/productsModel.ts
  19. 14
      src/app/models/userModel.ts
  20. 22
      src/app/pages/admin/Coupons/Active/active-coupons.component.html
  21. 72
      src/app/pages/admin/Coupons/AdminNewCoupons/admin-new-coupons.component.html
  22. 47
      src/app/pages/admin/Coupons/AdminNewCoupons/admin-new-coupons.component.ts
  23. 11
      src/app/pages/admin/Coupons/Inactive/inactive-coupons.component.html
  24. 69
      src/app/pages/admin/Coupons/NewCoupons/new-coupons.component.html
  25. 45
      src/app/pages/admin/Coupons/NewCoupons/new-coupons.component.ts
  26. 2
      src/app/pages/admin/Coupons/coupons.component.html
  27. 221
      src/app/pages/admin/Coupons/coupons.component.ts
  28. 4
      src/app/pages/admin/Coupons/coupons.module.ts
  29. 59
      src/app/pages/admin/Coupons/coupons.service.ts
  30. 139
      src/app/pages/admin/Followers/PersonalizedCoupons/personalized-coupons.component.html
  31. 11
      src/app/pages/admin/Followers/PersonalizedCoupons/personalized-coupons.component.scss
  32. 25
      src/app/pages/admin/Followers/PersonalizedCoupons/personalized-coupons.component.spec.ts
  33. 50
      src/app/pages/admin/Followers/PersonalizedCoupons/personalized-coupons.component.ts
  34. 16
      src/app/pages/admin/Followers/followers.component.html
  35. 129
      src/app/pages/admin/Followers/followers.component.ts
  36. 10
      src/app/pages/admin/Followers/followers.module.ts
  37. 4
      src/app/pages/admin/Followers/followers.resolvers.ts
  38. 96
      src/app/pages/admin/Followers/followers.service.ts
  39. 23
      src/app/pages/admin/Monthly_Desires/ListDesires/list-desires.component.html
  40. 3
      src/app/pages/admin/Monthly_Desires/ListDesires/list-desires.component.scss
  41. 114
      src/app/pages/admin/Monthly_Desires/ListDesires/list-desires.component.ts
  42. 32
      src/app/pages/admin/Monthly_Desires/desires.resolvers.ts
  43. 7
      src/app/pages/admin/Monthly_Desires/desires.routing.ts
  44. 49
      src/app/pages/admin/Monthly_Desires/desires.service.ts
  45. 144
      src/app/pages/admin/Products/AdminCouponDialog/admin-coupon-dialog.component.html
  46. 44
      src/app/pages/admin/Products/AdminCouponDialog/admin-coupon-dialog.component.ts
  47. 22
      src/app/pages/admin/Products/BusinessLookUp/business-look-up.component.html
  48. 115
      src/app/pages/admin/Products/BusinessLookUp/business-look-up.component.ts
  49. 128
      src/app/pages/admin/Products/CouponDialog/coupon-dialog.component.html
  50. 43
      src/app/pages/admin/Products/CouponDialog/coupon-dialog.component.ts
  51. 201
      src/app/pages/admin/Products/Details/details-product.component.html
  52. 78
      src/app/pages/admin/Products/Details/details-product.component.scss
  53. 25
      src/app/pages/admin/Products/Details/details-product.component.spec.ts
  54. 151
      src/app/pages/admin/Products/Details/details-product.component.ts
  55. 4
      src/app/pages/admin/Products/EditProduct/edit-product.component.html
  56. 20
      src/app/pages/admin/Products/EditProduct/edit-product.component.ts
  57. 57
      src/app/pages/admin/Products/NewProduct/new-product.component.html
  58. 5
      src/app/pages/admin/Products/NewProduct/new-product.component.scss
  59. 73
      src/app/pages/admin/Products/NewProduct/new-product.component.ts
  60. 46
      src/app/pages/admin/Products/ProductList/product-list.component.html
  61. 12
      src/app/pages/admin/Products/ProductList/product-list.component.scss
  62. 119
      src/app/pages/admin/Products/ProductList/product-list.component.ts
  63. 6
      src/app/pages/admin/Products/products.module.ts
  64. 8
      src/app/pages/admin/Products/products.routing.ts
  65. 73
      src/app/pages/admin/Products/products.service.ts
  66. 12
      src/app/pages/admin/Profile/DescriptionDialog/description-dialog.component.html
  67. 0
      src/app/pages/admin/Profile/DescriptionDialog/description-dialog.component.scss
  68. 25
      src/app/pages/admin/Profile/DescriptionDialog/description-dialog.component.spec.ts
  69. 44
      src/app/pages/admin/Profile/DescriptionDialog/description-dialog.component.ts
  70. 83
      src/app/pages/admin/Profile/InfoDialog/information-dialog.component.html
  71. 28
      src/app/pages/admin/Profile/InfoDialog/information-dialog.component.scss
  72. 25
      src/app/pages/admin/Profile/InfoDialog/information-dialog.component.spec.ts
  73. 35
      src/app/pages/admin/Profile/InfoDialog/information-dialog.component.ts
  74. 53
      src/app/pages/admin/Profile/Information/information.component.html
  75. 12
      src/app/pages/admin/Profile/Information/information.component.scss
  76. 12
      src/app/pages/admin/Profile/Information/information.component.spec.ts
  77. 51
      src/app/pages/admin/Profile/Information/information.component.ts
  78. 33
      src/app/pages/admin/Profile/Settings/settings.component.html
  79. 0
      src/app/pages/admin/Profile/Settings/settings.component.scss
  80. 0
      src/app/pages/admin/Profile/Settings/settings.component.spec.ts
  81. 17
      src/app/pages/admin/Profile/Settings/settings.component.ts
  82. 67
      src/app/pages/admin/Profile/profile.component.html
  83. 173
      src/app/pages/admin/Profile/profile.component.scss
  84. 68
      src/app/pages/admin/Profile/profile.component.ts
  85. 58
      src/app/pages/admin/Profile/profile.module.ts
  86. 35
      src/app/pages/admin/Profile/profile.routing.ts
  87. 64
      src/app/pages/admin/Profile/profile.service.ts
  88. 1
      src/app/pages/admin/Settings/settings.component.html
  89. 15
      src/app/pages/admin/Settings/settings.component.ts
  90. 12
      src/app/pages/admin/Settings/settings.module.ts
  91. 105
      src/app/pages/auth/unlock-session/unlock-session.component.html
  92. 129
      src/app/pages/auth/unlock-session/unlock-session.component.ts
  93. 32
      src/app/pages/auth/unlock-session/unlock-session.module.ts
  94. 9
      src/app/pages/auth/unlock-session/unlock-session.routing.ts
  95. 13
      src/app/pipes/coupon-worth.pipe.ts
  96. 18
      src/app/pipes/productDescriptionShort.pipe.ts
  97. BIN
      src/assets/images/300.png
  98. 3
      src/environments/environment.ts

11
package-lock.json

@ -34,6 +34,7 @@
"crypto-js": "3.3.0", "crypto-js": "3.3.0",
"dayjs": "^1.10.7", "dayjs": "^1.10.7",
"highlight.js": "11.2.0", "highlight.js": "11.2.0",
"jwt-decode": "^3.1.2",
"libphonenumber-js": "^1.9.49", "libphonenumber-js": "^1.9.49",
"lodash-es": "4.17.21", "lodash-es": "4.17.21",
"moment": "2.29.1", "moment": "2.29.1",
@ -9627,6 +9628,11 @@
"node >= 0.2.0" "node >= 0.2.0"
] ]
}, },
"node_modules/jwt-decode": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"node_modules/karma": { "node_modules/karma": {
"version": "6.3.16", "version": "6.3.16",
"resolved": "https://registry.npmjs.org/karma/-/karma-6.3.16.tgz", "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.16.tgz",
@ -22416,6 +22422,11 @@
"integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
"dev": true "dev": true
}, },
"jwt-decode": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"karma": { "karma": {
"version": "6.3.16", "version": "6.3.16",
"resolved": "https://registry.npmjs.org/karma/-/karma-6.3.16.tgz", "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.16.tgz",

1
package.json

@ -38,6 +38,7 @@
"crypto-js": "3.3.0", "crypto-js": "3.3.0",
"dayjs": "^1.10.7", "dayjs": "^1.10.7",
"highlight.js": "11.2.0", "highlight.js": "11.2.0",
"jwt-decode": "^3.1.2",
"libphonenumber-js": "^1.9.49", "libphonenumber-js": "^1.9.49",
"lodash-es": "4.17.21", "lodash-es": "4.17.21",
"moment": "2.29.1", "moment": "2.29.1",

1
src/app/app.component.ts

@ -13,4 +13,5 @@ export class AppComponent
constructor() constructor()
{ {
} }
} }

5
src/app/app.module.ts

@ -12,9 +12,10 @@ import { LayoutModule } from 'app/layout/layout.module';
import { AppComponent } from 'app/app.component'; import { AppComponent } from 'app/app.component';
import { appRoutes } from 'app/app.routing'; import { appRoutes } from 'app/app.routing';
import { LocationStrategy, PathLocationStrategy } from '@angular/common'; import { LocationStrategy, PathLocationStrategy } from '@angular/common';
import { SettingsComponent } from './pages/admin/Settings/settings.component';
import { tesoModule } from '@teso/teso.module'; import { tesoModule } from '@teso/teso.module';
import { NgxAwesomePopupModule,ConfirmBoxConfigModule} from '@costlydeveloper/ngx-awesome-popup'; import { NgxAwesomePopupModule,ConfirmBoxConfigModule} from '@costlydeveloper/ngx-awesome-popup';
import { ProductDescriptionShort } from './pipes/productDescriptionShort.pipe';
import { CouponWorthPipe } from './pipes/coupon-worth.pipe';
const routerConfig: ExtraOptions = { const routerConfig: ExtraOptions = {
preloadingStrategy : PreloadAllModules, preloadingStrategy : PreloadAllModules,
@ -24,7 +25,7 @@ const routerConfig: ExtraOptions = {
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
SettingsComponent
], ],
imports : [ imports : [
BrowserModule, BrowserModule,

26
src/app/app.resolvers.ts

@ -6,6 +6,11 @@ import { NavigationService } from 'app/core/navigation/navigation.service';
import { NotificationsService } from 'app/layout/common/notifications/notifications.service'; import { NotificationsService } from 'app/layout/common/notifications/notifications.service';
import { QuickChatService } from 'app/layout/common/quick-chat/quick-chat.service'; import { QuickChatService } from 'app/layout/common/quick-chat/quick-chat.service';
import { UserService } from 'app/core/user/user.service'; import { UserService } from 'app/core/user/user.service';
import { GoldCoinService } from './layout/common/goldcoin/goldcoin.service';
import { CouponsService } from './pages/admin/Coupons/coupons.service';
import { ProductsService } from './pages/admin/Products/products.service';
import { FollowersService } from './pages/admin/Followers/followers.service';
import { ProfileService } from './pages/admin/Profile/profile.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -20,9 +25,13 @@ export class InitialDataResolver implements Resolve<any>
private _navigationService: NavigationService, private _navigationService: NavigationService,
private _notificationsService: NotificationsService, private _notificationsService: NotificationsService,
private _quickChatService: QuickChatService, private _quickChatService: QuickChatService,
private _userService: UserService private _userService: UserService,
) private _couponService: CouponsService,
{ private _productService: ProductsService,
private _goldCoinService: GoldCoinService,
private _subscriberService:FollowersService,
private _profileService:ProfileService,
) {
} }
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -35,15 +44,20 @@ export class InitialDataResolver implements Resolve<any>
* @param route * @param route
* @param state * @param state
*/ */
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
{
// Fork join multiple API endpoint calls to wait all of them to finish // Fork join multiple API endpoint calls to wait all of them to finish
return forkJoin([ return forkJoin([
this._navigationService.get(), this._navigationService.get(),
this._messagesService.getAll(), this._messagesService.getAll(),
this._notificationsService.getAll(), this._notificationsService.getAll(),
this._quickChatService.getChats(), this._quickChatService.getChats(),
this._userService.get() this._userService.get(),
this._goldCoinService.getAll(),
this._couponService.getCategory(),
this._productService.getCategories(),
this._productService.getData(),
this._subscriberService.getData(),
this._profileService.getCategories()
]); ]);
} }
} }

3
src/app/app.routing.ts

@ -48,7 +48,7 @@ export const appRoutes: Route[] = [
}, },
children: [ children: [
{path: 'sign-out', loadChildren: () => import('app/pages/auth/sign-out/sign-out.module').then(m => m.AuthSignOutModule)}, {path: 'sign-out', loadChildren: () => import('app/pages/auth/sign-out/sign-out.module').then(m => m.AuthSignOutModule)},
{path: 'unlock-session', loadChildren: () => import('app/pages/auth/unlock-session/unlock-session.module').then(m => m.AuthUnlockSessionModule)}
] ]
}, },
@ -67,6 +67,7 @@ export const appRoutes: Route[] = [
{path: 'products', loadChildren: () => import('app/pages/admin/Products/products.module').then(m => m.ProductsModule)}, {path: 'products', loadChildren: () => import('app/pages/admin/Products/products.module').then(m => m.ProductsModule)},
{path: 'followers', loadChildren: () => import('app/pages/admin/Followers/followers.module').then(m => m.FollowersModule)}, {path: 'followers', loadChildren: () => import('app/pages/admin/Followers/followers.module').then(m => m.FollowersModule)},
{path: 'desires', loadChildren: () => import('app/pages/admin/Monthly_Desires/desires.module').then(m => m.DesiresModule)}, {path: 'desires', loadChildren: () => import('app/pages/admin/Monthly_Desires/desires.module').then(m => m.DesiresModule)},
{path: 'profile', loadChildren: () => import('app/pages/admin/Profile/profile.module').then(m => m.ProfileModule)},
] ]
} }
]; ];

21
src/app/core/auth/auth.interceptor.ts

@ -6,13 +6,11 @@ import { AuthService } from 'app/core/auth/auth.service';
import { AuthUtils } from 'app/core/auth/auth.utils'; import { AuthUtils } from 'app/core/auth/auth.utils';
@Injectable() @Injectable()
export class AuthInterceptor implements HttpInterceptor export class AuthInterceptor implements HttpInterceptor {
{
/** /**
* Constructor * Constructor
*/ */
constructor(private _authService: AuthService) constructor(private _authService: AuthService) {
{
} }
/** /**
@ -21,8 +19,7 @@ export class AuthInterceptor implements HttpInterceptor
* @param req * @param req
* @param next * @param next
*/ */
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
{
// Clone the request object // Clone the request object
let newReq = req.clone(); let newReq = req.clone();
@ -34,10 +31,15 @@ export class AuthInterceptor implements HttpInterceptor
// for the protected API routes which our response interceptor will // for the protected API routes which our response interceptor will
// catch and delete the access token from the local storage while logging // catch and delete the access token from the local storage while logging
// the user out from the app. // the user out from the app.
if ( this._authService.accessToken && !AuthUtils.isTokenExpired(this._authService.accessToken) ) if (this._authService.relevantToken)//&& !AuthUtils.isTokenExpired() )
{ {
newReq = req.clone({ newReq = req.clone({
headers: req.headers.set('Authorization', 'Bearer ' + this._authService.accessToken) headers: req.headers.set('Authorization', 'Bearer ' + this._authService.relevantToken)
.append('Accept', '*/*')
.append('Content-Type', 'application/json; charset=utf-8;')
.append("Access-Control-Allow-Origin", "*")
.append("Access-Control-Allow-Methods", "DELETE, POST, GET, OPTIONS")
.append("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
}); });
} }
@ -46,8 +48,7 @@ export class AuthInterceptor implements HttpInterceptor
catchError((error) => { catchError((error) => {
// Catch "401 Unauthorized" responses // Catch "401 Unauthorized" responses
if ( error instanceof HttpErrorResponse && error.status === 401 ) if (error instanceof HttpErrorResponse && error.status === 401) {
{
// Sign out // Sign out
this._authService.signOut(); this._authService.signOut();

32
src/app/core/auth/auth.service.ts

@ -37,6 +37,16 @@ export class AuthService
return localStorage.getItem('accessToken') ?? ''; return localStorage.getItem('accessToken') ?? '';
} }
set relevantToken(token: string)
{
localStorage.setItem('relevantToken', token);
}
get relevantToken(): string
{
return localStorage.getItem('relevantToken') ?? '';
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Public methods // @ Public methods
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -79,7 +89,7 @@ export class AuthService
// Store the access token in the local storage // Store the access token in the local storage
this.accessToken = response.accessToken; this.accessToken = response.accessToken;
this.relevantToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOiIyMjY0ODQyNzciLCJqdGkiOiI2ZWJiYzhlZC1hNTFkLTRjNTMtODBkOC05YWU2OTYyMzU4M2YiLCJpc3MiOiJURVNPIEFVVEggU0VSVkVSIiwiZXhwIjoxNjQ2NDA4NTU4LCJuYmYiOiIyMjY1NDQyNzciLCJzdWIiOiIxVEVTQlUwMDAwMDAwMCIsImJ1c2luZXNzSUQiOiIxVEVTQlUwMDAwMDAwMCIsInN1YnNjcmlwdGlvblBsYW4iOiJUVFMwMDEiLCJidXNpbmVzc05hbWUiOiJUZXNvIEdoYW5hIiwiYXVkIjoiVEVTTyJ9.HPgBtbrw8yFzzCUwMx7_3QbfX7LloANFZ-HFzLglDio";
// Set the authenticated flag to true // Set the authenticated flag to true
this._authenticated = true; this._authenticated = true;
@ -161,25 +171,23 @@ export class AuthService
/** /**
* Check the authentication status * Check the authentication status
*/ */
check(): Observable<boolean> check(): Observable<boolean> {
{
// Check if the user is logged in // Check if the user is logged in
if ( this._authenticated ) if (this._authenticated) {
{
return of(true); return of(true);
} }
// Check the access token availability // Check the access token availability
if ( !this.accessToken ) if (!this.accessToken) {
{ this.signOut();
return of(false); return of(false);
} }
// Check the access token expire date
if ( AuthUtils.isTokenExpired(this.accessToken) ) // // Check the access token expire date
{ // if (AuthUtils.isTokenExpired(this.accessToken)) {
return of(false); // return of(false);
} // }
// If the access token exists and it didn't expire, sign in using it // If the access token exists and it didn't expire, sign in using it
return this.signInUsingToken(); return this.signInUsingToken();

18
src/app/core/user/user.service.ts

@ -3,13 +3,15 @@ import { HttpClient } from '@angular/common/http';
import { Observable, ReplaySubject } from 'rxjs'; import { Observable, ReplaySubject } from 'rxjs';
import { map, tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { User } from 'app/core/user/user.types'; import { User } from 'app/core/user/user.types';
import { TesoBusinessDetail } from 'app/models/businessModel';
import { environment } from 'environments/environment';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class UserService export class UserService
{ {
private _user: ReplaySubject<User> = new ReplaySubject<User>(1); private _user: ReplaySubject<TesoBusinessDetail> = new ReplaySubject<TesoBusinessDetail>(1);
/** /**
* Constructor * Constructor
@ -27,13 +29,13 @@ export class UserService
* *
* @param value * @param value
*/ */
set user(value: User) set user(value: TesoBusinessDetail)
{ {
// Store the value // Store the value
this._user.next(value); this._user.next(value);
} }
get user$(): Observable<User> get user$(): Observable<TesoBusinessDetail>
{ {
return this._user.asObservable(); return this._user.asObservable();
} }
@ -45,9 +47,9 @@ export class UserService
/** /**
* Get the current logged in user data * Get the current logged in user data
*/ */
get(): Observable<User> get(): Observable<TesoBusinessDetail>
{ {
return this._httpClient.get<User>('api/common/user').pipe( return this._httpClient.get<TesoBusinessDetail>(environment.apiURL +'business/getProfile').pipe(
tap((user) => { tap((user) => {
this._user.next(user); this._user.next(user);
}) })
@ -59,11 +61,11 @@ export class UserService
* *
* @param user * @param user
*/ */
update(user: User): Observable<any> update(user: TesoBusinessDetail): Observable<any>
{ {
return this._httpClient.patch<User>('api/common/user', {user}).pipe( this._user.next(user);
return this._httpClient.patch<TesoBusinessDetail>('api/common/user', {user}).pipe(
map((response) => { map((response) => {
this._user.next(response);
}) })
); );
} }

2
src/app/layout/common/goldcoin/goldcoin.component.html

@ -1,6 +1,6 @@
<!-- <button mat-icon-button> --> <!-- <button mat-icon-button> -->
<div style="display: flex;justify-content:space-around;" class="goldDiv"> <div style="display: flex;justify-content:space-around;" class="goldDiv">
<img class="w-10" src="assets/images/gold1.png" alt="Logo image"> <img class="w-10" src="assets/images/gold1.png" alt="Logo image">
<span style="padding-top: 9px;font-size: medium;font-weight: 700;color: #64738b;">+{{gold}}</span> <span style="padding-top: 9px;font-size: medium;font-weight: 700;color: #64738b;">{{gold}}</span>
</div> </div>
<!-- </button> --> <!-- </button> -->

79
src/app/layout/common/goldcoin/goldcoin.component.ts

@ -1,25 +1,26 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core'; import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
import { Overlay, OverlayRef } from '@angular/cdk/overlay'; import { Overlay, OverlayRef } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal'; import { TemplatePortal } from '@angular/cdk/portal';
import { MatButton } from '@angular/material/button'; import { MatButton } from '@angular/material/button';
import { Subject } from 'rxjs'; import { takeUntil, takeWhile, tap } from 'rxjs/operators';
import { takeUntil } from 'rxjs/operators';
import { GoldCoinService } from './goldcoin.service'; import { GoldCoinService } from './goldcoin.service';
import { Finance } from 'app/models/businessModel';
import { interval, Subject, Subscription } from 'rxjs';
@Component({ @Component({
selector : 'goldcoins', selector: 'goldcoins',
templateUrl : './goldcoin.component.html', templateUrl: './goldcoin.component.html',
encapsulation : ViewEncapsulation.Emulated, encapsulation: ViewEncapsulation.Emulated,
styleUrls :["./goldcoin.component.scss"], styleUrls: ["./goldcoin.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
exportAs : 'goldcoins' exportAs: 'goldcoins'
}) })
export class GoldCoinComponent implements OnInit, OnDestroy export class GoldCoinComponent implements OnInit, OnDestroy, AfterViewInit {
{
gold: number = 50; gold: number = 0;
private _overlayRef: OverlayRef; private _overlayRef: OverlayRef;
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
sub: Subscription
/** /**
* Constructor * Constructor
@ -27,8 +28,15 @@ export class GoldCoinComponent implements OnInit, OnDestroy
constructor( constructor(
private _changeDetectorRef: ChangeDetectorRef, private _changeDetectorRef: ChangeDetectorRef,
private _goldcoinsService: GoldCoinService, private _goldcoinsService: GoldCoinService,
) ) {
{
}
ngAfterViewInit(): void {
interval(1000)
// .pipe(takeWhile(() => !stop))
.subscribe(() => {
this._goldcoinsService.getAll();
});
} }
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -38,36 +46,36 @@ export class GoldCoinComponent implements OnInit, OnDestroy
/** /**
* On init * On init
*/ */
ngOnInit(): void ngOnInit(): void {
{ this._goldcoinsService.notifications$
// Subscribe to notification changes .pipe(takeUntil(this._unsubscribeAll))
// this._notificationsService.notifications$ .subscribe((gold: Finance) => {
// .pipe(takeUntil(this._unsubscribeAll))
// .subscribe((notifications: Notification[]) => { // Load the notifications
this.gold = gold.gold;
// // Load the notifications
// this.notifications = notifications;
// Mark for check
// // Calculate the unread count this._changeDetectorRef.markForCheck();
// this._calculateUnreadCount(); });
this.sub = interval(100000)
// // Mark for check .subscribe((val) => {
// this._changeDetectorRef.markForCheck();
// }); this._goldcoinsService.getUpdate();
this._changeDetectorRef.markForCheck();
});
} }
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void ngOnDestroy(): void {
{
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
this._unsubscribeAll.next(); this._unsubscribeAll.next();
this._unsubscribeAll.complete(); this._unsubscribeAll.complete();
this.sub.unsubscribe();
// Dispose the overlay // Dispose the overlay
if ( this._overlayRef ) if (this._overlayRef) {
{
this._overlayRef.dispose(); this._overlayRef.dispose();
} }
} }
@ -89,8 +97,7 @@ export class GoldCoinComponent implements OnInit, OnDestroy
* @param index * @param index
* @param item * @param item
*/ */
trackByFn(index: number, item: any): any trackByFn(index: number, item: any): any {
{
return item.id || index; return item.id || index;
} }

136
src/app/layout/common/goldcoin/goldcoin.service.ts

@ -1,21 +1,21 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Observable, ReplaySubject } from 'rxjs'; import { interval, Observable, ReplaySubject } from 'rxjs';
import { Notification } from 'app/layout/common/notifications/notifications.types'; import { Notification } from 'app/layout/common/notifications/notifications.types';
import { map, switchMap, take, tap } from 'rxjs/operators'; import { map, switchMap, take, takeWhile, tap } from 'rxjs/operators';
import { Finance } from 'app/models/businessModel';
import { environment } from 'environments/environment';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class GoldCoinService export class GoldCoinService {
{ private _notifications: ReplaySubject<Finance> = new ReplaySubject<Finance>(1);
private _notifications: ReplaySubject<Notification[]> = new ReplaySubject<Notification[]>(1);
/** /**
* Constructor * Constructor
*/ */
constructor(private _httpClient: HttpClient) constructor(private _httpClient: HttpClient) {
{
} }
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -25,8 +25,7 @@ export class GoldCoinService
/** /**
* Getter for notifications * Getter for notifications
*/ */
get notifications$(): Observable<Notification[]> get notifications$(): Observable<Finance> {
{
return this._notifications.asObservable(); return this._notifications.asObservable();
} }
@ -37,119 +36,34 @@ export class GoldCoinService
/** /**
* Get all notifications * Get all notifications
*/ */
getAll(): Observable<Notification[]> getAll(): Observable<Finance> {
{ return this._httpClient.get<Finance>(environment.apiURL + 'api/business-finance').pipe(
return this._httpClient.get<Notification[]>('api/common/notifications').pipe(
tap((notifications) => { tap((notifications) => {
this._notifications.next(notifications); if (notifications == null) {
}) notifications = {
); businessID: "",
gold: 0,
} }
/**
* Create a notification
*
* @param notification
*/
create(notification: Notification): Observable<Notification>
{
return this.notifications$.pipe(
take(1),
switchMap(notifications => this._httpClient.post<Notification>('api/common/notifications', {notification}).pipe(
map((newNotification) => {
// Update the notifications with the new notification
this._notifications.next([...notifications, newNotification]);
// Return the new notification from observable
return newNotification;
})
))
);
} }
/**
* Update the notification
*
* @param id
* @param notification
*/
update(id: string, notification: Notification): Observable<Notification>
{
return this.notifications$.pipe(
take(1),
switchMap(notifications => this._httpClient.patch<Notification>('api/common/notifications', {
id,
notification
}).pipe(
map((updatedNotification: Notification) => {
// Find the index of the updated notification
const index = notifications.findIndex(item => item.id === id);
// Update the notification
notifications[index] = updatedNotification;
// Update the notifications
this._notifications.next(notifications); this._notifications.next(notifications);
// Return the updated notification
return updatedNotification;
}) })
))
); );
} }
getUpdate(): void {
/** this._httpClient.get<Finance>(environment.apiURL + 'api/business-finance').subscribe((notifications) => {
* Delete the notification if (notifications == null) {
* notifications = {
* @param id businessID: "",
*/ gold: 0,
delete(id: string): Observable<boolean> }
{ }
return this.notifications$.pipe(
take(1),
switchMap(notifications => this._httpClient.delete<boolean>('api/common/notifications', {params: {id}}).pipe(
map((isDeleted: boolean) => {
// Find the index of the deleted notification
const index = notifications.findIndex(item => item.id === id);
// Delete the notification
notifications.splice(index, 1);
// Update the notifications
this._notifications.next(notifications); this._notifications.next(notifications);
// Return the deleted status
return isDeleted;
}) })
))
);
} }
/** /**
* Mark all notifications as read * Create a notification
*
* @param notification
*/ */
markAllAsRead(): Observable<boolean>
{
return this.notifications$.pipe(
take(1),
switchMap(notifications => this._httpClient.get<boolean>('api/common/notifications/mark-all-as-read').pipe(
map((isUpdated: boolean) => {
// Go through all notifications and set them as read
notifications.forEach((notification, index) => {
notifications[index].read = true;
});
// Update the notifications
this._notifications.next(notifications);
// Return the updated status
return isUpdated;
})
))
);
}
} }

29
src/app/layout/common/user/user.component.html

@ -4,20 +4,13 @@
[matMenuTriggerFor]="userActions"> [matMenuTriggerFor]="userActions">
<span class="relative"> <span class="relative">
<img <img
class="w-7 h-7 rounded-full" class="w-10 h-10 rounded-full"
*ngIf="showAvatar && user.avatar" *ngIf="showAvatar && user.businessLogo"
[src]="user.avatar"> [src]="imageLoader(user.businessLogo)">
<mat-icon <mat-icon
*ngIf="!showAvatar || !user.avatar" *ngIf="!showAvatar || !user.businessLogo"
[svgIcon]="'heroicons_outline:user-circle'"></mat-icon> [svgIcon]="'heroicons_outline:user-circle'"></mat-icon>
<span
class="absolute right-0 bottom-0 w-2 h-2 rounded-full"
[ngClass]="{'mr-px mb-px': !showAvatar || !user.avatar,
'bg-green-500': user.status === 'online',
'bg-amber-500': user.status === 'away',
'bg-red-500': user.status === 'busy',
'bg-gray-400': user.status === 'not-visible'}"
></span>
</span> </span>
</button> </button>
@ -27,19 +20,17 @@
<button mat-menu-item> <button mat-menu-item>
<span class="flex flex-col leading-none"> <span class="flex flex-col leading-none">
<span>Signed in as</span> <span>Signed in as</span>
<span class="mt-1.5 text-md font-medium">{{user.email}}</span> <span class="mt-1.5 text-md font-medium">{{user.businessName}}</span>
</span> </span>
</button> </button>
<mat-divider class="my-2"></mat-divider> <mat-divider class="my-2"></mat-divider>
<button mat-menu-item> <button mat-menu-item (click)="profile()">
<mat-icon [svgIcon]="'heroicons_outline:user-circle'"></mat-icon> <mat-icon [svgIcon]="'heroicons_outline:user-circle'"></mat-icon>
<span>Profile</span> <span>Profile</span>
</button> </button>
<button <button mat-menu-item (click)="changePassword()">
mat-menu-item <mat-icon [svgIcon]="'heroicons_outline:lock-closed'"></mat-icon>
[matMenuTriggerFor]="userStatus"> <span class="mt-1.5 text-md "> Password</span>
<mat-icon [svgIcon]="'heroicons_outline:dots-circle-horizontal'"></mat-icon>
<span>Status</span>
</button> </button>
<mat-divider class="my-2"></mat-divider> <mat-divider class="my-2"></mat-divider>
<button <button

54
src/app/layout/common/user/user.component.ts

@ -5,22 +5,25 @@ import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { User } from 'app/core/user/user.types'; import { User } from 'app/core/user/user.types';
import { UserService } from 'app/core/user/user.service'; import { UserService } from 'app/core/user/user.service';
import { TesoBusinessDetail } from 'app/models/businessModel';
import { environment } from 'environments/environment';
import { MatDialog } from '@angular/material/dialog';
import { SettingsComponent } from 'app/pages/admin/Profile/Settings/settings.component';
@Component({ @Component({
selector : 'user', selector: 'user',
templateUrl : './user.component.html', templateUrl: './user.component.html',
encapsulation : ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
exportAs : 'user' exportAs: 'user'
}) })
export class UserComponent implements OnInit, OnDestroy export class UserComponent implements OnInit, OnDestroy {
{
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
static ngAcceptInputType_showAvatar: BooleanInput; static ngAcceptInputType_showAvatar: BooleanInput;
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
@Input() showAvatar: boolean = true; @Input() showAvatar: boolean = true;
user: User; user: TesoBusinessDetail;
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
@ -30,9 +33,9 @@ export class UserComponent implements OnInit, OnDestroy
constructor( constructor(
private _changeDetectorRef: ChangeDetectorRef, private _changeDetectorRef: ChangeDetectorRef,
private _router: Router, private _router: Router,
private _userService: UserService private _userService: UserService,
) private dialog: MatDialog,
{ ) {
} }
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -42,12 +45,11 @@ export class UserComponent implements OnInit, OnDestroy
/** /**
* On init * On init
*/ */
ngOnInit(): void ngOnInit(): void {
{
// Subscribe to user changes // Subscribe to user changes
this._userService.user$ this._userService.user$
.pipe(takeUntil(this._unsubscribeAll)) .pipe(takeUntil(this._unsubscribeAll))
.subscribe((user: User) => { .subscribe((user: TesoBusinessDetail) => {
this.user = user; this.user = user;
// Mark for check // Mark for check
@ -58,8 +60,7 @@ export class UserComponent implements OnInit, OnDestroy
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void ngOnDestroy(): void {
{
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
this._unsubscribeAll.next(); this._unsubscribeAll.next();
this._unsubscribeAll.complete(); this._unsubscribeAll.complete();
@ -74,26 +75,35 @@ export class UserComponent implements OnInit, OnDestroy
* *
* @param status * @param status
*/ */
updateUserStatus(status: string): void updateUserStatus(status: string): void {
{
// Return if user is not available // Return if user is not available
if ( !this.user ) if (!this.user) {
{
return; return;
} }
// Update the user // Update the user
this._userService.update({ this._userService.update({
...this.user, ...this.user,
status
}).subscribe(); }).subscribe();
} }
/** /**
* Sign out * Sign out
*/ */
signOut(): void signOut(): void {
{
this._router.navigate(['/sign-out']); this._router.navigate(['/sign-out']);
} }
profile(): void {
this._router.navigate(['/profile']);
}
imageLoader(path: string): string {
return environment.apiURL + `shoplogo/${path}`;
}
changePassword() {
const dialogRef = this.dialog.open(SettingsComponent,{
hasBackdrop: true,
disableClose:true,
})
}
} }

3
src/app/layout/common/user/user.module.ts

@ -4,11 +4,12 @@ import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { UserComponent } from 'app/layout/common/user/user.component'; import { UserComponent } from 'app/layout/common/user/user.component';
import { ProfileComponent } from 'app/pages/admin/Profile/profile.component';
import { SharedModule } from 'app/shared/shared.module'; import { SharedModule } from 'app/shared/shared.module';
@NgModule({ @NgModule({
declarations: [ declarations: [
UserComponent UserComponent,
], ],
imports : [ imports : [
MatButtonModule, MatButtonModule,

25
src/app/models/businessModel.ts

@ -0,0 +1,25 @@
export interface Finance {
businessID: string;
gold: number;
}
export interface TesoBusinessDetail {
businessId?: string,
Handle?: string,
businessName?: string,
businessEmail?: string,
businessTin?: string,
businessDescription?: string,
businessCategory?: string,
businessAddress?: string,
businessContact?: string,
businessLogo?: string,
dateOfEst?: Date,
businessDigitalAddress?: string,
businessLatitude?: string,
businessLongitude?: string,
}
export interface BusinessCategory {
categoryCode: string,
categoryName: string,
}

21
src/app/models/couponsModel.ts

@ -0,0 +1,21 @@
import { TesoBusinessDetail } from "./businessModel"
export interface CouponsModel {
couponID: string,
businessID: string,
targetProduct: string,
type: string,
quantity: number,
lower: number,
condition: string,
upper: number,
numberClaimed: number,
status: string,
expiration: Date,
target?: TesoBusinessDetail,
}
export interface CouponsType {
typeCode: string;
typeName: string;
}

40
src/app/models/productsModel.ts

@ -0,0 +1,40 @@
export interface ProductsModel {
productName: string;
businessID: string;
productDesc: string;
productID: string;
categoryID: string;
unitPrice: number;
productImage: string;
images: ProductImages[];
}
export interface ProductImages {
id: string;
productID: string;
path: string;
}
export interface ProductCategory {
catCode: string;
catName: string;
}
export interface DesiredProduct{
productID:string,
productName:string,
productImage:string,
cost:number,
category:string,
enlisted:boolean,
desires:number
}
export interface DesiredList{
mostWishedProduct?: DesiredProduct[],
mostWishedCategory?: DesiredProduct[],
availableProducts?: DesiredProduct[],
availableCategory?: DesiredProduct[],
}

14
src/app/models/userModel.ts

@ -0,0 +1,14 @@
export interface TesoUserDetails {
userGuid?: string;
username?: string;
firstname?: string;
surname?: string;
description?: string;
address?: string;
email?: string;
thumbnailDp?: string;
phonenumber?: string;
country?: string;
gender?: string;
dateOfBirth: Date;
}

22
src/app/pages/admin/Coupons/Active/active-coupons.component.html

@ -9,7 +9,7 @@
</th> </th>
<td mat-cell *matCellDef="let transaction"> <td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-wrap"> <span class="pr-6 whitespace-wrap">
{{transaction.name}} {{transaction.targetProduct}}
</span> </span>
</td> </td>
</ng-container> </ng-container>
@ -51,8 +51,11 @@
Range Range
</th> </th>
<td mat-cell *matCellDef="let transaction"> <td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;font-weight: bold;"> <span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;font-weight: bold;" *ngIf="!transaction.type.includes('FREEBIES')">
{{transaction.range}} {{transaction.lower}} - {{transaction.upper}}
</span>
<span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;font-weight: bold;" *ngIf="transaction.type.includes('FREEBIES')">
100% OFF
</span> </span>
</td> </td>
</ng-container> </ng-container>
@ -62,7 +65,7 @@
</th> </th>
<td mat-cell *matCellDef="let transaction"> <td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;"> <span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;">
{{transaction.claimed}} {{transaction.numberClaimed}}
</span> </span>
</td> </td>
</ng-container> </ng-container>
@ -93,9 +96,16 @@
</th> </th>
<td mat-cell *matCellDef="let transaction"> <td mat-cell *matCellDef="let transaction">
<div style="display: flex;"> <div style="display: flex;">
<button class=" sm:inline-flex" mat-flat-button style="background-color: #96260a;color:white;">Cancel <button class=" sm:inline-flex" mat-flat-button>
<a href="#" style="color: #0152cc;margin-right:10px;">Show Details</a>
</button> </button>
<button class=" sm:inline-flex" mat-flat-button [matMenuTriggerFor]="actionsMenu">
<mat-icon class="icon-size-5" matPrefix [svgIcon]="'heroicons_solid:dots-horizontal'">
</mat-icon>
</button>
<mat-menu #actionsMenu="matMenu">
<button mat-menu-item>Cancel</button>
</mat-menu>
</div> </div>
</td> </td>
</ng-container> </ng-container>

72
src/app/pages/admin/Coupons/AdminNewCoupons/admin-new-coupons.component.html

@ -15,26 +15,27 @@
</div> </div>
<div class="column"> <div class="column">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
Target Name {{businessTargetted.businessName}}
</mat-label> </mat-label>
<button style="background-color: blue;color:white;margin-right:20px;" mat-button (click)="lookupBusiness()">Search</button> <button style="background-color: blue;color:white;margin-right:20px;" mat-button
(click)="lookupBusiness()">Search</button>
</div> </div>
</div> </div>
<hr/> <hr />
<div class="columns" style="display: flex;align-items: center"> <div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline"> <div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
<strong class="tileHead">Target Name </strong> <strong class="tileHead">Product Name </strong>
</mat-label> </mat-label>
</div> </div>
<div class="column"> <div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;"> <mat-form-field appearance="fill" style="min-width: 200px;">
<mat-select required> <mat-select [(ngModel)]="selectedProduct">
<mat-option value="low">Product 1</mat-option> <mat-option *ngFor="let product of products" [value]="product">
<mat-option value="standard">Product 2</mat-option> {{product.productName}}
<mat-option value="high">Product 3</mat-option> </mat-option>
<mat-option value="high">Product 4</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>
@ -46,7 +47,7 @@
</div> </div>
<div class="column"> <div class="column">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
GH¢200 {{selectedProduct.unitPrice | currency:"GHS "}}
</mat-label> </mat-label>
</div> </div>
</div> </div>
@ -58,12 +59,12 @@
</div> </div>
<div class="column"> <div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;"> <mat-form-field appearance="fill" style="min-width: 200px;">
<mat-select required> <mat-select [(ngModel)]="selectedType">
<mat-option value="low">Freebie</mat-option> <mat-option *ngFor="let type of couponTypes" [value]="type">
<mat-option value="standard">Discount</mat-option> {{type.typeName}}
<mat-option value="high">Proximity Freebie</mat-option> </mat-option>
<mat-option value="high">Proximity Discount</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>
@ -91,7 +92,7 @@
</div> </div>
</div> </div>
<div style="display: flex;align-items: baseline"> <div style="display: flex;align-items: baseline" *ngIf="!isScreenSmall">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
<strong class="tileHead">Percentage Off </strong> <strong class="tileHead">Percentage Off </strong>
</mat-label> </mat-label>
@ -106,15 +107,34 @@
<span matSuffix>% &nbsp;</span> <span matSuffix>% &nbsp;</span>
</mat-form-field> </mat-form-field>
</div> </div>
<div *ngIf="isScreenSmall">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Percentage Off </strong>
</mat-label>
<div style="display: flex;align-items: baseline">
<mat-form-field appearance="outline" style="margin-right:10px;">
<span matPrefix>From &nbsp;</span>
<input matInput type="number" [(ngModel)]="fromWorth">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline">
<mat-form-field appearance="outline" style="margin-left:10px;">
<span matPrefix>To &nbsp;</span>
<input matInput type="number" [(ngModel)]="toWorth">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
</div>
<div style="display: flex;align-items: baseline"> <div style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
<strong class="tileHead">Coupon Worth </strong> <strong class="tileHead">Coupon Worth </strong>
</mat-label> </mat-label>
<mat-label> <mat-label>
From GH¢200 To GH¢800 From {{selectedProduct.unitPrice | couponWorth:fromWorth}} To {{selectedProduct.unitPrice | couponWorth:toWorth}}
</mat-label> </mat-label>
</div> </div>
<div style="display: flex;align-items: baseline;margin-top: 20px;"> <div style="display: flex;align-items: baseline;margin-top: 20px;" *ngIf="!isScreenSmall">
<mat-label style="margin-right: 30px;"> <mat-label style="margin-right: 30px;">
<strong class="tileHead">Date Of Expiration </strong> <strong class="tileHead">Date Of Expiration </strong>
</mat-label> </mat-label>
@ -125,6 +145,22 @@
<span matPrefix>Time &nbsp;</span> <span matPrefix>Time &nbsp;</span>
<input matInput type="time"> <input matInput type="time">
</mat-form-field> </mat-form-field>
</div>
<div *ngIf="isScreenSmall">
<div style="display: flex;align-items: baseline;margin-top: 20px;">
<mat-form-field appearance="outline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Date Of Expiration </strong>
</mat-label>
<input matInput style="margin-left:10px;" type="date">
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline;">
<mat-form-field appearance="outline">
<span matPrefix>Time &nbsp;</span>
<input matInput type="time">
</mat-form-field>
</div>
</div> </div>
<div class="row" style="display: flex;justify-content:space-around;width:100%;"> <div class="row" style="display: flex;justify-content:space-around;width:100%;">
<button style="background-color: blue;color:white;margin-right:20px;" mat-button>Generate Coupons</button> <button style="background-color: blue;color:white;margin-right:20px;" mat-button>Generate Coupons</button>

47
src/app/pages/admin/Coupons/AdminNewCoupons/admin-new-coupons.component.ts

@ -1,22 +1,63 @@
import { Component, Inject, OnInit } from '@angular/core'; import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { TesoBusinessDetail } from 'app/models/businessModel';
import { CouponsType } from 'app/models/couponsModel';
import { ProductsModel } from 'app/models/productsModel';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { BusinessLookUpComponent } from '../../Products/BusinessLookUp/business-look-up.component'; import { BusinessLookUpComponent } from '../../Products/BusinessLookUp/business-look-up.component';
import { ProductsService } from '../../Products/products.service';
import { CouponsService } from '../coupons.service';
@Component({ @Component({
selector: 'app-admin-new-coupons', selector: 'app-admin-new-coupons',
templateUrl: './admin-new-coupons.component.html', templateUrl: './admin-new-coupons.component.html',
styleUrls: ['./admin-new-coupons.component.scss'] styleUrls: ['./admin-new-coupons.component.scss']
}) })
export class AdminNewCouponsComponent implements OnInit { export class AdminNewCouponsComponent implements OnInit, OnDestroy {
constructor(@Inject(MAT_DIALOG_DATA) public data: { product: any }, public dialog: MatDialog,) { } businessTargetted: TesoBusinessDetail = {};
products: ProductsModel[] = [];
fromWorth:number;
toWorth:number;
selectedProduct: any = {};
selectedType: any = {};
isLoading: boolean = false;
isScreenSmall: boolean;
couponTypes: CouponsType[] = [];
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(@Inject(MAT_DIALOG_DATA) public data: { product: any },
public dialog: MatDialog, private _productService: ProductsService,
private _couponService: CouponsService, private _tesoMediaWatcherService: tesoMediaWatcherService,) { }
ngOnInit(): void { ngOnInit(): void {
this._couponService.categories$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.couponTypes = d;
});
this._productService.data$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.products = d;
});
this._tesoMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({ matchingAliases }) => {
// Check if the screen is small
this.isScreenSmall = !matchingAliases.includes('md');
});
} }
lookupBusiness() { lookupBusiness() {
const dialogRef = this.dialog.open(BusinessLookUpComponent, { const dialogRef = this.dialog.open(BusinessLookUpComponent, {
disableClose: true, disableClose: true,
hasBackdrop: true, hasBackdrop: true,
});
dialogRef.afterClosed().subscribe((result) => {
this.businessTargetted = result;
}); });
} }
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
} }

11
src/app/pages/admin/Coupons/Inactive/inactive-coupons.component.html

@ -9,7 +9,7 @@
</th> </th>
<td mat-cell *matCellDef="let transaction"> <td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-wrap"> <span class="pr-6 whitespace-wrap">
{{transaction.name}} {{transaction.targetProduct}}
</span> </span>
</td> </td>
</ng-container> </ng-container>
@ -51,8 +51,11 @@
Range Range
</th> </th>
<td mat-cell *matCellDef="let transaction"> <td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;font-weight: bold;"> <span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;font-weight: bold;" *ngIf="!transaction.type.includes('FREEBIES')">
{{transaction.range}} {{transaction.lower}} - {{transaction.upper}}
</span>
<span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;font-weight: bold;" *ngIf="transaction.type.includes('FREEBIES')">
100% OFF
</span> </span>
</td> </td>
</ng-container> </ng-container>
@ -62,7 +65,7 @@
</th> </th>
<td mat-cell *matCellDef="let transaction"> <td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;"> <span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;">
{{transaction.claimed}} {{transaction.numberClaimed}}
</span> </span>
</td> </td>
</ng-container> </ng-container>

69
src/app/pages/admin/Coupons/NewCoupons/new-coupons.component.html

@ -10,17 +10,17 @@
<div class="columns" style="display: flex;align-items: center"> <div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline"> <div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
<strong class="tileHead">Target Product </strong> <strong class="tileHead">Product Name </strong>
</mat-label> </mat-label>
</div> </div>
<div class="column"> <div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;"> <mat-form-field appearance="fill" style="min-width: 200px;">
<mat-select required> <mat-select [(ngModel)]="selectedProduct">
<mat-option value="low">Product 1</mat-option> <mat-option *ngFor="let product of products" [value]="product">
<mat-option value="standard">Product 2</mat-option> {{product.productName}}
<mat-option value="high">Product 3</mat-option> </mat-option>
<mat-option value="high">Product 4</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>
@ -32,7 +32,7 @@
</div> </div>
<div class="column"> <div class="column">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
GH¢200 {{selectedProduct.unitPrice | currency:"GHS "}}
</mat-label> </mat-label>
</div> </div>
</div> </div>
@ -44,12 +44,12 @@
</div> </div>
<div class="column"> <div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;"> <mat-form-field appearance="fill" style="min-width: 200px;">
<mat-select required> <mat-select [(ngModel)]="selectedType">
<mat-option value="low">Freebie</mat-option> <mat-option *ngFor="let type of couponTypes" [value]="type">
<mat-option value="standard">Discount</mat-option> {{type.typeName}}
<mat-option value="high">Proximity Freebie</mat-option> </mat-option>
<mat-option value="high">Proximity Discount</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>
@ -77,7 +77,7 @@
</div> </div>
</div> </div>
<div style="display: flex;align-items: baseline"> <div style="display: flex;align-items: baseline" *ngIf="!isScreenSmall">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
<strong class="tileHead">Percentage Off </strong> <strong class="tileHead">Percentage Off </strong>
</mat-label> </mat-label>
@ -92,29 +92,66 @@
<span matSuffix>% &nbsp;</span> <span matSuffix>% &nbsp;</span>
</mat-form-field> </mat-form-field>
</div> </div>
<div *ngIf="isScreenSmall">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Percentage Off </strong>
</mat-label>
<div style="display: flex;align-items: baseline">
<mat-form-field appearance="outline" style="margin-right:10px;">
<span matPrefix>From &nbsp;</span>
<input matInput type="number" [(ngModel)]="fromWorth" min="0" max="100">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline">
<mat-form-field appearance="outline" style="margin-left:10px;">
<span matPrefix>To &nbsp;</span>
<input matInput type="number" [(ngModel)]="toWorth" min="0" max="100">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
</div>
<div style="display: flex;align-items: baseline"> <div style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
<strong class="tileHead">Coupon Worth </strong> <strong class="tileHead">Coupon Worth </strong>
</mat-label> </mat-label>
<mat-label> <mat-label>
From GH¢200 To GH¢800 From {{selectedProduct.unitPrice | couponWorth:fromWorth}} To {{selectedProduct.unitPrice |
couponWorth:toWorth}}
</mat-label>
</div>
<div style="display: flex;align-items: baseline;margin-top: 20px;" *ngIf="!isScreenSmall">
<mat-label style="margin-right: 30px;">
<strong class="tileHead">Date Of Expiration </strong>
</mat-label> </mat-label>
<mat-form-field appearance="outline">
<input matInput style="margin-left:10px;" type="date">
</mat-form-field>
<mat-form-field appearance="fill" style="width: 200px;">
<span matPrefix>Time &nbsp;</span>
<input matInput type="time">
</mat-form-field>
</div> </div>
<div *ngIf="isScreenSmall">
<div style="display: flex;align-items: baseline;margin-top: 20px;"> <div style="display: flex;align-items: baseline;margin-top: 20px;">
<mat-form-field appearance="outline">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
<strong class="tileHead">Date Of Expiration </strong> <strong class="tileHead">Date Of Expiration </strong>
</mat-label> </mat-label>
<mat-form-field appearance="outline">
<input matInput style="margin-left:10px;" type="date"> <input matInput style="margin-left:10px;" type="date">
</mat-form-field> </mat-form-field>
<mat-form-field appearance="fill"> </div>
<div style="display: flex;align-items: baseline;">
<mat-form-field appearance="outline">
<span matPrefix>Time &nbsp;</span> <span matPrefix>Time &nbsp;</span>
<input matInput type="time"> <input matInput type="time">
</mat-form-field> </mat-form-field>
</div> </div>
</div>
<div class="row" style="display: flex;justify-content:space-around;width:100%;"> <div class="row" style="display: flex;justify-content:space-around;width:100%;">
<button style="background-color: blue;color:white;margin-right:20px;" mat-button>Generate Coupons</button> <button style="background-color: blue;color:white;margin-right:20px;" mat-button>Generate Coupons</button>
</div> </div>
</div> </div>
</mat-dialog-content> </mat-dialog-content>

45
src/app/pages/admin/Coupons/NewCoupons/new-coupons.component.ts

@ -1,15 +1,50 @@
import { Component, OnInit } from '@angular/core'; import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { CouponsType } from 'app/models/couponsModel';
import { ProductsModel } from 'app/models/productsModel';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ProductsService } from '../../Products/products.service';
import { CouponsService } from '../coupons.service';
@Component({ @Component({
selector: 'app-new-coupons', selector: 'app-new-coupons',
templateUrl: './new-coupons.component.html', templateUrl: './new-coupons.component.html',
styleUrls: ['./new-coupons.component.scss'] styleUrls: ['./new-coupons.component.scss']
}) })
export class NewCouponsComponent implements OnInit { export class NewCouponsComponent implements OnInit,OnDestroy {
products: ProductsModel[] = [];
constructor() { } selectedProduct:any={};
selectedType:any={};
isLoading: boolean = false;
couponTypes:CouponsType[]=[];
fromWorth:number;
toWorth:number;
isScreenSmall:boolean;
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(@Inject(MAT_DIALOG_DATA) public data: { product: any },
public dialog: MatDialog, private _productService: ProductsService,
private _couponService: CouponsService, private _tesoMediaWatcherService: tesoMediaWatcherService,) { }
ngOnInit(): void { ngOnInit(): void {
} this._couponService.categories$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.couponTypes = d;
});
this._productService.data$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.products = d;
});
this._tesoMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({ matchingAliases }) => {
// Check if the screen is small
this.isScreenSmall = !matchingAliases.includes('md');
});
}
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
} }

2
src/app/pages/admin/Coupons/coupons.component.html

@ -6,7 +6,7 @@
<div class="flex items-center justify-between w-full" style="margin-bottom: 20px;"> <div class="flex items-center justify-between w-full" style="margin-bottom: 20px;">
<div> <div>
<div class="text-3xl font-semibold tracking-tight leading-8">Coupons</div> <div class="text-3xl font-semibold tracking-tight leading-8">Coupons</div>
<div class="font-medium tracking-tight text-secondary">Here is a list of all your coupons on Teso (850) <div class="font-medium tracking-tight text-secondary">Here is a list of all your coupons on Teso ({{allcoupons.length}})
</div> </div>
</div> </div>

221
src/app/pages/admin/Coupons/coupons.component.ts

@ -4,8 +4,13 @@ import { MatTableDataSource } from '@angular/material/table';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup'; import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher'; import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { CouponsModel } from 'app/models/couponsModel';
import { throwIfEmpty } from 'rxjs/operators';
import { AdminNewCouponsComponent } from './AdminNewCoupons/admin-new-coupons.component'; import { AdminNewCouponsComponent } from './AdminNewCoupons/admin-new-coupons.component';
import { CouponsService } from './coupons.service';
import { NewCouponsComponent } from './NewCoupons/new-coupons.component'; import { NewCouponsComponent } from './NewCoupons/new-coupons.component';
import jwt_decode from 'jwt-decode';
import { AuthService } from 'app/core/auth/auth.service';
@Component({ @Component({
selector: 'app-coupons', selector: 'app-coupons',
@ -15,210 +20,48 @@ import { NewCouponsComponent } from './NewCoupons/new-coupons.component';
export class CouponsComponent implements OnInit { export class CouponsComponent implements OnInit {
activecouponsData: MatTableDataSource<any> = new MatTableDataSource(); activecouponsData: MatTableDataSource<any> = new MatTableDataSource();
inactivecouponsData: MatTableDataSource<any> = new MatTableDataSource(); inactivecouponsData: MatTableDataSource<any> = new MatTableDataSource();
allcoupons: CouponsModel[] = [];
constructor(private router: Router, private _tesoMediaWatcherService: tesoMediaWatcherService, constructor(private router: Router, private _tesoMediaWatcherService: tesoMediaWatcherService,
public dialog: MatDialog, private confirmBoxEvokeService: ConfirmBoxEvokeService) { } public dialog: MatDialog, private confirmBoxEvokeService: ConfirmBoxEvokeService,
private _couponsService: CouponsService, private _authService: AuthService) {
ngOnInit(): void {
this.activecouponsData.data = [
{
id: '2bfa2be5-7688-48d5-b5ac-dc0d9ac97f14',
expiration:"2022-03-15",
name: 'Nadia Mcknight',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: '77a4383b-b5a5-4943-bc46-04c3431d1566',
expiration:"2022-03-15",
name: 'Best Blackburn',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: '8bb0f597-673a-47ca-8c77-2f83219cb9af',
expiration:"2022-03-15",
name: 'Duncan Carver',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: 'c318e31f-1d74-49c5-8dae-2bc5805e2fdb',
expiration:"2022-03-15",
name: 'Martin Richards',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: '0a8bc517-631a-4a93-aacc-000fa2e8294c',
expiration:"2022-03-15",
name: 'Candice Munoz',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: 'a4c9945a-757b-40b0-8942-d20e0543cabd',
expiration:"2022-03-15",
name: 'Vickie Mosley',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: 'b8258ccf-48b5-46a2-9c95-e0bd7580c645',
expiration:"2022-03-15",
name: 'Tina Harris',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: 'f004ea79-98fc-436c-9ba5-6cfe32fe583d',
expiration:"2022-03-15",
name: 'Holt Manning',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: '8b69fe2d-d7cc-4a3d-983d-559173e37d37',
expiration:"2022-03-15",
name: 'Misty Ramsey',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
} }
];
this.inactivecouponsData.data = [ ngOnInit(): void {
{
id: '2bfa2be5-7688-48d5-b5ac-dc0d9ac97f14', this._couponsService.getData().subscribe((response) => {
expiration:"2022-03-15", this.allcoupons = response;
name: 'Nadia Mcknight', this.activecouponsData.data = this.allcoupons.filter(t => t.status.toLowerCase() == "active");
range: '60% - 95%', this.inactivecouponsData.data = this.allcoupons.filter(t => t.status.toLowerCase() != "active");
condition: 'By one get one free', });
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: '77a4383b-b5a5-4943-bc46-04c3431d1566',
expiration:"2022-03-15",
name: 'Best Blackburn',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: '8bb0f597-673a-47ca-8c77-2f83219cb9af',
expiration:"2022-03-15",
name: 'Duncan Carver',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: 'c318e31f-1d74-49c5-8dae-2bc5805e2fdb',
expiration:"2022-03-15",
name: 'Martin Richards',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: '0a8bc517-631a-4a93-aacc-000fa2e8294c',
expiration:"2022-03-15",
name: 'Candice Munoz',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: 'a4c9945a-757b-40b0-8942-d20e0543cabd',
expiration:"2022-03-15",
name: 'Vickie Mosley',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: 'b8258ccf-48b5-46a2-9c95-e0bd7580c645',
expiration:"2022-03-15",
name: 'Tina Harris',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: 'f004ea79-98fc-436c-9ba5-6cfe32fe583d',
expiration:"2022-03-15",
name: 'Holt Manning',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: '8b69fe2d-d7cc-4a3d-983d-559173e37d37',
expiration:"2022-03-15",
name: 'Misty Ramsey',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
}
]
} }
generateCoupons(){ generateCoupons() {
if (!true) { const tokenInfo = this.getDecodedAccessToken(this._authService.relevantToken);
const dialogRef = this.dialog.open(NewCouponsComponent, {
if (tokenInfo.businessID != "1TESBU00000000") {
const dialogRef = this.dialog.open(AdminNewCouponsComponent, {
disableClose: true, disableClose: true,
hasBackdrop: true, hasBackdrop: true,
}); });
} else { } else {
const dialogRef = this.dialog.open(AdminNewCouponsComponent, { const dialogRef = this.dialog.open(NewCouponsComponent, {
disableClose: true, disableClose: true,
hasBackdrop: true, hasBackdrop: true,
}); });
}
}
getDecodedAccessToken(token: string): any {
try {
return jwt_decode(token);
} catch (Error) {
return null;
} }
} }
} }

4
src/app/pages/admin/Coupons/coupons.module.ts

@ -26,6 +26,7 @@ import { ActiveCouponsComponent } from './Active/active-coupons.component';
import { InactiveCouponsComponent } from './Inactive/inactive-coupons.component'; import { InactiveCouponsComponent } from './Inactive/inactive-coupons.component';
import { NewCouponsComponent } from './NewCoupons/new-coupons.component'; import { NewCouponsComponent } from './NewCoupons/new-coupons.component';
import { AdminNewCouponsComponent } from './AdminNewCoupons/admin-new-coupons.component'; import { AdminNewCouponsComponent } from './AdminNewCoupons/admin-new-coupons.component';
import { CouponWorthPipe } from 'app/pipes/coupon-worth.pipe';
@NgModule({ @NgModule({
@ -34,7 +35,8 @@ import { AdminNewCouponsComponent } from './AdminNewCoupons/admin-new-coupons.co
ActiveCouponsComponent, ActiveCouponsComponent,
InactiveCouponsComponent, InactiveCouponsComponent,
NewCouponsComponent, NewCouponsComponent,
AdminNewCouponsComponent AdminNewCouponsComponent,
CouponWorthPipe
], ],
imports: [ imports: [
RouterModule.forChild(couponsRoutes), RouterModule.forChild(couponsRoutes),

59
src/app/pages/admin/Coupons/coupons.service.ts

@ -0,0 +1,59 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { analytics as analyticsData } from 'app/mock-api/dashboards/analytics/data';
import { environment } from 'environments/environment';
import { CouponsModel, CouponsType } from 'app/models/couponsModel';
@Injectable({
providedIn: 'root'
})
export class CouponsService {
private _data: BehaviorSubject<any> = new BehaviorSubject(null);
private _couponCategory: BehaviorSubject<CouponsType[]> = new BehaviorSubject(null);
/**
* Constructor
*/
constructor(private _httpClient: HttpClient) {
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Getter for data
*/
get data$(): Observable<any> {
return this._data.asObservable();
}
get categories$(): Observable<CouponsType[]> {
return this._couponCategory.asObservable();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Get data
*/
getData(): Observable<any> {
return this._httpClient.get(environment.apiURL + `allcoupons`).pipe(
tap((response: any) => {
this._data.next(response);
})
);
}
getCategory(): Observable<CouponsType[]> {
return this._httpClient.get(environment.apiURL + `api/CouponTypes`).pipe(
tap((response: CouponsType[]) => {
this._couponCategory.next(response);
})
);
}
}

139
src/app/pages/admin/Followers/PersonalizedCoupons/personalized-coupons.component.html

@ -0,0 +1,139 @@
<div class="row" style="display: flex;justify-content:space-between">
<div class="row" style="display: flex;justify-content:center;width: 100%;">
<h2 mat-dialog-title style="text-align: center;">Send a coupon to {{data.subscriber.firstname}}
{{data.subscriber.surname}}</h2>
</div>
<!-- <button mat-button mat-dialog-close>Cancel</button> -->
<button mat-button [mat-dialog-close]="true" cdkFocusInitial>x</button>
</div>
<mat-dialog-content class="mat-typography">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6">
<!-- <div class="flex flex-col flex-auto w-full p-8 text-center">
<div class="w-32 h-32 mx-auto rounded-full overflow-hidden">
<img class="w-full h-full object-cover" [src]="imageLoader(data.subscriber.thumbnailDp)">
</div>
</div> -->
<div class="columns" style="display: flex;align-items: center" *ngIf="tesoGhana">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Target Business </strong>
</mat-label>
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
{{businessTargetted.businessName}}
</mat-label>
<button style="background-color: blue;color:white;margin-right:20px;" mat-button
(click)="lookupBusiness()">Search</button>
</div>
</div>
<hr *ngIf="tesoGhana" />
<div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Product Name </strong>
</mat-label>
</div>
<div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;">
<mat-select [(ngModel)]="selectedProduct">
<mat-option *ngFor="let product of products" [value]="product">
{{product.productName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="columns" style="display: flex;align-items: center;margin-top: 15px;">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Original Price </strong>
</mat-label>
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
{{selectedProduct.unitPrice | currency:"GHS "}}
</mat-label>
</div>
</div>
<div class="columns" style="display: flex;align-items: center;margin-top: 15px;">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 20px;">
<strong class="tileHead">Coupon Type</strong>
</mat-label>
</div>
<div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;">
<mat-select [(ngModel)]="selectedType">
<mat-option value="TESCP005">DISCOUNT </mat-option>
<mat-option value="TESCP006">FREEBIE </mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="columns" style="display: flex;align-items: center">
<div class="column">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Coupon Condition</strong>
</mat-label>
</div>
<div class="column">
<textarea name="" id="" cols="30" rows="5" style="border: 1px solid #cbd5e1;"></textarea>
</div>
</div>
<div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Number Of Coupons</strong>
</mat-label>
</div>
<div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;">
<input matInput style="margin-left:10px;" type="number">
</mat-form-field>
</div>
</div>
<div style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Percentage Off </strong>
</mat-label>
<mat-form-field appearance="fill" style="margin-right:10px;">
<span matPrefix>From &nbsp;</span>
<input matInput type="number">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
<mat-form-field appearance="fill" style="margin-left:10px;">
<span matPrefix>To &nbsp;</span>
<input matInput type="number">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Coupon Worth </strong>
</mat-label>
<mat-label>
From GH¢200 To GH¢800
</mat-label>
</div>
<div style="display: flex;align-items: baseline;margin-top: 20px;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Date Of Expiration </strong>
</mat-label>
<mat-form-field appearance="outline">
<input matInput style="margin-left:10px;" type="date">
</mat-form-field>
<mat-form-field appearance="fill">
<span matPrefix>Time &nbsp;</span>
<input matInput type="time">
</mat-form-field>
</div>
<div class="row" style="display: flex;justify-content:space-around;width:100%;">
<button style="background-color: blue;color:white;margin-right:20px;" mat-button>Generate Coupons</button>
</div>
</div>
</mat-dialog-content>

11
src/app/pages/admin/Followers/PersonalizedCoupons/personalized-coupons.component.scss

@ -0,0 +1,11 @@
.columns {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}
.column {
flex: 50%;
font-size: medium;
}

25
src/app/pages/admin/Followers/PersonalizedCoupons/personalized-coupons.component.spec.ts

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PersonalizedCouponsComponent } from './personalized-coupons.component';
describe('PersonalizedCouponsComponent', () => {
let component: PersonalizedCouponsComponent;
let fixture: ComponentFixture<PersonalizedCouponsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PersonalizedCouponsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PersonalizedCouponsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

50
src/app/pages/admin/Followers/PersonalizedCoupons/personalized-coupons.component.ts

@ -0,0 +1,50 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { TesoBusinessDetail } from 'app/models/businessModel';
import { CouponsType } from 'app/models/couponsModel';
import { ProductsModel } from 'app/models/productsModel';
import { TesoUserDetails } from 'app/models/userModel';
import { environment } from 'environments/environment';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { CouponsService } from '../../Coupons/coupons.service';
import { BusinessLookUpComponent } from '../../Products/BusinessLookUp/business-look-up.component';
import { ProductsService } from '../../Products/products.service';
@Component({
selector: 'app-personalized-coupons',
templateUrl: './personalized-coupons.component.html',
styleUrls: ['./personalized-coupons.component.scss']
})
export class PersonalizedCouponsComponent implements OnInit {
tesoGhana:boolean = false;
businessTargetted: TesoBusinessDetail = {};
private _unsubscribeAll: Subject<any> = new Subject<any>();
isLoading: boolean = false;
products: ProductsModel[] = [];
selectedProduct: any = {};
selectedType: string;
couponTypes: CouponsType[] = [];
constructor(private _productService: ProductsService,
@Inject(MAT_DIALOG_DATA) public data: { subscriber: TesoUserDetails },
public dialog: MatDialog,) { }
ngOnInit(): void {
this._productService.data$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.products = d;
});
}
imageLoader(path: string): string {
return environment.apiURL + `followeruserdp/${path}`;
}
lookupBusiness() {
const dialogRef = this.dialog.open(BusinessLookUpComponent, {
disableClose: true,
hasBackdrop: true,
});
dialogRef.afterClosed().subscribe((result) => {
this.businessTargetted = result;
});
}
}

16
src/app/pages/admin/Followers/followers.component.html

@ -13,26 +13,26 @@
<!-- Search --> <!-- Search -->
<mat-form-field class="teso-mat-dense teso-mat-no-subscript teso-mat-rounded min-w-64"> <mat-form-field class="teso-mat-dense teso-mat-no-subscript teso-mat-rounded min-w-64">
<mat-icon class="icon-size-5" matPrefix [svgIcon]="'heroicons_solid:search'"></mat-icon> <mat-icon class="icon-size-5" matPrefix [svgIcon]="'heroicons_solid:search'"></mat-icon>
<input matInput [autocomplete]="'off'" [placeholder]="'Look-up subscriber'"> <input matInput [autocomplete]="'off'" [placeholder]="'Look-up subscriber'" [formControl]="searchInputControl">
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 w-full min-w-0"> <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 w-full min-w-0">
<ng-container *ngFor="let member of data.teamMembers"> <ng-container *ngFor="let member of data">
<div class="flex flex-col flex-auto items-center bg-card shadow rounded-2xl overflow-hidden"> <div class="flex flex-col flex-auto items-center bg-card shadow rounded-2xl overflow-hidden">
<!-- Avatar & Info --> <!-- Avatar & Info -->
<div class="flex flex-col flex-auto w-full p-8 text-center"> <div class="flex flex-col flex-auto w-full p-8 text-center">
<div class="w-32 h-32 mx-auto rounded-full overflow-hidden"> <div class="w-32 h-32 mx-auto rounded-full overflow-hidden">
<img class="w-full h-full object-cover" [src]="member.avatar"> <img class="w-full h-full object-cover" [src]="imageLoader(member.thumbnailDp)">
</div> </div>
<div class="mt-6 font-medium">{{member.name}}</div> <div class="mt-6 font-medium">{{member.firstname}} {{member.surname}}</div>
<div class="text-secondary">{{member.title}}</div> <div class="text-secondary">{{member.username}}</div>
</div> </div>
<!-- Email & Phone --> <!-- Email & Phone -->
<div class="flex items-center w-full border-t divide-x"> <div class="flex items-center w-full border-t divide-x">
<a class="flex flex-auto items-center justify-center py-4 hover:bg-hover" [href]="'mailto:' + member.email"> <a class="flex flex-auto items-center justify-center py-4 hover:bg-hover" href="javascript:void(0)" (click)="personalizedCoupon(member)">
<mat-icon class="icon-size-5 text-hint" [svgIcon]="'heroicons_solid:credit-card'"> <mat-icon class="icon-size-5 text-hint" [svgIcon]="'heroicons_solid:credit-card'">
</mat-icon> </mat-icon>
<span class="ml-2">Send Coupon</span> <span class="ml-2">Send Coupon</span>
@ -41,10 +41,10 @@
<mat-icon class="icon-size-5 text-hint" [svgIcon]="'heroicons_solid:mail'"></mat-icon> <mat-icon class="icon-size-5 text-hint" [svgIcon]="'heroicons_solid:mail'"></mat-icon>
<span class="ml-2">Send Message</span> <span class="ml-2">Send Message</span>
</a> --> </a> -->
<a class="flex flex-auto items-center justify-center py-4 hover:bg-hover" [href]="'tel:' + member.phone"> <!-- <a class="flex flex-auto items-center justify-center py-4 hover:bg-hover" [href]="'tel:' + member.phone">
<mat-icon class="icon-size-5 text-hint" [svgIcon]="'heroicons_solid:user'"></mat-icon> <mat-icon class="icon-size-5 text-hint" [svgIcon]="'heroicons_solid:user'"></mat-icon>
<span class="ml-2">View Profile</span> <span class="ml-2">View Profile</span>
</a> </a> -->
</div> </div>
<!-- <div class="flex items-center w-full border-t divide-x"> <!-- <div class="flex items-center w-full border-t divide-x">
</div> --> </div> -->

129
src/app/pages/admin/Followers/followers.component.ts

@ -1,8 +1,14 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { TesoUserDetails } from 'app/models/userModel';
import { environment } from 'environments/environment';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { debounceTime, map, switchMap, takeUntil } from 'rxjs/operators';
import { FollowersService } from './followers.service'; import { FollowersService } from './followers.service';
import { PersonalizedCouponsComponent } from './PersonalizedCoupons/personalized-coupons.component';
@Component({ @Component({
selector: 'app-followers', selector: 'app-followers',
@ -11,100 +17,34 @@ import { FollowersService } from './followers.service';
}) })
export class FollowersComponent implements OnInit { export class FollowersComponent implements OnInit {
data: any; data: TesoUserDetails[] = [];
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
searchInputControl: FormControl = new FormControl();
constructor( constructor(
private _followersService: FollowersService, private _followersService: FollowersService,
private _router: Router private _router: Router,
public dialog: MatDialog, private confirmBoxEvokeService: ConfirmBoxEvokeService,
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
this.data = { this._followersService.data$
teamMembers : [ .pipe(takeUntil(this._unsubscribeAll))
{ .subscribe((data: TesoUserDetails[]) => {
id : '2bfa2be5-7688-48d5-b5ac-dc0d9ac97f14', this.data = data;
avatar: 'assets/images/avatars/female-10.jpg', });
name : 'Nadia Mcknight', this.searchInputControl.valueChanges
email : 'nadiamcknight@mail.com', .pipe(
phone : '+1-943-511-2203', takeUntil(this._unsubscribeAll),
title : 'Project Director' debounceTime(300),
}, switchMap((query) => {
{ // this.isLoading = true;
id : '77a4383b-b5a5-4943-bc46-04c3431d1566', return this.data = this._followersService.filterFollowers(query);
avatar: 'assets/images/avatars/male-19.jpg', }),
name : 'Best Blackburn', map(() => {
email : 'blackburn.best@beadzza.me', // this.isLoading = false;
phone : '+1-814-498-3701', })
title : 'Senior Developer' )
}, .subscribe();
{
id : '8bb0f597-673a-47ca-8c77-2f83219cb9af',
avatar: 'assets/images/avatars/male-14.jpg',
name : 'Duncan Carver',
email : 'duncancarver@mail.info',
phone : '+1-968-547-2111',
title : 'Senior Developer'
},
{
id : 'c318e31f-1d74-49c5-8dae-2bc5805e2fdb',
avatar: 'assets/images/avatars/male-01.jpg',
name : 'Martin Richards',
email : 'martinrichards@mail.biz',
phone : '+1-902-500-2668',
title : 'Junior Developer'
},
{
id : '0a8bc517-631a-4a93-aacc-000fa2e8294c',
avatar: 'assets/images/avatars/female-20.jpg',
name : 'Candice Munoz',
email : 'candicemunoz@mail.co.uk',
phone : '+1-838-562-2769',
title : 'Lead Designer'
},
{
id : 'a4c9945a-757b-40b0-8942-d20e0543cabd',
avatar: 'assets/images/avatars/female-01.jpg',
name : 'Vickie Mosley',
email : 'vickiemosley@mail.net',
phone : '+1-939-555-3054',
title : 'Designer'
},
{
id : 'b8258ccf-48b5-46a2-9c95-e0bd7580c645',
avatar: 'assets/images/avatars/female-02.jpg',
name : 'Tina Harris',
email : 'tinaharris@mail.ca',
phone : '+1-933-464-2431',
title : 'Designer'
},
{
id : 'f004ea79-98fc-436c-9ba5-6cfe32fe583d',
avatar: 'assets/images/avatars/male-02.jpg',
name : 'Holt Manning',
email : 'holtmanning@mail.org',
phone : '+1-822-531-2600',
title : 'Marketing Manager'
},
{
id : '8b69fe2d-d7cc-4a3d-983d-559173e37d37',
avatar: 'assets/images/avatars/female-03.jpg',
name : 'Misty Ramsey',
email : 'mistyramsey@mail.us',
phone : '+1-990-457-2106',
title : 'Consultant'
}
]
}
// this._followersService.data$
// .pipe(takeUntil(this._unsubscribeAll))
// .subscribe((data) => {
// // Store the data
// this.data = data;
// console.log(this.data)
// // Prepare the chart data
// });
} }
ngOnDestroy(): void { ngOnDestroy(): void {
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
@ -125,4 +65,15 @@ export class FollowersComponent implements OnInit {
trackByFn(index: number, item: any): any { trackByFn(index: number, item: any): any {
return item.id || index; return item.id || index;
} }
imageLoader(path: string): string {
return environment.apiURL + `followeruserdp/${path}`;
}
personalizedCoupon(user: TesoUserDetails) {
const dialogRef = this.dialog.open(PersonalizedCouponsComponent, {
disableClose: true,
hasBackdrop: true,
data: { subscriber: user },
});
}
} }

10
src/app/pages/admin/Followers/followers.module.ts

@ -16,12 +16,16 @@ import { TranslocoModule } from '@ngneat/transloco';
import { followersRoutes } from './followers.routing'; import { followersRoutes } from './followers.routing';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { PersonalizedCouponsComponent } from './PersonalizedCoupons/personalized-coupons.component';
import { MatDialogModule } from '@angular/material/dialog';
import { MatSelectModule } from '@angular/material/select';
@NgModule({ @NgModule({
declarations: [ declarations: [
FollowersComponent FollowersComponent,
PersonalizedCouponsComponent
], ],
imports: [ imports: [
RouterModule.forChild(followersRoutes), RouterModule.forChild(followersRoutes),
@ -32,12 +36,12 @@ import { MatInputModule } from '@angular/material/input';
MatFormFieldModule, MatFormFieldModule,
MatInputModule, MatInputModule,
MatMenuModule, MatMenuModule,
MatDialogModule,
MatProgressBarModule, MatProgressBarModule,
MatRippleModule, MatRippleModule,
// MatSidenavModule,
MatSortModule, MatSortModule,
MatTableModule, MatTableModule,
// MatTabsModule, MatSelectModule,
TranslocoModule, TranslocoModule,
SharedModule SharedModule
] ]

4
src/app/pages/admin/Followers/followers.resolvers.ts

@ -11,7 +11,7 @@ export class FollowersResolver implements Resolve<any>
/** /**
* Constructor * Constructor
*/ */
constructor(private _projectService: FollowersService) constructor(private _subscriberService: FollowersService)
{ {
} }
@ -27,6 +27,6 @@ export class FollowersResolver implements Resolve<any>
*/ */
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>
{ {
return ;//this._projectService.getData(); return this._subscriberService.getData();
} }
} }

96
src/app/pages/admin/Followers/followers.service.ts

@ -2,13 +2,15 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { TesoUserDetails } from 'app/models/userModel';
import { environment } from 'environments/environment';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class FollowersService export class FollowersService
{ {
private _data: BehaviorSubject<any> = new BehaviorSubject(null); private _data: BehaviorSubject<TesoUserDetails[]> = new BehaviorSubject(null);
/** /**
* Constructor * Constructor
@ -24,7 +26,7 @@ export class FollowersService
/** /**
* Getter for data * Getter for data
*/ */
get data$(): Observable<any> get data$(): Observable<TesoUserDetails[]>
{ {
return this._data.asObservable(); return this._data.asObservable();
} }
@ -36,89 +38,15 @@ export class FollowersService
/** /**
* Get data * Get data
*/ */
getData() getData():Observable<TesoUserDetails[]>
{ {
var project = { return this._httpClient.get(environment.apiURL + 'business/getsubscribers').pipe(
teamMembers : [ tap((response: any) => {
{ this._data.next(response);
id : '2bfa2be5-7688-48d5-b5ac-dc0d9ac97f14', })
avatar: 'assets/images/avatars/female-10.jpg', );
name : 'Nadia Mcknight',
email : 'nadiamcknight@mail.com',
phone : '+1-943-511-2203',
title : 'Project Director'
},
{
id : '77a4383b-b5a5-4943-bc46-04c3431d1566',
avatar: 'assets/images/avatars/male-19.jpg',
name : 'Best Blackburn',
email : 'blackburn.best@beadzza.me',
phone : '+1-814-498-3701',
title : 'Senior Developer'
},
{
id : '8bb0f597-673a-47ca-8c77-2f83219cb9af',
avatar: 'assets/images/avatars/male-14.jpg',
name : 'Duncan Carver',
email : 'duncancarver@mail.info',
phone : '+1-968-547-2111',
title : 'Senior Developer'
},
{
id : 'c318e31f-1d74-49c5-8dae-2bc5805e2fdb',
avatar: 'assets/images/avatars/male-01.jpg',
name : 'Martin Richards',
email : 'martinrichards@mail.biz',
phone : '+1-902-500-2668',
title : 'Junior Developer'
},
{
id : '0a8bc517-631a-4a93-aacc-000fa2e8294c',
avatar: 'assets/images/avatars/female-20.jpg',
name : 'Candice Munoz',
email : 'candicemunoz@mail.co.uk',
phone : '+1-838-562-2769',
title : 'Lead Designer'
},
{
id : 'a4c9945a-757b-40b0-8942-d20e0543cabd',
avatar: 'assets/images/avatars/female-01.jpg',
name : 'Vickie Mosley',
email : 'vickiemosley@mail.net',
phone : '+1-939-555-3054',
title : 'Designer'
},
{
id : 'b8258ccf-48b5-46a2-9c95-e0bd7580c645',
avatar: 'assets/images/avatars/female-02.jpg',
name : 'Tina Harris',
email : 'tinaharris@mail.ca',
phone : '+1-933-464-2431',
title : 'Designer'
},
{
id : 'f004ea79-98fc-436c-9ba5-6cfe32fe583d',
avatar: 'assets/images/avatars/male-02.jpg',
name : 'Holt Manning',
email : 'holtmanning@mail.org',
phone : '+1-822-531-2600',
title : 'Marketing Manager'
},
{
id : '8b69fe2d-d7cc-4a3d-983d-559173e37d37',
avatar: 'assets/images/avatars/female-03.jpg',
name : 'Misty Ramsey',
email : 'mistyramsey@mail.us',
phone : '+1-990-457-2106',
title : 'Consultant'
}
]
} }
// return this._httpClient.get('api/dashboards/project').pipe( filterFollowers(name: string): TesoUserDetails[] {
// tap((response: any) => { return this._data.getValue().filter(p => p.firstname.toLowerCase().includes(name) || p.surname.toLowerCase().includes(name) ||p.username.toLowerCase().includes(name));
// this._data.next(response);
// })
// );
return project
} }
} }

23
src/app/pages/admin/Monthly_Desires/ListDesires/list-desires.component.html

@ -5,7 +5,7 @@
<!-- Title and action buttons --> <!-- Title and action buttons -->
<div class="flex items-center justify-between w-full" style="margin-bottom: 20px;" *ngIf="!isScreenSmall"> <div class="flex items-center justify-between w-full" style="margin-bottom: 20px;" *ngIf="!isScreenSmall">
<div> <div>
<div class="text-3xl font-semibold tracking-tight leading-8">Products</div> <div class="text-3xl font-semibold tracking-tight leading-8">Monthly Desires</div>
<div class="font-medium tracking-tight text-secondary">Here is a list of all products in your Teso (850) <div class="font-medium tracking-tight text-secondary">Here is a list of all products in your Teso (850)
</div> </div>
</div> </div>
@ -21,7 +21,7 @@
</div> </div>
<div class="flex items-center justify-between w-full" style="margin-bottom: 20px;" *ngIf="isScreenSmall"> <div class="flex items-center justify-between w-full" style="margin-bottom: 20px;" *ngIf="isScreenSmall">
<div> <div>
<div class="text-3xl font-semibold tracking-tight leading-8">Products</div> <div class="text-3xl font-semibold tracking-tight leading-8">Monthly Desires</div>
<div class="font-medium tracking-tight text-secondary">(850) products <div class="font-medium tracking-tight text-secondary">(850) products
</div> </div>
</div> </div>
@ -41,18 +41,18 @@
</div> </div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 w-full min-w-0"> <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 w-full min-w-0">
<ng-container *ngFor="let product of data.teamproducts"> <ng-container *ngFor="let product of data.mostWishedProduct">
<!-- <div class="flex flex-col flex-auto items-center bg-card shadow rounded-2xl overflow-hidden"> -->
<div class="product"> <div class="product">
<div class="product-content"> <div class="product-content">
<div class="product-img"> <div class="product-img">
<img [src]="product.image" alt="product image" style="max-height:200px;"> <img [src]="imageLoader(product.productImage)" alt="product image" style="max-height:200px;" *ngIf="product.productImage != null">
<!-- <img [src]="imageLoader(product.productImage)" alt="product image" style="max-height:200px;" *ngIf="productImage == null"> -->
</div> </div>
<div class="product-btns"> <div class="product-btns">
<button type="button" class="btn-buy" (click)="generateCoupon(product)" *ngIf="product.title.includes('Developer')"> <button type="button" class="btn-buy" (click)="generateCoupon(product.productID)" *ngIf="availableP.includes(product.productID)">
Generate Coupons Generate Coupons
</button> </button>
<button type="button" class="btn-cart" (click)="createProduct()" *ngIf="!product.title.includes('Developer')"> <button type="button" class="btn-cart" (click)="createProduct()" *ngIf="!availableP.includes(product.productID)">
Add Product Add Product
</button> </button>
</div> </div>
@ -61,12 +61,11 @@
<div class="product-info"> <div class="product-info">
<div class="product-info-top"> <div class="product-info-top">
<h2 class="sm-title">@product.CategoryID</h2> <h2 class="sm-title">{{product.category}}</h2>
</div> </div>
<a href="#" class="product-name">@product.ProductName</a> <a href="javascript:void(0)" class="product-name" *ngIf="product.productName === null">{{product.productID}}</a>
<p class="product-price"><span></span></p> <a href="javascript:void(0)" class="product-name" *ngIf="product.productName != null">{{product.productName}}</a>
<p class="product-price"><span>&#8373;</span> @product.UnitPrice</p> <p class="product-price"><mat-icon style="color: red;" class="icon-size-5" [svgIcon]="'heroicons_solid:heart'"></mat-icon> {{product.desires}}</p>
<p>@product.ProductDesc</p>
</div> </div>
<!-- </div> --> <!-- </div> -->
</div> </div>

3
src/app/pages/admin/Monthly_Desires/ListDesires/list-desires.component.scss

@ -257,8 +257,7 @@ img {
} }
.product-price:first-of-type { .product-price:first-of-type {
text-decoration: line-through; color: #003445;
color: #40c9a2;
} }
.product-img img { .product-img img {

114
src/app/pages/admin/Monthly_Desires/ListDesires/list-desires.component.ts

@ -5,10 +5,14 @@ import { MatSort } from '@angular/material/sort';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup'; import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher'; import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { DesiredList, DesiredProduct } from 'app/models/productsModel';
import { environment } from 'environments/environment';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { AdminCouponDialogComponent } from '../../Products/AdminCouponDialog/admin-coupon-dialog.component'; import { AdminCouponDialogComponent } from '../../Products/AdminCouponDialog/admin-coupon-dialog.component';
import { CouponDialogComponent } from '../../Products/CouponDialog/coupon-dialog.component'; import { CouponDialogComponent } from '../../Products/CouponDialog/coupon-dialog.component';
import { ProductsService } from '../../Products/products.service';
import { DesiresService } from '../desires.service';
@Component({ @Component({
selector: 'app-list-desires', selector: 'app-list-desires',
@ -17,13 +21,15 @@ import { CouponDialogComponent } from '../../Products/CouponDialog/coupon-dialog
}) })
export class ListDesiresComponent implements OnInit { export class ListDesiresComponent implements OnInit {
data: { teamproducts: { id: string; image: string; name: string; email: string; phone: string; title: string; }[]; }; data: DesiredList;
@ViewChild(MatPaginator) private _paginator: MatPaginator; @ViewChild(MatPaginator) private _paginator: MatPaginator;
@ViewChild(MatSort) private _sort: MatSort; @ViewChild(MatSort) private _sort: MatSort;
isScreenSmall: boolean; isScreenSmall: boolean;
availableP:any[]=[];
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(private router: Router, private _tesoMediaWatcherService: tesoMediaWatcherService, constructor(private router: Router, private _tesoMediaWatcherService: tesoMediaWatcherService,
public dialog: MatDialog, private confirmBoxEvokeService: ConfirmBoxEvokeService) { } public dialog: MatDialog, private confirmBoxEvokeService: ConfirmBoxEvokeService,
private _desireService: DesiresService,private _productService: ProductsService) { }
ngOnDestroy(): void { ngOnDestroy(): void {
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
@ -40,115 +46,37 @@ export class ListDesiresComponent implements OnInit {
// Check if the screen is small // Check if the screen is small
this.isScreenSmall = !matchingAliases.includes('md'); this.isScreenSmall = !matchingAliases.includes('md');
}); });
this.data = {
teamproducts: [ this._desireService.data$.pipe(takeUntil(this._unsubscribeAll)).subscribe((data: DesiredList) => {
{ this.data = data;
id: '2bfa2be5-7688-48d5-b5ac-dc0d9ac97f14', this.availableP = data.availableProducts.map(p => p.productID);
image: 'assets/images/brands/bagset.png', });
name: 'Nadia Mcknight',
email: 'nadiamcknight@mail.com',
phone: '+1-943-511-2203',
title: 'Project Director'
},
{
id: '77a4383b-b5a5-4943-bc46-04c3431d1566',
image: 'assets/images/brands/bagset.png',
name: 'Best Blackburn',
email: 'blackburn.best@beadzza.me',
phone: '+1-814-498-3701',
title: 'Senior Developer'
},
{
id: '8bb0f597-673a-47ca-8c77-2f83219cb9af',
image: 'assets/images/brands/bagset.png',
name: 'Duncan Carver',
email: 'duncancarver@mail.info',
phone: '+1-968-547-2111',
title: 'Senior Developer'
},
{
id: 'c318e31f-1d74-49c5-8dae-2bc5805e2fdb',
image: 'assets/images/brands/bagset.png',
name: 'Martin Richards',
email: 'martinrichards@mail.biz',
phone: '+1-902-500-2668',
title: 'Junior Developer'
},
{
id: '0a8bc517-631a-4a93-aacc-000fa2e8294c',
image: 'assets/images/brands/bagset.png',
name: 'Candice Munoz',
email: 'candicemunoz@mail.co.uk',
phone: '+1-838-562-2769',
title: 'Lead Designer'
},
{
id: 'a4c9945a-757b-40b0-8942-d20e0543cabd',
image: 'assets/images/brands/bagset.png',
name: 'Vickie Mosley',
email: 'vickiemosley@mail.net',
phone: '+1-939-555-3054',
title: 'Designer'
},
{
id: 'b8258ccf-48b5-46a2-9c95-e0bd7580c645',
image: 'assets/images/brands/bagset.png',
name: 'Tina Harris',
email: 'tinaharris@mail.ca',
phone: '+1-933-464-2431',
title: 'Designer'
},
{
id: 'f004ea79-98fc-436c-9ba5-6cfe32fe583d',
image: 'assets/images/brands/bagset.png',
name: 'Holt Manning',
email: 'holtmanning@mail.org',
phone: '+1-822-531-2600',
title: 'Marketing Manager'
},
{
id: '8b69fe2d-d7cc-4a3d-983d-559173e37d37',
image: 'assets/images/brands/bagset.png',
name: 'Misty Ramsey',
email: 'mistyramsey@mail.us',
phone: '+1-990-457-2106',
title: 'Consultant'
}
]
}
} }
createProduct() { createProduct() {
this.router.navigate(['products/create-product']); this.router.navigate(['products/create-product']);
} }
editProduct(productID: any) {
this.router.navigate(['products/' + productID]);
}
generateCoupon(product: any) { generateCoupon(product: any) {
const selectedproduct = this._productService.getProduct(product);
if (!true) { if (!true) {
const dialogRef = this.dialog.open(CouponDialogComponent, { const dialogRef = this.dialog.open(CouponDialogComponent, {
disableClose: true, disableClose: true,
hasBackdrop: true, hasBackdrop: true,
data: { product: product }, data: { product: selectedproduct },
}); });
} else { } else {
const dialogRef = this.dialog.open(AdminCouponDialogComponent, { const dialogRef = this.dialog.open(AdminCouponDialogComponent, {
disableClose: true, disableClose: true,
hasBackdrop: true, hasBackdrop: true,
data: { product: product }, data: { product: selectedproduct },
}); });
} }
} }
deleteProduct(product: any) { imageLoader(path: string): string {
this.confirmBoxEvokeService.danger("Delete Product", return environment.apiURL + `imagingproducts/${path}`;
"Are you sure you would like to delete this product from your inventory ?",
"Yes", "No")
.subscribe((response: any) => {
console.log(response)
if (response.clickedButtonID === "yes") {
this.data.teamproducts = this.data.teamproducts.filter((e) => e == product);
}
});
} }
} }

32
src/app/pages/admin/Monthly_Desires/desires.resolvers.ts

@ -0,0 +1,32 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { DesiresService } from './desires.service';
@Injectable({
providedIn: 'root'
})
export class DesiresResolver implements Resolve<any>
{
/**
* Constructor
*/
constructor(private _desiresService: DesiresService)
{
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Resolver
*
* @param route
* @param state
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>
{
return this._desiresService.getData();
}
}

7
src/app/pages/admin/Monthly_Desires/desires.routing.ts

@ -2,15 +2,16 @@ import { Route } from '@angular/router';
import { EditProductComponent } from '../Products/EditProduct/edit-product.component'; import { EditProductComponent } from '../Products/EditProduct/edit-product.component';
import { NewProductComponent } from '../Products/NewProduct/new-product.component'; import { NewProductComponent } from '../Products/NewProduct/new-product.component';
import { DesiresComponent } from './desires.component'; import { DesiresComponent } from './desires.component';
import { DesiresResolver } from './desires.resolvers';
import { ListDesiresComponent } from './ListDesires/list-desires.component'; import { ListDesiresComponent } from './ListDesires/list-desires.component';
export const desiresRoutes: Route[] = [ export const desiresRoutes: Route[] = [
{ {
path : '', path : '',
component: DesiresComponent, component: DesiresComponent,
// resolve : { resolve : {
// data: FollowersResolver data: DesiresResolver
// } },
children : [ children : [
{ {
path : '', path : '',

49
src/app/pages/admin/Monthly_Desires/desires.service.ts

@ -0,0 +1,49 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { analytics as analyticsData } from 'app/mock-api/dashboards/analytics/data';
import { environment } from 'environments/environment';
import { DesiredList, DesiredProduct, ProductCategory, ProductsModel } from 'app/models/productsModel';
@Injectable({
providedIn: 'root'
})
export class DesiresService {
private _data: BehaviorSubject<any> = new BehaviorSubject(null);
/**
* Constructor
*/
constructor(private _httpClient: HttpClient) {
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Getter for data
*/
get data$(): Observable<DesiredList> {
return this._data.asObservable();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Get data
*/
getData(): Observable<DesiredList> {
return this._httpClient.get(environment.apiURL + `desires/pull-monthly`).pipe(
tap((response: DesiredList) => {
this._data.next(response);
})
);
}
}

144
src/app/pages/admin/Products/AdminCouponDialog/admin-coupon-dialog.component.html

@ -1,12 +1,12 @@
<div class="row" style="display: flex;justify-content:space-between"> <div class="row" style="display: flex;justify-content:space-between">
<div class="row" style="display: flex;justify-content:center;width: 100%;"> <div class="row" style="display: flex;justify-content:center;width: 100%;">
<h2 mat-dialog-title style="text-align: center;">Coupons for {{data.product.name}}</h2> <h2 mat-dialog-title style="text-align: center;">Coupons for {{data.product.productName}}</h2>
</div> </div>
<!-- <button mat-button mat-dialog-close>Cancel</button> --> <!-- <button mat-button mat-dialog-close>Cancel</button> -->
<button mat-button [mat-dialog-close]="true" cdkFocusInitial>x</button> <button mat-button [mat-dialog-close]="true" cdkFocusInitial>x</button>
</div> </div>
<mat-dialog-content class="mat-typography"> <mat-dialog-content class="mat-typography">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6"> <div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6" *ngIf="!isScreenSmall">
<div class="columns" style="display: flex;align-items: center"> <div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline"> <div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
@ -15,7 +15,7 @@
</div> </div>
<div class="column"> <div class="column">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
Target Name {{businessTargetted.businessName}}
</mat-label> </mat-label>
<button style="background-color: blue;color:white;margin-right:20px;" mat-button (click)="lookupBusiness()">Search</button> <button style="background-color: blue;color:white;margin-right:20px;" mat-button (click)="lookupBusiness()">Search</button>
</div> </div>
@ -24,12 +24,12 @@
<div class="columns" style="display: flex;align-items: center"> <div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline"> <div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
<strong class="tileHead">Target Name </strong> <strong class="tileHead">Product Name </strong>
</mat-label> </mat-label>
</div> </div>
<div class="column"> <div class="column">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
Target Name {{data.product.productName}}
</mat-label> </mat-label>
</div> </div>
</div> </div>
@ -41,7 +41,7 @@
</div> </div>
<div class="column"> <div class="column">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
GH¢200 {{data.product.unitPrice | currency:"GHS "}}
</mat-label> </mat-label>
</div> </div>
</div> </div>
@ -53,12 +53,12 @@
</div> </div>
<div class="column"> <div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;"> <mat-form-field appearance="fill" style="min-width: 200px;">
<mat-select required> <mat-select [(ngModel)]="selectedType">
<mat-option value="low">Freebie</mat-option> <mat-option *ngFor="let type of couponTypes" [value]="type">
<mat-option value="standard">Discount</mat-option> {{type.typeName}}
<mat-option value="high">Proximity Freebie</mat-option> </mat-option>
<mat-option value="high">Proximity Discount</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>
@ -126,4 +126,126 @@
</div> </div>
</div> </div>
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6" *ngIf="isScreenSmall">
<div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Target Business </strong>
</mat-label>
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
{{businessTargetted.businessName}}
</mat-label>
<button style="background-color: blue;color:white;margin-right:20px;" mat-button (click)="lookupBusiness()">Search</button>
</div>
</div>
<hr/>
<div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Product Name </strong>
</mat-label>
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
{{data.product.productName}}
</mat-label>
</div>
</div>
<div class="columns" style="display: flex;align-items: center;margin-top: 15px;">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Original Price </strong>
</mat-label>
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
{{data.product.unitPrice | currency:"GHS "}}
</mat-label>
</div>
</div>
<div class="columns" style="display: flex;align-items: center;margin-top: 15px;">
<div class="column">
<mat-form-field appearance="outline" style="min-width: 200px;">
<mat-label style="margin-right: 20px;">
<strong class="tileHead">Coupon Type</strong>
</mat-label>
<mat-select [(ngModel)]="selectedType">
<mat-option *ngFor="let type of couponTypes" [value]="type">
{{type.typeName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="columns" style="display: flex;align-items: center">
<div class="column">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Coupon Condition</strong>
</mat-label>
</div>
<div class="column">
<textarea name="" id="" cols="30" rows="5" style="border: 1px solid #cbd5e1;"></textarea>
</div>
</div>
<div class="columns" style="display: flex;align-items: center">
<div class="column">
<mat-form-field appearance="outline" style="min-width: 200px;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Number Of Coupons</strong>
</mat-label>
<input matInput style="margin-left:10px;" type="number">
</mat-form-field>
</div>
</div>
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Percentage Off </strong>
</mat-label>
<div style="display: flex;align-items: baseline">
<mat-form-field appearance="outline" style="margin-right:10px;">
<span matPrefix>From &nbsp;</span>
<input matInput type="number">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline">
<mat-form-field appearance="outline" style="margin-left:10px;">
<span matPrefix>To &nbsp;</span>
<input matInput type="number">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Coupon Worth </strong>
</mat-label>
<mat-label>
From GH¢200 To GH¢800
</mat-label>
</div>
<div style="display: flex;align-items: baseline;margin-top: 20px;">
<mat-form-field appearance="outline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Date Of Expiration </strong>
</mat-label>
<input matInput style="margin-left:10px;" type="date">
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline;">
<mat-form-field appearance="outline">
<span matPrefix>Time &nbsp;</span>
<input matInput type="time">
</mat-form-field>
</div>
<div class="row" style="display: flex;justify-content:space-around;width:100%;">
<button style="background-color: blue;color:white;margin-right:20px;" mat-button>Generate Coupons</button>
</div>
</div>
</mat-dialog-content> </mat-dialog-content>

44
src/app/pages/admin/Products/AdminCouponDialog/admin-coupon-dialog.component.ts

@ -1,17 +1,45 @@
import { Component, Inject, OnInit } from '@angular/core'; import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { TesoBusinessDetail } from 'app/models/businessModel';
import { CouponsType } from 'app/models/couponsModel';
import { ProductsModel } from 'app/models/productsModel';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { CouponsService } from '../../Coupons/coupons.service';
import { BusinessLookUpComponent } from '../BusinessLookUp/business-look-up.component'; import { BusinessLookUpComponent } from '../BusinessLookUp/business-look-up.component';
import { ProductsService } from '../products.service';
@Component({ @Component({
selector: 'admin-coupon-dialog', selector: 'admin-coupon-dialog',
templateUrl: './admin-coupon-dialog.component.html', templateUrl: './admin-coupon-dialog.component.html',
styleUrls: ['./admin-coupon-dialog.component.scss'] styleUrls: ['./admin-coupon-dialog.component.scss']
}) })
export class AdminCouponDialogComponent implements OnInit { export class AdminCouponDialogComponent implements OnInit,OnDestroy {
businessTargetted:TesoBusinessDetail={};
constructor(@Inject(MAT_DIALOG_DATA) public data: { product: any }, public dialog: MatDialog,) { } private _unsubscribeAll: Subject<any> = new Subject<any>();
isLoading:boolean=false;
selectedProduct:any={};
selectedType:any={};
couponTypes:CouponsType[]=[];
isScreenSmall: boolean;
constructor(
private _couponService: CouponsService,@Inject(MAT_DIALOG_DATA) public data: { product: ProductsModel },
public dialog: MatDialog,private _tesoMediaWatcherService: tesoMediaWatcherService,
private confirmBoxEvokeService: ConfirmBoxEvokeService,) { }
ngOnInit(): void { ngOnInit(): void {
this._couponService.categories$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.couponTypes = d;
});
this._tesoMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({ matchingAliases }) => {
// Check if the screen is small
this.isScreenSmall = !matchingAliases.includes('md');
});
} }
lookupBusiness() { lookupBusiness() {
const dialogRef = this.dialog.open(BusinessLookUpComponent, { const dialogRef = this.dialog.open(BusinessLookUpComponent, {
@ -19,5 +47,13 @@ export class AdminCouponDialogComponent implements OnInit {
hasBackdrop: true, hasBackdrop: true,
}); });
dialogRef.afterClosed().subscribe((result) => {
this.businessTargetted = result;
});
}
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
} }
} }

22
src/app/pages/admin/Products/BusinessLookUp/business-look-up.component.html

@ -6,7 +6,7 @@
<!-- Search --> <!-- Search -->
<mat-form-field class="teso-mat-dense teso-mat-no-subscript teso-mat-rounded min-w-64"> <mat-form-field class="teso-mat-dense teso-mat-no-subscript teso-mat-rounded min-w-64">
<mat-icon class="icon-size-5" matPrefix [svgIcon]="'heroicons_solid:search'"></mat-icon> <mat-icon class="icon-size-5" matPrefix [svgIcon]="'heroicons_solid:search'"></mat-icon>
<input matInput [autocomplete]="'off'" [placeholder]="'Enter Business Name or email here'"> <input matInput [autocomplete]="'off'" [placeholder]="'Enter business Name or email here'">
</mat-form-field> </mat-form-field>
<button class="ml-3" mat-flat-button [color]="'primary'" style="margin-left: 10px;"> <button class="ml-3" mat-flat-button [color]="'primary'" style="margin-left: 10px;">
<mat-icon class="icon-size-5" [svgIcon]="'heroicons_solid:search'"></mat-icon> <mat-icon class="icon-size-5" [svgIcon]="'heroicons_solid:search'"></mat-icon>
@ -16,52 +16,52 @@
<div class="flex flex-shrink-0 items-center justify-center" style="margin-top: 10px !important;"> <div class="flex flex-shrink-0 items-center justify-center" style="margin-top: 10px !important;">
<a href="javascript:void(0)" <a href="javascript:void(0)"
style="color: #1890ff;text-decoration: none;background-color: transparent;outline: 0;cursor: pointer;">Target style="color: #1890ff;text-decoration: none;background-color: transparent;outline: 0;cursor: pointer;">Target
Business not registered on teso ?</a> business not registered on teso ?</a>
</div> </div>
<table class="w-full bg-transparent" mat-table matSort [dataSource]="typesDataSource" [trackBy]="trackByFn" <table class="w-full bg-transparent" mat-table matSort [dataSource]="typesDataSource" [trackBy]="trackByFn"
#typesTable> #typesTable>
<!-- Transaction ID --> <!-- Transaction ID -->
<ng-container matColumnDef="transactionId"> <ng-container matColumnDef="businessId">
<th mat-header-cell mat-sort-header *matHeaderCellDef> <th mat-header-cell mat-sort-header *matHeaderCellDef>
Business Business
</th> </th>
<td mat-cell *matCellDef="let transaction" style="display: flex;padding:10px;"> <td mat-cell *matCellDef="let transaction" style="display: flex;padding:10px;">
<div id="circle"> <div id="circle">
<div> <div>
<span>{{transaction.name.substring(0,2).toUpperCase()}}</span> <span>{{transaction.businessName.substring(0,2).toUpperCase()}}</span>
</div> </div>
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
<span class="pr-6 font-medium text-sm text-secondary whitespace-nowrap"> <span class="pr-6 font-medium text-sm text-secondary whitespace-nowrap">
{{transaction.name}} {{transaction.businessName}}
</span> </span>
<span class="pr-6 font-small text-sm text-secondary whitespace-nowrap"> <span class="pr-6 font-small text-sm text-secondary whitespace-nowrap">
{{transaction.email}} {{transaction.businessEmail}}
</span> </span>
</div> </div>
</td> </td>
</ng-container> </ng-container>
<!-- Request Name --> <!-- Request Name -->
<ng-container matColumnDef="name"> <ng-container matColumnDef="businessContact">
<th mat-header-cell mat-sort-header *matHeaderCellDef> <th mat-header-cell mat-sort-header *matHeaderCellDef>
Contact Contact
</th> </th>
<td mat-cell *matCellDef="let transaction"> <td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-wrap"> <span class="pr-6 whitespace-wrap">
{{transaction.phone}} {{transaction.businessContact}}
</span> </span>
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="members"> <ng-container matColumnDef="businessDigitalAddress">
<th mat-header-cell mat-sort-header *matHeaderCellDef> <th mat-header-cell mat-sort-header *matHeaderCellDef>
Address Address
</th> </th>
<td mat-cell *matCellDef="let transaction"> <td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-wrap"> <span class="pr-6 whitespace-wrap">
AK-151-0000 {{transaction.businessDigitalAddress}}
</span> </span>
</td> </td>
</ng-container> </ng-container>
@ -72,7 +72,7 @@
</th> </th>
<td mat-cell *matCellDef="let transaction"> <td mat-cell *matCellDef="let transaction">
<div style="display: flex;"> <div style="display: flex;">
<button class="sm:inline-flex" mat-flat-button [color]="'primary'"> <button class="sm:inline-flex" mat-flat-button [color]="'primary'" (click)="selectBusiness(transaction)">
Select Select
</button> </button>
</div> </div>

115
src/app/pages/admin/Products/BusinessLookUp/business-look-up.component.ts

@ -1,101 +1,12 @@
import { Component, OnInit, ViewChild } from '@angular/core'; import { Component, Inject, OnInit, ViewChild } from '@angular/core';
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatSort } from '@angular/material/sort'; import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { TesoBusinessDetail } from 'app/models/businessModel';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
export const finance = { import { ProfileService } from '../../Profile/profile.service';
types: [
{
id: '1b6fd296-bc6a-4d45-bf4f-e45519a58cf5',
transactionId: '528651571NT',
name: 'Morgan Page',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-10-07T22:22:37.274Z'
},
{
id: '2dec6074-98bd-4623-9526-6480e4776569',
transactionId: '421436904YT',
name: 'Nita Hebert',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-12-18T14:51:24.461Z'
},
{
id: 'ae7c065f-4197-4021-a799-7a221822ad1d',
transactionId: '685377421YT',
name: 'Marsha Chambers',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-12-25T17:52:14.304Z'
},
{
id: '0c43dd40-74f6-49d5-848a-57a4a45772ab',
transactionId: '884960091RT',
name: 'Charmaine Jackson',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-11-29T06:32:16.111Z'
},
{
id: 'e5c9f0ed-a64c-4bfe-a113-29f80b4e162c',
transactionId: '361402213NT',
name: 'Maura Carey',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-11-24T12:13:23.064Z'
}, {
id: '1b6fd296-bc6a-4d45-bf4f-e45519a58cf5',
transactionId: '528651571NT',
name: 'Morgan Page',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-10-07T22:22:37.274Z'
},
{
id: '2dec6074-98bd-4623-9526-6480e4776569',
transactionId: '421436904YT',
name: 'Nita Hebert',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-12-18T14:51:24.461Z'
},
{
id: 'ae7c065f-4197-4021-a799-7a221822ad1d',
transactionId: '685377421YT',
name: 'Marsha Chambers',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-12-25T17:52:14.304Z'
},
{
id: '0c43dd40-74f6-49d5-848a-57a4a45772ab',
transactionId: '884960091RT',
name: 'Charmaine Jackson',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-11-29T06:32:16.111Z'
},
{
id: 'e5c9f0ed-a64c-4bfe-a113-29f80b4e162c',
transactionId: '361402213NT',
name: 'Maura Carey',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-11-24T12:13:23.064Z'
},
]
};
@Component({ @Component({
selector: 'app-business-look-up', selector: 'app-business-look-up',
templateUrl: './business-look-up.component.html', templateUrl: './business-look-up.component.html',
@ -105,17 +16,23 @@ export class BusinessLookUpComponent implements OnInit {
@ViewChild('typesTable', { read: MatSort }) typesTableMatSort: MatSort; @ViewChild('typesTable', { read: MatSort }) typesTableMatSort: MatSort;
searchInputControl: FormControl = new FormControl(); searchInputControl: FormControl = new FormControl();
data: any; data: any;
typesDataSource: MatTableDataSource<any> = new MatTableDataSource(); typesDataSource: MatTableDataSource<TesoBusinessDetail> = new MatTableDataSource();
typesTableColumns: string[] = ['transactionId', 'name', 'members', 'actions']; typesTableColumns: string[] = ['businessId', 'businessContact', 'businessDigitalAddress', 'actions'];
htmlRequest: string; htmlRequest: string;
transferType: string; transferType: string;
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor() { } constructor(private _businessService: ProfileService, public dialogRef: MatDialogRef<BusinessLookUpComponent>,
) { }
ngOnInit(): void { ngOnInit(): void {
this.typesDataSource.data = finance.types; this._businessService.getData().subscribe((data) => {
this.typesDataSource.data = data;
});
} }
trackByFn(index: number, item: any): any { trackByFn(index: number, item: any): any {
return item.id || index; return item.businessId || index;
}
selectBusiness(business: TesoBusinessDetail) {
this.dialogRef.close(business);
} }
} }

128
src/app/pages/admin/Products/CouponDialog/coupon-dialog.component.html

@ -1,21 +1,21 @@
<div class="row" style="display: flex;justify-content:space-between"> <div class="row" style="display: flex;justify-content:space-between">
<div class="row" style="display: flex;justify-content:center;width: 100%;"> <div class="row" style="display: flex;justify-content:center;width: 100%;">
<h2 mat-dialog-title style="text-align: center;">Coupons for {{data.product.name}}</h2> <h2 mat-dialog-title style="text-align: center;">Coupons for {{data.product.productName}}</h2>
</div> </div>
<!-- <button mat-button mat-dialog-close>Cancel</button> --> <!-- <button mat-button mat-dialog-close>Cancel</button> -->
<button mat-button [mat-dialog-close]="true" cdkFocusInitial>x</button> <button mat-button [mat-dialog-close]="true" cdkFocusInitial>x</button>
</div> </div>
<mat-dialog-content class="mat-typography"> <mat-dialog-content class="mat-typography">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6"> <div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6" *ngIf="!isScreenSmall">
<div class="columns" style="display: flex;align-items: center"> <div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline"> <div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
<strong class="tileHead">Target Name </strong> <strong class="tileHead">Product Name </strong>
</mat-label> </mat-label>
</div> </div>
<div class="column"> <div class="column">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
Target Name {{data.product.productName}}
</mat-label> </mat-label>
</div> </div>
</div> </div>
@ -27,7 +27,7 @@
</div> </div>
<div class="column"> <div class="column">
<mat-label style="margin-right: 48px;"> <mat-label style="margin-right: 48px;">
GH¢200 {{data.product.unitPrice | currency:"GHS "}}
</mat-label> </mat-label>
</div> </div>
</div> </div>
@ -39,12 +39,12 @@
</div> </div>
<div class="column"> <div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;"> <mat-form-field appearance="fill" style="min-width: 200px;">
<mat-select required> <mat-select [(ngModel)]="selectedType">
<mat-option value="low">Freebie</mat-option> <mat-option *ngFor="let type of couponTypes" [value]="type">
<mat-option value="standard">Discount</mat-option> {{type.typeName}}
<mat-option value="high">Proximity Freebie</mat-option> </mat-option>
<mat-option value="high">Proximity Discount</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>
@ -112,4 +112,112 @@
</div> </div>
</div> </div>
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6" *ngIf="isScreenSmall">
<div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Product Name </strong>
</mat-label>
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
{{data.product.productName}}
</mat-label>
</div>
</div>
<div class="columns" style="display: flex;align-items: center;margin-top: 15px;">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Original Price </strong>
</mat-label>
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
{{data.product.unitPrice | currency:"GHS "}}
</mat-label>
</div>
</div>
<div class="columns" style="display: flex;align-items: center;margin-top: 15px;">
<div class="column">
<mat-form-field appearance="outline" style="min-width: 200px;">
<mat-label style="margin-right: 20px;">
<strong class="tileHead">Coupon Type</strong>
</mat-label>
<mat-select [(ngModel)]="selectedType">
<mat-option *ngFor="let type of couponTypes" [value]="type">
{{type.typeName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="columns" style="display: flex;align-items: center">
<div class="column">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Coupon Condition</strong>
</mat-label>
</div>
<div class="column">
<textarea name="" id="" cols="30" rows="5" style="border: 1px solid #cbd5e1;"></textarea>
</div>
</div>
<div class="columns" style="display: flex;align-items: center">
<div class="column">
<mat-form-field appearance="outline" style="min-width: 200px;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Number Of Coupons</strong>
</mat-label>
<input matInput style="margin-left:10px;" type="number">
</mat-form-field>
</div>
</div>
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Percentage Off </strong>
</mat-label>
<div style="display: flex;align-items: baseline">
<mat-form-field appearance="outline" style="margin-right:10px;">
<span matPrefix>From &nbsp;</span>
<input matInput type="number">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline">
<mat-form-field appearance="outline" style="margin-left:10px;">
<span matPrefix>To &nbsp;</span>
<input matInput type="number">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Coupon Worth </strong>
</mat-label>
<mat-label>
From GH¢200 To GH¢800
</mat-label>
</div>
<div style="display: flex;align-items: baseline;margin-top: 20px;">
<mat-form-field appearance="outline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Date Of Expiration </strong>
</mat-label>
<input matInput style="margin-left:10px;" type="date">
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline;">
<mat-form-field appearance="outline">
<span matPrefix>Time &nbsp;</span>
<input matInput type="time">
</mat-form-field>
</div>
<div class="row" style="display: flex;justify-content:space-around;width:100%;">
<button style="background-color: blue;color:white;margin-right:20px;" mat-button>Generate Coupons</button>
</div>
</div>
</mat-dialog-content> </mat-dialog-content>

43
src/app/pages/admin/Products/CouponDialog/coupon-dialog.component.ts

@ -1,16 +1,47 @@
import { Component, Inject, OnInit } from '@angular/core'; import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { CouponsType } from 'app/models/couponsModel';
import { ProductsModel } from 'app/models/productsModel';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { CouponsService } from '../../Coupons/coupons.service';
import { ProductsService } from '../products.service';
@Component({ @Component({
selector: 'app-coupon-dialog', selector: 'app-coupon-dialog',
templateUrl: './coupon-dialog.component.html', templateUrl: './coupon-dialog.component.html',
styleUrls: ['./coupon-dialog.component.scss'] styleUrls: ['./coupon-dialog.component.scss']
}) })
export class CouponDialogComponent implements OnInit { export class CouponDialogComponent implements OnInit,OnDestroy {
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(@Inject(MAT_DIALOG_DATA) public data: { product: any },) { } isLoading:boolean=false;
selectedProduct:any={};
selectedType:any={};
couponTypes:CouponsType[]=[];
isScreenSmall: boolean;
constructor(
private _couponService: CouponsService,@Inject(MAT_DIALOG_DATA) public data: { product: ProductsModel },
public dialog: MatDialog,private _productService: ProductsService,private _tesoMediaWatcherService: tesoMediaWatcherService,
private confirmBoxEvokeService: ConfirmBoxEvokeService,) { }
ngOnInit(): void { ngOnInit(): void {
} this._couponService.categories$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.couponTypes = d;
});
this._tesoMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({ matchingAliases }) => {
// Check if the screen is small
this.isScreenSmall = !matchingAliases.includes('md');
});
}
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
} }

201
src/app/pages/admin/Products/Details/details-product.component.html

@ -0,0 +1,201 @@
<div class="absolute inset-0 flex flex-col min-w-0 " *ngIf="!isScreenSmall">
<mat-drawer-container class="flex-auto h-full">
<!-- Drawer content -->
<mat-drawer-content class="flex flex-col">
<!-- Header -->
<div
class="flex flex-0 items-center py-2 pl-4 pr-6 sm:py-4 md:pl-6 md:pr-8 border-b lg:border-b-0 bg-card dark:bg-transparent">
<!-- Title & Actions -->
<button mat-icon-button [routerLink]="['../../']">
<mat-icon [svgIcon]="'heroicons_outline:arrow-sm-left'"></mat-icon>
</button>
<h2 class="ml-2.5 text-md sm:text-xl font-medium tracking-tight truncate">
Product # {{product.productID}}
</h2>
</div>
<div
class=" flex flex-0 items-center py-2 pl-4 pr-6 sm:py-4 md:pl-6 md:pr-8 border-b lg:border-b-0 bg-card dark:bg-transparent">
<div class="flex-auto" cdkScrollable>
<div class="min-h-180 border-2 border-dashed border-gray-300 rounded-2xl py-2 pl-4 pr-6 sm:py-4 md:pl-6 md:pr-8 mx-auto"
style="max-width: 780px;">
<mat-tab-group mat-align-tabs="center">
<mat-tab label="Product Information">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6"
style="overflow-y: hidden !important;">
<div style="display: flex;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Product Name </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<input disabled matInput style="margin-left:10px;" type="text"
value="{{product.productName}}">
</mat-form-field>
</div>
<div style="display:flex;">
<mat-label style="margin-right: 20px;">
<strong class="tileHead">Product Category</strong>
</mat-label>
<mat-label style="width: 350px;">
<span class="tileHead">{{product.categoryID}}</span>
</mat-label>
</div>
<div style="display: flex;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Original Price </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<span matPrefix>GH¢ &nbsp;</span>
<input disabled matInput style="margin-left:10px;" type="number"
value={{product.unitPrice}}>
</mat-form-field>
</div>
<div class="columns" style="display: flex;justify-content:space-around;">
<div class="column">
<div>
<strong class="tileHead">Product Description :</strong>
</div>
</div>
</div>
<div class="column">
<quill-editor [modules]="quillConfig" [(ngModel)]=product.productDesc
placeholder="Further details">
</quill-editor>
</div>
</div>
</mat-tab>
<mat-tab label="Images">
<div class="py-2 pl-4 pr-6 sm:py-4 md:pl-6 md:pr-8"
style="min-height: 550px !important;">
<div class="flex justify-between">
<div>
<div class="font-medium tracking-tight text-secondary">Add up to 10 high
quality images of the product
</div>
<div class="font-medium tracking-tight text-secondary">
{{productImages.length}} out of
<strong>10</strong>
</div>
</div>
</div>
<div class="imageList">
<div class="imageHolder" *ngFor="let images of product.images">
<img src="{{images.imageSRC}}" class="productImage">
<button class="deleteelement" style="border:none;"
(click)="removeImage(images)">
<mat-icon [svgIcon]="'heroicons_outline:trash'" style="color: red;">
</mat-icon>
</button>
</div>
</div>
</div>
</mat-tab>
</mat-tab-group>
</div>
</div>
</div>
</mat-drawer-content>
</mat-drawer-container>
</div>
<div class="absolute inset-0 flex flex-col min-w-0 " *ngIf="isScreenSmall">
<mat-drawer-container class="flex-auto h-full">
<!-- Drawer content -->
<mat-drawer-content class="flex flex-col">
<!-- Header -->
<div
class="flex flex-0 items-center py-2 pl-4 pr-6 sm:py-4 md:pl-6 md:pr-8 border-b lg:border-b-0 bg-card dark:bg-transparent">
<!-- Title & Actions -->
<button mat-icon-button [routerLink]="['../../']">
<mat-icon [svgIcon]="'heroicons_outline:arrow-sm-left'"></mat-icon>
</button>
<h2 class="ml-2.5 text-md sm:text-xl font-medium tracking-tight truncate">
Product # {{product.productID}}
</h2>
</div>
<div
class=" flex flex-0 items-center py-2 pl-4 pr-6 sm:py-4 md:pl-6 md:pr-8 border-b lg:border-b-0 bg-card dark:bg-transparent">
<div class="flex-auto overflow-y-auto" cdkScrollable>
<div class="min-h-180 border-2 border-dashed border-gray-300 rounded-2xl py-2 pl-4 pr-6 sm:py-4 md:pl-6 md:pr-8 mx-auto"
style="max-width: 780px;">
<mat-tab-group mat-align-tabs="center">
<mat-tab label="Product Information">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6"
style="overflow-y: hidden !important;">
<div style="display: flex;">
<mat-label style="margin-right: 20px;">
<strong>Product Name </strong>
</mat-label>
<mat-label>{{product.productName}}</mat-label>
</div>
<hr/>
<div style="display:flex;">
<mat-label style="margin-right: 20px;">
<strong>Product Category</strong>
</mat-label>
<mat-label style="margin-right: 20px;">
<span style="font-size: 13px;">{{product.categoryID}}</span>
</mat-label>
</div>
<hr/>
<div style="display: flex;">
<mat-label style="margin-right: 48px;">
<strong>Original Price </strong>
</mat-label>
<mat-label>
{{product.unitPrice | currency:"GHS "}}
</mat-label>
</div>
<hr/>
<div class="columns" style="display: flex;justify-content:space-around;">
<div class="column">
<div>
<strong>Product Description :</strong>
</div>
</div>
</div>
<div class="column">
<quill-editor [modules]="quillConfig" [(ngModel)]="product.productDesc"
placeholder="Further details">
</quill-editor>
</div>
</div>
</mat-tab>
<mat-tab label="Images">
<div class="py-2 pl-2 pr-2 sm:py-4 md:pl-6 md:pr-8">
<div class="flex justify-between">
<div>
<div class="font-medium tracking-tight text-secondary">Add up to 10 high
quality images of the product
</div>
<div class="font-medium tracking-tight text-secondary">0 out of
<strong>10</strong>
</div>
</div>
</div>
<div class="imageList">
<div class="imageHolder" *ngFor="let images of productImages">
<img src="{{images.imageSRC}}" class="productImage">
<button class="deleteelement" style="border:none;"
(click)="removeImage(images)">
<mat-icon [svgIcon]="'heroicons_outline:trash'" style="color: red;">
</mat-icon>
</button>
</div>
</div>
</div>
</mat-tab>
</mat-tab-group>
</div>
</div>
</div>
</mat-drawer-content>
</mat-drawer-container>
</div>

78
src/app/pages/admin/Products/Details/details-product.component.scss

@ -0,0 +1,78 @@
.columns {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}
.column {
flex: 50%;
font-size: medium;
}
.tileHead {
font-size: 17px;
color: #003445;
font-family: Roboto, "Helvetica Neue", sans-serif;
line-height: 3rem;
}
.ticketLine > a:hover {
color: cornflowerblue;
text-decoration: underline;
}
.ticketLine > a {
color: cornflowerblue;
font-style: italic;
}
::ng-deep {
@media screen and (min-width: 992px) {
.ql-container .ql-editor {
min-height: 160px;
max-height: 270px;
height: 270px;
}
}
}
.imageList {
display: flex;
flex-wrap: wrap;
}
.imageHolder {
flex-grow: 1;
width: 22%;
max-width: 180px;
height: 180px;
margin: 10px;
border: 3px solid #003445;
padding: 5px;
position: relative;
}
.productImage{
max-width: 130px;
margin: 10px;
}
.deleteelement {
position: absolute;
top: 5px;
right: 5px;
}
@media screen and (max-width: 992px) {
.imageHolder {
flex-grow: 1;
width: 22%;
max-width: 180px;
min-width: 180px;
height: 180px;
margin: 10px;
border: 3px solid #003445;
padding: 5px;
position: relative;
}
}

25
src/app/pages/admin/Products/Details/details-product.component.spec.ts

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DetailsProductComponent } from './details-product.component';
describe('DetailsProductComponent', () => {
let component: DetailsProductComponent;
let fixture: ComponentFixture<DetailsProductComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DetailsProductComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DetailsProductComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

151
src/app/pages/admin/Products/Details/details-product.component.ts

@ -0,0 +1,151 @@
import { Component, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Router, ActivatedRoute } from '@angular/router';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { ProductUpload } from 'app/models/generalModel';
import { ProductsModel } from 'app/models/productsModel';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ProductsService } from '../products.service';
const modules = {
toolbar: [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
]
};
@Component({
selector: 'app-details-product',
templateUrl: './details-product.component.html',
styleUrls: ['./details-product.component.scss']
})
export class DetailsProductComponent implements OnInit {
productID: string;
title: string;
status: string = "Status";
htmlRequest: string;
transferType: string;
placeHolder: string = "";
selected: any;
isScreenSmall: boolean;
productImages: ProductUpload[] = [];
product: ProductsModel;
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(private _tesoMediaWatcherService: tesoMediaWatcherService,
private confirmBoxEvokeService: ConfirmBoxEvokeService,
private router: Router, private route: ActivatedRoute, private productServie: ProductsService) {
var snapshot = route.snapshot;
this.productID = snapshot.paramMap.get('id');
this.product = productServie.getProduct(this.productID);
if(this.product == null){
this.router.navigate(['products']);
}
}
ngOnInit(): void {
this._tesoMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({ matchingAliases }) => {
// Check if the screen is small
this.isScreenSmall = !matchingAliases.includes('md');
});
}
quillConfig = modules;
items = [
{ id: 1, name: 'Python' },
{ id: 2, name: 'Node Js' },
{ id: 3, name: 'Java' },
{ id: 4, name: 'PHP', disabled: true },
{ id: 5, name: 'Django' },
{ id: 6, name: 'Angular' },
{ id: 7, name: 'Vue' },
{ id: 8, name: 'ReactJs' },
];
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
// readURL(event: Event): void {
// if (event.target.files && event.target.files[0]) {
// const file = event.target.files[0];
// const reader = new FileReader();
// reader.onload = e => this.imageSrc = reader.result;
// reader.readAsDataURL(file);
// }
// }
onFileSelected(event) {
const file: File = event.target.files[0];
if (file.type.includes("image")) {
console.log(file)
if (this.productImages.length == 0) {
var productImage: ProductUpload = {
file: file,
highlight: true,
imageSRC: "",
};
let reader = new FileReader();
reader.onload = (event: any) => {
productImage.imageSRC = event.target.result;
}
reader.readAsDataURL(file);
this.productImages.push(productImage);
} else {
var productImage: ProductUpload = {
file: file,
highlight: false,
imageSRC: "",
};
let reader = new FileReader();
reader.onload = (event: any) => {
productImage.imageSRC = event.target.result;
}
reader.readAsDataURL(file);
this.productImages.push(productImage);
}
} else {
this.confirmBoxEvokeService.danger("Incompatible File", "Only images can be added", "OK").subscribe();
}
}
removeImage(item: ProductUpload) {
this.productImages = this.productImages.filter((e) => e != item);
}
submit() {
if (this.productImages.length == 0) {
this.confirmBoxEvokeService.warning("Add Product Images", "To add a new product, you must add at least one image to support it", "OK").subscribe();
} else {
}
}
}

4
src/app/pages/admin/Products/EditProduct/edit-product.component.html

@ -11,7 +11,7 @@
<mat-icon [svgIcon]="'heroicons_outline:arrow-sm-left'"></mat-icon> <mat-icon [svgIcon]="'heroicons_outline:arrow-sm-left'"></mat-icon>
</button> </button>
<h2 class="ml-2.5 text-md sm:text-xl font-medium tracking-tight truncate"> <h2 class="ml-2.5 text-md sm:text-xl font-medium tracking-tight truncate">
Product # Product # {{product.productID}}
</h2> </h2>
</div> </div>
<div <div
@ -127,7 +127,7 @@
<mat-icon [svgIcon]="'heroicons_outline:arrow-sm-left'"></mat-icon> <mat-icon [svgIcon]="'heroicons_outline:arrow-sm-left'"></mat-icon>
</button> </button>
<h2 class="ml-2.5 text-md sm:text-xl font-medium tracking-tight truncate"> <h2 class="ml-2.5 text-md sm:text-xl font-medium tracking-tight truncate">
Product # Product # {{product.productID}}
</h2> </h2>
</div> </div>
<div <div

20
src/app/pages/admin/Products/EditProduct/edit-product.component.ts

@ -1,9 +1,12 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup'; import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher'; import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { ProductUpload } from 'app/models/generalModel'; import { ProductUpload } from 'app/models/generalModel';
import { ProductsModel } from 'app/models/productsModel';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { ProductsService } from '../products.service';
const modules = { const modules = {
toolbar: [ toolbar: [
['bold', 'italic', 'underline', 'strike'], ['bold', 'italic', 'underline', 'strike'],
@ -23,7 +26,7 @@ const modules = {
[{ 'align': [] }], [{ 'align': [] }],
['clean'], ['clean'],
['link']
] ]
}; };
@ -34,14 +37,27 @@ const modules = {
styleUrls: ['./edit-product.component.scss'] styleUrls: ['./edit-product.component.scss']
}) })
export class EditProductComponent implements OnInit { export class EditProductComponent implements OnInit {
productID: string;
title: string;
status: string = "Status";
htmlRequest: string; htmlRequest: string;
transferType: string; transferType: string;
placeHolder: string = ""; placeHolder: string = "";
selected: any; selected: any;
isScreenSmall: boolean; isScreenSmall: boolean;
productImages: ProductUpload[] = []; productImages: ProductUpload[] = [];
product: ProductsModel;
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(private _tesoMediaWatcherService: tesoMediaWatcherService, private confirmBoxEvokeService: ConfirmBoxEvokeService) { } constructor(private _tesoMediaWatcherService: tesoMediaWatcherService,
private confirmBoxEvokeService: ConfirmBoxEvokeService,
private router: Router, private route: ActivatedRoute, private productServie: ProductsService) {
var snapshot = route.snapshot;
this.productID = snapshot.paramMap.get('id');
this.product = productServie.getProduct(this.productID);
if(this.product == null){
this.router.navigate(['products']);
}
}
ngOnInit(): void { ngOnInit(): void {
this._tesoMediaWatcherService.onMediaChange$ this._tesoMediaWatcherService.onMediaChange$

57
src/app/pages/admin/Products/NewProduct/new-product.component.html

@ -36,10 +36,11 @@
<strong class="tileHead">Product Category</strong> <strong class="tileHead">Product Category</strong>
</mat-label> </mat-label>
<mat-form-field appearance="fill" style="width: 350px;"> <mat-form-field appearance="fill" style="width: 350px;">
<mat-select required> <mat-select [(ngModel)]="selected">
<mat-option value="low">Lowdsadsadsadsadsadsa</mat-option> <mat-option *ngFor="let category of categories"
<mat-option value="standard">Standardasdsadasdas</mat-option> [value]="category.catCode">
<mat-option value="high">Highsadsadsadas</mat-option> {{category.catName}}
</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
@ -61,7 +62,7 @@
</div> </div>
</div> </div>
<div class="column"> <div class="column">
<quill-editor [modules]="quillConfig" [(ngModel)]="htmlRequest" <quill-editor [modules]="quillConfig" [(ngModel)]="newProduct.productDesc"
placeholder="Further details"> placeholder="Further details">
</quill-editor> </quill-editor>
@ -83,10 +84,11 @@
</div> </div>
</div> </div>
<input style="display: none" type="file" (change)="onFileSelected($event)" <input style="display: none" type="file" (change)="onFileSelected($event)"
#fileInput accept="image/*" capture="user"> #fileInput accept="image/*">
<button style="background-color: blue;color:white;margin-right:20px;" mat-button <button style="background-color: blue;color:white;margin-right:20px;" mat-button
(click)="fileInput.click()" [disabled]="productImages.length > 9">Add Image (click)="fileInput.click()" [disabled]="productImages.length > 9">Add Image
</button> </button>
</div> </div>
<div class="imageList"> <div class="imageList">
<div class="imageHolder" *ngFor="let images of productImages"> <div class="imageHolder" *ngFor="let images of productImages">
@ -139,32 +141,33 @@
<mat-tab label="Product Information"> <mat-tab label="Product Information">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6" <div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6"
style="overflow-y: hidden !important;"> style="overflow-y: hidden !important;">
<div style="display: flex;"> <div style="display: flex;justify-content: space-between;">
<mat-form-field appearance="outline">
<mat-label style="margin-right: 20px;"> <mat-label style="margin-right: 20px;">
<strong>Product Name </strong> <strong>Product Name </strong>
</mat-label> </mat-label>
<mat-form-field appearance="fill">
<input matInput style="margin-left:10px;" type="text"> <input matInput style="margin-left:10px;" type="text">
</mat-form-field> </mat-form-field>
</div> </div>
<div style="display:flex;"> <div style="display: flex;justify-content: space-between;">
<mat-form-field appearance="outline" style="width: 350px;">
<mat-label style="margin-right: 20px;"> <mat-label style="margin-right: 20px;">
<strong>Product Category</strong> <strong>Product Category</strong>
</mat-label> </mat-label>
<mat-form-field appearance="fill"> <mat-select [(ngModel)]="selected">
<mat-select required> <mat-option *ngFor="let category of categories"
<mat-option value="low">Lowdsadsadsadsadsadsa</mat-option> [value]="category.catCode">
<mat-option value="standard">Standardasdsadasdas</mat-option> {{category.catName}}
<mat-option value="high">Highsadsadsadas</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<div style="display: flex;"> <div style="display: flex;justify-content: space-between;">
<mat-label style="margin-right: 48px;"> <mat-form-field appearance="outline">
<strong>Original Price </strong> <mat-label>
<strong> Price </strong>
</mat-label> </mat-label>
<mat-form-field appearance="fill"> <span matSuffix>GH¢ &nbsp;</span>
<span matPrefix>GH¢ &nbsp;</span>
<input matInput style="margin-left:10px;" type="number"> <input matInput style="margin-left:10px;" type="number">
</mat-form-field> </mat-form-field>
</div> </div>
@ -192,17 +195,24 @@
<div class="font-medium tracking-tight text-secondary">Add up to 10 high <div class="font-medium tracking-tight text-secondary">Add up to 10 high
quality images of the product quality images of the product
</div> </div>
<div class="font-medium tracking-tight text-secondary">0 out of <div class="font-medium tracking-tight text-secondary">
{{productImages.length}} out of
<strong>10</strong> <strong>10</strong>
</div> </div>
</div> </div>
<input style="display: none" type="file" (change)="onFileSelected($event)" <input style="display: none" type="file" (change)="onFileSelected($event)"
#fileInput accept="image/*" capture="user"> #takePhoto accept="image/*" capture="user">
<input style="display: none" type="file" (change)="onFileSelected($event)"
#fileInput accept="image/*">
<button style="background-color: blue;color:white;margin-right:20px;" mat-button <button style="background-color: blue;color:white;margin-right:20px;" mat-button
(click)="fileInput.click()" [disabled]="productImages.length > 9"> [disabled]="productImages.length > 9" [matMenuTriggerFor]="actionsMenu">
<mat-icon [svgIcon]="'heroicons_outline:plus'" style="color: white;"> <mat-icon [svgIcon]="'heroicons_outline:plus'" style="color: white;">
</mat-icon> </mat-icon>
</button> </button>
<mat-menu #actionsMenu="matMenu">
<button (click)="takePhoto.click()" mat-menu-item>Take Photo</button>
<button (click)="takePhoto.click()" mat-menu-item>Add From Library</button>
</mat-menu>
</div> </div>
<div class="imageList"> <div class="imageList">
<div class="imageHolder" *ngFor="let images of productImages"> <div class="imageHolder" *ngFor="let images of productImages">
@ -218,7 +228,8 @@
</mat-tab> </mat-tab>
</mat-tab-group> </mat-tab-group>
<div class="row" style="display: flex;justify-content:space-around;width:100%;"> <div class="row" style="display: flex;justify-content:space-around;width:100%;">
<button style="background-color: green;color:white;margin-right:20px;" mat-button (click)="submit()">Add <button style="background-color: green;color:white;margin-right:20px;" mat-button
(click)="submit()">Add
Product</button> Product</button>
<button style="background-color: firebrick;color:white;margin-left:10px;" mat-button <button style="background-color: firebrick;color:white;margin-left:10px;" mat-button
cdkFocusInitial>Cancel</button> cdkFocusInitial>Cancel</button>

5
src/app/pages/admin/Products/NewProduct/new-product.component.scss

@ -46,7 +46,7 @@
flex-grow: 1; flex-grow: 1;
width: 22%; width: 22%;
max-width: 180px; max-width: 180px;
height: 180px; height: 200px;
margin: 10px; margin: 10px;
border: 3px solid #003445; border: 3px solid #003445;
padding: 5px; padding: 5px;
@ -55,6 +55,7 @@
.productImage{ .productImage{
max-width: 130px; max-width: 130px;
margin: 10px; margin: 10px;
max-height: 170px;
} }
.deleteelement { .deleteelement {
position: absolute; position: absolute;
@ -69,7 +70,7 @@
width: 22%; width: 22%;
max-width: 180px; max-width: 180px;
min-width: 180px; min-width: 180px;
height: 180px; height: 200px;
margin: 10px; margin: 10px;
border: 3px solid #003445; border: 3px solid #003445;
padding: 5px; padding: 5px;

73
src/app/pages/admin/Products/NewProduct/new-product.component.ts

@ -1,9 +1,11 @@
import { Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { tesoMediaWatcherService } from '@teso/services/media-watcher'; import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { ProductUpload } from 'app/models/generalModel'; import { ProductUpload } from 'app/models/generalModel';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup'; import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { ProductsService } from '../products.service';
import { ProductCategory, ProductsModel } from 'app/models/productsModel';
const modules = { const modules = {
toolbar: [ toolbar: [
['bold', 'italic', 'underline', 'strike'], ['bold', 'italic', 'underline', 'strike'],
@ -23,7 +25,7 @@ const modules = {
[{ 'align': [] }], [{ 'align': [] }],
['clean'], ['clean'],
['link']
] ]
}; };
@ -41,10 +43,25 @@ export class NewProductComponent implements OnInit, OnDestroy {
selected: any; selected: any;
isScreenSmall: boolean; isScreenSmall: boolean;
productImages: ProductUpload[] = []; productImages: ProductUpload[] = [];
private _unsubscribeAll: Subject<any> = new Subject<any>(); categories: ProductCategory[] = [];
constructor(private _tesoMediaWatcherService: tesoMediaWatcherService, private confirmBoxEvokeService: ConfirmBoxEvokeService) { } newProduct: ProductsModel={
productName: '',
businessID: '',
productDesc: '',
productID: '',
categoryID: '',
unitPrice: 0,
productImage: '',
images: []
};
ngOnInit(): void { private _unsubscribeAll: Subject < any > = new Subject<any>();
constructor(private _tesoMediaWatcherService: tesoMediaWatcherService,
private confirmBoxEvokeService: ConfirmBoxEvokeService,
private changeDetector:ChangeDetectorRef,
private _productService: ProductsService) { }
ngOnInit(): void {
this._tesoMediaWatcherService.onMediaChange$ this._tesoMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll)) .pipe(takeUntil(this._unsubscribeAll))
.subscribe(({ matchingAliases }) => { .subscribe(({ matchingAliases }) => {
@ -52,9 +69,15 @@ export class NewProductComponent implements OnInit, OnDestroy {
// Check if the screen is small // Check if the screen is small
this.isScreenSmall = !matchingAliases.includes('md'); this.isScreenSmall = !matchingAliases.includes('md');
}); });
}
quillConfig = modules; this._productService.categories$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
items = [ this.categories = d;
this.changeDetector.markForCheck();
// this.isLoading = false;
});
}
quillConfig = modules;
items = [
{ id: 1, name: 'Python' }, { id: 1, name: 'Python' },
{ id: 2, name: 'Node Js' }, { id: 2, name: 'Node Js' },
{ id: 3, name: 'Java' }, { id: 3, name: 'Java' },
@ -63,24 +86,24 @@ export class NewProductComponent implements OnInit, OnDestroy {
{ id: 6, name: 'Angular' }, { id: 6, name: 'Angular' },
{ id: 7, name: 'Vue' }, { id: 7, name: 'Vue' },
{ id: 8, name: 'ReactJs' }, { id: 8, name: 'ReactJs' },
]; ];
ngOnDestroy(): void { ngOnDestroy(): void {
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
this._unsubscribeAll.next(); this._unsubscribeAll.next();
this._unsubscribeAll.complete(); this._unsubscribeAll.complete();
} }
// readURL(event: Event): void { // readURL(event: Event): void {
// if (event.target.files && event.target.files[0]) { // if (event.target.files && event.target.files[0]) {
// const file = event.target.files[0]; // const file = event.target.files[0];
// const reader = new FileReader(); // const reader = new FileReader();
// reader.onload = e => this.imageSrc = reader.result; // reader.onload = e => this.imageSrc = reader.result;
// reader.readAsDataURL(file); // reader.readAsDataURL(file);
// } // }
// } // }
onFileSelected(event) { onFileSelected(event) {
const file: File = event.target.files[0]; const file: File = event.target.files[0];
if (file.type.includes("image")) { if (file.type.includes("image")) {
@ -117,17 +140,17 @@ export class NewProductComponent implements OnInit, OnDestroy {
} else { } else {
this.confirmBoxEvokeService.danger("Incompatible File", "Only images can be added", "OK").subscribe(); this.confirmBoxEvokeService.danger("Incompatible File", "Only images can be added", "OK").subscribe();
} }
} }
removeImage(item: ProductUpload) { removeImage(item: ProductUpload) {
this.productImages = this.productImages.filter((e) => e != item); this.productImages = this.productImages.filter((e) => e != item);
} }
submit() { submit() {
if (this.productImages.length == 0) { if (this.productImages.length == 0) {
this.confirmBoxEvokeService.warning("Add Product Images", "To add a new product, you must add at least one image to support it", "OK").subscribe(); this.confirmBoxEvokeService.warning("Add Product Images", "To add a new product, you must add at least one image to support it", "OK").subscribe();
} else { } else {
} }
} }
} }

46
src/app/pages/admin/Products/ProductList/product-list.component.html

@ -1,12 +1,13 @@
<div class="flex flex-col flex-auto w-full"> <div class="flex flex-col flex-auto w-full">
<div class="flex flex-wrap w-full max-w-screen-xl mx-auto p-6 md:p-8"> <div class="flex flex-wrap w-full max-w-screen-xl mx-auto p-6 md:p-8">
<!-- Title and action buttons --> <!-- Title and action buttons -->
<div class="flex items-center justify-between w-full" style="margin-bottom: 20px;" *ngIf="!isScreenSmall"> <div class="flex items-center justify-between w-full" *ngIf="!isScreenSmall">
<div> <div>
<div class="text-3xl font-semibold tracking-tight leading-8">Products</div> <div class="text-3xl font-semibold tracking-tight leading-8">Products</div>
<div class="font-medium tracking-tight text-secondary">Here is a list of all products in your Teso (850) <div class="font-medium tracking-tight text-secondary">Here is a list of all products in your Teso
({{data.length}})
</div> </div>
</div> </div>
@ -19,15 +20,18 @@
<!-- Search --> <!-- Search -->
<mat-form-field class="teso-mat-dense teso-mat-no-subscript teso-mat-rounded min-w-64"> <mat-form-field class="teso-mat-dense teso-mat-no-subscript teso-mat-rounded min-w-64">
<mat-icon class="icon-size-5" matPrefix [svgIcon]="'heroicons_solid:search'"></mat-icon> <mat-icon class="icon-size-5" matPrefix [svgIcon]="'heroicons_solid:search'"></mat-icon>
<input matInput [autocomplete]="'off'" [placeholder]="'Look-up product'"> <input matInput
[formControl]="searchInputControl"
[autocomplete]="'off'" [placeholder]="'Look-up product'">
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>
<div class="flex items-center justify-between w-full" style="margin-bottom: 20px;" *ngIf="isScreenSmall"> <div class="flex items-center justify-between w-full" style="margin-bottom: 20px;" *ngIf="isScreenSmall">
<div> <div>
<div class="text-3xl font-semibold tracking-tight leading-8">Products</div> <div class="text-3xl font-semibold tracking-tight leading-8">Products</div>
<div class="font-medium tracking-tight text-secondary">(850) products <div class="font-medium tracking-tight text-secondary">({{data.length}}) products
</div> </div>
</div> </div>
@ -45,16 +49,21 @@
</div> </div>
</div> </div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 w-full min-w-0"> <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 w-full min-w-0 min-h-200"
<ng-container *ngFor="let product of data.teamproducts"> style="margin-bottom: 50px;margin-top: 20px;">
<div class="absolute inset-x-0" *ngIf="isLoading">
<mat-progress-bar [mode]="'indeterminate'"></mat-progress-bar>
</div>
<ng-container *ngFor="let product of data">
<!-- <div class="flex flex-col flex-auto items-center bg-card shadow rounded-2xl overflow-hidden"> --> <!-- <div class="flex flex-col flex-auto items-center bg-card shadow rounded-2xl overflow-hidden"> -->
<div class="product"> <div class="product">
<div class="product-content"> <div class="product-content">
<div class="product-img"> <div class="product-img">
<img [src]="product.image" alt="product image" style="max-height:200px;"> <img [src]="imageLoader(product.productImage)" alt="product image"
style="max-height:200px;">
</div> </div>
<div class="product-btns"> <div class="product-btns">
<button type="button" class="btn-cart" (click)="editProduct(product.id)"> <button type="button" class="btn-cart" (click)="editProduct(product.productID)">
Edit Product Edit Product
</button> </button>
<button type="button" class="btn-buy" (click)="generateCoupon(product)"> <button type="button" class="btn-buy" (click)="generateCoupon(product)">
@ -66,25 +75,30 @@
<div class="product-info"> <div class="product-info">
<div class="product-info-top"> <div class="product-info-top">
<h2 class="sm-title">@product.CategoryID</h2> <h2 class="sm-title">{{product.categoryID}}</h2>
<button class="btn2" style="border:none;" (click)="deleteProduct(product)"> <button class="btn2" style="border:none;" (click)="deleteProduct(product)">
<mat-icon [svgIcon]="'heroicons_outline:trash'" style="color: #1e293b;"> <mat-icon [svgIcon]="'heroicons_outline:trash'" style="color: #1e293b;">
</mat-icon> </mat-icon>
</button> </button>
</div> </div>
<a href="#" class="product-name">@product.ProductName</a> <a href="javascript:void(0)" class="product-name">{{product.productName}}</a>
<p class="product-price"><span></span></p> <p class="product-price"><span>{{product.unitPrice | currency:"GHS "}}</span></p>
<p class="product-price"><span>&#8373;</span> @product.UnitPrice</p> <p>{{product.productDesc | productDescShort}}</p>
<p>@product.ProductDesc</p> <div style="width: 100%;display: flex;justify-content: center;margin-top:30px;">
<button (click)="details(product.productID)">
<a href="javascript:void(0)" style="color: #469ae8;">See Details</a>
</button>
</div>
</div> </div>
<!-- </div> --> <!-- </div> -->
</div> </div>
</ng-container> </ng-container>
</div> </div>
<mat-paginator <mat-paginator
class="sticky sm:absolute sm:inset-x-0 sm:bottom-0 border-b sm:border-t sm:border-b-0 z-10 bg-gray-50 dark:bg-transparent" class="absolute sm:absolute sm:inset-x-0 sm:bottom-0 border-b sm:border-t sm:border-b-0 z-10 bg-gray-50 dark:bg-transparent"
[pageSizeOptions]="[5, 10, 25, 100]" [showFirstLastButtons]="true"></mat-paginator> [pageSizeOptions]="[5, 10, 25, 100]" [showFirstLastButtons]="true"></mat-paginator>
<ng-template #noProducts> <ng-template *ngIf="data.length == 0">
<div class="p-8 sm:p-16 border-t text-4xl font-semibold tracking-tight text-center">There are no products! <div class="p-8 sm:p-16 border-t text-4xl font-semibold tracking-tight text-center">There are no products!
</div> </div>
</ng-template> </ng-template>

12
src/app/pages/admin/Products/ProductList/product-list.component.scss

@ -126,6 +126,7 @@ img {
} }
.lg-title { .lg-title {
text-align: center;
font-size: 2.5rem; font-size: 2.5rem;
font-weight: 500; font-weight: 500;
text-align: center; text-align: center;
@ -139,8 +140,9 @@ img {
} }
.sm-title { .sm-title {
text-align: center;
font-weight: 300; font-weight: 300;
font-size: 1rem; font-size: 0.89rem;
text-transform: uppercase; text-transform: uppercase;
} }
@ -227,11 +229,13 @@ img {
} }
.product-info { .product-info {
text-align: center;
background: white; background: white;
padding: 2rem; padding: 2rem;
} }
.product-info-top { .product-info-top {
text-align: center;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
@ -242,10 +246,11 @@ img {
} }
.product-name { .product-name {
text-align: center;
color: black; color: black;
display: block; display: block;
text-decoration: none; text-decoration: none;
font-size: 1rem; font-size: 0.89rem;
text-transform: uppercase; text-transform: uppercase;
font-weight: bold; font-weight: bold;
} }
@ -257,7 +262,6 @@ img {
} }
.product-price:first-of-type { .product-price:first-of-type {
text-decoration: line-through;
color: #40c9a2; color: #40c9a2;
} }
@ -278,6 +282,7 @@ img {
} }
.off-info .sm-title { .off-info .sm-title {
text-align: center;
background: #40c9a2; background: #40c9a2;
color: white; color: white;
display: inline-block; display: inline-block;
@ -324,6 +329,7 @@ img {
.product-collection .text-light { .product-collection .text-light {
opacity: 1; opacity: 1;
text-align: center;
font-size: 0.8; font-size: 0.8;
font-weight: 400; font-weight: 400;
line-height: 1.7; line-height: 1.7;

119
src/app/pages/admin/Products/ProductList/product-list.component.ts

@ -1,14 +1,18 @@
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { FormControl } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { MatPaginator } from '@angular/material/paginator'; import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort'; import { MatSort } from '@angular/material/sort';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup'; import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher'; import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { ProductCategory, ProductsModel } from 'app/models/productsModel';
import { environment } from 'environments/environment';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { debounceTime, map, switchMap, takeUntil } from 'rxjs/operators';
import { AdminCouponDialogComponent } from '../AdminCouponDialog/admin-coupon-dialog.component'; import { AdminCouponDialogComponent } from '../AdminCouponDialog/admin-coupon-dialog.component';
import { CouponDialogComponent } from '../CouponDialog/coupon-dialog.component'; import { CouponDialogComponent } from '../CouponDialog/coupon-dialog.component';
import { ProductsService } from '../products.service';
@Component({ @Component({
selector: 'app-product-list', selector: 'app-product-list',
@ -16,13 +20,16 @@ import { CouponDialogComponent } from '../CouponDialog/coupon-dialog.component';
styleUrls: ['./product-list.component.scss'] styleUrls: ['./product-list.component.scss']
}) })
export class ProductListComponent implements OnInit, OnDestroy { export class ProductListComponent implements OnInit, OnDestroy {
data: { teamproducts: { id: string; image: string; name: string; email: string; phone: string; title: string; }[]; }; data: ProductsModel[] = [];
@ViewChild(MatPaginator) private _paginator: MatPaginator; @ViewChild(MatPaginator) private _paginator: MatPaginator;
@ViewChild(MatSort) private _sort: MatSort; @ViewChild(MatSort) private _sort: MatSort;
isScreenSmall: boolean; isScreenSmall: boolean;
isLoading: boolean = true;
searchInputControl: FormControl = new FormControl();
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(private router: Router, private _tesoMediaWatcherService: tesoMediaWatcherService, constructor(private router: Router, private _tesoMediaWatcherService: tesoMediaWatcherService,
public dialog: MatDialog, private confirmBoxEvokeService: ConfirmBoxEvokeService) { } public dialog: MatDialog, private confirmBoxEvokeService: ConfirmBoxEvokeService, private _productService: ProductsService) {
}
ngOnDestroy(): void { ngOnDestroy(): void {
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
@ -39,82 +46,26 @@ export class ProductListComponent implements OnInit, OnDestroy {
// Check if the screen is small // Check if the screen is small
this.isScreenSmall = !matchingAliases.includes('md'); this.isScreenSmall = !matchingAliases.includes('md');
}); });
this.data = {
teamproducts: [ this._productService.dataProductsPageOnly$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
{ this.data = d;
id: '2bfa2be5-7688-48d5-b5ac-dc0d9ac97f14', this.isLoading = false;
image: 'assets/images/brands/bagset.png', });
name: 'Nadia Mcknight',
email: 'nadiamcknight@mail.com', this.searchInputControl.valueChanges
phone: '+1-943-511-2203', .pipe(
title: 'Project Director' takeUntil(this._unsubscribeAll),
}, debounceTime(300),
{ switchMap((query) => {
id: '77a4383b-b5a5-4943-bc46-04c3431d1566', // this.isLoading = true;
image: 'assets/images/brands/bagset.png', return this.data = this._productService.filterProducts(query);
name: 'Best Blackburn', }),
email: 'blackburn.best@beadzza.me', map(()=>{
phone: '+1-814-498-3701', this.isLoading = false;
title: 'Senior Developer' })
}, )
{ .subscribe((e)=>this.isLoading = false );
id: '8bb0f597-673a-47ca-8c77-2f83219cb9af',
image: 'assets/images/brands/bagset.png',
name: 'Duncan Carver',
email: 'duncancarver@mail.info',
phone: '+1-968-547-2111',
title: 'Senior Developer'
},
{
id: 'c318e31f-1d74-49c5-8dae-2bc5805e2fdb',
image: 'assets/images/brands/bagset.png',
name: 'Martin Richards',
email: 'martinrichards@mail.biz',
phone: '+1-902-500-2668',
title: 'Junior Developer'
},
{
id: '0a8bc517-631a-4a93-aacc-000fa2e8294c',
image: 'assets/images/brands/bagset.png',
name: 'Candice Munoz',
email: 'candicemunoz@mail.co.uk',
phone: '+1-838-562-2769',
title: 'Lead Designer'
},
{
id: 'a4c9945a-757b-40b0-8942-d20e0543cabd',
image: 'assets/images/brands/bagset.png',
name: 'Vickie Mosley',
email: 'vickiemosley@mail.net',
phone: '+1-939-555-3054',
title: 'Designer'
},
{
id: 'b8258ccf-48b5-46a2-9c95-e0bd7580c645',
image: 'assets/images/brands/bagset.png',
name: 'Tina Harris',
email: 'tinaharris@mail.ca',
phone: '+1-933-464-2431',
title: 'Designer'
},
{
id: 'f004ea79-98fc-436c-9ba5-6cfe32fe583d',
image: 'assets/images/brands/bagset.png',
name: 'Holt Manning',
email: 'holtmanning@mail.org',
phone: '+1-822-531-2600',
title: 'Marketing Manager'
},
{
id: '8b69fe2d-d7cc-4a3d-983d-559173e37d37',
image: 'assets/images/brands/bagset.png',
name: 'Misty Ramsey',
email: 'mistyramsey@mail.us',
phone: '+1-990-457-2106',
title: 'Consultant'
}
]
}
} }
createProduct() { createProduct() {
this.router.navigate(['products/create-product']); this.router.navigate(['products/create-product']);
@ -144,10 +95,16 @@ export class ProductListComponent implements OnInit, OnDestroy {
"Are you sure you would like to delete this product from your inventory ?", "Are you sure you would like to delete this product from your inventory ?",
"Yes", "No") "Yes", "No")
.subscribe((response: any) => { .subscribe((response: any) => {
console.log(response)
if (response.clickedButtonID === "yes") { if (response.clickedButtonID === "yes") {
this.data.teamproducts = this.data.teamproducts.filter((e) => e == product); this.data = this.data.filter((e) => e != product);
} }
}); });
} }
imageLoader(path: string): string {
return environment.apiURL + `imagingproducts/${path}`;
}
details(productID: any) {
this.router.navigate(['products/details/' + productID]);
}
} }

6
src/app/pages/admin/Products/products.module.ts

@ -30,6 +30,8 @@ import { CouponDialogComponent } from './CouponDialog/coupon-dialog.component';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { AdminCouponDialogComponent } from './AdminCouponDialog/admin-coupon-dialog.component'; import { AdminCouponDialogComponent } from './AdminCouponDialog/admin-coupon-dialog.component';
import { BusinessLookUpComponent } from './BusinessLookUp/business-look-up.component'; import { BusinessLookUpComponent } from './BusinessLookUp/business-look-up.component';
import { ProductDescriptionShort } from 'app/pipes/productDescriptionShort.pipe';
import { DetailsProductComponent } from './Details/details-product.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -40,7 +42,9 @@ import { BusinessLookUpComponent } from './BusinessLookUp/business-look-up.compo
SanitizerUrlPipe, SanitizerUrlPipe,
CouponDialogComponent, CouponDialogComponent,
AdminCouponDialogComponent, AdminCouponDialogComponent,
BusinessLookUpComponent BusinessLookUpComponent,
ProductDescriptionShort,
DetailsProductComponent
], ],
imports: [ imports: [
RouterModule.forChild(productsRoutes), RouterModule.forChild(productsRoutes),

8
src/app/pages/admin/Products/products.routing.ts

@ -1,4 +1,5 @@
import { Route } from '@angular/router'; import { Route } from '@angular/router';
import { DetailsProductComponent } from './Details/details-product.component';
import { EditProductComponent } from './EditProduct/edit-product.component'; import { EditProductComponent } from './EditProduct/edit-product.component';
import { NewProductComponent } from './NewProduct/new-product.component'; import { NewProductComponent } from './NewProduct/new-product.component';
import { ProductListComponent } from './ProductList/product-list.component'; import { ProductListComponent } from './ProductList/product-list.component';
@ -30,6 +31,13 @@ export const productsRoutes: Route[] = [
// resolve : { // resolve : {
// course: AcademyCourseResolver // course: AcademyCourseResolver
// } // }
},
{
path : 'details/:id',
component: DetailsProductComponent,
// resolve : {
// course: AcademyCourseResolver
// }
} }
] ]
} }

73
src/app/pages/admin/Products/products.service.ts

@ -0,0 +1,73 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { analytics as analyticsData } from 'app/mock-api/dashboards/analytics/data';
import { environment } from 'environments/environment';
import { ProductsModule } from './products.module';
import { ProductCategory, ProductsModel } from 'app/models/productsModel';
@Injectable({
providedIn: 'root'
})
export class ProductsService {
private _data: BehaviorSubject<any> = new BehaviorSubject(null);
private _dataFilterable: BehaviorSubject<any> = new BehaviorSubject(null);
private products: ProductsModel[] = [];
private _productsCategory: BehaviorSubject<ProductCategory[]> = new BehaviorSubject(null);
/**
* Constructor
*/
constructor(private _httpClient: HttpClient) {
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Getter for data
*/
get data$(): Observable<ProductsModel[]> {
return this._dataFilterable.asObservable();
}
get dataProductsPageOnly$(): Observable<ProductsModel[]> {
return this._data.asObservable();
}
get categories$(): Observable<ProductCategory[]> {
return this._productsCategory.asObservable();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Get data
*/
getData(): Observable<ProductsModel[]> {
return this._httpClient.get(environment.apiURL + `products/allproducts`).pipe(
tap((response: ProductsModel[]) => {
this._data.next(response);
this._dataFilterable.next(response);
this.products = response;
})
);
}
filterProducts(productName: string): ProductsModel[] {
return this._dataFilterable.getValue().filter(p => p.productName.toLowerCase().includes(productName));
// this.products = response;
}
getProduct(id: string): ProductsModel {
const found = this.products.find(item => item.productID === id);
return found;
}
getCategories(): Observable<ProductCategory[]> {
return this._httpClient.get(environment.apiURL + `productcategories`).pipe(
tap((response: ProductCategory[]) => {
this._productsCategory.next(response);
}));
}
}

12
src/app/pages/admin/Profile/DescriptionDialog/description-dialog.component.html

@ -0,0 +1,12 @@
<div class="row" style="display: flex;justify-content:space-between">
<div class="row" style="display: flex;justify-content:center;width: 100%;">
<h2 mat-dialog-title style="text-align: center;">Shop Description</h2>
</div>
<button mat-button [mat-dialog-close]="data.details" cdkFocusInitial>x</button>
</div>
<mat-dialog-content class="mat-typography">
<quill-editor [modules]="quillConfig" [(ngModel)]="data.details"
placeholder="Further details">
</quill-editor>
</mat-dialog-content>

0
src/app/pages/admin/Settings/settings.component.scss → src/app/pages/admin/Profile/DescriptionDialog/description-dialog.component.scss

25
src/app/pages/admin/Profile/DescriptionDialog/description-dialog.component.spec.ts

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DescriptionDialogComponent } from './description-dialog.component';
describe('DescriptionDialogComponent', () => {
let component: DescriptionDialogComponent;
let fixture: ComponentFixture<DescriptionDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DescriptionDialogComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DescriptionDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

44
src/app/pages/admin/Profile/DescriptionDialog/description-dialog.component.ts

@ -0,0 +1,44 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { TesoBusinessDetail } from 'app/models/businessModel';
const modules = {
toolbar: [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ 'header': 1 }, { 'header': 2 }],
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
[{ 'script': 'sub' }, { 'script': 'super' }],
[{ 'indent': '-1' }, { 'indent': '+1' }],
[{ 'direction': 'rtl' }],
[{ 'size': ['small', false, 'large', 'huge'] }],
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ 'color': [] }, { 'background': [] }],
[{ 'font': [] }],
[{ 'align': [] }],
['clean'],
['link']
]
};
@Component({
selector: 'app-description-dialog',
templateUrl: './description-dialog.component.html',
styleUrls: ['./description-dialog.component.scss']
})
export class DescriptionDialogComponent implements OnInit {
constructor(@Inject(MAT_DIALOG_DATA) public data: { details: string },
public dialogRef: MatDialogRef<DescriptionDialogComponent>,) { }
quillConfig = modules;
ngOnInit(): void {
}
closeDialog() {
this.dialogRef.close(this.data.details);
}
}

83
src/app/pages/admin/Profile/InfoDialog/information-dialog.component.html

@ -0,0 +1,83 @@
<div class="row" style="display: flex;justify-content:space-between;">
<div class="row" style="display: flex;justify-content:center;width: 100%;">
<h3 mat-dialog-title style="text-align: center;">Business Information</h3>
</div>
<button mat-button [mat-dialog-close]="data.details" cdkFocusInitial>x</button>
</div>
<mat-dialog-content class="mat-typography">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6" style="overflow-y: hidden !important;">
<div style="display: flex;justify-content:space-between;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Shop Name </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<input matInput style="margin-left:10px;" type="text" [(ngModel)]="data.details.businessName">
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;">
<mat-label style="margin-right: 20px;">
<strong class="tileHead">Shop Category</strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<mat-select [(ngModel)]="selectedCategory">
<mat-option *ngFor="let category of businessCategories" [value]="category">
{{category.categoryName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Physical Address </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<input matInput style="margin-left:10px;" type="text" [(ngModel)]="data.details.businessAddress">
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Digital Address </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<span matPrefix>
<a href="https://ghanapostgps.com/map/" target="blank">
<mat-icon class="icon-size-5" [svgIcon]="'heroicons_solid:information-circle'"
style="color: #0152cc;" matTooltip="visit https://ghanapostgps.com/map/">
</mat-icon> &nbsp;
</a>
</span>
<input matInput style="margin-left:10px;" type="text" [(ngModel)]="data.details.businessDigitalAddress">
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Date Of Establishment </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<input matInput style="margin-left:10px;" type="date" [(ngModel)]="data.details.dateOfEst">
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Telephone </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<input matInput style="margin-left:10px;" type="text" [(ngModel)]="data.details.businessContact"
disabled>
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Email Address </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<input matInput style="margin-left:10px;" type="text" [(ngModel)]="data.details.businessEmail" disabled>
</mat-form-field>
</div>
<div style="display: flex;justify-content:center;width: 100%;">
<button mat-button style="background-color: green;color: white;">Confirm Changes</button>
</div>
</div>
</mat-dialog-content>

28
src/app/pages/admin/Profile/InfoDialog/information-dialog.component.scss

@ -0,0 +1,28 @@
.columns {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}
.column {
flex: 50%;
font-size: medium;
}
.tileHead {
font-size: 17px;
color: #003445;
font-family: Roboto, "Helvetica Neue", sans-serif;
line-height: 3rem;
}
.ticketLine > a:hover {
color: cornflowerblue;
text-decoration: underline;
}
.ticketLine > a {
color: cornflowerblue;
font-style: italic;
}

25
src/app/pages/admin/Profile/InfoDialog/information-dialog.component.spec.ts

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { InformationDialogComponent } from './information-dialog.component';
describe('InformationDialogComponent', () => {
let component: InformationDialogComponent;
let fixture: ComponentFixture<InformationDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ InformationDialogComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(InformationDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

35
src/app/pages/admin/Profile/InfoDialog/information-dialog.component.ts

@ -0,0 +1,35 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { UserService } from 'app/core/user/user.service';
import { BusinessCategory, TesoBusinessDetail } from 'app/models/businessModel';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ProfileService } from '../profile.service';
@Component({
selector: 'app-information-dialog',
templateUrl: './information-dialog.component.html',
styleUrls: ['./information-dialog.component.scss']
})
export class InformationDialogComponent implements OnInit {
businessCategories: BusinessCategory[] = [];
selectedCategory: BusinessCategory;
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(@Inject(MAT_DIALOG_DATA) public data: { details: TesoBusinessDetail },
public dialogRef: MatDialogRef<InformationDialogComponent>, private _profileService: ProfileService,
private _userService:UserService) { }
ngOnInit(): void {
this._profileService.categories$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.businessCategories = d;
this.selectedCategory = this.businessCategories.find((c)=> c.categoryCode.toLowerCase() == this.data.details.businessCategory.toLowerCase());
});
}
closeDialog() {
this.data.details.businessCategory = this.selectedCategory.categoryCode;
this._userService.update(this.data.details);
this.dialogRef.close(true);
}
}

53
src/app/pages/admin/Profile/Information/information.component.html

@ -0,0 +1,53 @@
<div class="InfoHold">
<div style="display: flex;justify-content: space-between;max-height: 46px;align-items: center;">
<div>
<h4>
<mat-icon class="icon-size-5" [svgIcon]="'heroicons_solid:information-circle'"></mat-icon> Business
Information
</h4>
</div>
<div>
<button mat-button style="background-color: #0152cc;color: white;" (click)="editShopInformation()">Edit Information</button>
</div>
</div>
<mat-divider class="my-2"></mat-divider>
<table style="width: 100%;" >
<tbody>
<tr class="tableau">
<th scope="row" class="text-dark">Shop Name: </th>
<td id="business-name-id">{{details.businessName}}</td>
</tr>
<tr class="tableau">
<th scope="row" class="text-dark">Shop Category: </th>
<td id="shop-category1">{{currentCategory}}</td>
</tr>
<tr class="tableau">
<th scope="row" class="text-dark">Physical Address: </th>
<td id="physicaladdress1">{{details.businessAddress}}</td>
</tr>
<tr class="tableau">
<th scope="row" class="text-dark">Digital Address: </th>
<td id="digitaladdress1">{{details.businessDigitalAddress}}</td>
</tr>
<tr class="tableau">
<th scope="row" class="text-dark">Date of Establishment: </th>
<td id="dateest1">
{{details.dateOfEst | date}}</td>
</tr>
<tr class="tableau">
<th scope="row" class="text-dark">Phone Number: </th>
<td id="tel-id">{{details.businessContact}}</td>
</tr>
<tr class="tableau">
<th scope="row" class="text-dark">Email Address: </th>
<td id="email-id">{{details.businessEmail}}</td>
</tr>
</tbody>
</table>
</div>
<div class="text-center" style="margin-top:10px;">
<!-- <RadzenButton Text="Save Changes" Click="editProduct" Style="background-color:forestgreen !important;" /> -->
</div>

12
src/app/pages/admin/Profile/Information/information.component.scss

@ -0,0 +1,12 @@
.InfoHold{
padding-left: 5%;
padding-right: 5%;
}
.tableau{
margin: 20px;
height: 45px;
padding: 20px;
border-bottom: 1px solid gray;
text-align: start;
align-items: flex-start;
}

12
src/app/pages/admin/Products/EditProduct/edit-product.component.spec.ts → src/app/pages/admin/Profile/Information/information.component.spec.ts

@ -1,20 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EditProductComponent } from './edit-product.component'; import { InformationComponent } from './information.component';
describe('EditProductComponent', () => { describe('InformationComponent', () => {
let component: EditProductComponent; let component: InformationComponent;
let fixture: ComponentFixture<EditProductComponent>; let fixture: ComponentFixture<InformationComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ EditProductComponent ] declarations: [ InformationComponent ]
}) })
.compileComponents(); .compileComponents();
}); });
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(EditProductComponent); fixture = TestBed.createComponent(InformationComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

51
src/app/pages/admin/Profile/Information/information.component.ts

@ -0,0 +1,51 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatTableDataSource } from '@angular/material/table';
import { Router } from '@angular/router';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { UserService } from 'app/core/user/user.service';
import { BusinessCategory, TesoBusinessDetail } from 'app/models/businessModel';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { FollowersService } from '../../Followers/followers.service';
import { DescriptionDialogComponent } from '../DescriptionDialog/description-dialog.component';
import { InformationDialogComponent } from '../InfoDialog/information-dialog.component';
import { ProfileService } from '../profile.service';
@Component({
selector: 'business-information',
templateUrl: './information.component.html',
styleUrls: ['./information.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'business-information'
})
export class InformationComponent implements OnInit {
@Input() details: TesoBusinessDetail;
currentCategory: string;
categories: BusinessCategory[] = [];
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(private router: Router, private _tesoMediaWatcherService: tesoMediaWatcherService,
private _profileService: ProfileService, private _changeRef: ChangeDetectorRef,
public dialog: MatDialog,
private confirmBoxEvokeService: ConfirmBoxEvokeService, private _userService: UserService) { }
ngOnInit(): void {
this._profileService.categories$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.categories = d;
this.currentCategory = d.find(c => c.categoryCode == this.details.businessCategory).categoryName;
});
}
editShopInformation() {
const dialogReference = this.dialog.open(InformationDialogComponent, {
disableClose: true,
hasBackdrop: true,
data: { details: this.details },
});
dialogReference.afterClosed().subscribe((d) => {
if (d)
this._userService.get();
});
}
}

33
src/app/pages/admin/Profile/Settings/settings.component.html

@ -0,0 +1,33 @@
<mat-dialog-content class="mat-typography">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6" style="overflow-y: hidden !important;">
<div style="display: flex;justify-content:space-between;">
<mat-form-field appearance="outline" style="width: 350px;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Old Password </strong>
</mat-label>
<input matInput style="margin-left:10px;" type="password" >
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;">
<mat-form-field appearance="outline" style="width: 350px;">
<mat-label style="margin-right: 20px;">
<strong class="tileHead">New Password</strong>
</mat-label>
<input matInput style="margin-left:10px;" type="password" >
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;">
<mat-form-field appearance="outline" style="width: 350px;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Confirm Password </strong>
</mat-label>
<input matInput style="margin-left:10px;" type="password">
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;width: 100%;">
<button mat-button style="background-color: green;color: white;">Confirm</button>
<button mat-button style="background-color: maroon;color: white;" [mat-dialog-close]="true" cdkFocusInitial>Cancel</button>
</div>
</div>
</mat-dialog-content>

0
src/app/pages/admin/Profile/Settings/settings.component.scss

0
src/app/pages/admin/Settings/settings.component.spec.ts → src/app/pages/admin/Profile/Settings/settings.component.spec.ts

17
src/app/pages/admin/Profile/Settings/settings.component.ts

@ -0,0 +1,17 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
@Component({
selector: 'settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'settings'
})
export class SettingsComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

67
src/app/pages/admin/Profile/profile.component.html

@ -1 +1,66 @@
<p>profile works!</p> <!-- <div class="flex flex-col flex-auto w-full">
<div class="flex flex-wrap w-full max-w-screen-xl mx-auto p-6 md:p-8"> -->
<div class="columns">
<div class="col-md-3">
<div class="card card-primary card-outline">
<div class="card-body box-profile">
<form>
<input style="display: none" type="file" (change)="onFileSelected($event)"
#fileInput accept="image/*">
<div class="text-center">
<img id="logo" class="profile-user-img img-fluid img-circle" [src]="imageLoader(profile.businessLogo)"
style="height:150px;width:150px;" *ngIf="newProfilePicture === undefined">
<img id="logo" class="profile-user-img img-fluid img-circle" src="{{newProfilePicture.imageSRC}}"
style="height:150px;width:150px;" *ngIf="newProfilePicture != undefined">
</div>
<div class="text-center" style="margin-top:10px;">
<button mat-button style="background-color: #0152cc;color: white;"
(click)="fileInput.click()" *ngIf="newProfilePicture === undefined">Select</button>
<button mat-button style="background-color: rgb(163, 35, 35);color: white;"
(click)="clear()" *ngIf="newProfilePicture != undefined">Clear</button>
</div>
</form>
<h3 class="profile-username text-center">{{profile.businessName}}</h3>
<ul class="list-group list-group-unbordered mb-3">
<li class="list-group-item">
<b style="margin-right: 10px;">Followers : </b>
<a href="followers" class="float-right" style="text-decoration: none;color: #0152cc;"> {{subscribers.length}}</a>
</li>
<li class="list-group-item">
<button class=" sm:inline-flex" mat-flat-button style="background-color: #0152cc;margin-right:10px;">
<a href="javascript:void(0)" style="color: #fff;text-decoration: none;margin-right:10px;" >Generate QR-Code</a>
</button>
<!-- <a class="btn btn-secondary btn-lg" onclick="generateCode"></a> -->
</li>
</ul>
</div>
</div>
<div class="card card-primary">
<div class="card-header" style="display: flex;justify-content: space-between;">
<h3 class="card-title">Shop Description</h3>
<!-- <div class="card-tools" (click)="openEdit()"> -->
<button mat-button style="background: transparent;" (click)="editShopDescription()">
<mat-icon class="icon-size-3" [svgIcon]="'heroicons_solid:pencil-alt'" style="color: #fff;"></mat-icon>
</button>
<!-- </div> -->
</div>
<div class="card-body">
<p class="text-muted">
<strong><i class="fas fa-book mr-1"></i> </strong>
<span id="shop-desc">{{profile.businessDescription}}</span>
</p>
</div>
</div>
</div>
<div class="col-md-9">
<div class="card">
<business-information [details]="profile"></business-information>
</div>
</div>
</div>
<!-- </div>
</div> -->

173
src/app/pages/admin/Profile/profile.component.scss

@ -0,0 +1,173 @@
.card-primary.card-outline {
border-top: 3px solid #0152cc;
}
.card-primary:not(.card-outline) > .card-header, .card-primary:not(.card-outline) > .card-header a {
color: #fff;
}
.card-primary:not(.card-outline) > .card-header {
background-color: #0152cc;
}
.card {
box-shadow: 0 0 1px rgb(0 0 0 / 13%), 0 1px 3px rgb(0 0 0 / 20%);
margin-bottom: 1rem;
}
.card-header {
background-color: transparent;
border-bottom: 1px solid rgba(0,0,0,.125);
padding: .75rem 1.25rem;
position: relative;
border-top-left-radius: .25rem;
border-top-right-radius: .25rem;
}
.list-group-unbordered > .list-group-item {
border-left: 0;
border-radius: 0;
border-right: 0;
padding-left: 0;
padding-right: 0;
}
.list-group-item {
position: relative;
display: block;
padding: .75rem 1.25rem;
background-color: #fff;
border: 1px solid rgba(0,0,0,.125);
}
.card-body {
-ms-flex: 1 1 auto;
flex: 1 1 auto;
min-height: 1px;
padding: 1.25rem;
}
.card-body {
-ms-flex: 1 1 auto;
flex: 1 1 auto;
min-height: 1px;
padding: 1.25rem;
}
form {
display: block;
margin-top: 0em;
}
.img-circle {
border-radius: 50%;
}
.profile-user-img {
border: 3px solid #adb5bd;
margin: 0 auto;
padding: 3px;
width: 100px;
}
.img-fluid {
max-width: 100%;
height: auto;
}
img {
vertical-align: middle;
border-style: none;
}
.text-center {
text-align: center !important;
}
.card-title {
float: left;
font-size: 1.1rem;
font-weight: 400;
margin: 0;
}
.card-title {
margin-bottom: .75rem;
}
.card {
position: relative;
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
background-color: #fff;
background-clip: border-box;
border: 0 solid rgba(0,0,0,.125);
border-radius: .25rem;
}
.card {
position: relative;
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
background-color: #fff;
background-clip: border-box;
border: 0 solid rgba(0,0,0,.125);
border-radius: .25rem;
padding-top: 30px;
}
.ui-button.btn-primary, .btn-primary.ui-paginator-element {
background-color: #0152cc;
}
.logo {
display: block;
width: 100px;
margin: 0px auto;
border-radius: 20px;
background: #222;
padding: 10px;
}
.columns {
display: flex;
flex-direction: row;
width: 100%;
}
@media only screen and (max-width: 600px) {
.columns {
display: flex;
flex-direction:row;
flex-wrap: wrap;
width: 100%;
}
.col-md-9{
flex: 30%;
font-size: medium;
margin: 10px;
padding: 10px;
}
}
.column {
flex: 50%;
font-size: medium;
}
.col-md-3{
flex: 30%;
font-size: medium;
margin: 10px;
padding: 10px;
}
.col-md-9{
flex: 70%;
font-size: medium;
margin: 10px;
padding: 10px;
}

68
src/app/pages/admin/Profile/profile.component.ts

@ -1,15 +1,75 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { UserService } from 'app/core/user/user.service';
import { TesoBusinessDetail } from 'app/models/businessModel';
import { TesoUserDetails } from 'app/models/userModel';
import { environment } from 'environments/environment';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { FollowersService } from '../Followers/followers.service';
import { DescriptionDialogComponent } from './DescriptionDialog/description-dialog.component';
import { ProfileService } from './profile.service';
@Component({ @Component({
selector: 'app-profile', selector: 'app-profile',
templateUrl: './profile.component.html', templateUrl: './profile.component.html',
styleUrls: ['./profile.component.scss'] styleUrls: ['./profile.component.scss'],
encapsulation: ViewEncapsulation.ShadowDom,
}) })
export class ProfileComponent implements OnInit { export class ProfileComponent implements OnInit {
profile: TesoBusinessDetail = {};
constructor() { } subscribers:TesoUserDetails[]=[];
newProfilePicture:any;
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(private router: Router, private _tesoMediaWatcherService: tesoMediaWatcherService,
private _profileService: ProfileService, private _userService: UserService,
private _followersService: FollowersService, public dialog: MatDialog,
private confirmBoxEvokeService: ConfirmBoxEvokeService,) { }
ngOnInit(): void { ngOnInit(): void {
this._userService.user$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.profile = d;
});
this._followersService.data$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.subscribers = d;
});
}
imageLoader(path: string): string {
return environment.apiURL + `shoplogo/${path}`;
}
editShopDescription(){
const dialogReference = this.dialog.open(DescriptionDialogComponent,{
disableClose: true,
hasBackdrop: true,
data: { details: this.profile.businessDescription },
});
dialogReference.afterClosed().subscribe((d)=> {
this.profile.businessDescription = d;
});
} }
onFileSelected(event) {
const file: File = event.target.files[0];
if (file.type.includes("image")) {
this.newProfilePicture = {
file: file,
highlight: true,
imageSRC: "",
};
let reader = new FileReader();
reader.onload = (event: any) => {
this.newProfilePicture.imageSRC = event.target.result;
}
reader.readAsDataURL(file);
} else {
this.confirmBoxEvokeService.danger("Incompatible File", "Only images can be added", "OK").subscribe();
}
}
clear(){
this.newProfilePicture={};
}
} }

58
src/app/pages/admin/Profile/profile.module.ts

@ -1,12 +1,66 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { ProfileComponent } from './profile.component';
import { RouterModule } from '@angular/router';
import { QuillModule } from 'ngx-quill';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatRippleModule } from '@angular/material/core';
import { MatDialogModule } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslocoModule } from '@ngneat/transloco';
import { SharedModule } from 'app/shared/shared.module';
import { profileRoutes } from './profile.routing';
import { InformationComponent } from './Information/information.component';
import { SettingsComponent } from './Settings/settings.component';
import { DescriptionDialogComponent } from './DescriptionDialog/description-dialog.component';
import { InformationDialogComponent } from './InfoDialog/information-dialog.component';
@NgModule({ @NgModule({
declarations: [], declarations: [
ProfileComponent,
InformationComponent,
SettingsComponent,
DescriptionDialogComponent,
InformationDialogComponent
],
imports: [ imports: [
CommonModule RouterModule.forChild(profileRoutes),
QuillModule.forRoot(),
CommonModule,
MatButtonModule,
MatButtonToggleModule,
MatDividerModule,
MatIconModule,
MatFormFieldModule,
MatInputModule,
MatMenuModule,
MatProgressBarModule,
MatRippleModule,
MatSidenavModule,
MatSortModule,
MatTableModule,
MatTabsModule,
MatDialogModule,
TranslocoModule,
SharedModule,
MatPaginatorModule,
MatSelectModule,
MatTooltipModule,
] ]
}) })
export class ProfileModule { } export class ProfileModule { }

35
src/app/pages/admin/Profile/profile.routing.ts

@ -0,0 +1,35 @@
import { Route } from '@angular/router';
import { EditProductComponent } from '../Products/EditProduct/edit-product.component';
import { NewProductComponent } from '../Products/NewProduct/new-product.component';
import { ProfileComponent } from './profile.component';
export const profileRoutes: Route[] = [
{
path : '',
component: ProfileComponent,
// resolve : {
// data: DesiresResolver
// },
// children : [
// {
// path : '',
// pathMatch: 'full',
// component: ListDesiresComponent,
// },
// {
// path : 'create-product',
// component: NewProductComponent,
// // resolve : {
// // course: AcademyCourseResolver
// // }
// },
// // {
// // path : ':id',
// // component: EditProductComponent,
// // // resolve : {
// // // course: AcademyCourseResolver
// // // }
// // }
// ]
}
];

64
src/app/pages/admin/Profile/profile.service.ts

@ -0,0 +1,64 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { analytics as analyticsData } from 'app/mock-api/dashboards/analytics/data';
import { environment } from 'environments/environment';
import { BusinessCategory, TesoBusinessDetail } from 'app/models/businessModel';
@Injectable({
providedIn: 'root'
})
export class ProfileService {
private _data: BehaviorSubject<any> = new BehaviorSubject(null);
private business: TesoBusinessDetail[] = [];
private _businessCategory: BehaviorSubject<BusinessCategory[]> = new BehaviorSubject(null);
/**
* Constructor
*/
constructor(private _httpClient: HttpClient) {
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Getter for data
*/
get data$(): Observable<any> {
return this._data.asObservable();
}
get categories$(): Observable<BusinessCategory[]> {
return this._businessCategory.asObservable();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Get data
*/
getData(business: string = ".null."): Observable<any> {
return this._httpClient.get(environment.apiURL + `business/search/filter?businessName=${business}`).pipe(
tap((response: any) => {
this._data.next(response);
this.business = response;
})
);
}
getBusiness(id: string): TesoBusinessDetail {
const found = this.business.find(item => item.businessId === id);
return found;
}
getCategories(): Observable<BusinessCategory[]> {
return this._httpClient.get(environment.apiURL + `api/businesscategories1`).pipe(
tap((response: BusinessCategory[]) => {
this._businessCategory.next(response);
}));
}
}

1
src/app/pages/admin/Settings/settings.component.html

@ -1 +0,0 @@
<p>settings works!</p>

15
src/app/pages/admin/Settings/settings.component.ts

@ -1,15 +0,0 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss']
})
export class SettingsComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

12
src/app/pages/admin/Settings/settings.module.ts

@ -1,12 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
declarations: [],
imports: [
CommonModule
]
})
export class SettingsModule { }

105
src/app/pages/auth/unlock-session/unlock-session.component.html

@ -1,105 +0,0 @@
<div class="flex flex-col sm:flex-row items-center md:items-start sm:justify-center md:justify-start flex-auto min-w-0">
<div class="md:flex md:items-center md:justify-end w-full sm:w-auto md:h-full md:w-1/2 py-8 px-4 sm:p-12 md:p-16 sm:rounded-2xl md:rounded-none sm:shadow md:shadow-none sm:bg-card">
<div class="w-full max-w-80 sm:w-80 mx-auto sm:mx-0">
<!-- Logo -->
<div class="w-12">
<img src="assets/images/logo/logo.svg">
</div>
<!-- Title -->
<div class="mt-8 text-4xl font-extrabold tracking-tight leading-tight">Unlock your session</div>
<div class="mt-0.5 font-medium">Your session is locked due to inactivity</div>
<!-- Alert -->
<teso-alert class="mt-8 -mb-4" *ngIf="showAlert" [appearance]="'outline'" [showIcon]="false" [type]="alert.type" [@shake]="alert.type === 'error'">
{{alert.message}}
</teso-alert>
<!-- Unlock form -->
<form class="mt-8" [formGroup]="unlockSessionForm" #unlockSessionNgForm="ngForm">
<!-- Name field -->
<mat-form-field class="w-full">
<mat-label>Full name</mat-label>
<input id="name" matInput [formControlName]="'name'">
</mat-form-field>
<!-- Password field -->
<mat-form-field class="w-full">
<mat-label>Password</mat-label>
<input id="password" matInput type="password" [formControlName]="'password'" #passwordField>
<button mat-icon-button type="button" (click)="passwordField.type === 'password' ? passwordField.type = 'text' : passwordField.type = 'password'" matSuffix>
<mat-icon
class="icon-size-5"
*ngIf="passwordField.type === 'password'"
[svgIcon]="'heroicons_solid:eye'"></mat-icon>
<mat-icon
class="icon-size-5"
*ngIf="passwordField.type === 'text'"
[svgIcon]="'heroicons_solid:eye-off'"></mat-icon>
</button>
<mat-error>
Password is required
</mat-error>
</mat-form-field>
<!-- Submit button -->
<button class="teso-mat-button-large w-full mt-3" mat-flat-button [color]="'primary'" [disabled]="unlockSessionForm.disabled" (click)="unlock()">
<span *ngIf="!unlockSessionForm.disabled">
Unlock your session
</span>
<mat-progress-spinner
*ngIf="unlockSessionForm.disabled"
[diameter]="24"
[mode]="'indeterminate'"></mat-progress-spinner>
</button>
<!-- Form footer -->
<div class="mt-8 text-md font-medium text-secondary">
<span>I'm not</span>
<a class="ml-1 text-primary-500 hover:underline" [routerLink]="['/sign-out']">{{name}}</a>
</div>
</form>
</div>
</div>
<div class="relative hidden md:flex flex-auto items-center justify-center w-1/2 h-full p-16 lg:px-28 overflow-hidden bg-gray-800 dark:border-l">
<!-- Background - @formatter:off -->
<!-- Rings -->
<svg class="absolute inset-0 pointer-events-none" viewBox="0 0 960 540" width="100%" height="100%" preserveAspectRatio="xMidYMax slice" xmlns="http://www.w3.org/2000/svg">
<g class="text-gray-700 opacity-25" fill="none" stroke="currentColor" stroke-width="100">
<circle r="234" cx="196" cy="23"></circle>
<circle r="234" cx="790" cy="491"></circle>
</g>
</svg>
<!-- Dots -->
<svg class="absolute -top-16 -right-16 text-gray-700" viewBox="0 0 220 192" width="220" height="192" fill="none">
<defs>
<pattern id="837c3e70-6c3a-44e6-8854-cc48c737b659" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="4" height="4" fill="currentColor"></rect>
</pattern>
</defs>
<rect width="220" height="192" fill="url(#837c3e70-6c3a-44e6-8854-cc48c737b659)"></rect>
</svg>
<!-- @formatter:on -->
<!-- Content -->
<div class="z-10 relative w-full max-w-2xl">
<div class="text-7xl font-bold leading-none text-gray-100">
<div>Welcome to</div>
<div>our community</div>
</div>
<div class="mt-6 text-lg tracking-tight leading-6 text-gray-400">
teso helps developers to build organized and well coded dashboards full of beautiful and rich modules. Join us and start building your application today.
</div>
<div class="flex items-center mt-8">
<div class="flex flex-0 items-center -space-x-1.5">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/female-18.jpg">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/female-11.jpg">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/male-09.jpg">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/male-16.jpg">
</div>
<div class="ml-4 font-medium tracking-tight text-gray-400">More than 17k people joined us, it's your turn</div>
</div>
</div>
</div>
</div>

129
src/app/pages/auth/unlock-session/unlock-session.component.ts

@ -1,129 +0,0 @@
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { tesoAnimations } from '@teso/animations';
import { AuthService } from 'app/core/auth/auth.service';
import { UserService } from 'app/core/user/user.service';
import { tesoAlertType } from '@teso/components/alert';
@Component({
selector : 'auth-unlock-session',
templateUrl : './unlock-session.component.html',
encapsulation: ViewEncapsulation.None,
animations : tesoAnimations
})
export class AuthUnlockSessionComponent implements OnInit
{
@ViewChild('unlockSessionNgForm') unlockSessionNgForm: NgForm;
alert: { type: tesoAlertType; message: string } = {
type : 'success',
message: ''
};
name: string;
showAlert: boolean = false;
unlockSessionForm: FormGroup;
private _email: string;
/**
* Constructor
*/
constructor(
private _activatedRoute: ActivatedRoute,
private _authService: AuthService,
private _formBuilder: FormBuilder,
private _router: Router,
private _userService: UserService
)
{
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void
{
// Get the user's name
this._userService.user$.subscribe((user) => {
this.name = user.name;
this._email = user.email;
});
// Create the form
this.unlockSessionForm = this._formBuilder.group({
name : [
{
value : this.name,
disabled: true
}
],
password: ['', Validators.required]
});
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Unlock
*/
unlock(): void
{
// Return if the form is invalid
if ( this.unlockSessionForm.invalid )
{
return;
}
// Disable the form
this.unlockSessionForm.disable();
// Hide the alert
this.showAlert = false;
this._authService.unlockSession({
email : this._email ?? '',
password: this.unlockSessionForm.get('password').value
}).subscribe(
() => {
// Set the redirect url.
// The '/signed-in-redirect' is a dummy url to catch the request and redirect the user
// to the correct page after a successful sign in. This way, that url can be set via
// routing file and we don't have to touch here.
const redirectURL = this._activatedRoute.snapshot.queryParamMap.get('redirectURL') || '/signed-in-redirect';
// Navigate to the redirect url
this._router.navigateByUrl(redirectURL);
},
(response) => {
// Re-enable the form
this.unlockSessionForm.enable();
// Reset the form
this.unlockSessionNgForm.resetForm({
name: {
value : this.name,
disabled: true
}
});
// Set the alert
this.alert = {
type : 'error',
message: 'Invalid password'
};
// Show the alert
this.showAlert = true;
}
);
}
}

32
src/app/pages/auth/unlock-session/unlock-session.module.ts

@ -1,32 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { tesoCardModule } from '@teso/components/card';
import { tesoAlertModule } from '@teso/components/alert';
import { SharedModule } from 'app/shared/shared.module';
import { AuthUnlockSessionComponent } from 'app/pages/auth/unlock-session/unlock-session.component';
import { authUnlockSessionRoutes } from 'app/pages/auth/unlock-session/unlock-session.routing';
@NgModule({
declarations: [
AuthUnlockSessionComponent
],
imports : [
RouterModule.forChild(authUnlockSessionRoutes),
MatButtonModule,
MatFormFieldModule,
MatIconModule,
MatInputModule,
MatProgressSpinnerModule,
tesoCardModule,
tesoAlertModule,
SharedModule
]
})
export class AuthUnlockSessionModule
{
}

9
src/app/pages/auth/unlock-session/unlock-session.routing.ts

@ -1,9 +0,0 @@
import { Route } from '@angular/router';
import { AuthUnlockSessionComponent } from 'app/pages/auth/unlock-session/unlock-session.component';
export const authUnlockSessionRoutes: Route[] = [
{
path : '',
component: AuthUnlockSessionComponent
}
];

13
src/app/pipes/coupon-worth.pipe.ts

@ -0,0 +1,13 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'couponWorth'
})
export class CouponWorthPipe implements PipeTransform {
transform(value: number,rate:number): string {
var worth = (rate * value)/100;
return `GH¢ ${worth}`;
}
}

18
src/app/pipes/productDescriptionShort.pipe.ts

@ -0,0 +1,18 @@
import { Pipe, PipeTransform } from "@angular/core";
@Pipe({
name: 'productDescShort'
})
export class ProductDescriptionShort implements PipeTransform {
constructor(
) { }
transform(value: string): string {
if(value.length > 57){
return value.substring(0, 58) + "....";
}else{
return value;
}
}
}

BIN
src/assets/images/300.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

3
src/environments/environment.ts

@ -3,7 +3,8 @@
// The list of file replacements can be found in `angular.json`. // The list of file replacements can be found in `angular.json`.
export const environment = { export const environment = {
production: false production: false,
apiURL:"https://test.tesoapp.com/"
}; };
/* /*

Loading…
Cancel
Save