From 23d666768c164da78a044950791bc7703097b9a2 Mon Sep 17 00:00:00 2001 From: JUNG YI DAM Date: Wed, 13 Jul 2022 04:50:42 +0000 Subject: [PATCH 1/6] =?UTF-8?q?=ED=8C=8C=ED=8A=B8=EB=84=88=20=EB=A7=A4?= =?UTF-8?q?=EC=9E=A5=20page=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/app.routing.ts | 7 + .../mock-api/apps/member/partner-store/api.ts | 221 +++++++++++ .../apps/member/partner-store/data.ts | 33 ++ src/app/mock-api/common/navigation/data.ts | 7 + src/app/mock-api/index.ts | 2 + .../member/partner-store/components/index.ts | 3 + .../components/list.component.html | 356 ++++++++++++++++++ .../components/list.component.ts | 198 ++++++++++ .../models/partner-store-pagination.ts | 8 + .../partner-store/models/partner-store.ts | 29 ++ .../partner-store/partner-store.module.ts | 50 +++ .../partner-store/partner-store.routing.ts | 24 ++ .../resolvers/partner-store.resolver.ts | 89 +++++ .../services/partner-store.service.ts | 158 ++++++++ src/assets/i18n/en.json | 1 + src/assets/i18n/ko.json | 1 + 16 files changed, 1187 insertions(+) create mode 100644 src/app/mock-api/apps/member/partner-store/api.ts create mode 100644 src/app/mock-api/apps/member/partner-store/data.ts create mode 100644 src/app/modules/admin/member/partner-store/components/index.ts create mode 100644 src/app/modules/admin/member/partner-store/components/list.component.html create mode 100644 src/app/modules/admin/member/partner-store/components/list.component.ts create mode 100644 src/app/modules/admin/member/partner-store/models/partner-store-pagination.ts create mode 100644 src/app/modules/admin/member/partner-store/models/partner-store.ts create mode 100644 src/app/modules/admin/member/partner-store/partner-store.module.ts create mode 100644 src/app/modules/admin/member/partner-store/partner-store.routing.ts create mode 100644 src/app/modules/admin/member/partner-store/resolvers/partner-store.resolver.ts create mode 100644 src/app/modules/admin/member/partner-store/services/partner-store.service.ts diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index 9e0c0c7..3900260 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -206,6 +206,13 @@ export const appRoutes: Route[] = [ 'app/modules/admin/member/partner-office/partner-office.module' ).then((m: any) => m.PartnerOfficeModule), }, + { + path: 'partner-store', + loadChildren: () => + import( + 'app/modules/admin/member/partner-store/partner-store.module' + ).then((m: any) => m.PartnerStoreModule), + }, ], }, { diff --git a/src/app/mock-api/apps/member/partner-store/api.ts b/src/app/mock-api/apps/member/partner-store/api.ts new file mode 100644 index 0000000..c35ba8c --- /dev/null +++ b/src/app/mock-api/apps/member/partner-store/api.ts @@ -0,0 +1,221 @@ +import { Injectable } from '@angular/core'; +import { assign, cloneDeep } from 'lodash-es'; +import { FuseMockApiService, FuseMockApiUtils } from '@fuse/lib/mock-api'; +import { partnerStores as partnerStoresData } from './data'; + +@Injectable({ + providedIn: 'root', +}) +export class MemberPartnerStoreMockApi { + private _partnerStores: any[] = partnerStoresData; + + /** + * Constructor + */ + constructor(private _fuseMockApiService: FuseMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ PartnerStores - GET + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onGet('api/apps/member/partner-store/partner-stores', 300) + .reply(({ request }) => { + // Get available queries + const search = request.params.get('search'); + const sort = request.params.get('sort') || 'name'; + const order = request.params.get('order') || 'asc'; + const page = parseInt(request.params.get('page') ?? '1', 10); + const size = parseInt(request.params.get('size') ?? '10', 10); + + // Clone the partnerStores + let partnerStores: any[] | null = cloneDeep(this._partnerStores); + + // Sort the partnerStores + if (sort === 'sku' || sort === 'name' || sort === 'active') { + partnerStores.sort((a, b) => { + const fieldA = a[sort].toString().toUpperCase(); + const fieldB = b[sort].toString().toUpperCase(); + return order === 'asc' + ? fieldA.localeCompare(fieldB) + : fieldB.localeCompare(fieldA); + }); + } else { + partnerStores.sort((a, b) => + order === 'asc' ? a[sort] - b[sort] : b[sort] - a[sort] + ); + } + + // If search exists... + if (search) { + // Filter the partnerStores + partnerStores = partnerStores.filter( + (contact: any) => + contact.name && + contact.name.toLowerCase().includes(search.toLowerCase()) + ); + } + + // Paginate - Start + const partnerStoresLength = partnerStores.length; + + // Calculate pagination details + const begin = page * size; + const end = Math.min(size * (page + 1), partnerStoresLength); + const lastPage = Math.max(Math.ceil(partnerStoresLength / size), 1); + + // Prepare the pagination object + let pagination = {}; + + // If the requested page number is bigger than + // the last possible page number, return null for + // partnerStores but also send the last possible page so + // the app can navigate to there + if (page > lastPage) { + partnerStores = null; + pagination = { + lastPage, + }; + } else { + // Paginate the results by size + partnerStores = partnerStores.slice(begin, end); + + // Prepare the pagination mock-api + pagination = { + length: partnerStoresLength, + size: size, + page: page, + lastPage: lastPage, + startIndex: begin, + endIndex: end - 1, + }; + } + + // Return the response + return [ + 200, + { + partnerStores, + pagination, + }, + ]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ PartnerStore - GET + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onGet('api/apps/member/partne-store/partner-store') + .reply(({ request }) => { + // Get the id from the params + const id = request.params.get('id'); + + // Clone the partnerStores + const partnerStores = cloneDeep(this._partnerStores); + + // Find the partnerStore + const partnerStore = partnerStores.find((item: any) => item.id === id); + + // Return the response + return [200, partnerStore]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ PartnerStore - POST + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onPost('api/apps/member/partner-store/partner-store') + .reply(() => { + // Generate a new partnerStore + const newPartnerStore = { + id: FuseMockApiUtils.guid(), + category: '', + name: 'A New User', + description: '', + tags: [], + sku: '', + barcode: '', + brand: '', + vendor: '', + stock: '', + reserved: '', + cost: '', + basePrice: '', + taxPercent: '', + price: '', + weight: '', + thumbnail: '', + images: [], + active: false, + }; + + // Unshift the new partnerStore + this._partnerStores.unshift(newPartnerStore); + + // Return the response + return [200, newPartnerStore]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ PartnerStore - PATCH + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onPatch('api/apps/member/partner-store/partner-store') + .reply(({ request }) => { + // Get the id and partnerStore + const id = request.body.id; + const partnerStore = cloneDeep(request.body.partnerStore); + + // Prepare the updated partnerStore + let updatedPartnerStore = null; + + // Find the partnerStore and update it + this._partnerStores.forEach((item, index, partnerStores) => { + if (item.id === id) { + // Update the partnerStore + partnerStores[index] = assign( + {}, + partnerStores[index], + partnerStore + ); + + // Store the updated partnerStore + updatedPartnerStore = partnerStores[index]; + } + }); + + // Return the response + return [200, updatedPartnerStore]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ PartnerStore - DELETE + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onDelete('api/apps/member/partner-store/partner-store') + .reply(({ request }) => { + // Get the id + const id = request.params.get('id'); + + // Find the partnerStore and delete it + this._partnerStores.forEach((item, index) => { + if (item.id === id) { + this._partnerStores.splice(index, 1); + } + }); + + // Return the response + return [200, true]; + }); + } +} diff --git a/src/app/mock-api/apps/member/partner-store/data.ts b/src/app/mock-api/apps/member/partner-store/data.ts new file mode 100644 index 0000000..60dc6e4 --- /dev/null +++ b/src/app/mock-api/apps/member/partner-store/data.ts @@ -0,0 +1,33 @@ +/* eslint-disable */ + +export const partnerStores = [ + { + id: 'on00', + totalPartnerCount: '5', + totalHoldingMoney: 303675, + totalComp: 108933, + total: 412608, + branchCount: 1, + divisionCount: 1, + officeCount: 1, + storeCount: 1, + memberCount: 1, + nickname: 'on00', + accountHolder: '11', + phoneNumber: '010-1111-1111', + calculateType: '롤링', + ownCash: 50000, + ownComp: 1711, + ownCoupon: 50000, + gameMoney: 0, + todayComp: 0, + totalDeposit: 0, + totalWithdraw: 0, + balance: 0, + registDate: '2022-06-12 15:38', + finalSigninDate: '', + ip: '', + state: '정상', + note: '회원등록', + }, +]; diff --git a/src/app/mock-api/common/navigation/data.ts b/src/app/mock-api/common/navigation/data.ts index d7bbdce..0910ae4 100644 --- a/src/app/mock-api/common/navigation/data.ts +++ b/src/app/mock-api/common/navigation/data.ts @@ -102,6 +102,13 @@ export const defaultNavigation: FuseNavigationItem[] = [ icon: 'heroicons_outline:academic-cap', link: '/member/partner-office', }, + { + id: 'member.partner-store', + title: 'Partner Store', + type: 'basic', + icon: 'heroicons_outline:academic-cap', + link: '/member/partner-store', + }, ], }, { diff --git a/src/app/mock-api/index.ts b/src/app/mock-api/index.ts index 4449d8e..53cf5d6 100644 --- a/src/app/mock-api/index.ts +++ b/src/app/mock-api/index.ts @@ -19,6 +19,7 @@ import { MemberPartnerMockApi } from './apps/member/partner/api'; import { MemberPartnerBranchMockApi } from './apps/member/partner-branch/api'; import { MemberPartnerDivisionMockApi } from './apps/member/partner-division/api'; import { MemberPartnerOfficeMockApi } from './apps/member/partner-office/api'; +import { MemberPartnerStoreMockApi } from './apps/member/partner-store/api'; import { MessagesMockApi } from 'app/mock-api/common/messages/api'; import { NavigationMockApi } from 'app/mock-api/common/navigation/api'; import { NotesMockApi } from 'app/mock-api/apps/notes/api'; @@ -58,6 +59,7 @@ export const mockApiServices = [ MemberPartnerBranchMockApi, MemberPartnerDivisionMockApi, MemberPartnerOfficeMockApi, + MemberPartnerStoreMockApi, MessagesMockApi, NavigationMockApi, NotesMockApi, diff --git a/src/app/modules/admin/member/partner-store/components/index.ts b/src/app/modules/admin/member/partner-store/components/index.ts new file mode 100644 index 0000000..04759eb --- /dev/null +++ b/src/app/modules/admin/member/partner-store/components/index.ts @@ -0,0 +1,3 @@ +import { ListComponent } from './list.component'; + +export const COMPONENTS = [ListComponent]; diff --git a/src/app/modules/admin/member/partner-store/components/list.component.html b/src/app/modules/admin/member/partner-store/components/list.component.html new file mode 100644 index 0000000..84fc25d --- /dev/null +++ b/src/app/modules/admin/member/partner-store/components/list.component.html @@ -0,0 +1,356 @@ +
+ +
+ +
+ +
+ +
매장
+ +
+ + + + + + + 40 + 60 + 80 + 100 + + + + + LV.1 + LV.2 + LV.3 + LV.4 + + + + + 정상 + 대기 + 탈퇴 + 휴면 + 블랙 + 정지 + + + + + 카지노제한 + 슬롯제한 + + + + + 계좌입금 + + + + + 카지노콤프 + 슬롯콤프 + 배팅콤프 + 첫충콤프 + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+ + +
+ There are no partner stores! +
+
+
+
+
diff --git a/src/app/modules/admin/member/partner-store/components/list.component.ts b/src/app/modules/admin/member/partner-store/components/list.component.ts new file mode 100644 index 0000000..561bb85 --- /dev/null +++ b/src/app/modules/admin/member/partner-store/components/list.component.ts @@ -0,0 +1,198 @@ +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnDestroy, + OnInit, + ViewChild, + ViewEncapsulation, +} from '@angular/core'; +import { + FormBuilder, + FormControl, + FormGroup, + Validators, +} from '@angular/forms'; +import { MatCheckboxChange } from '@angular/material/checkbox'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { + debounceTime, + map, + merge, + Observable, + Subject, + switchMap, + takeUntil, +} from 'rxjs'; +import { fuseAnimations } from '@fuse/animations'; +import { FuseConfirmationService } from '@fuse/services/confirmation'; + +import { User } from '../../user/models/user'; +import { PartnerStore } from '../models/partner-store'; +import { PartnerStorePagination } from '../models/partner-store-pagination'; +import { PartnerStoreService } from '../services/partner-store.service'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'partner-store-list', + templateUrl: './list.component.html', + styles: [ + /* language=SCSS */ + ` + .inventory-grid { + grid-template-columns: 60px auto 40px; + + @screen sm { + grid-template-columns: 60px 60px 60px 60px 60px 60px auto 60px; + } + + @screen md { + grid-template-columns: 60px 60px 60px 60px 60px 60px auto 60px 60px; + } + + @screen lg { + grid-template-columns: 60px 70px 70px 70px 70px 100px 60px 60px auto 60px 60px 60px 60px; + } + } + `, + ], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + animations: fuseAnimations, +}) +export class ListComponent implements OnInit, AfterViewInit, OnDestroy { + @ViewChild(MatPaginator) private _paginator!: MatPaginator; + @ViewChild(MatSort) private _sort!: MatSort; + + partnerStores$!: Observable; + users$!: Observable; + + isLoading = false; + searchInputControl = new FormControl(); + selectedPartnerStore?: PartnerStore; + pagination?: PartnerStorePagination; + + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor( + private _changeDetectorRef: ChangeDetectorRef, + private _fuseConfirmationService: FuseConfirmationService, + private _formBuilder: FormBuilder, + private _partnerStoreService: PartnerStoreService, + private router: Router + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Get the pagination + this._partnerStoreService.pagination$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((pagination: PartnerStorePagination | undefined) => { + // Update the pagination + this.pagination = pagination; + + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + + // Get the products + this.partnerStores$ = this._partnerStoreService.partnerStores$; + } + + /** + * After view init + */ + ngAfterViewInit(): void { + if (this._sort && this._paginator) { + // Set the initial sort + this._sort.sort({ + id: 'name', + start: 'asc', + disableClear: true, + }); + + // Mark for check + this._changeDetectorRef.markForCheck(); + + // If the partnerStore changes the sort order... + this._sort.sortChange + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(() => { + // Reset back to the first page + this._paginator.pageIndex = 0; + }); + + // Get products if sort or page changes + merge(this._sort.sortChange, this._paginator.page) + .pipe( + switchMap(() => { + this.isLoading = true; + return this._partnerStoreService.getPartnerStores( + this._paginator.pageIndex, + this._paginator.pageSize, + this._sort.active, + this._sort.direction + ); + }), + map(() => { + this.isLoading = false; + }) + ) + .subscribe(); + } + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + viewUserDetail(id: string): void { + let url: string = 'member/user/' + id; + this.router.navigateByUrl(url); + } + // ----------------------------------------------------------------------------------------------------- + // @ Private methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Create product + */ + __createProduct(): void {} + + /** + * Toggle product details + * + * @param productId + */ + __toggleDetails(productId: string): void {} + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + __trackByFn(index: number, item: any): any { + return item.id || index; + } +} diff --git a/src/app/modules/admin/member/partner-store/models/partner-store-pagination.ts b/src/app/modules/admin/member/partner-store/models/partner-store-pagination.ts new file mode 100644 index 0000000..3e723c7 --- /dev/null +++ b/src/app/modules/admin/member/partner-store/models/partner-store-pagination.ts @@ -0,0 +1,8 @@ +export interface PartnerStorePagination { + length: number; + size: number; + page: number; + lastPage: number; + startIndex: number; + endIndex: number; +} diff --git a/src/app/modules/admin/member/partner-store/models/partner-store.ts b/src/app/modules/admin/member/partner-store/models/partner-store.ts new file mode 100644 index 0000000..048280e --- /dev/null +++ b/src/app/modules/admin/member/partner-store/models/partner-store.ts @@ -0,0 +1,29 @@ +export interface PartnerStore { + id?: string; + totalPartnerCount?: number; + totalHoldingMoney?: number; + totalComp?: number; + total?: number; + branchCount?: number; + divisionCount?: number; + officeCount?: number; + storeCount?: number; + memberCount?: number; + nickname?: string; + accountHolder?: string; + phoneNumber?: string; + calculateType?: string; + ownCash?: number; + ownComp?: number; + ownCoupon?: number; + gameMoney?: number; + todayComp?: number; + totalDeposit?: number; + totalWithdraw?: number; + balance?: number; + registDate?: string; + finalSigninDate?: string; + ip?: string; + state?: string; + note?: string; +} diff --git a/src/app/modules/admin/member/partner-store/partner-store.module.ts b/src/app/modules/admin/member/partner-store/partner-store.module.ts new file mode 100644 index 0000000..20703db --- /dev/null +++ b/src/app/modules/admin/member/partner-store/partner-store.module.ts @@ -0,0 +1,50 @@ +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 { MatPaginatorModule } from '@angular/material/paginator'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatRippleModule } from '@angular/material/core'; +import { MatSortModule } from '@angular/material/sort'; +import { MatSelectModule } from '@angular/material/select'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatGridListModule } from '@angular/material/grid-list'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatCheckboxModule } from '@angular/material/checkbox'; + +import { TranslocoModule } from '@ngneat/transloco'; + +import { SharedModule } from 'app/shared/shared.module'; + +import { COMPONENTS } from './components'; + +import { partnerStoreRoutes } from './partner-store.routing'; + +@NgModule({ + declarations: [COMPONENTS], + imports: [ + TranslocoModule, + SharedModule, + RouterModule.forChild(partnerStoreRoutes), + + MatButtonModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatPaginatorModule, + MatProgressBarModule, + MatRippleModule, + MatSortModule, + MatSelectModule, + MatTooltipModule, + MatGridListModule, + MatSlideToggleModule, + MatRadioModule, + MatCheckboxModule, + ], +}) +export class PartnerStoreModule {} diff --git a/src/app/modules/admin/member/partner-store/partner-store.routing.ts b/src/app/modules/admin/member/partner-store/partner-store.routing.ts new file mode 100644 index 0000000..ffa65be --- /dev/null +++ b/src/app/modules/admin/member/partner-store/partner-store.routing.ts @@ -0,0 +1,24 @@ +import { Route } from '@angular/router'; + +import { ListComponent } from './components/list.component'; +import { ViewComponent } from '../user/components/view.component'; + +import { PartnerStoresResolver } from './resolvers/partner-store.resolver'; +import { UserResolver } from '../user/resolvers/user.resolver'; + +export const partnerStoreRoutes: Route[] = [ + { + path: '', + component: ListComponent, + resolve: { + partnerStores: PartnerStoresResolver, + }, + }, + { + path: ':id', + component: ViewComponent, + resolve: { + users: UserResolver, + }, + }, +]; diff --git a/src/app/modules/admin/member/partner-store/resolvers/partner-store.resolver.ts b/src/app/modules/admin/member/partner-store/resolvers/partner-store.resolver.ts new file mode 100644 index 0000000..42e6c18 --- /dev/null +++ b/src/app/modules/admin/member/partner-store/resolvers/partner-store.resolver.ts @@ -0,0 +1,89 @@ +import { Injectable } from '@angular/core'; +import { + ActivatedRouteSnapshot, + Resolve, + Router, + RouterStateSnapshot, +} from '@angular/router'; +import { catchError, Observable, throwError } from 'rxjs'; + +import { PartnerStore } from '../models/partner-store'; +import { PartnerStorePagination } from '../models/partner-store-pagination'; +import { PartnerStoreService } from '../services/partner-store.service'; + +@Injectable({ + providedIn: 'root', +}) +export class PartnerStoreResolver implements Resolve { + /** + * Constructor + */ + constructor( + private _partnerStoreService: PartnerStoreService, + private _router: Router + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Resolver + * + * @param route + * @param state + */ + resolve( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable { + return this._partnerStoreService + .getPartnerStoreById(route.paramMap.get('id')) + .pipe( + // Error here means the requested product is not available + catchError((error) => { + // Log the error + console.error(error); + + // Get the parent url + const parentUrl = state.url.split('/').slice(0, -1).join('/'); + + // Navigate to there + this._router.navigateByUrl(parentUrl); + + // Throw an error + return throwError(error); + }) + ); + } +} + +@Injectable({ + providedIn: 'root', +}) +export class PartnerStoresResolver implements Resolve { + /** + * Constructor + */ + constructor(private _partnerStoreService: PartnerStoreService) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Resolver + * + * @param route + * @param state + */ + resolve( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable<{ + pagination: PartnerStorePagination; + partnerStores: PartnerStore[]; + }> { + return this._partnerStoreService.getPartnerStores(); + } +} diff --git a/src/app/modules/admin/member/partner-store/services/partner-store.service.ts b/src/app/modules/admin/member/partner-store/services/partner-store.service.ts new file mode 100644 index 0000000..c0780d6 --- /dev/null +++ b/src/app/modules/admin/member/partner-store/services/partner-store.service.ts @@ -0,0 +1,158 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { + BehaviorSubject, + filter, + map, + Observable, + of, + switchMap, + take, + tap, + throwError, +} from 'rxjs'; + +import { PartnerStore } from '../models/partner-store'; +import { PartnerStorePagination } from '../models/partner-store-pagination'; + +@Injectable({ + providedIn: 'root', +}) +export class PartnerStoreService { + // Private + private __pagination = new BehaviorSubject< + PartnerStorePagination | undefined + >(undefined); + private __partnerStore = new BehaviorSubject( + undefined + ); + private __partnerStores = new BehaviorSubject( + undefined + ); + + /** + * Constructor + */ + constructor(private _httpClient: HttpClient) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Getter for pagination + */ + get pagination$(): Observable { + return this.__pagination.asObservable(); + } + + /** + * Getter for partnerStore + */ + get partnerStore$(): Observable { + return this.__partnerStore.asObservable(); + } + + /** + * Getter for partnerStores + */ + get partnerStores$(): Observable { + return this.__partnerStores.asObservable(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Get partnerStores + * + * + * @param page + * @param size + * @param sort + * @param order + * @param search + */ + getPartnerStores( + page: number = 0, + size: number = 10, + sort: string = 'name', + order: 'asc' | 'desc' | '' = 'asc', + search: string = '' + ): Observable<{ + pagination: PartnerStorePagination; + partnerStores: PartnerStore[]; + }> { + return this._httpClient + .get<{ + pagination: PartnerStorePagination; + partnerStores: PartnerStore[]; + }>('api/apps/member/partner-store/partner-stores', { + params: { + page: '' + page, + size: '' + size, + sort, + order, + search, + }, + }) + .pipe( + tap((response) => { + this.__pagination.next(response.pagination); + this.__partnerStores.next(response.partnerStores); + }) + ); + } + + /** + * Get product by id + */ + getPartnerStoreById(id: string | null): Observable { + return this.__partnerStores.pipe( + take(1), + map((partnerStores) => { + // Find the product + const partnerStore = + partnerStores?.find((item) => item.id === id) || undefined; + + // Update the product + this.__partnerStore.next(partnerStore); + + // Return the product + return partnerStore; + }), + switchMap((product) => { + if (!product) { + return throwError('Could not found product with id of ' + id + '!'); + } + + return of(product); + }) + ); + } + + /** + * Create product + */ + createPartnerStore(): Observable { + return this.partnerStores$.pipe( + take(1), + switchMap((partnerStores) => + this._httpClient + .post('api/apps/member/partner-store/product', {}) + .pipe( + map((newPartnerStore) => { + // Update the partnerStores with the new product + if (!!partnerStores) { + this.__partnerStores.next([newPartnerStore, ...partnerStores]); + } + + // Return the new product + return newPartnerStore; + }) + ) + ) + ); + } +} diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 2c877da..6c465f7 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -11,6 +11,7 @@ "Partner Branch": "Partner Branch", "Partner Division": "Partner Division", "Partner Office": "Partner Office", + "Partner Store": "Partner Store", "Analytics": "Analytics", "Deposit": "Deposit", "Withdraw": "Withdraw", diff --git a/src/assets/i18n/ko.json b/src/assets/i18n/ko.json index 31ece60..05017bf 100644 --- a/src/assets/i18n/ko.json +++ b/src/assets/i18n/ko.json @@ -11,6 +11,7 @@ "Partner Branch": "대본", "Partner Division": "부본", "Partner Office": "총판", + "Partner Store": "매장", "Analytics": "Analytics", "Deposit": "입금관리", "Withdraw": "출금관리", From 08cf1f1f97d56a0c09121b32f80e0794d397488b Mon Sep 17 00:00:00 2001 From: JUNG YI DAM Date: Wed, 13 Jul 2022 05:08:04 +0000 Subject: [PATCH 2/6] bug fix --- .../deposit/components/list.component.html | 66 +++++-------------- .../withdraw/components/list.component.html | 50 ++++---------- .../casino/components/list.component.html | 36 +++------- .../evolution/components/list.component.html | 40 ++++------- .../powerball/components/list.component.html | 36 +++------- .../game/slot/components/list.component.html | 40 ++++------- .../components/list.component.html | 46 ++++--------- .../components/list.component.html | 40 +++++------ .../components/list.component.html | 40 +++++------ .../components/list.component.html | 36 +++++----- .../components/list.component.html | 40 +++++------ .../partner/components/list.component.html | 36 +++++----- .../components/list.component.html | 66 ++++++------------- 13 files changed, 193 insertions(+), 379 deletions(-) diff --git a/src/app/modules/admin/bank/deposit/components/list.component.html b/src/app/modules/admin/bank/deposit/components/list.component.html index 32ae528..c2b0fec 100644 --- a/src/app/modules/admin/bank/deposit/components/list.component.html +++ b/src/app/modules/admin/bank/deposit/components/list.component.html @@ -85,55 +85,23 @@ matSortDisableClear >
- - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/src/app/modules/admin/member/partner-branch/components/list.component.html b/src/app/modules/admin/member/partner-branch/components/list.component.html index 1a04a5e..e279fa6 100644 --- a/src/app/modules/admin/member/partner-branch/components/list.component.html +++ b/src/app/modules/admin/member/partner-branch/components/list.component.html @@ -162,28 +162,24 @@ > - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/src/app/modules/admin/member/partner-division/components/list.component.html b/src/app/modules/admin/member/partner-division/components/list.component.html index a70e83c..4847b7c 100644 --- a/src/app/modules/admin/member/partner-division/components/list.component.html +++ b/src/app/modules/admin/member/partner-division/components/list.component.html @@ -164,28 +164,24 @@ > - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/src/app/modules/admin/member/partner-mainoffice/components/list.component.html b/src/app/modules/admin/member/partner-mainoffice/components/list.component.html index 241793f..1b2bc42 100644 --- a/src/app/modules/admin/member/partner-mainoffice/components/list.component.html +++ b/src/app/modules/admin/member/partner-mainoffice/components/list.component.html @@ -164,28 +164,26 @@ > - - - - - - - - - - - + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/src/app/modules/admin/member/partner/components/list.component.html b/src/app/modules/admin/member/partner/components/list.component.html index 380355d..32ec3fd 100644 --- a/src/app/modules/admin/member/partner/components/list.component.html +++ b/src/app/modules/admin/member/partner/components/list.component.html @@ -78,24 +78,24 @@ matSortDisableClear >
- - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + From b9735293afe7dc62de1fd566e15aadb3e0184b37 Mon Sep 17 00:00:00 2001 From: JUNG YI DAM Date: Wed, 13 Jul 2022 05:23:31 +0000 Subject: [PATCH 3/6] =?UTF-8?q?=EC=B6=94=EC=B2=9C=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20page=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/app.routing.ts | 7 + .../apps/member/partner-recommendation/api.ts | 235 +++++++++++ .../member/partner-recommendation/data.ts | 33 ++ src/app/mock-api/common/navigation/data.ts | 7 + src/app/mock-api/index.ts | 2 + .../components/index.ts | 3 + .../components/list.component.html | 368 ++++++++++++++++++ .../components/list.component.ts | 199 ++++++++++ .../partner-recommendation-pagination.ts | 8 + .../models/partner-recommendation.ts | 29 ++ .../partner-recommendation.module.ts | 50 +++ .../partner-recommendation.routing.ts | 24 ++ .../partner-recommendation.resolver.ts | 91 +++++ .../partner-recommendation.service.ts | 168 ++++++++ src/assets/i18n/en.json | 1 + src/assets/i18n/ko.json | 1 + 16 files changed, 1226 insertions(+) create mode 100644 src/app/mock-api/apps/member/partner-recommendation/api.ts create mode 100644 src/app/mock-api/apps/member/partner-recommendation/data.ts create mode 100644 src/app/modules/admin/member/partner-recommendation/components/index.ts create mode 100644 src/app/modules/admin/member/partner-recommendation/components/list.component.html create mode 100644 src/app/modules/admin/member/partner-recommendation/components/list.component.ts create mode 100644 src/app/modules/admin/member/partner-recommendation/models/partner-recommendation-pagination.ts create mode 100644 src/app/modules/admin/member/partner-recommendation/models/partner-recommendation.ts create mode 100644 src/app/modules/admin/member/partner-recommendation/partner-recommendation.module.ts create mode 100644 src/app/modules/admin/member/partner-recommendation/partner-recommendation.routing.ts create mode 100644 src/app/modules/admin/member/partner-recommendation/resolvers/partner-recommendation.resolver.ts create mode 100644 src/app/modules/admin/member/partner-recommendation/services/partner-recommendation.service.ts diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index 3900260..5c3a3dc 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -213,6 +213,13 @@ export const appRoutes: Route[] = [ 'app/modules/admin/member/partner-store/partner-store.module' ).then((m: any) => m.PartnerStoreModule), }, + { + path: 'partner-recommendation', + loadChildren: () => + import( + 'app/modules/admin/member/partner-recommendation/partner-recommendation.module' + ).then((m: any) => m.PartnerRecommendationModule), + }, ], }, { diff --git a/src/app/mock-api/apps/member/partner-recommendation/api.ts b/src/app/mock-api/apps/member/partner-recommendation/api.ts new file mode 100644 index 0000000..6411665 --- /dev/null +++ b/src/app/mock-api/apps/member/partner-recommendation/api.ts @@ -0,0 +1,235 @@ +import { Injectable } from '@angular/core'; +import { assign, cloneDeep } from 'lodash-es'; +import { FuseMockApiService, FuseMockApiUtils } from '@fuse/lib/mock-api'; +import { partnerRecommendations as partnerRecommendationsData } from './data'; + +@Injectable({ + providedIn: 'root', +}) +export class MemberPartnerRecommendationMockApi { + private _partnerRecommendations: any[] = partnerRecommendationsData; + + /** + * Constructor + */ + constructor(private _fuseMockApiService: FuseMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ PartnerRecommendations - GET + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onGet( + 'api/apps/member/partner-recommendation/partner-recommendations', + 300 + ) + .reply(({ request }) => { + // Get available queries + const search = request.params.get('search'); + const sort = request.params.get('sort') || 'name'; + const order = request.params.get('order') || 'asc'; + const page = parseInt(request.params.get('page') ?? '1', 10); + const size = parseInt(request.params.get('size') ?? '10', 10); + + // Clone the partnerRecommendations + let partnerRecommendations: any[] | null = cloneDeep( + this._partnerRecommendations + ); + + // Sort the partnerRecommendations + if (sort === 'sku' || sort === 'name' || sort === 'active') { + partnerRecommendations.sort((a, b) => { + const fieldA = a[sort].toString().toUpperCase(); + const fieldB = b[sort].toString().toUpperCase(); + return order === 'asc' + ? fieldA.localeCompare(fieldB) + : fieldB.localeCompare(fieldA); + }); + } else { + partnerRecommendations.sort((a, b) => + order === 'asc' ? a[sort] - b[sort] : b[sort] - a[sort] + ); + } + + // If search exists... + if (search) { + // Filter the partnerRecommendations + partnerRecommendations = partnerRecommendations.filter( + (contact: any) => + contact.name && + contact.name.toLowerCase().includes(search.toLowerCase()) + ); + } + + // Paginate - Start + const partnerRecommendationsLength = partnerRecommendations.length; + + // Calculate pagination details + const begin = page * size; + const end = Math.min(size * (page + 1), partnerRecommendationsLength); + const lastPage = Math.max( + Math.ceil(partnerRecommendationsLength / size), + 1 + ); + + // Prepare the pagination object + let pagination = {}; + + // If the requested page number is bigger than + // the last possible page number, return null for + // partnerRecommendations but also send the last possible page so + // the app can navigate to there + if (page > lastPage) { + partnerRecommendations = null; + pagination = { + lastPage, + }; + } else { + // Paginate the results by size + partnerRecommendations = partnerRecommendations.slice(begin, end); + + // Prepare the pagination mock-api + pagination = { + length: partnerRecommendationsLength, + size: size, + page: page, + lastPage: lastPage, + startIndex: begin, + endIndex: end - 1, + }; + } + + // Return the response + return [ + 200, + { + partnerRecommendations, + pagination, + }, + ]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ PartnerRecommendation - GET + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onGet('api/apps/member/partne-recommendation/partner-recommendation') + .reply(({ request }) => { + // Get the id from the params + const id = request.params.get('id'); + + // Clone the partnerRecommendations + const partnerRecommendations = cloneDeep(this._partnerRecommendations); + + // Find the partnerRecommendation + const partnerRecommendation = partnerRecommendations.find( + (item: any) => item.id === id + ); + + // Return the response + return [200, partnerRecommendation]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ PartnerRecommendation - POST + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onPost('api/apps/member/partner-recommendation/partner-recommendation') + .reply(() => { + // Generate a new partnerRecommendation + const newPartnerRecommendation = { + id: FuseMockApiUtils.guid(), + category: '', + name: 'A New User', + description: '', + tags: [], + sku: '', + barcode: '', + brand: '', + vendor: '', + stock: '', + reserved: '', + cost: '', + basePrice: '', + taxPercent: '', + price: '', + weight: '', + thumbnail: '', + images: [], + active: false, + }; + + // Unshift the new partnerRecommendation + this._partnerRecommendations.unshift(newPartnerRecommendation); + + // Return the response + return [200, newPartnerRecommendation]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ PartnerRecommendation - PATCH + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onPatch('api/apps/member/partner-recommendation/partner-recommendation') + .reply(({ request }) => { + // Get the id and partnerRecommendation + const id = request.body.id; + const partnerRecommendation = cloneDeep( + request.body.partnerRecommendation + ); + + // Prepare the updated partnerRecommendation + let updatedPartnerRecommendation = null; + + // Find the partnerRecommendation and update it + this._partnerRecommendations.forEach( + (item, index, partnerRecommendations) => { + if (item.id === id) { + // Update the partnerRecommendation + partnerRecommendations[index] = assign( + {}, + partnerRecommendations[index], + partnerRecommendation + ); + + // Store the updated partnerRecommendation + updatedPartnerRecommendation = partnerRecommendations[index]; + } + } + ); + + // Return the response + return [200, updatedPartnerRecommendation]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ PartnerRecommendation - DELETE + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onDelete('api/apps/member/partner-recommendation/partner-recommendation') + .reply(({ request }) => { + // Get the id + const id = request.params.get('id'); + + // Find the partnerRecommendation and delete it + this._partnerRecommendations.forEach((item, index) => { + if (item.id === id) { + this._partnerRecommendations.splice(index, 1); + } + }); + + // Return the response + return [200, true]; + }); + } +} diff --git a/src/app/mock-api/apps/member/partner-recommendation/data.ts b/src/app/mock-api/apps/member/partner-recommendation/data.ts new file mode 100644 index 0000000..e9a512b --- /dev/null +++ b/src/app/mock-api/apps/member/partner-recommendation/data.ts @@ -0,0 +1,33 @@ +/* eslint-disable */ + +export const partnerRecommendations = [ + { + id: 'on00', + totalPartnerCount: '5', + totalHoldingMoney: 303675, + totalComp: 108933, + total: 412608, + branchCount: 1, + divisionCount: 1, + officeCount: 1, + storeCount: 1, + memberCount: 1, + nickname: 'on00', + accountHolder: '11', + phoneNumber: '010-1111-1111', + calculateType: '롤링', + ownCash: 50000, + ownComp: 1711, + ownCoupon: 50000, + gameMoney: 0, + todayComp: 0, + totalDeposit: 0, + totalWithdraw: 0, + balance: 0, + registDate: '2022-06-12 15:38', + finalSigninDate: '', + ip: '', + state: '정상', + note: '', + }, +]; diff --git a/src/app/mock-api/common/navigation/data.ts b/src/app/mock-api/common/navigation/data.ts index 0910ae4..69d881f 100644 --- a/src/app/mock-api/common/navigation/data.ts +++ b/src/app/mock-api/common/navigation/data.ts @@ -109,6 +109,13 @@ export const defaultNavigation: FuseNavigationItem[] = [ icon: 'heroicons_outline:academic-cap', link: '/member/partner-store', }, + { + id: 'member.partner-recommendation', + title: 'Partner Recommendation', + type: 'basic', + icon: 'heroicons_outline:academic-cap', + link: '/member/partner-recommendation', + }, ], }, { diff --git a/src/app/mock-api/index.ts b/src/app/mock-api/index.ts index 53cf5d6..da9f986 100644 --- a/src/app/mock-api/index.ts +++ b/src/app/mock-api/index.ts @@ -20,6 +20,7 @@ import { MemberPartnerBranchMockApi } from './apps/member/partner-branch/api'; import { MemberPartnerDivisionMockApi } from './apps/member/partner-division/api'; import { MemberPartnerOfficeMockApi } from './apps/member/partner-office/api'; import { MemberPartnerStoreMockApi } from './apps/member/partner-store/api'; +import { MemberPartnerRecommendationMockApi } from './apps/member/partner-recommendation/api'; import { MessagesMockApi } from 'app/mock-api/common/messages/api'; import { NavigationMockApi } from 'app/mock-api/common/navigation/api'; import { NotesMockApi } from 'app/mock-api/apps/notes/api'; @@ -60,6 +61,7 @@ export const mockApiServices = [ MemberPartnerDivisionMockApi, MemberPartnerOfficeMockApi, MemberPartnerStoreMockApi, + MemberPartnerRecommendationMockApi, MessagesMockApi, NavigationMockApi, NotesMockApi, diff --git a/src/app/modules/admin/member/partner-recommendation/components/index.ts b/src/app/modules/admin/member/partner-recommendation/components/index.ts new file mode 100644 index 0000000..04759eb --- /dev/null +++ b/src/app/modules/admin/member/partner-recommendation/components/index.ts @@ -0,0 +1,3 @@ +import { ListComponent } from './list.component'; + +export const COMPONENTS = [ListComponent]; diff --git a/src/app/modules/admin/member/partner-recommendation/components/list.component.html b/src/app/modules/admin/member/partner-recommendation/components/list.component.html new file mode 100644 index 0000000..f0b2e6e --- /dev/null +++ b/src/app/modules/admin/member/partner-recommendation/components/list.component.html @@ -0,0 +1,368 @@ +
+ +
+ +
+ +
+ +
추천코드등록
+ +
+ + + + + + + 40 + 60 + 80 + 100 + + + + + LV.1 + LV.2 + LV.3 + LV.4 + + + + + 정상 + 대기 + 탈퇴 + 휴면 + 블랙 + 정지 + + + + + 카지노제한 + 슬롯제한 + + + + + 계좌입금 + + + + + 카지노콤프 + 슬롯콤프 + 배팅콤프 + 첫충콤프 + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+ + +
+ There are no partner recommendations! +
+
+
+
+
diff --git a/src/app/modules/admin/member/partner-recommendation/components/list.component.ts b/src/app/modules/admin/member/partner-recommendation/components/list.component.ts new file mode 100644 index 0000000..ce710ab --- /dev/null +++ b/src/app/modules/admin/member/partner-recommendation/components/list.component.ts @@ -0,0 +1,199 @@ +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnDestroy, + OnInit, + ViewChild, + ViewEncapsulation, +} from '@angular/core'; +import { + FormBuilder, + FormControl, + FormGroup, + Validators, +} from '@angular/forms'; +import { MatCheckboxChange } from '@angular/material/checkbox'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { + debounceTime, + map, + merge, + Observable, + Subject, + switchMap, + takeUntil, +} from 'rxjs'; +import { fuseAnimations } from '@fuse/animations'; +import { FuseConfirmationService } from '@fuse/services/confirmation'; + +import { User } from '../../user/models/user'; +import { PartnerRecommendation } from '../models/partner-recommendation'; +import { PartnerRecommendationPagination } from '../models/partner-recommendation-pagination'; +import { PartnerRecommendationService } from '../services/partner-recommendation.service'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'partner-recommendation-list', + templateUrl: './list.component.html', + styles: [ + /* language=SCSS */ + ` + .inventory-grid { + grid-template-columns: 60px auto 40px; + + @screen sm { + grid-template-columns: 60px 60px 60px 60px 60px 60px auto 60px; + } + + @screen md { + grid-template-columns: 60px 60px 60px 60px 60px 60px auto 60px 60px; + } + + @screen lg { + grid-template-columns: 60px 70px 70px 70px 70px 100px 60px 60px auto 60px 60px 60px 60px; + } + } + `, + ], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + animations: fuseAnimations, +}) +export class ListComponent implements OnInit, AfterViewInit, OnDestroy { + @ViewChild(MatPaginator) private _paginator!: MatPaginator; + @ViewChild(MatSort) private _sort!: MatSort; + + partnerRecommendations$!: Observable; + users$!: Observable; + + isLoading = false; + searchInputControl = new FormControl(); + selectedPartnerRecommendation?: PartnerRecommendation; + pagination?: PartnerRecommendationPagination; + + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor( + private _changeDetectorRef: ChangeDetectorRef, + private _fuseConfirmationService: FuseConfirmationService, + private _formBuilder: FormBuilder, + private _partnerRecommendationService: PartnerRecommendationService, + private router: Router + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Get the pagination + this._partnerRecommendationService.pagination$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((pagination: PartnerRecommendationPagination | undefined) => { + // Update the pagination + this.pagination = pagination; + + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + + // Get the products + this.partnerRecommendations$ = + this._partnerRecommendationService.partnerRecommendations$; + } + + /** + * After view init + */ + ngAfterViewInit(): void { + if (this._sort && this._paginator) { + // Set the initial sort + this._sort.sort({ + id: 'name', + start: 'asc', + disableClear: true, + }); + + // Mark for check + this._changeDetectorRef.markForCheck(); + + // If the partnerRecommendation changes the sort order... + this._sort.sortChange + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(() => { + // Reset back to the first page + this._paginator.pageIndex = 0; + }); + + // Get products if sort or page changes + merge(this._sort.sortChange, this._paginator.page) + .pipe( + switchMap(() => { + this.isLoading = true; + return this._partnerRecommendationService.getPartnerRecommendations( + this._paginator.pageIndex, + this._paginator.pageSize, + this._sort.active, + this._sort.direction + ); + }), + map(() => { + this.isLoading = false; + }) + ) + .subscribe(); + } + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + viewUserDetail(id: string): void { + let url: string = 'member/user/' + id; + this.router.navigateByUrl(url); + } + // ----------------------------------------------------------------------------------------------------- + // @ Private methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Create product + */ + __createProduct(): void {} + + /** + * Toggle product details + * + * @param productId + */ + __toggleDetails(productId: string): void {} + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + __trackByFn(index: number, item: any): any { + return item.id || index; + } +} diff --git a/src/app/modules/admin/member/partner-recommendation/models/partner-recommendation-pagination.ts b/src/app/modules/admin/member/partner-recommendation/models/partner-recommendation-pagination.ts new file mode 100644 index 0000000..7b46ebd --- /dev/null +++ b/src/app/modules/admin/member/partner-recommendation/models/partner-recommendation-pagination.ts @@ -0,0 +1,8 @@ +export interface PartnerRecommendationPagination { + length: number; + size: number; + page: number; + lastPage: number; + startIndex: number; + endIndex: number; +} diff --git a/src/app/modules/admin/member/partner-recommendation/models/partner-recommendation.ts b/src/app/modules/admin/member/partner-recommendation/models/partner-recommendation.ts new file mode 100644 index 0000000..568a532 --- /dev/null +++ b/src/app/modules/admin/member/partner-recommendation/models/partner-recommendation.ts @@ -0,0 +1,29 @@ +export interface PartnerRecommendation { + id?: string; + totalPartnerCount?: number; + totalHoldingMoney?: number; + totalComp?: number; + total?: number; + branchCount?: number; + divisionCount?: number; + officeCount?: number; + storeCount?: number; + memberCount?: number; + nickname?: string; + accountHolder?: string; + phoneNumber?: string; + calculateType?: string; + ownCash?: number; + ownComp?: number; + ownCoupon?: number; + gameMoney?: number; + todayComp?: number; + totalDeposit?: number; + totalWithdraw?: number; + balance?: number; + registDate?: string; + finalSigninDate?: string; + ip?: string; + state?: string; + note?: string; +} diff --git a/src/app/modules/admin/member/partner-recommendation/partner-recommendation.module.ts b/src/app/modules/admin/member/partner-recommendation/partner-recommendation.module.ts new file mode 100644 index 0000000..4fa45c3 --- /dev/null +++ b/src/app/modules/admin/member/partner-recommendation/partner-recommendation.module.ts @@ -0,0 +1,50 @@ +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 { MatPaginatorModule } from '@angular/material/paginator'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatRippleModule } from '@angular/material/core'; +import { MatSortModule } from '@angular/material/sort'; +import { MatSelectModule } from '@angular/material/select'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatGridListModule } from '@angular/material/grid-list'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatCheckboxModule } from '@angular/material/checkbox'; + +import { TranslocoModule } from '@ngneat/transloco'; + +import { SharedModule } from 'app/shared/shared.module'; + +import { COMPONENTS } from './components'; + +import { partnerRecommendationRoutes } from './partner-recommendation.routing'; + +@NgModule({ + declarations: [COMPONENTS], + imports: [ + TranslocoModule, + SharedModule, + RouterModule.forChild(partnerRecommendationRoutes), + + MatButtonModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatPaginatorModule, + MatProgressBarModule, + MatRippleModule, + MatSortModule, + MatSelectModule, + MatTooltipModule, + MatGridListModule, + MatSlideToggleModule, + MatRadioModule, + MatCheckboxModule, + ], +}) +export class PartnerRecommendationModule {} diff --git a/src/app/modules/admin/member/partner-recommendation/partner-recommendation.routing.ts b/src/app/modules/admin/member/partner-recommendation/partner-recommendation.routing.ts new file mode 100644 index 0000000..6883352 --- /dev/null +++ b/src/app/modules/admin/member/partner-recommendation/partner-recommendation.routing.ts @@ -0,0 +1,24 @@ +import { Route } from '@angular/router'; + +import { ListComponent } from './components/list.component'; +import { ViewComponent } from '../user/components/view.component'; + +import { PartnerRecommendationsResolver } from './resolvers/partner-recommendation.resolver'; +import { UserResolver } from '../user/resolvers/user.resolver'; + +export const partnerRecommendationRoutes: Route[] = [ + { + path: '', + component: ListComponent, + resolve: { + partnerRecommendations: PartnerRecommendationsResolver, + }, + }, + { + path: ':id', + component: ViewComponent, + resolve: { + users: UserResolver, + }, + }, +]; diff --git a/src/app/modules/admin/member/partner-recommendation/resolvers/partner-recommendation.resolver.ts b/src/app/modules/admin/member/partner-recommendation/resolvers/partner-recommendation.resolver.ts new file mode 100644 index 0000000..aafc6eb --- /dev/null +++ b/src/app/modules/admin/member/partner-recommendation/resolvers/partner-recommendation.resolver.ts @@ -0,0 +1,91 @@ +import { Injectable } from '@angular/core'; +import { + ActivatedRouteSnapshot, + Resolve, + Router, + RouterStateSnapshot, +} from '@angular/router'; +import { catchError, Observable, throwError } from 'rxjs'; + +import { PartnerRecommendation } from '../models/partner-recommendation'; +import { PartnerRecommendationPagination } from '../models/partner-recommendation-pagination'; +import { PartnerRecommendationService } from '../services/partner-recommendation.service'; + +@Injectable({ + providedIn: 'root', +}) +export class PartnerRecommendationResolver implements Resolve { + /** + * Constructor + */ + constructor( + private _partnerRecommendationService: PartnerRecommendationService, + private _router: Router + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Resolver + * + * @param route + * @param state + */ + resolve( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable { + return this._partnerRecommendationService + .getPartnerRecommendationById(route.paramMap.get('id')) + .pipe( + // Error here means the requested product is not available + catchError((error) => { + // Log the error + console.error(error); + + // Get the parent url + const parentUrl = state.url.split('/').slice(0, -1).join('/'); + + // Navigate to there + this._router.navigateByUrl(parentUrl); + + // Throw an error + return throwError(error); + }) + ); + } +} + +@Injectable({ + providedIn: 'root', +}) +export class PartnerRecommendationsResolver implements Resolve { + /** + * Constructor + */ + constructor( + private _partnerRecommendationService: PartnerRecommendationService + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Resolver + * + * @param route + * @param state + */ + resolve( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable<{ + pagination: PartnerRecommendationPagination; + partnerRecommendations: PartnerRecommendation[]; + }> { + return this._partnerRecommendationService.getPartnerRecommendations(); + } +} diff --git a/src/app/modules/admin/member/partner-recommendation/services/partner-recommendation.service.ts b/src/app/modules/admin/member/partner-recommendation/services/partner-recommendation.service.ts new file mode 100644 index 0000000..9f06595 --- /dev/null +++ b/src/app/modules/admin/member/partner-recommendation/services/partner-recommendation.service.ts @@ -0,0 +1,168 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { + BehaviorSubject, + filter, + map, + Observable, + of, + switchMap, + take, + tap, + throwError, +} from 'rxjs'; + +import { PartnerRecommendation } from '../models/partner-recommendation'; +import { PartnerRecommendationPagination } from '../models/partner-recommendation-pagination'; + +@Injectable({ + providedIn: 'root', +}) +export class PartnerRecommendationService { + // Private + private __pagination = new BehaviorSubject< + PartnerRecommendationPagination | undefined + >(undefined); + private __partnerRecommendation = new BehaviorSubject< + PartnerRecommendation | undefined + >(undefined); + private __partnerRecommendations = new BehaviorSubject< + PartnerRecommendation[] | undefined + >(undefined); + + /** + * Constructor + */ + constructor(private _httpClient: HttpClient) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Getter for pagination + */ + get pagination$(): Observable { + return this.__pagination.asObservable(); + } + + /** + * Getter for partnerRecommendation + */ + get partnerRecommendation$(): Observable { + return this.__partnerRecommendation.asObservable(); + } + + /** + * Getter for partnerRecommendations + */ + get partnerRecommendations$(): Observable< + PartnerRecommendation[] | undefined + > { + return this.__partnerRecommendations.asObservable(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Get partnerRecommendations + * + * + * @param page + * @param size + * @param sort + * @param order + * @param search + */ + getPartnerRecommendations( + page: number = 0, + size: number = 10, + sort: string = 'name', + order: 'asc' | 'desc' | '' = 'asc', + search: string = '' + ): Observable<{ + pagination: PartnerRecommendationPagination; + partnerRecommendations: PartnerRecommendation[]; + }> { + return this._httpClient + .get<{ + pagination: PartnerRecommendationPagination; + partnerRecommendations: PartnerRecommendation[]; + }>('api/apps/member/partner-recommendation/partner-recommendations', { + params: { + page: '' + page, + size: '' + size, + sort, + order, + search, + }, + }) + .pipe( + tap((response) => { + this.__pagination.next(response.pagination); + this.__partnerRecommendations.next(response.partnerRecommendations); + }) + ); + } + + /** + * Get product by id + */ + getPartnerRecommendationById( + id: string | null + ): Observable { + return this.__partnerRecommendations.pipe( + take(1), + map((partnerRecommendations) => { + // Find the product + const partnerRecommendation = + partnerRecommendations?.find((item) => item.id === id) || undefined; + + // Update the product + this.__partnerRecommendation.next(partnerRecommendation); + + // Return the product + return partnerRecommendation; + }), + switchMap((product) => { + if (!product) { + return throwError('Could not found product with id of ' + id + '!'); + } + + return of(product); + }) + ); + } + + /** + * Create product + */ + createPartnerRecommendation(): Observable { + return this.partnerRecommendations$.pipe( + take(1), + switchMap((partnerRecommendations) => + this._httpClient + .post( + 'api/apps/member/partner-recommendation/product', + {} + ) + .pipe( + map((newPartnerRecommendation) => { + // Update the partnerRecommendations with the new product + if (!!partnerRecommendations) { + this.__partnerRecommendations.next([ + newPartnerRecommendation, + ...partnerRecommendations, + ]); + } + + // Return the new product + return newPartnerRecommendation; + }) + ) + ) + ); + } +} diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 6c465f7..e307559 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -12,6 +12,7 @@ "Partner Division": "Partner Division", "Partner Office": "Partner Office", "Partner Store": "Partner Store", + "Partner Recommendation": "Partner Recommendation", "Analytics": "Analytics", "Deposit": "Deposit", "Withdraw": "Withdraw", diff --git a/src/assets/i18n/ko.json b/src/assets/i18n/ko.json index 05017bf..e1c2b97 100644 --- a/src/assets/i18n/ko.json +++ b/src/assets/i18n/ko.json @@ -12,6 +12,7 @@ "Partner Division": "부본", "Partner Office": "총판", "Partner Store": "매장", + "Partner Recommendation": "추천코드등록", "Analytics": "Analytics", "Deposit": "입금관리", "Withdraw": "출금관리", From b13ffeea4e4c1a6e0b4e98680e2c3206ca7e3b0c Mon Sep 17 00:00:00 2001 From: JUNG YI DAM Date: Wed, 13 Jul 2022 05:58:28 +0000 Subject: [PATCH 4/6] =?UTF-8?q?=EC=BF=A0=ED=8F=B0=EB=B0=9C=ED=96=89?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20page=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/app.routing.ts | 7 + src/app/mock-api/apps/member/coupon/api.ts | 217 +++++++++++ src/app/mock-api/apps/member/coupon/data.ts | 33 ++ src/app/mock-api/common/navigation/data.ts | 7 + src/app/mock-api/index.ts | 2 + .../admin/member/coupon/components/index.ts | 3 + .../coupon/components/list.component.html | 355 ++++++++++++++++++ .../coupon/components/list.component.ts | 198 ++++++++++ .../admin/member/coupon/coupon.module.ts | 50 +++ .../admin/member/coupon/coupon.routing.ts | 24 ++ .../member/coupon/models/coupon-pagination.ts | 8 + .../admin/member/coupon/models/coupon.ts | 29 ++ .../coupon/resolvers/coupon.resolver.ts | 84 +++++ .../member/coupon/services/coupon.service.ts | 153 ++++++++ src/assets/i18n/en.json | 1 + src/assets/i18n/ko.json | 1 + 16 files changed, 1172 insertions(+) create mode 100644 src/app/mock-api/apps/member/coupon/api.ts create mode 100644 src/app/mock-api/apps/member/coupon/data.ts create mode 100644 src/app/modules/admin/member/coupon/components/index.ts create mode 100644 src/app/modules/admin/member/coupon/components/list.component.html create mode 100644 src/app/modules/admin/member/coupon/components/list.component.ts create mode 100644 src/app/modules/admin/member/coupon/coupon.module.ts create mode 100644 src/app/modules/admin/member/coupon/coupon.routing.ts create mode 100644 src/app/modules/admin/member/coupon/models/coupon-pagination.ts create mode 100644 src/app/modules/admin/member/coupon/models/coupon.ts create mode 100644 src/app/modules/admin/member/coupon/resolvers/coupon.resolver.ts create mode 100644 src/app/modules/admin/member/coupon/services/coupon.service.ts diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index 5c3a3dc..cc75f7b 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -220,6 +220,13 @@ export const appRoutes: Route[] = [ 'app/modules/admin/member/partner-recommendation/partner-recommendation.module' ).then((m: any) => m.PartnerRecommendationModule), }, + { + path: 'coupon', + loadChildren: () => + import('app/modules/admin/member/coupon/coupon.module').then( + (m: any) => m.CouponModule + ), + }, ], }, { diff --git a/src/app/mock-api/apps/member/coupon/api.ts b/src/app/mock-api/apps/member/coupon/api.ts new file mode 100644 index 0000000..2d704fb --- /dev/null +++ b/src/app/mock-api/apps/member/coupon/api.ts @@ -0,0 +1,217 @@ +import { Injectable } from '@angular/core'; +import { assign, cloneDeep } from 'lodash-es'; +import { FuseMockApiService, FuseMockApiUtils } from '@fuse/lib/mock-api'; +import { coupons as couponsData } from './data'; + +@Injectable({ + providedIn: 'root', +}) +export class MemberCouponMockApi { + private _coupons: any[] = couponsData; + + /** + * Constructor + */ + constructor(private _fuseMockApiService: FuseMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Coupons - GET + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onGet('api/apps/member/coupon/coupons', 300) + .reply(({ request }) => { + // Get available queries + const search = request.params.get('search'); + const sort = request.params.get('sort') || 'name'; + const order = request.params.get('order') || 'asc'; + const page = parseInt(request.params.get('page') ?? '1', 10); + const size = parseInt(request.params.get('size') ?? '10', 10); + + // Clone the coupons + let coupons: any[] | null = cloneDeep(this._coupons); + + // Sort the coupons + if (sort === 'sku' || sort === 'name' || sort === 'active') { + coupons.sort((a, b) => { + const fieldA = a[sort].toString().toUpperCase(); + const fieldB = b[sort].toString().toUpperCase(); + return order === 'asc' + ? fieldA.localeCompare(fieldB) + : fieldB.localeCompare(fieldA); + }); + } else { + coupons.sort((a, b) => + order === 'asc' ? a[sort] - b[sort] : b[sort] - a[sort] + ); + } + + // If search exists... + if (search) { + // Filter the coupons + coupons = coupons.filter( + (contact: any) => + contact.name && + contact.name.toLowerCase().includes(search.toLowerCase()) + ); + } + + // Paginate - Start + const couponsLength = coupons.length; + + // Calculate pagination details + const begin = page * size; + const end = Math.min(size * (page + 1), couponsLength); + const lastPage = Math.max(Math.ceil(couponsLength / size), 1); + + // Prepare the pagination object + let pagination = {}; + + // If the requested page number is bigger than + // the last possible page number, return null for + // coupons but also send the last possible page so + // the app can navigate to there + if (page > lastPage) { + coupons = null; + pagination = { + lastPage, + }; + } else { + // Paginate the results by size + coupons = coupons.slice(begin, end); + + // Prepare the pagination mock-api + pagination = { + length: couponsLength, + size: size, + page: page, + lastPage: lastPage, + startIndex: begin, + endIndex: end - 1, + }; + } + + // Return the response + return [ + 200, + { + coupons, + pagination, + }, + ]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Coupon - GET + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onGet('api/apps/member/coupon/coupon') + .reply(({ request }) => { + // Get the id from the params + const id = request.params.get('id'); + + // Clone the coupons + const coupons = cloneDeep(this._coupons); + + // Find the coupon + const coupon = coupons.find((item: any) => item.id === id); + + // Return the response + return [200, coupon]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Coupon - POST + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onPost('api/apps/member/coupon/coupon') + .reply(() => { + // Generate a new coupon + const newCoupon = { + id: FuseMockApiUtils.guid(), + category: '', + name: 'A New User', + description: '', + tags: [], + sku: '', + barcode: '', + brand: '', + vendor: '', + stock: '', + reserved: '', + cost: '', + basePrice: '', + taxPercent: '', + price: '', + weight: '', + thumbnail: '', + images: [], + active: false, + }; + + // Unshift the new coupon + this._coupons.unshift(newCoupon); + + // Return the response + return [200, newCoupon]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Coupon - PATCH + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onPatch('api/apps/member/coupon/coupon') + .reply(({ request }) => { + // Get the id and coupon + const id = request.body.id; + const coupon = cloneDeep(request.body.coupon); + + // Prepare the updated coupon + let updatedCoupon = null; + + // Find the coupon and update it + this._coupons.forEach((item, index, coupons) => { + if (item.id === id) { + // Update the coupon + coupons[index] = assign({}, coupons[index], coupon); + + // Store the updated coupon + updatedCoupon = coupons[index]; + } + }); + + // Return the response + return [200, updatedCoupon]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Coupon - DELETE + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onDelete('api/apps/member/coupon/coupon') + .reply(({ request }) => { + // Get the id + const id = request.params.get('id'); + + // Find the coupon and delete it + this._coupons.forEach((item, index) => { + if (item.id === id) { + this._coupons.splice(index, 1); + } + }); + + // Return the response + return [200, true]; + }); + } +} diff --git a/src/app/mock-api/apps/member/coupon/data.ts b/src/app/mock-api/apps/member/coupon/data.ts new file mode 100644 index 0000000..6b24003 --- /dev/null +++ b/src/app/mock-api/apps/member/coupon/data.ts @@ -0,0 +1,33 @@ +/* eslint-disable */ + +export const coupons = [ + { + id: 'on00', + totalPartnerCount: '5', + totalHoldingMoney: 303675, + totalComp: 108933, + total: 412608, + branchCount: 1, + divisionCount: 1, + officeCount: 1, + storeCount: 1, + memberCount: 1, + nickname: 'on00', + accountHolder: '11', + phoneNumber: '010-1111-1111', + calculateType: '롤링', + ownCash: 50000, + ownComp: 1711, + ownCoupon: 50000, + gameMoney: 0, + todayComp: 0, + totalDeposit: 0, + totalWithdraw: 0, + balance: 0, + registDate: '2022-06-12 15:38', + finalSigninDate: '', + ip: '', + state: '정상', + note: '', + }, +]; diff --git a/src/app/mock-api/common/navigation/data.ts b/src/app/mock-api/common/navigation/data.ts index 69d881f..570b663 100644 --- a/src/app/mock-api/common/navigation/data.ts +++ b/src/app/mock-api/common/navigation/data.ts @@ -116,6 +116,13 @@ export const defaultNavigation: FuseNavigationItem[] = [ icon: 'heroicons_outline:academic-cap', link: '/member/partner-recommendation', }, + { + id: 'member.coupon', + title: 'Coupon', + type: 'basic', + icon: 'heroicons_outline:academic-cap', + link: '/member/coupon', + }, ], }, { diff --git a/src/app/mock-api/index.ts b/src/app/mock-api/index.ts index da9f986..8f2a377 100644 --- a/src/app/mock-api/index.ts +++ b/src/app/mock-api/index.ts @@ -21,6 +21,7 @@ import { MemberPartnerDivisionMockApi } from './apps/member/partner-division/api import { MemberPartnerOfficeMockApi } from './apps/member/partner-office/api'; import { MemberPartnerStoreMockApi } from './apps/member/partner-store/api'; import { MemberPartnerRecommendationMockApi } from './apps/member/partner-recommendation/api'; +import { MemberCouponMockApi } from './apps/member/coupon/api'; import { MessagesMockApi } from 'app/mock-api/common/messages/api'; import { NavigationMockApi } from 'app/mock-api/common/navigation/api'; import { NotesMockApi } from 'app/mock-api/apps/notes/api'; @@ -62,6 +63,7 @@ export const mockApiServices = [ MemberPartnerOfficeMockApi, MemberPartnerStoreMockApi, MemberPartnerRecommendationMockApi, + MemberCouponMockApi, MessagesMockApi, NavigationMockApi, NotesMockApi, diff --git a/src/app/modules/admin/member/coupon/components/index.ts b/src/app/modules/admin/member/coupon/components/index.ts new file mode 100644 index 0000000..04759eb --- /dev/null +++ b/src/app/modules/admin/member/coupon/components/index.ts @@ -0,0 +1,3 @@ +import { ListComponent } from './list.component'; + +export const COMPONENTS = [ListComponent]; diff --git a/src/app/modules/admin/member/coupon/components/list.component.html b/src/app/modules/admin/member/coupon/components/list.component.html new file mode 100644 index 0000000..0da6106 --- /dev/null +++ b/src/app/modules/admin/member/coupon/components/list.component.html @@ -0,0 +1,355 @@ +
+ +
+ +
+ +
+ +
쿠폰발행리스트
+ +
+ + + + + + + 40 + 60 + 80 + 100 + + + + + LV.1 + LV.2 + LV.3 + LV.4 + + + + + 정상 + 대기 + 탈퇴 + 휴면 + 블랙 + 정지 + + + + + 카지노제한 + 슬롯제한 + + + + + 계좌입금 + + + + + 카지노콤프 + 슬롯콤프 + 배팅콤프 + 첫충콤프 + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+ + +
+ There are no coupons! +
+
+
+
+
diff --git a/src/app/modules/admin/member/coupon/components/list.component.ts b/src/app/modules/admin/member/coupon/components/list.component.ts new file mode 100644 index 0000000..1bb948e --- /dev/null +++ b/src/app/modules/admin/member/coupon/components/list.component.ts @@ -0,0 +1,198 @@ +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnDestroy, + OnInit, + ViewChild, + ViewEncapsulation, +} from '@angular/core'; +import { + FormBuilder, + FormControl, + FormGroup, + Validators, +} from '@angular/forms'; +import { MatCheckboxChange } from '@angular/material/checkbox'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { + debounceTime, + map, + merge, + Observable, + Subject, + switchMap, + takeUntil, +} from 'rxjs'; +import { fuseAnimations } from '@fuse/animations'; +import { FuseConfirmationService } from '@fuse/services/confirmation'; + +import { User } from '../../user/models/user'; +import { Coupon } from '../models/coupon'; +import { CouponPagination } from '../models/coupon-pagination'; +import { CouponService } from '../services/coupon.service'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'partner-branch-list', + templateUrl: './list.component.html', + styles: [ + /* language=SCSS */ + ` + .inventory-grid { + grid-template-columns: 60px auto 40px; + + @screen sm { + grid-template-columns: 60px 60px 60px 60px 60px 60px auto 60px; + } + + @screen md { + grid-template-columns: 60px 60px 60px 60px 60px 60px auto 60px 60px; + } + + @screen lg { + grid-template-columns: 60px 70px 70px 70px 70px 100px 60px 60px auto 60px 60px 60px 60px; + } + } + `, + ], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + animations: fuseAnimations, +}) +export class ListComponent implements OnInit, AfterViewInit, OnDestroy { + @ViewChild(MatPaginator) private _paginator!: MatPaginator; + @ViewChild(MatSort) private _sort!: MatSort; + + coupons$!: Observable; + users$!: Observable; + + isLoading = false; + searchInputControl = new FormControl(); + selectedCoupon?: Coupon; + pagination?: CouponPagination; + + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor( + private _changeDetectorRef: ChangeDetectorRef, + private _fuseConfirmationService: FuseConfirmationService, + private _formBuilder: FormBuilder, + private _couponService: CouponService, + private router: Router + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Get the pagination + this._couponService.pagination$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((pagination: CouponPagination | undefined) => { + // Update the pagination + this.pagination = pagination; + + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + + // Get the products + this.coupons$ = this._couponService.coupons$; + } + + /** + * After view init + */ + ngAfterViewInit(): void { + if (this._sort && this._paginator) { + // Set the initial sort + this._sort.sort({ + id: 'name', + start: 'asc', + disableClear: true, + }); + + // Mark for check + this._changeDetectorRef.markForCheck(); + + // If the coupon changes the sort order... + this._sort.sortChange + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(() => { + // Reset back to the first page + this._paginator.pageIndex = 0; + }); + + // Get products if sort or page changes + merge(this._sort.sortChange, this._paginator.page) + .pipe( + switchMap(() => { + this.isLoading = true; + return this._couponService.getCoupons( + this._paginator.pageIndex, + this._paginator.pageSize, + this._sort.active, + this._sort.direction + ); + }), + map(() => { + this.isLoading = false; + }) + ) + .subscribe(); + } + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + viewUserDetail(id: string): void { + let url: string = 'member/user/' + id; + this.router.navigateByUrl(url); + } + // ----------------------------------------------------------------------------------------------------- + // @ Private methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Create product + */ + __createProduct(): void {} + + /** + * Toggle product details + * + * @param productId + */ + __toggleDetails(productId: string): void {} + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + __trackByFn(index: number, item: any): any { + return item.id || index; + } +} diff --git a/src/app/modules/admin/member/coupon/coupon.module.ts b/src/app/modules/admin/member/coupon/coupon.module.ts new file mode 100644 index 0000000..90ef0ee --- /dev/null +++ b/src/app/modules/admin/member/coupon/coupon.module.ts @@ -0,0 +1,50 @@ +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 { MatPaginatorModule } from '@angular/material/paginator'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatRippleModule } from '@angular/material/core'; +import { MatSortModule } from '@angular/material/sort'; +import { MatSelectModule } from '@angular/material/select'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatGridListModule } from '@angular/material/grid-list'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatCheckboxModule } from '@angular/material/checkbox'; + +import { TranslocoModule } from '@ngneat/transloco'; + +import { SharedModule } from 'app/shared/shared.module'; + +import { COMPONENTS } from './components'; + +import { CouponRoutes } from './coupon.routing'; + +@NgModule({ + declarations: [COMPONENTS], + imports: [ + TranslocoModule, + SharedModule, + RouterModule.forChild(CouponRoutes), + + MatButtonModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatPaginatorModule, + MatProgressBarModule, + MatRippleModule, + MatSortModule, + MatSelectModule, + MatTooltipModule, + MatGridListModule, + MatSlideToggleModule, + MatRadioModule, + MatCheckboxModule, + ], +}) +export class CouponModule {} diff --git a/src/app/modules/admin/member/coupon/coupon.routing.ts b/src/app/modules/admin/member/coupon/coupon.routing.ts new file mode 100644 index 0000000..5d8d8b2 --- /dev/null +++ b/src/app/modules/admin/member/coupon/coupon.routing.ts @@ -0,0 +1,24 @@ +import { Route } from '@angular/router'; + +import { ListComponent } from './components/list.component'; +import { ViewComponent } from '../user/components/view.component'; + +import { CouponsResolver } from './resolvers/coupon.resolver'; +import { UserResolver } from '../user/resolvers/user.resolver'; + +export const CouponRoutes: Route[] = [ + { + path: '', + component: ListComponent, + resolve: { + Coupons: CouponsResolver, + }, + }, + { + path: ':id', + component: ViewComponent, + resolve: { + users: UserResolver, + }, + }, +]; diff --git a/src/app/modules/admin/member/coupon/models/coupon-pagination.ts b/src/app/modules/admin/member/coupon/models/coupon-pagination.ts new file mode 100644 index 0000000..c0d5939 --- /dev/null +++ b/src/app/modules/admin/member/coupon/models/coupon-pagination.ts @@ -0,0 +1,8 @@ +export interface CouponPagination { + length: number; + size: number; + page: number; + lastPage: number; + startIndex: number; + endIndex: number; +} diff --git a/src/app/modules/admin/member/coupon/models/coupon.ts b/src/app/modules/admin/member/coupon/models/coupon.ts new file mode 100644 index 0000000..d21561e --- /dev/null +++ b/src/app/modules/admin/member/coupon/models/coupon.ts @@ -0,0 +1,29 @@ +export interface Coupon { + id?: string; + totalPartnerCount?: number; + totalHoldingMoney?: number; + totalComp?: number; + total?: number; + branchCount?: number; + divisionCount?: number; + officeCount?: number; + storeCount?: number; + memberCount?: number; + nickname?: string; + accountHolder?: string; + phoneNumber?: string; + calculateType?: string; + ownCash?: number; + ownComp?: number; + ownCoupon?: number; + gameMoney?: number; + todayComp?: number; + totalDeposit?: number; + totalWithdraw?: number; + balance?: number; + registDate?: string; + finalSigninDate?: string; + ip?: string; + state?: string; + note?: string; +} diff --git a/src/app/modules/admin/member/coupon/resolvers/coupon.resolver.ts b/src/app/modules/admin/member/coupon/resolvers/coupon.resolver.ts new file mode 100644 index 0000000..eee8669 --- /dev/null +++ b/src/app/modules/admin/member/coupon/resolvers/coupon.resolver.ts @@ -0,0 +1,84 @@ +import { Injectable } from '@angular/core'; +import { + ActivatedRouteSnapshot, + Resolve, + Router, + RouterStateSnapshot, +} from '@angular/router'; +import { catchError, Observable, throwError } from 'rxjs'; + +import { Coupon } from '../models/coupon'; +import { CouponPagination } from '../models/coupon-pagination'; +import { CouponService } from '../services/coupon.service'; + +@Injectable({ + providedIn: 'root', +}) +export class CouponResolver implements Resolve { + /** + * Constructor + */ + constructor(private _couponService: CouponService, private _router: Router) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Resolver + * + * @param route + * @param state + */ + resolve( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable { + return this._couponService.getCouponById(route.paramMap.get('id')).pipe( + // Error here means the requested product is not available + catchError((error) => { + // Log the error + console.error(error); + + // Get the parent url + const parentUrl = state.url.split('/').slice(0, -1).join('/'); + + // Navigate to there + this._router.navigateByUrl(parentUrl); + + // Throw an error + return throwError(error); + }) + ); + } +} + +@Injectable({ + providedIn: 'root', +}) +export class CouponsResolver implements Resolve { + /** + * Constructor + */ + constructor(private _couponService: CouponService) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Resolver + * + * @param route + * @param state + */ + resolve( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable<{ + pagination: CouponPagination; + coupons: Coupon[]; + }> { + return this._couponService.getCoupons(); + } +} diff --git a/src/app/modules/admin/member/coupon/services/coupon.service.ts b/src/app/modules/admin/member/coupon/services/coupon.service.ts new file mode 100644 index 0000000..f3d4bc1 --- /dev/null +++ b/src/app/modules/admin/member/coupon/services/coupon.service.ts @@ -0,0 +1,153 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { + BehaviorSubject, + filter, + map, + Observable, + of, + switchMap, + take, + tap, + throwError, +} from 'rxjs'; + +import { Coupon } from '../models/coupon'; +import { CouponPagination } from '../models/coupon-pagination'; + +@Injectable({ + providedIn: 'root', +}) +export class CouponService { + // Private + private __pagination = new BehaviorSubject( + undefined + ); + private __coupon = new BehaviorSubject(undefined); + private __coupons = new BehaviorSubject(undefined); + + /** + * Constructor + */ + constructor(private _httpClient: HttpClient) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Getter for pagination + */ + get pagination$(): Observable { + return this.__pagination.asObservable(); + } + + /** + * Getter for coupon + */ + get coupon$(): Observable { + return this.__coupon.asObservable(); + } + + /** + * Getter for coupons + */ + get coupons$(): Observable { + return this.__coupons.asObservable(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Get coupons + * + * + * @param page + * @param size + * @param sort + * @param order + * @param search + */ + getCoupons( + page: number = 0, + size: number = 10, + sort: string = 'name', + order: 'asc' | 'desc' | '' = 'asc', + search: string = '' + ): Observable<{ + pagination: CouponPagination; + coupons: Coupon[]; + }> { + return this._httpClient + .get<{ + pagination: CouponPagination; + coupons: Coupon[]; + }>('api/apps/member/coupon/coupons', { + params: { + page: '' + page, + size: '' + size, + sort, + order, + search, + }, + }) + .pipe( + tap((response) => { + this.__pagination.next(response.pagination); + this.__coupons.next(response.coupons); + }) + ); + } + + /** + * Get product by id + */ + getCouponById(id: string | null): Observable { + return this.__coupons.pipe( + take(1), + map((coupons) => { + // Find the product + const coupon = coupons?.find((item) => item.id === id) || undefined; + + // Update the product + this.__coupon.next(coupon); + + // Return the product + return coupon; + }), + switchMap((product) => { + if (!product) { + return throwError('Could not found product with id of ' + id + '!'); + } + + return of(product); + }) + ); + } + + /** + * Create product + */ + createCoupon(): Observable { + return this.coupons$.pipe( + take(1), + switchMap((coupons) => + this._httpClient + .post('api/apps/member/coupon/product', {}) + .pipe( + map((newCoupon) => { + // Update the coupons with the new product + if (!!coupons) { + this.__coupons.next([newCoupon, ...coupons]); + } + + // Return the new product + return newCoupon; + }) + ) + ) + ); + } +} diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index e307559..497e538 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -13,6 +13,7 @@ "Partner Office": "Partner Office", "Partner Store": "Partner Store", "Partner Recommendation": "Partner Recommendation", + "Coupon": "Coupon", "Analytics": "Analytics", "Deposit": "Deposit", "Withdraw": "Withdraw", diff --git a/src/assets/i18n/ko.json b/src/assets/i18n/ko.json index e1c2b97..3a8c04e 100644 --- a/src/assets/i18n/ko.json +++ b/src/assets/i18n/ko.json @@ -13,6 +13,7 @@ "Partner Office": "총판", "Partner Store": "매장", "Partner Recommendation": "추천코드등록", + "Coupon": "쿠폰발행리스트", "Analytics": "Analytics", "Deposit": "입금관리", "Withdraw": "출금관리", From 2994960b64d693adf78fda54c6eda90899784774 Mon Sep 17 00:00:00 2001 From: JUNG YI DAM Date: Wed, 13 Jul 2022 06:46:57 +0000 Subject: [PATCH 5/6] =?UTF-8?q?=EC=BF=A0=ED=8F=B0=EB=B0=9C=ED=96=89=20?= =?UTF-8?q?=EB=A8=B8=EB=8B=88=EB=A1=9C=EA=B7=B8=20page=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/list.component.html | 360 ++++++++++++++++++ 1 file changed, 360 insertions(+) create mode 100644 src/app/modules/admin/member/coupon-moneylog/components/list.component.html diff --git a/src/app/modules/admin/member/coupon-moneylog/components/list.component.html b/src/app/modules/admin/member/coupon-moneylog/components/list.component.html new file mode 100644 index 0000000..b503e71 --- /dev/null +++ b/src/app/modules/admin/member/coupon-moneylog/components/list.component.html @@ -0,0 +1,360 @@ +
+ +
+ +
+ +
+ +
쿠폰발행 머니로그
+ +
+ + + + + + + 40 + 60 + 80 + 100 + + + + + LV.1 + LV.2 + LV.3 + LV.4 + + + + + 정상 + 대기 + 탈퇴 + 휴면 + 블랙 + 정지 + + + + + 카지노제한 + 슬롯제한 + + + + + 계좌입금 + + + + + 카지노콤프 + 슬롯콤프 + 배팅콤프 + 첫충콤프 + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+ + +
+ There are no coupon moneylogs! +
+
+
+
+
From 3473e61f0bffcf522d2beb73d708d2381a46befd Mon Sep 17 00:00:00 2001 From: JUNG YI DAM Date: Wed, 13 Jul 2022 06:47:59 +0000 Subject: [PATCH 6/6] =?UTF-8?q?=EC=BF=A0=ED=8F=B0=EB=B0=9C=ED=96=89?= =?UTF-8?q?=EB=A8=B8=EB=8B=88=EB=A1=9C=EA=B7=B8=20page=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/app.routing.ts | 7 + .../apps/member/coupon-moneylog/api.ts | 223 ++++++++++++++++++ .../apps/member/coupon-moneylog/data.ts | 33 +++ src/app/mock-api/common/navigation/data.ts | 7 + src/app/mock-api/index.ts | 2 + .../coupon-moneylog/components/index.ts | 3 + .../components/list.component.ts | 198 ++++++++++++++++ .../coupon-moneylog/coupon-moneylog.module.ts | 50 ++++ .../coupon-moneylog.routing.ts | 24 ++ .../models/coupon-moneylog-pagination.ts | 8 + .../coupon-moneylog/models/coupon-moneylog.ts | 29 +++ .../resolvers/coupon-moneylog.resolver.ts | 89 +++++++ .../services/coupon-moneylog.service.ts | 161 +++++++++++++ .../coupon/components/list.component.ts | 2 +- src/assets/i18n/en.json | 1 + src/assets/i18n/ko.json | 1 + 16 files changed, 837 insertions(+), 1 deletion(-) create mode 100644 src/app/mock-api/apps/member/coupon-moneylog/api.ts create mode 100644 src/app/mock-api/apps/member/coupon-moneylog/data.ts create mode 100644 src/app/modules/admin/member/coupon-moneylog/components/index.ts create mode 100644 src/app/modules/admin/member/coupon-moneylog/components/list.component.ts create mode 100644 src/app/modules/admin/member/coupon-moneylog/coupon-moneylog.module.ts create mode 100644 src/app/modules/admin/member/coupon-moneylog/coupon-moneylog.routing.ts create mode 100644 src/app/modules/admin/member/coupon-moneylog/models/coupon-moneylog-pagination.ts create mode 100644 src/app/modules/admin/member/coupon-moneylog/models/coupon-moneylog.ts create mode 100644 src/app/modules/admin/member/coupon-moneylog/resolvers/coupon-moneylog.resolver.ts create mode 100644 src/app/modules/admin/member/coupon-moneylog/services/coupon-moneylog.service.ts diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index cc75f7b..9e91b4e 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -227,6 +227,13 @@ export const appRoutes: Route[] = [ (m: any) => m.CouponModule ), }, + { + path: 'coupon-moneylog', + loadChildren: () => + import( + 'app/modules/admin/member/coupon-moneylog/coupon-moneylog.module' + ).then((m: any) => m.CouponMoneylogModule), + }, ], }, { diff --git a/src/app/mock-api/apps/member/coupon-moneylog/api.ts b/src/app/mock-api/apps/member/coupon-moneylog/api.ts new file mode 100644 index 0000000..c023a1a --- /dev/null +++ b/src/app/mock-api/apps/member/coupon-moneylog/api.ts @@ -0,0 +1,223 @@ +import { Injectable } from '@angular/core'; +import { assign, cloneDeep } from 'lodash-es'; +import { FuseMockApiService, FuseMockApiUtils } from '@fuse/lib/mock-api'; +import { couponMoneylogs as couponMoneylogsData } from './data'; + +@Injectable({ + providedIn: 'root', +}) +export class MemberCouponMoneylogMockApi { + private _couponMoneylogs: any[] = couponMoneylogsData; + + /** + * Constructor + */ + constructor(private _fuseMockApiService: FuseMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ CouponMoneylogs - GET + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onGet('api/apps/member/coupon-moneylog/coupon-moneylogs', 300) + .reply(({ request }) => { + // Get available queries + const search = request.params.get('search'); + const sort = request.params.get('sort') || 'name'; + const order = request.params.get('order') || 'asc'; + const page = parseInt(request.params.get('page') ?? '1', 10); + const size = parseInt(request.params.get('size') ?? '10', 10); + + // Clone the couponMoneylogs + let couponMoneylogs: any[] | null = cloneDeep(this._couponMoneylogs); + + // Sort the couponMoneylogs + if (sort === 'sku' || sort === 'name' || sort === 'active') { + couponMoneylogs.sort((a, b) => { + const fieldA = a[sort].toString().toUpperCase(); + const fieldB = b[sort].toString().toUpperCase(); + return order === 'asc' + ? fieldA.localeCompare(fieldB) + : fieldB.localeCompare(fieldA); + }); + } else { + couponMoneylogs.sort((a, b) => + order === 'asc' ? a[sort] - b[sort] : b[sort] - a[sort] + ); + } + + // If search exists... + if (search) { + // Filter the couponMoneylogs + couponMoneylogs = couponMoneylogs.filter( + (contact: any) => + contact.name && + contact.name.toLowerCase().includes(search.toLowerCase()) + ); + } + + // Paginate - Start + const couponMoneylogsLength = couponMoneylogs.length; + + // Calculate pagination details + const begin = page * size; + const end = Math.min(size * (page + 1), couponMoneylogsLength); + const lastPage = Math.max(Math.ceil(couponMoneylogsLength / size), 1); + + // Prepare the pagination object + let pagination = {}; + + // If the requested page number is bigger than + // the last possible page number, return null for + // couponMoneylogs but also send the last possible page so + // the app can navigate to there + if (page > lastPage) { + couponMoneylogs = null; + pagination = { + lastPage, + }; + } else { + // Paginate the results by size + couponMoneylogs = couponMoneylogs.slice(begin, end); + + // Prepare the pagination mock-api + pagination = { + length: couponMoneylogsLength, + size: size, + page: page, + lastPage: lastPage, + startIndex: begin, + endIndex: end - 1, + }; + } + + // Return the response + return [ + 200, + { + couponMoneylogs, + pagination, + }, + ]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ CouponMoneylog - GET + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onGet('api/apps/member/coupon-moneylog/coupon-moneylog') + .reply(({ request }) => { + // Get the id from the params + const id = request.params.get('id'); + + // Clone the couponMoneylogs + const couponMoneylogs = cloneDeep(this._couponMoneylogs); + + // Find the couponMoneylog + const couponMoneylog = couponMoneylogs.find( + (item: any) => item.id === id + ); + + // Return the response + return [200, couponMoneylog]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ CouponMoneylog - POST + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onPost('api/apps/member/coupon-moneylog/coupon-moneylog') + .reply(() => { + // Generate a new couponMoneylog + const newCouponMoneylog = { + id: FuseMockApiUtils.guid(), + category: '', + name: 'A New User', + description: '', + tags: [], + sku: '', + barcode: '', + brand: '', + vendor: '', + stock: '', + reserved: '', + cost: '', + basePrice: '', + taxPercent: '', + price: '', + weight: '', + thumbnail: '', + images: [], + active: false, + }; + + // Unshift the new couponMoneylog + this._couponMoneylogs.unshift(newCouponMoneylog); + + // Return the response + return [200, newCouponMoneylog]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ CouponMoneylog - PATCH + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onPatch('api/apps/member/coupon-moneylog/coupon-moneylog') + .reply(({ request }) => { + // Get the id and couponMoneylog + const id = request.body.id; + const couponMoneylog = cloneDeep(request.body.couponMoneylog); + + // Prepare the updated couponMoneylog + let updatedCouponMoneylog = null; + + // Find the couponMoneylog and update it + this._couponMoneylogs.forEach((item, index, couponMoneylogs) => { + if (item.id === id) { + // Update the couponMoneylog + couponMoneylogs[index] = assign( + {}, + couponMoneylogs[index], + couponMoneylog + ); + + // Store the updated couponMoneylog + updatedCouponMoneylog = couponMoneylogs[index]; + } + }); + + // Return the response + return [200, updatedCouponMoneylog]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ CouponMoneylog - DELETE + // ----------------------------------------------------------------------------------------------------- + this._fuseMockApiService + .onDelete('api/apps/member/coupon-moneylog/coupon-moneylog') + .reply(({ request }) => { + // Get the id + const id = request.params.get('id'); + + // Find the couponMoneylog and delete it + this._couponMoneylogs.forEach((item, index) => { + if (item.id === id) { + this._couponMoneylogs.splice(index, 1); + } + }); + + // Return the response + return [200, true]; + }); + } +} diff --git a/src/app/mock-api/apps/member/coupon-moneylog/data.ts b/src/app/mock-api/apps/member/coupon-moneylog/data.ts new file mode 100644 index 0000000..ce0d23a --- /dev/null +++ b/src/app/mock-api/apps/member/coupon-moneylog/data.ts @@ -0,0 +1,33 @@ +/* eslint-disable */ + +export const couponMoneylogs = [ + { + id: 'on00', + totalPartnerCount: '5', + totalHoldingMoney: 303675, + totalComp: 108933, + total: 412608, + branchCount: 1, + divisionCount: 1, + officeCount: 1, + storeCount: 1, + memberCount: 1, + nickname: 'on00', + accountHolder: '11', + phoneNumber: '010-1111-1111', + calculateType: '롤링', + ownCash: 50000, + ownComp: 1711, + ownCoupon: 50000, + gameMoney: 0, + todayComp: 0, + totalDeposit: 0, + totalWithdraw: 0, + balance: 0, + registDate: '2022-06-12 15:38', + finalSigninDate: '', + ip: '', + state: '정상', + note: '', + }, +]; diff --git a/src/app/mock-api/common/navigation/data.ts b/src/app/mock-api/common/navigation/data.ts index 570b663..703ea4c 100644 --- a/src/app/mock-api/common/navigation/data.ts +++ b/src/app/mock-api/common/navigation/data.ts @@ -123,6 +123,13 @@ export const defaultNavigation: FuseNavigationItem[] = [ icon: 'heroicons_outline:academic-cap', link: '/member/coupon', }, + { + id: 'member.coupon-moneylog', + title: 'Coupon Moneylog', + type: 'basic', + icon: 'heroicons_outline:academic-cap', + link: '/member/coupon-moneylog', + }, ], }, { diff --git a/src/app/mock-api/index.ts b/src/app/mock-api/index.ts index 8f2a377..972794b 100644 --- a/src/app/mock-api/index.ts +++ b/src/app/mock-api/index.ts @@ -22,6 +22,7 @@ import { MemberPartnerOfficeMockApi } from './apps/member/partner-office/api'; import { MemberPartnerStoreMockApi } from './apps/member/partner-store/api'; import { MemberPartnerRecommendationMockApi } from './apps/member/partner-recommendation/api'; import { MemberCouponMockApi } from './apps/member/coupon/api'; +import { MemberCouponMoneylogMockApi } from './apps/member/coupon-moneylog/api'; import { MessagesMockApi } from 'app/mock-api/common/messages/api'; import { NavigationMockApi } from 'app/mock-api/common/navigation/api'; import { NotesMockApi } from 'app/mock-api/apps/notes/api'; @@ -64,6 +65,7 @@ export const mockApiServices = [ MemberPartnerStoreMockApi, MemberPartnerRecommendationMockApi, MemberCouponMockApi, + MemberCouponMoneylogMockApi, MessagesMockApi, NavigationMockApi, NotesMockApi, diff --git a/src/app/modules/admin/member/coupon-moneylog/components/index.ts b/src/app/modules/admin/member/coupon-moneylog/components/index.ts new file mode 100644 index 0000000..04759eb --- /dev/null +++ b/src/app/modules/admin/member/coupon-moneylog/components/index.ts @@ -0,0 +1,3 @@ +import { ListComponent } from './list.component'; + +export const COMPONENTS = [ListComponent]; diff --git a/src/app/modules/admin/member/coupon-moneylog/components/list.component.ts b/src/app/modules/admin/member/coupon-moneylog/components/list.component.ts new file mode 100644 index 0000000..2cd98da --- /dev/null +++ b/src/app/modules/admin/member/coupon-moneylog/components/list.component.ts @@ -0,0 +1,198 @@ +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnDestroy, + OnInit, + ViewChild, + ViewEncapsulation, +} from '@angular/core'; +import { + FormBuilder, + FormControl, + FormGroup, + Validators, +} from '@angular/forms'; +import { MatCheckboxChange } from '@angular/material/checkbox'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { + debounceTime, + map, + merge, + Observable, + Subject, + switchMap, + takeUntil, +} from 'rxjs'; +import { fuseAnimations } from '@fuse/animations'; +import { FuseConfirmationService } from '@fuse/services/confirmation'; + +import { User } from '../../user/models/user'; +import { CouponMoneylog } from '../models/coupon-moneylog'; +import { CouponMoneylogPagination } from '../models/coupon-moneylog-pagination'; +import { CouponMoneylogService } from '../services/coupon-moneylog.service'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'coupon-moneylog-list', + templateUrl: './list.component.html', + styles: [ + /* language=SCSS */ + ` + .inventory-grid { + grid-template-columns: 60px auto 40px; + + @screen sm { + grid-template-columns: 60px 60px 60px 60px 60px 60px auto 60px; + } + + @screen md { + grid-template-columns: 60px 60px 60px 60px 60px 60px auto 60px 60px; + } + + @screen lg { + grid-template-columns: 60px 70px 70px 70px 70px 100px 60px 60px auto 60px 60px 60px 60px; + } + } + `, + ], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + animations: fuseAnimations, +}) +export class ListComponent implements OnInit, AfterViewInit, OnDestroy { + @ViewChild(MatPaginator) private _paginator!: MatPaginator; + @ViewChild(MatSort) private _sort!: MatSort; + + couponMoneylogs$!: Observable; + users$!: Observable; + + isLoading = false; + searchInputControl = new FormControl(); + selectedCouponMoneylog?: CouponMoneylog; + pagination?: CouponMoneylogPagination; + + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor( + private _changeDetectorRef: ChangeDetectorRef, + private _fuseConfirmationService: FuseConfirmationService, + private _formBuilder: FormBuilder, + private _couponMoneylogService: CouponMoneylogService, + private router: Router + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Get the pagination + this._couponMoneylogService.pagination$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((pagination: CouponMoneylogPagination | undefined) => { + // Update the pagination + this.pagination = pagination; + + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + + // Get the products + this.couponMoneylogs$ = this._couponMoneylogService.couponMoneylogs$; + } + + /** + * After view init + */ + ngAfterViewInit(): void { + if (this._sort && this._paginator) { + // Set the initial sort + this._sort.sort({ + id: 'name', + start: 'asc', + disableClear: true, + }); + + // Mark for check + this._changeDetectorRef.markForCheck(); + + // If the couponMoneylog changes the sort order... + this._sort.sortChange + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(() => { + // Reset back to the first page + this._paginator.pageIndex = 0; + }); + + // Get products if sort or page changes + merge(this._sort.sortChange, this._paginator.page) + .pipe( + switchMap(() => { + this.isLoading = true; + return this._couponMoneylogService.getCouponMoneylogs( + this._paginator.pageIndex, + this._paginator.pageSize, + this._sort.active, + this._sort.direction + ); + }), + map(() => { + this.isLoading = false; + }) + ) + .subscribe(); + } + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + viewUserDetail(id: string): void { + let url: string = 'member/user/' + id; + this.router.navigateByUrl(url); + } + // ----------------------------------------------------------------------------------------------------- + // @ Private methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Create product + */ + __createProduct(): void {} + + /** + * Toggle product details + * + * @param productId + */ + __toggleDetails(productId: string): void {} + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + __trackByFn(index: number, item: any): any { + return item.id || index; + } +} diff --git a/src/app/modules/admin/member/coupon-moneylog/coupon-moneylog.module.ts b/src/app/modules/admin/member/coupon-moneylog/coupon-moneylog.module.ts new file mode 100644 index 0000000..9b8a534 --- /dev/null +++ b/src/app/modules/admin/member/coupon-moneylog/coupon-moneylog.module.ts @@ -0,0 +1,50 @@ +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 { MatPaginatorModule } from '@angular/material/paginator'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatRippleModule } from '@angular/material/core'; +import { MatSortModule } from '@angular/material/sort'; +import { MatSelectModule } from '@angular/material/select'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatGridListModule } from '@angular/material/grid-list'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatCheckboxModule } from '@angular/material/checkbox'; + +import { TranslocoModule } from '@ngneat/transloco'; + +import { SharedModule } from 'app/shared/shared.module'; + +import { COMPONENTS } from './components'; + +import { CouponMoneylogRoutes } from './coupon-moneylog.routing'; + +@NgModule({ + declarations: [COMPONENTS], + imports: [ + TranslocoModule, + SharedModule, + RouterModule.forChild(CouponMoneylogRoutes), + + MatButtonModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatPaginatorModule, + MatProgressBarModule, + MatRippleModule, + MatSortModule, + MatSelectModule, + MatTooltipModule, + MatGridListModule, + MatSlideToggleModule, + MatRadioModule, + MatCheckboxModule, + ], +}) +export class CouponMoneylogModule {} diff --git a/src/app/modules/admin/member/coupon-moneylog/coupon-moneylog.routing.ts b/src/app/modules/admin/member/coupon-moneylog/coupon-moneylog.routing.ts new file mode 100644 index 0000000..5df336e --- /dev/null +++ b/src/app/modules/admin/member/coupon-moneylog/coupon-moneylog.routing.ts @@ -0,0 +1,24 @@ +import { Route } from '@angular/router'; + +import { ListComponent } from './components/list.component'; +import { ViewComponent } from '../user/components/view.component'; + +import { CouponMoneylogsResolver } from './resolvers/coupon-moneylog.resolver'; +import { UserResolver } from '../user/resolvers/user.resolver'; + +export const CouponMoneylogRoutes: Route[] = [ + { + path: '', + component: ListComponent, + resolve: { + CouponMoneylogs: CouponMoneylogsResolver, + }, + }, + { + path: ':id', + component: ViewComponent, + resolve: { + users: UserResolver, + }, + }, +]; diff --git a/src/app/modules/admin/member/coupon-moneylog/models/coupon-moneylog-pagination.ts b/src/app/modules/admin/member/coupon-moneylog/models/coupon-moneylog-pagination.ts new file mode 100644 index 0000000..6528c9d --- /dev/null +++ b/src/app/modules/admin/member/coupon-moneylog/models/coupon-moneylog-pagination.ts @@ -0,0 +1,8 @@ +export interface CouponMoneylogPagination { + length: number; + size: number; + page: number; + lastPage: number; + startIndex: number; + endIndex: number; +} diff --git a/src/app/modules/admin/member/coupon-moneylog/models/coupon-moneylog.ts b/src/app/modules/admin/member/coupon-moneylog/models/coupon-moneylog.ts new file mode 100644 index 0000000..600d6ee --- /dev/null +++ b/src/app/modules/admin/member/coupon-moneylog/models/coupon-moneylog.ts @@ -0,0 +1,29 @@ +export interface CouponMoneylog { + id?: string; + totalPartnerCount?: number; + totalHoldingMoney?: number; + totalComp?: number; + total?: number; + branchCount?: number; + divisionCount?: number; + officeCount?: number; + storeCount?: number; + memberCount?: number; + nickname?: string; + accountHolder?: string; + phoneNumber?: string; + calculateType?: string; + ownCash?: number; + ownComp?: number; + ownCoupon?: number; + gameMoney?: number; + todayComp?: number; + totalDeposit?: number; + totalWithdraw?: number; + balance?: number; + registDate?: string; + finalSigninDate?: string; + ip?: string; + state?: string; + note?: string; +} diff --git a/src/app/modules/admin/member/coupon-moneylog/resolvers/coupon-moneylog.resolver.ts b/src/app/modules/admin/member/coupon-moneylog/resolvers/coupon-moneylog.resolver.ts new file mode 100644 index 0000000..1c9aad9 --- /dev/null +++ b/src/app/modules/admin/member/coupon-moneylog/resolvers/coupon-moneylog.resolver.ts @@ -0,0 +1,89 @@ +import { Injectable } from '@angular/core'; +import { + ActivatedRouteSnapshot, + Resolve, + Router, + RouterStateSnapshot, +} from '@angular/router'; +import { catchError, Observable, throwError } from 'rxjs'; + +import { CouponMoneylog } from '../models/coupon-moneylog'; +import { CouponMoneylogPagination } from '../models/coupon-moneylog-pagination'; +import { CouponMoneylogService } from '../services/coupon-moneylog.service'; + +@Injectable({ + providedIn: 'root', +}) +export class CouponMoneylogResolver implements Resolve { + /** + * Constructor + */ + constructor( + private _couponMoneylogService: CouponMoneylogService, + private _router: Router + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Resolver + * + * @param route + * @param state + */ + resolve( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable { + return this._couponMoneylogService + .getCouponMoneylogById(route.paramMap.get('id')) + .pipe( + // Error here means the requested product is not available + catchError((error) => { + // Log the error + console.error(error); + + // Get the parent url + const parentUrl = state.url.split('/').slice(0, -1).join('/'); + + // Navigate to there + this._router.navigateByUrl(parentUrl); + + // Throw an error + return throwError(error); + }) + ); + } +} + +@Injectable({ + providedIn: 'root', +}) +export class CouponMoneylogsResolver implements Resolve { + /** + * Constructor + */ + constructor(private _couponMoneylogService: CouponMoneylogService) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Resolver + * + * @param route + * @param state + */ + resolve( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable<{ + pagination: CouponMoneylogPagination; + couponMoneylogs: CouponMoneylog[]; + }> { + return this._couponMoneylogService.getCouponMoneylogs(); + } +} diff --git a/src/app/modules/admin/member/coupon-moneylog/services/coupon-moneylog.service.ts b/src/app/modules/admin/member/coupon-moneylog/services/coupon-moneylog.service.ts new file mode 100644 index 0000000..d07ca53 --- /dev/null +++ b/src/app/modules/admin/member/coupon-moneylog/services/coupon-moneylog.service.ts @@ -0,0 +1,161 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { + BehaviorSubject, + filter, + map, + Observable, + of, + switchMap, + take, + tap, + throwError, +} from 'rxjs'; + +import { CouponMoneylog } from '../models/coupon-moneylog'; +import { CouponMoneylogPagination } from '../models/coupon-moneylog-pagination'; + +@Injectable({ + providedIn: 'root', +}) +export class CouponMoneylogService { + // Private + private __pagination = new BehaviorSubject< + CouponMoneylogPagination | undefined + >(undefined); + private __couponMoneylog = new BehaviorSubject( + undefined + ); + private __couponMoneylogs = new BehaviorSubject( + undefined + ); + + /** + * Constructor + */ + constructor(private _httpClient: HttpClient) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Getter for pagination + */ + get pagination$(): Observable { + return this.__pagination.asObservable(); + } + + /** + * Getter for couponMoneylog + */ + get couponMoneylog$(): Observable { + return this.__couponMoneylog.asObservable(); + } + + /** + * Getter for couponMoneylogs + */ + get couponMoneylogs$(): Observable { + return this.__couponMoneylogs.asObservable(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Get couponMoneylogs + * + * + * @param page + * @param size + * @param sort + * @param order + * @param search + */ + getCouponMoneylogs( + page: number = 0, + size: number = 10, + sort: string = 'name', + order: 'asc' | 'desc' | '' = 'asc', + search: string = '' + ): Observable<{ + pagination: CouponMoneylogPagination; + couponMoneylogs: CouponMoneylog[]; + }> { + return this._httpClient + .get<{ + pagination: CouponMoneylogPagination; + couponMoneylogs: CouponMoneylog[]; + }>('api/apps/member/coupon-moneylog/coupon-moneylogs', { + params: { + page: '' + page, + size: '' + size, + sort, + order, + search, + }, + }) + .pipe( + tap((response) => { + this.__pagination.next(response.pagination); + this.__couponMoneylogs.next(response.couponMoneylogs); + }) + ); + } + + /** + * Get product by id + */ + getCouponMoneylogById(id: string | null): Observable { + return this.__couponMoneylogs.pipe( + take(1), + map((couponMoneylogs) => { + // Find the product + const couponMoneylog = + couponMoneylogs?.find((item) => item.id === id) || undefined; + + // Update the product + this.__couponMoneylog.next(couponMoneylog); + + // Return the product + return couponMoneylog; + }), + switchMap((product) => { + if (!product) { + return throwError('Could not found product with id of ' + id + '!'); + } + + return of(product); + }) + ); + } + + /** + * Create product + */ + createCouponMoneylog(): Observable { + return this.couponMoneylogs$.pipe( + take(1), + switchMap((couponMoneylogs) => + this._httpClient + .post('api/apps/member/coupon-moneylog/product', {}) + .pipe( + map((newCouponMoneylog) => { + // Update the couponMoneylogs with the new product + if (!!couponMoneylogs) { + this.__couponMoneylogs.next([ + newCouponMoneylog, + ...couponMoneylogs, + ]); + } + + // Return the new product + return newCouponMoneylog; + }) + ) + ) + ); + } +} diff --git a/src/app/modules/admin/member/coupon/components/list.component.ts b/src/app/modules/admin/member/coupon/components/list.component.ts index 1bb948e..f817b90 100644 --- a/src/app/modules/admin/member/coupon/components/list.component.ts +++ b/src/app/modules/admin/member/coupon/components/list.component.ts @@ -36,7 +36,7 @@ import { CouponService } from '../services/coupon.service'; import { Router } from '@angular/router'; @Component({ - selector: 'partner-branch-list', + selector: 'coupon-list', templateUrl: './list.component.html', styles: [ /* language=SCSS */ diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 497e538..5d79623 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -14,6 +14,7 @@ "Partner Store": "Partner Store", "Partner Recommendation": "Partner Recommendation", "Coupon": "Coupon", + "Coupon Moneylog": "Coupon Moneylog", "Analytics": "Analytics", "Deposit": "Deposit", "Withdraw": "Withdraw", diff --git a/src/assets/i18n/ko.json b/src/assets/i18n/ko.json index 3a8c04e..e9e09e7 100644 --- a/src/assets/i18n/ko.json +++ b/src/assets/i18n/ko.json @@ -14,6 +14,7 @@ "Partner Store": "매장", "Partner Recommendation": "추천코드등록", "Coupon": "쿠폰발행리스트", + "Coupon Moneylog": "쿠폰발행머니로그", "Analytics": "Analytics", "Deposit": "입금관리", "Withdraw": "출금관리",