diff --git a/UI/package-lock.json b/UI/package-lock.json index 8ac828b..e5503af 100644 --- a/UI/package-lock.json +++ b/UI/package-lock.json @@ -14,6 +14,9 @@ "@angular/forms": "^21.0.0", "@angular/platform-browser": "^21.0.0", "@angular/router": "^21.0.0", + "@ngrx/effects": "^21.0.1", + "@ngrx/store": "^21.0.1", + "@ngrx/store-devtools": "^21.0.1", "@primeuix/themes": "^2.0.2", "@tailwindcss/postcss": "^4.1.17", "file-saver": "^2.0.5", @@ -2611,6 +2614,47 @@ "@tybys/wasm-util": "^0.10.1" } }, + "node_modules/@ngrx/effects": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-21.0.1.tgz", + "integrity": "sha512-hSdpToAiSYa5FJ/CAygQHpnCaF2S1HO7q/57ob3XvNTWmkofa0VqI/IIe4W57bojh2YOWCJ91SCn3kAjymaV3g==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/core": "^21.0.0", + "@ngrx/store": "21.0.1", + "rxjs": "^6.5.3 || ^7.5.0" + } + }, + "node_modules/@ngrx/store": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-21.0.1.tgz", + "integrity": "sha512-2hGnw/c5o8nmKzyx7TrUUM7FXjE2zqjX0EF+wLbw9Oy/L+VdCmx+ZI1BFjuAR4B8PKEWHG2KSbOM13SMNkpZiA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/core": "^21.0.0", + "rxjs": "^6.5.3 || ^7.5.0" + } + }, + "node_modules/@ngrx/store-devtools": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-21.0.1.tgz", + "integrity": "sha512-G9fO7CFwYUpz8+JZ9uny+lVJ7iv6PcFJDg+jae5CCrAUIiflnR8gbwD8exAd2AODpxPCpmnSojuLNpLDAcwGzQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/core": "^21.0.0", + "@ngrx/store": "21.0.1", + "rxjs": "^6.5.3 || ^7.5.0" + } + }, "node_modules/@npmcli/agent": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", diff --git a/UI/package.json b/UI/package.json index fd88027..02b8212 100644 --- a/UI/package.json +++ b/UI/package.json @@ -29,6 +29,9 @@ "@angular/forms": "^21.0.0", "@angular/platform-browser": "^21.0.0", "@angular/router": "^21.0.0", + "@ngrx/effects": "^21.0.1", + "@ngrx/store": "^21.0.1", + "@ngrx/store-devtools": "^21.0.1", "@primeuix/themes": "^2.0.2", "@tailwindcss/postcss": "^4.1.17", "file-saver": "^2.0.5", @@ -51,4 +54,4 @@ "typescript": "~5.9.2", "vitest": "^4.0.8" } -} +} \ No newline at end of file diff --git a/UI/src.rar b/UI/src.rar new file mode 100644 index 0000000..2fe2df1 Binary files /dev/null and b/UI/src.rar differ diff --git a/UI/src/app/app.config.ts b/UI/src/app/app.config.ts index dd21d28..a86d774 100644 --- a/UI/src/app/app.config.ts +++ b/UI/src/app/app.config.ts @@ -8,23 +8,57 @@ import { routes } from './app.routes'; import MyPreset from './mythem'; import { JwtInterceptor, ErrorInterceptor, initializeApp } from './shares'; +import { provideStore } from '@ngrx/store'; +import { provideStoreDevtools } from '@ngrx/store-devtools'; +import { isDevMode } from '@angular/core'; +import { provideEffects } from '@ngrx/effects'; +import { staffEffects,staffsReducer } from './state'; + export const appConfig: ApplicationConfig = { providers: [ + provideStore({ + myStaffs: staffsReducer, + }), + provideStoreDevtools({ + maxAge: 25, + logOnly: !isDevMode() + }), provideBrowserGlobalErrorListeners(), - MessageService, ConfirmationService, - provideAppInitializer(initializeApp()), - provideHttpClient(withInterceptors([JwtInterceptor, ErrorInterceptor])), + MessageService, ConfirmationService, + provideAppInitializer(initializeApp()), + provideHttpClient(withInterceptors([JwtInterceptor, ErrorInterceptor])), provideRouter(routes, withComponentInputBinding()), - providePrimeNG({ - theme: { - preset: MyPreset, - options: { - cssLayer: { + providePrimeNG({ + theme: { + preset: MyPreset, + options: { + cssLayer: { name: 'primeng', order: 'theme, base, primeng' } - } } - }), - ] + } + }), + provideEffects([staffEffects]) +] }; + + + +/* + +import { Store } from '@ngrx/store'; + private readonly store = inject(Store); + protected books = this.store.selectSignal(selectBooks); + protected onAdd(id: number) { + this.store.dispatch(StaffsActions.addStaff({ id })); + } + + protected onRemove(id: number) { + this.store.dispatch(StaffsActions.removeStaff({ id })); + } + on ngOnInit() { + use it normal get from API first +and subscribe( (x) => this.store.dispatch(StaffApiAction.LoadStaffList({x}))) +} +*/ \ No newline at end of file diff --git a/UI/src/app/person/person.edit.ts b/UI/src/app/person/person.edit.ts index d0ff381..17d1ac5 100644 --- a/UI/src/app/person/person.edit.ts +++ b/UI/src/app/person/person.edit.ts @@ -85,15 +85,16 @@ export class PersonEdit implements OnInit, OnDestroy { alive: [false], //Validators.required fatherId:[0], motherId:[0] - - }); -constructor( private cdr: ChangeDetectorRef,public dialogService: DialogService, - public ref: DynamicDialogRef, public config: DynamicDialogConfig, - - private router: Router, private route: ActivatedRoute, - private appSetting: AppSettingService, +constructor( + private cdr: ChangeDetectorRef, + public dialogService: DialogService, + public ref: DynamicDialogRef, + public config: DynamicDialogConfig, + private router: Router, + private route: ActivatedRoute, + private appSetting: AppSettingService, ) { this.hostsite = this.appSetting.appSetting.attachment; } @@ -118,11 +119,8 @@ constructor( private cdr: ChangeDetectorRef,public dialogService: DialogService, this.motherList.push(item); else this.fatherList.push(item); - } - } - - + } } getClassForRequire(prev:string,name:string){ diff --git a/UI/src/app/staff/staff.component.html b/UI/src/app/staff/staff.component.html index 898fd58..6a4b7dc 100644 --- a/UI/src/app/staff/staff.component.html +++ b/UI/src/app/staff/staff.component.html @@ -28,7 +28,8 @@
- + diff --git a/UI/src/app/staff/staff.component.ts b/UI/src/app/staff/staff.component.ts index d40f117..01cd78d 100644 --- a/UI/src/app/staff/staff.component.ts +++ b/UI/src/app/staff/staff.component.ts @@ -2,13 +2,8 @@ import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, inject, ChangeDe import { StaffView ,StaffSearch } from '../models'; -/* ngRx */ -/* -import { Store, select } from '@ngrx/store'; -import * as validationActions from './store/actions/validationpoint.action'; -import * as validationSelector from './store/selectors/validationpoint.selector'; -import { appValidationState } from './store/reducers'; -*/ +import { Store } from '@ngrx/store'; +import { StaffsActions, StaffsApiActions } from '../state/staff.actions'; import { take } from 'rxjs/operators'; import { Subscription } from 'rxjs'; import { Router } from '@angular/router'; @@ -19,6 +14,7 @@ import { FormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; import { ButtonModule } from 'primeng/button'; import { InputTextModule } from 'primeng/inputtext'; +import { selectStaffLoaded,selectState } from '../state/staff.selectors'; @Component({ selector: 'staff-list', @@ -28,13 +24,16 @@ import { InputTextModule } from 'primeng/inputtext'; changeDetection: ChangeDetectionStrategy.OnPush }) export class StaffComponent implements OnInit, OnDestroy{ - + private readonly store = inject(Store); private subscription:Subscription = new Subscription(); firstname = ''; email = ''; lastname = ''; loading = false; userList:StaffView[] = []; + appState = this.store.selectSignal(selectState); + users = this.appState().staffs; + staffloadyet = this.appState().staffloaded; private cd = inject(ChangeDetectorRef); msg ="[Staff component]"; @@ -99,25 +98,32 @@ export class StaffComponent implements OnInit, OnDestroy{ const canSearch = true; // this.canSearch(); if (canSearch) { - this.loading = true; + const criteria = this.getSearchCiteria(); this.staffService.searchCriteria = criteria; - this.subscription.add( - this.staffService.searchStaffs(criteria).subscribe( { - next: result => { - this.loading = false; - // console.log(this.msg + "search load Data", result); - this.userList = result.data; - this.cd.detectChanges(); - }, - error: e => { - const message = e || e.message; - // this.toastr.error(message); - this.loading = false; - console.log("error ", e); + console.log("search function store staffload yet",this.staffloadyet, this.users); + if (this.staffloadyet != true) { + this.loading = true; + const loadStaff$ = this.staffService.searchStaffs(criteria); + this.subscription.add( + loadStaff$.subscribe( { + next: result => { + + this.loading = false; + + this.userList = result.data; + this.store.dispatch(StaffsApiActions.loadStaffSuccess({staffs:this.userList, staffLoad: true})); + this.cd.detectChanges(); + }, + error: e => { + const message = e || e.message; + // this.toastr.error(message); + this.loading = false; + console.log("error ", e); + } + }) + ); } - }) - ); } } newUser():void { diff --git a/UI/src/app/state/app.state.ts b/UI/src/app/state/app.state.ts new file mode 100644 index 0000000..4ae9696 --- /dev/null +++ b/UI/src/app/state/app.state.ts @@ -0,0 +1,6 @@ +import { StaffView } from '../models'; + +export interface AppState { + staffs: Array; + staffloaded : boolean; +} \ No newline at end of file diff --git a/UI/src/app/state/index.ts b/UI/src/app/state/index.ts new file mode 100644 index 0000000..442b98a --- /dev/null +++ b/UI/src/app/state/index.ts @@ -0,0 +1,5 @@ +export * from './app.state'; +export * from './staff.actions'; +export * from './staff.effects'; +export * from './staff.reducer'; +export * from './staff.selectors'; \ No newline at end of file diff --git a/UI/src/app/state/staff.actions.ts b/UI/src/app/state/staff.actions.ts new file mode 100644 index 0000000..9a63a2f --- /dev/null +++ b/UI/src/app/state/staff.actions.ts @@ -0,0 +1,19 @@ +import { createActionGroup, emptyProps, props } from '@ngrx/store'; +import { StaffSearch, StaffView } from '../models'; + +export const StaffsActions = createActionGroup({ + source: 'Staffs', + events: { + 'Add Staff': props<{ staffId: number }>(), + 'Remove Staff': props<{ staffId: number }>(), + 'Load Staff': props<{criteria: StaffSearch}>(), + }, +}); + +export const StaffsApiActions = createActionGroup({ + source: 'Staffs API', + events: { + 'LoadStaffSuccess': props<{ staffs: Array; staffLoad: boolean }>(), + 'LoadStaffFail': props<{ error: string }>(), + }, +}); \ No newline at end of file diff --git a/UI/src/app/state/staff.effects.ts b/UI/src/app/state/staff.effects.ts new file mode 100644 index 0000000..5b1f1f9 --- /dev/null +++ b/UI/src/app/state/staff.effects.ts @@ -0,0 +1,29 @@ +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { catchError, filter, map, of, switchMap, exhaustMap } from 'rxjs'; +//import { concatLatestFrom } from '@ngrx/operators'; +import { HttpErrorResponse } from '@angular/common/http'; +import { inject, Injectable } from '@angular/core'; +import { Store } from '@ngrx/store'; + +import { StaffsActions, StaffsApiActions } from './staff.actions'; + +import { StaffService } from '../staff/staff.service'; +import { StaffSearch } from '../models'; + +@Injectable() +export class staffEffects { + private actions$: Actions = inject(Actions); + private staffService: StaffService = inject(StaffService); + private store: Store = inject(Store); + + LoadStaffs$ = createEffect(() => + this.actions$.pipe( + ofType(StaffsActions.loadStaff), + exhaustMap((action: {criteria:StaffSearch}) => + this.staffService.searchStaffs(action.criteria).pipe( + map((response) => StaffsApiActions.loadStaffSuccess({staffs: response.data, staffLoad: true})), + catchError((err: HttpErrorResponse) => of(StaffsApiActions.loadStaffFail({ error: err.error}))) + ) + ) + )); +} \ No newline at end of file diff --git a/UI/src/app/state/staff.reducer.ts b/UI/src/app/state/staff.reducer.ts new file mode 100644 index 0000000..f48c90c --- /dev/null +++ b/UI/src/app/state/staff.reducer.ts @@ -0,0 +1,24 @@ +import { createReducer, on } from '@ngrx/store'; + +import { StaffsApiActions } from './staff.actions'; +import { StaffView } from '../models'; +import { AppState } from './app.state'; + +export const initialState: AppState ={ + staffs:[], + staffloaded: false +} + +export const staffsReducer = createReducer( + initialState, + on(StaffsApiActions.loadStaffSuccess, ( + state, action) => { + return { + ...state, + staffs: action.staffs, + staffloaded: true + + } + } +) +); diff --git a/UI/src/app/state/staff.selectors.ts b/UI/src/app/state/staff.selectors.ts new file mode 100644 index 0000000..fc6df81 --- /dev/null +++ b/UI/src/app/state/staff.selectors.ts @@ -0,0 +1,16 @@ +import { createSelector, createFeatureSelector } from '@ngrx/store'; +import { StaffView } from '../models'; +import { AppState } from './app.state'; + +export const selectState = createFeatureSelector('myStaffs'); + +// 2. Create a selector for a specific piece of state +export const selectStaff = createSelector( + selectState, + (state: AppState) => state.staffs +); + +// 2. Create a selector for a specific piece of state +export const selectStaffLoaded = createSelector( + selectState, + (state: AppState) => state.staffloaded);