diff --git a/src/app/answers/answers/answers.component.ts b/src/app/answers/answers/answers.component.ts index 9c7fde06..83d151ec 100644 --- a/src/app/answers/answers/answers.component.ts +++ b/src/app/answers/answers/answers.component.ts @@ -1,5 +1,5 @@ -import {map, shareReplay, finalize} from 'rxjs/operators'; +import { map, shareReplay, finalize, take, filter } from 'rxjs/operators'; import { LoadAnswerDetailsAction, LoadAnswerPreviewAction, updateFilters, updatePageInfo } from '../../store/answer/answer.actions'; import { AnswerState } from '../../store/answer/answer.reducer'; import { FormState } from '../../store/form/form.reducer'; @@ -16,7 +16,7 @@ import { AnswerThread } from 'src/app/models/answer.thread.model'; import { ActivatedRoute, Router } from '@angular/router'; import { AnswerFilters } from 'src/app/models/answer.filters.model'; import { getAnswerThreads, getFilters } from 'src/app/store/answer/answer.selectors'; -import { fetchCountiesFromAnswers } from 'src/app/store/county/county.actions'; +import { CountryAnswersFetchAction } from 'src/app/store/county/county.actions'; import { County } from 'src/app/store/county/county.state'; import { getCounties } from 'src/app/store/county/county.selectors'; import { AnswerExtra } from 'src/app/models/answer.extra.model'; @@ -63,9 +63,12 @@ export class AnswersComponent implements OnInit { map(threads => threads.map(c => ({ ...c, locationType: (c as any).urbanArea ? 'Urban' : 'Rural' }))) ); filters$: Observable = this.store.select(getFilters); - counties$: Observable[]> = this.store.select(getCounties).pipe( - map((counties: County[]) => [{ name: '' }, ...(counties || [])]), - ); + + public counties$ = this.store.select(state => state.county).pipe( + map(countyList => countyList?.counties), + filter(counties => !!counties), + map((counties: County[]) => [{ name: '' }, ...(counties || [])]) + ) constructor( private store: Store, @@ -83,11 +86,16 @@ export class AnswersComponent implements OnInit { ngOnInit() { this.formState = this.store.pipe(select(state => state.form)); - - this.store.dispatch(fetchCountiesFromAnswers()); + this.store + .pipe( + select(s => s.county), + take(1), + map(_ => new CountryAnswersFetchAction()) + ) + .subscribe(action => this.store.dispatch(action)); } - requestFilteredData (filters) { + requestFilteredData(filters) { this.store.dispatch(updateFilters(filters)); this.store.dispatch(new LoadAnswerPreviewAction(1, undefined, true)); } @@ -102,7 +110,7 @@ export class AnswersComponent implements OnInit { // TODO: call proper API } - onResetFilters () { + onResetFilters() { this.store.dispatch(updateFilters({})); this.store.dispatch(new LoadAnswerPreviewAction(1, undefined, true)); } @@ -121,7 +129,7 @@ export class AnswersComponent implements OnInit { return value !== null && value !== ''; } - downloadAnswers (rawFilters) { + downloadAnswers(rawFilters) { if (!confirm(this.translate.instant('ANSWERS_DOWNLOAD_CONFIRMATION'))) { return; } diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts index c94dc387..9bff57e6 100644 --- a/src/app/components/components.module.ts +++ b/src/app/components/components.module.ts @@ -1,35 +1,36 @@ -import {SharedModule} from '../shared/shared.module'; -import {NgModule} from '@angular/core'; -import {AnswerExtraQuestionsComponent} from './answer/answer-extra-questions/answer-extra-questions.component'; -import {LoginComponent} from './login/login.component'; -import {StatisticsValueComponent} from './statistics/statistics-value/statistics-value.component'; -import {AnswerNoteComponent} from './answer/answer-note/answer-note.component'; -import {StatisticsDetailsComponent} from './statistics/statistics-details/statistics-details.component'; -import {CategoricalQuestionComponent} from './answer/categorical-question/categorical-question.component'; -import {StatisticsCardComponent} from './statistics/statistics-card/statistics-card.component'; -import {StatisticsComponent} from './statistics/statistics.component'; -import {AnswerFormListComponent} from './answer/answer-form-list/answer-form-list.component'; -import {AnswerDetailsComponent} from './answer/answer-details/answer-details.component'; -import {AnswerComponent} from './answer/answer.component'; -import {ObserversComponent} from './observers/observers.component'; -import {AnswerListComponent} from './answer/answers-list/answer-list.component'; -import {HeaderComponent} from './header/header.component'; -import {ObserverCardComponent} from './observers/observer-card/observer-card.component'; -import {OberverRowComponent} from './observers/oberver-row/oberver-row.component'; -import {ObserverProfileComponent} from './observers/observer-profile/observer-profile.component'; -import {NotificationsComponent} from './notifications/notifications.component'; -import {NgMultiSelectDropDownModule} from 'ng-multiselect-dropdown'; -import {FormCreateComponent} from './forms/form-create/form-create.component'; -import {SectionComponent} from './forms/section/section.component'; -import {QuestionComponent} from './forms/question/question.component'; -import {PredefinedOptionsModalComponent} from './forms/predefined-options-modal/predefined-options-modal.component'; -import {OptionComponent} from './forms/option/option.component'; -import {FormsComponent} from './forms/forms.component'; -import {DragDropModule} from '@angular/cdk/drag-drop'; +import { PollingStationsComponent } from './polling-stations/polling-stations.component'; +import { SharedModule } from '../shared/shared.module'; +import { NgModule } from '@angular/core'; +import { AnswerExtraQuestionsComponent } from './answer/answer-extra-questions/answer-extra-questions.component'; +import { LoginComponent } from './login/login.component'; +import { StatisticsValueComponent } from './statistics/statistics-value/statistics-value.component'; +import { AnswerNoteComponent } from './answer/answer-note/answer-note.component'; +import { StatisticsDetailsComponent } from './statistics/statistics-details/statistics-details.component'; +import { CategoricalQuestionComponent } from './answer/categorical-question/categorical-question.component'; +import { StatisticsCardComponent } from './statistics/statistics-card/statistics-card.component'; +import { StatisticsComponent } from './statistics/statistics.component'; +import { AnswerFormListComponent } from './answer/answer-form-list/answer-form-list.component'; +import { AnswerDetailsComponent } from './answer/answer-details/answer-details.component'; +import { AnswerComponent } from './answer/answer.component'; +import { ObserversComponent } from './observers/observers.component'; +import { AnswerListComponent } from './answer/answers-list/answer-list.component'; +import { HeaderComponent } from './header/header.component'; +import { ObserverCardComponent } from './observers/observer-card/observer-card.component'; +import { OberverRowComponent } from './observers/oberver-row/oberver-row.component'; +import { ObserverProfileComponent } from './observers/observer-profile/observer-profile.component'; +import { NotificationsComponent } from './notifications/notifications.component'; +import { NgMultiSelectDropDownModule } from 'ng-multiselect-dropdown'; +import { FormCreateComponent } from './forms/form-create/form-create.component'; +import { SectionComponent } from './forms/section/section.component'; +import { QuestionComponent } from './forms/question/question.component'; +import { PredefinedOptionsModalComponent } from './forms/predefined-options-modal/predefined-options-modal.component'; +import { OptionComponent } from './forms/option/option.component'; +import { FormsComponent } from './forms/forms.component'; +import { DragDropModule } from '@angular/cdk/drag-drop'; -import {TableModule} from '../table/table.module' -import {ObserverImportComponent} from './observers/observer-import/observer-import.component'; -import {NotificationHistoryComponent} from './notifications/notification-history/notification-history.component'; +import { TableModule } from '../table/table.module' +import { ObserverImportComponent } from './observers/observer-import/observer-import.component'; +import { NotificationHistoryComponent } from './notifications/notification-history/notification-history.component'; export let components = [ AnswerComponent, @@ -58,6 +59,7 @@ export let components = [ NotificationHistoryComponent, LoginComponent, ObserverImportComponent, + PollingStationsComponent ]; @NgModule({ diff --git a/src/app/components/header/header.component.html b/src/app/components/header/header.component.html index f3ab5bcd..78ed27f7 100644 --- a/src/app/components/header/header.component.html +++ b/src/app/components/header/header.component.html @@ -1,51 +1,32 @@ + \ No newline at end of file diff --git a/src/app/components/polling-stations/polling-stations.component.html b/src/app/components/polling-stations/polling-stations.component.html new file mode 100644 index 00000000..d6dfeb7c --- /dev/null +++ b/src/app/components/polling-stations/polling-stations.component.html @@ -0,0 +1,149 @@ +
+
+
+

{{ "FORMS" | translate }}

+
+
+
+ + + + + + + {{ 'IMPORT_COUNTRY' | translate }} + + + + + + + + {{ 'EXPORT_COUNTRY' | translate }} + + + + + + + + {{ 'ADD_COUNTRY' | translate }} + +
+
+
+ +
+
+
+
+ + +
+ +
+
+ + + {{ 'FILTER' | translate }} + +   + + {{ 'RESET' | translate }} + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {{ 'NO_POLLING_STATIONS' | translate }} +
+
+ {{ "COUNTY_NAME" | translate }} + + {{ "CODE" | translate }} + + {{ "STATIONS" | translate }} + + {{ "DIASPORA" | translate }} + + {{ "ACTIONS" | translate }} +
+ {{ county.name }} + + {{ county.code }} + + {{ county.numberOfPollingStations }} + + + +
+ +
+ + +
+
+
+ +
+
+
+ +
\ No newline at end of file diff --git a/src/app/components/polling-stations/polling-stations.component.scss b/src/app/components/polling-stations/polling-stations.component.scss new file mode 100644 index 00000000..d7444e7f --- /dev/null +++ b/src/app/components/polling-stations/polling-stations.component.scss @@ -0,0 +1,43 @@ +.btn-group { + app-base-button { + margin-left:1rem; + } +} +.filter-container { + margin:1rem; +} + +.form-control { + width:350px; +} + +.w-15 { + width: 15%; +} + +.no-wrap { + white-space: nowrap; +} + +.centered-cell { + text-align: center; + vertical-align: middle; +} + +.cdk-drop-list-dragging .cdk-drag { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +.cdk-drag-animating { + transition: transform 300ms cubic-bezier(0, 0, 0.2, 1); +} + +.cdk-drag-placeholder { + opacity: 0; +} + +.cdk-drag-preview { + box-sizing: border-box; + border-radius: 4px; + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); +} diff --git a/src/app/components/polling-stations/polling-stations.component.ts b/src/app/components/polling-stations/polling-stations.component.ts new file mode 100644 index 00000000..4ce5e066 --- /dev/null +++ b/src/app/components/polling-stations/polling-stations.component.ts @@ -0,0 +1,98 @@ +import { County, CountyState } from './../../store/county/county.state'; +import { AppState } from './../../store/store.module'; +import { select, Store } from '@ngrx/store'; +import { Component, Inject, OnInit } from '@angular/core'; +import { map, take, tap, takeUntil } from 'rxjs/operators'; +import { CountryPollingDragAndDropAction, CountryPollingMoveToFirstAction, CountryPollingStationFetchAction } from 'src/app/store/county/county.actions'; +import { Subject } from 'rxjs'; +import { BASE_BUTTON_VARIANTS, Variants } from 'src/app/shared/base-button/base-button.component'; +import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; +import { FormBuilder } from '@angular/forms'; + +@Component({ + selector: 'app-polling-stations', + templateUrl: './polling-stations.component.html', + styleUrls: ['./polling-stations.component.scss'], +}) +export class PollingStationsComponent implements OnInit { + private destroy$: Subject = new Subject(); + private countyList: County[] = []; + public filteredCountryList: County[] = []; + private currentFilter: string | null = null; + + public filtersForm = this.fb.group({ + filter: '' + }); + + + constructor( + private store: Store, + private fb: FormBuilder, + @Inject(BASE_BUTTON_VARIANTS) public BaseButtonVariants: typeof Variants) { } + + ngOnInit() { + this.getList(); + this.handleCountyData(); + } + + ngOnDestroy() { + this.destroy$.next(true); + this.destroy$.unsubscribe(); + } + + private getList() { + this.store + .pipe( + select(s => s.county), + take(1), + map(_ => new CountryPollingStationFetchAction()) + ) + .subscribe(action => this.store.dispatch(action)); + } + + private handleCountyData(): void { + this.store.select(state => state.county).pipe( + map(countyList => this.countyList = countyList?.counties), + tap(countyList => this.currentFilter ? this.filterList(this.currentFilter) : this.filteredCountryList = countyList), + takeUntil(this.destroy$) + ).subscribe(); + } + + onReorder(event: CdkDragDrop) { + moveItemInArray(this.filteredCountryList, event.previousIndex, event.currentIndex); + this.filteredCountryList = this.convertOrderWithIndex(this.filteredCountryList); + + // TODO: might have to have a bounce/timeout so we don't hammer the backend + // Won't be able to test until backend is complete. + this.store.dispatch(new CountryPollingDragAndDropAction(this.countyList)) + } + + private convertOrderWithIndex(list: County[]): County[] { + list.map((item, index) => { + item.order = index + }) + return list; + } + + public filterList(text: string): void { + this.currentFilter = text; + const newFilter = [...this.countyList]; + this.filteredCountryList = newFilter.filter(item => + item.name.toLocaleLowerCase().includes(text.toLowerCase()) || + item.code.toLocaleLowerCase().includes(text.toLowerCase())) + + } + + public onResetFilters(): void { + this.currentFilter = null; + this.filteredCountryList = [...this.countyList]; + } + + public moveToFirst(item: County): void { + this.store.dispatch(new CountryPollingMoveToFirstAction(item)) + } + + public deleteCountry(item: County): void { + + } +} diff --git a/src/app/routing/app.routes.ts b/src/app/routing/app.routes.ts index f47c88bb..d2c5cbf4 100644 --- a/src/app/routing/app.routes.ts +++ b/src/app/routing/app.routes.ts @@ -16,7 +16,8 @@ import { NotificationsComponent } from '../components/notifications/notification import { FormsComponent } from '../components/forms/forms.component'; import { FormCreateComponent } from '../components/forms/form-create/form-create.component'; import { ObserverImportComponent } from '../components/observers/observer-import/observer-import.component'; -import {NotificationHistoryComponent} from '../components/notifications/notification-history/notification-history.component'; +import { NotificationHistoryComponent } from '../components/notifications/notification-history/notification-history.component'; +import { PollingStationsComponent } from '../components/polling-stations/polling-stations.component'; export let appRoutes: Routes = [ { @@ -90,5 +91,10 @@ export let appRoutes: Routes = [ path: 'notifications/history', component: NotificationHistoryComponent, canActivate: [AuthGuard] + }, + { + path: 'polling-stations', + component: PollingStationsComponent, + canActivate: [AuthGuard] } ]; diff --git a/src/app/store/county/county.actions.ts b/src/app/store/county/county.actions.ts index a9513a1f..46bd8b34 100644 --- a/src/app/store/county/county.actions.ts +++ b/src/app/store/county/county.actions.ts @@ -1,16 +1,173 @@ -import { createAction, props } from "@ngrx/store"; + +import { Action } from "@ngrx/store"; +import { actionType } from "../util"; import { County } from "./county.state"; -export const fetchCountiesFromAnswers = createAction( - '[Answers Page] Fetch Counties' -); -export const fetchCountriesSuccess = createAction( - '[County Effects] Counties Fetched Success', - props<{ counties: County[] }>() -); +export class CountryActionTypes { + static readonly FETCH_COUNTRIES_FROM_ANSWERS = actionType('[Answers Page] Fetch Counties'); + static readonly FETCH_COUNTRIES_SUCCESS = actionType('[County Effects] Counties Fetched Success'); + static readonly FETCH_COUNTRIES_FAILURE = actionType('[County Effects] Counties Fetched Failure'); + static readonly FETCH_COUNTRIES_FOR_POLLING_STATIONS = actionType('[Polling Station Page] Fetch Counties'); + static readonly FETCH_COUNTRIES_FOR_POLLING_STATIONS_SUCCESS = actionType('[Polling Station Effects] Counties Fetched Success'); + static readonly FETCH_COUNTRIES_FOR_POLLING_STATIONS_FAILURE = actionType('[Polling Station Effects] Counties Fetched Failure'); + static readonly POST_COUNTRIES_FOR_POLLING_STATIONS_DROP_AND_DROP_ORDER = actionType('[Polling Station Page] Drag and Dropped Post'); + static readonly POST_COUNTRIES_FOR_POLLING_STATIONS_DROP_AND_DROP_ORDER_SUCCESS = actionType('[Polling Station Page] Drag and Dropped Success'); + static readonly POST_COUNTRIES_FOR_POLLING_STATIONS_DROP_AND_DROP_ORDER_FAILURE = actionType('[Polling Station Page] Drag and Dropped Failure'); + static readonly POST_COUNTRIES_FOR_POLLING_STATIONS_MOVE_TO_FIRST = actionType('[Polling Station Page] Move to First Post'); + static readonly POST_COUNTRIES_FOR_POLLING_STATIONS_MOVE_TO_FIRST_SUCCESS = actionType('[Polling Station Page] Move to First Success'); + static readonly POST_COUNTRIES_FOR_POLLING_STATIONS_MOVE_TO_FIRST_FAILURE = actionType('[Polling Station Page] Move to First Failure'); + static readonly POST_COUNTRIES_FOR_POLLING_STATIONS_DELETE = actionType('[Polling Station Page] DELETE Post'); + static readonly POST_COUNTRIES_FOR_POLLING_STATIONS_DELETE_SUCCESS = actionType('[Polling Station Page] DELETE Success'); + static readonly POST_COUNTRIES_FOR_POLLING_STATIONS_DELETE_FAILURE = actionType('[Polling Station Page] DELETE Failure'); +} + +export type CountryActions = + CountryAnswersFetchAction | + CountryAnswersErrorAction | + CountryAnswersSuccessAction | + CountryPollingStationFetchAction | + CountryPollingStationErrorAction | + CountryPollingStationSuccessAction | + CountryPollingDragAndDropAction | + CountryPollingDragAndDropErrorAction | + CountryPollingDragAndDropSuccessAction | + CountryPollingMoveToFirstAction | + CountryPollingMoveToFirstErrorAction | + CountryPollingMoveToFirstSuccessAction | + CountryPollingDeleteAction | + CountryPollingDeleteErrorAction | + CountryPollingDeleteSuccessAction; + + +export class CountryAnswersFetchAction implements Action { + readonly type = CountryActionTypes.FETCH_COUNTRIES_FROM_ANSWERS; +} + +export class CountryAnswersErrorAction implements Action { + readonly type = CountryActionTypes.FETCH_COUNTRIES_FAILURE; + errorMessage: string; + + constructor(errorMessage: string) { + this.errorMessage = errorMessage; + } +} + +export class CountryAnswersSuccessAction implements Action { + readonly type = CountryActionTypes.FETCH_COUNTRIES_SUCCESS; + countries: County[]; + + constructor(countries: County[]) { + this.countries = countries; + } + +} + + +export class CountryPollingStationFetchAction implements Action { + readonly type = CountryActionTypes.FETCH_COUNTRIES_FOR_POLLING_STATIONS; + + constructor() { } +} + +export class CountryPollingStationErrorAction implements Action { + readonly type = CountryActionTypes.FETCH_COUNTRIES_FOR_POLLING_STATIONS_FAILURE; + errorMessage: string; + + constructor(errorMessage: string) { + this.errorMessage = errorMessage; + } +} + +export class CountryPollingStationSuccessAction implements Action { + readonly type = CountryActionTypes.FETCH_COUNTRIES_FOR_POLLING_STATIONS_SUCCESS; + countries: County[]; + + constructor(countries: County[]) { + this.countries = countries; + } +} + + +export class CountryPollingDragAndDropAction implements Action { + readonly type = CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_DROP_AND_DROP_ORDER; + countries: County[]; + + constructor(countries: County[]) { + this.countries = countries; + } +} + +export class CountryPollingDragAndDropErrorAction implements Action { + readonly type = CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_DROP_AND_DROP_ORDER_FAILURE; + errorMessage: string; + + constructor(errorMessage: string) { + this.errorMessage = errorMessage; + } +} + +export class CountryPollingDragAndDropSuccessAction implements Action { + readonly type = CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_DROP_AND_DROP_ORDER_SUCCESS; + countries: County[]; + + constructor(countries: County[]) { + this.countries = countries; + } +} + + +export class CountryPollingMoveToFirstAction implements Action { + readonly type = CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_MOVE_TO_FIRST; + country: County; + + constructor(country: County) { + this.country = country; + } +} + +export class CountryPollingMoveToFirstErrorAction implements Action { + readonly type = CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_MOVE_TO_FIRST_FAILURE; + errorMessage: string; + + constructor(errorMessage: string) { + this.errorMessage = errorMessage; + } +} + +export class CountryPollingMoveToFirstSuccessAction implements Action { + readonly type = CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_MOVE_TO_FIRST_SUCCESS; + countries: County[]; + + constructor(countries: County[]) { + this.countries = countries; + } +} + +export class CountryPollingDeleteAction implements Action { + readonly type = CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_DELETE; + country: County; + + constructor(country: County) { + this.country = country; + } +} + +export class CountryPollingDeleteErrorAction implements Action { + readonly type = CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_DELETE_FAILURE; + errorMessage: string; + + constructor(errorMessage: string) { + this.errorMessage = errorMessage; + } +} + +export class CountryPollingDeleteSuccessAction implements Action { + readonly type = CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_DELETE_SUCCESS; + countries: County[]; + + constructor(countries: County[]) { + this.countries = countries; + } +} -export const fetchCountriesFailure = createAction( - '[County Effects] Counties Fetched Failure', - props<{ errorMessage: string }>() -); \ No newline at end of file diff --git a/src/app/store/county/county.effects.ts b/src/app/store/county/county.effects.ts index b68d5949..e64db79d 100644 --- a/src/app/store/county/county.effects.ts +++ b/src/app/store/county/county.effects.ts @@ -1,6 +1,6 @@ +import { CountryActionTypes, CountryAnswersErrorAction, CountryAnswersSuccessAction, CountryPollingDeleteErrorAction, CountryPollingDeleteSuccessAction, CountryPollingDragAndDropErrorAction, CountryPollingDragAndDropSuccessAction, CountryPollingStationErrorAction, CountryPollingStationSuccessAction } from './county.actions'; import { Injectable } from '@angular/core'; -import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { fetchCountiesFromAnswers, fetchCountriesFailure, fetchCountriesSuccess } from './county.actions'; +import { Actions, createEffect, Effect, ofType } from '@ngrx/effects'; import { ApiService } from '../../core/apiService/api.service'; import { environment } from '../../../environments/environment'; import { catchError, filter, map, switchMap, tap, withLatestFrom } from 'rxjs/operators'; @@ -13,26 +13,68 @@ import { getCounties } from './county.selectors'; @Injectable() export class CountyEffects { private baseURL: string = environment.apiUrl; - private fetchCountiesURL = this.baseURL + '/api/v1/county'; - + private fetchCountiesURL = this.baseURL + '/api/v1/county'; + + constructor( + private actions$: Actions, + private apiService: ApiService, + private store: Store + ) { } + fetchCounties$ = createEffect( () => this.actions$.pipe( - ofType(fetchCountiesFromAnswers), + ofType(CountryActionTypes.FETCH_COUNTRIES_FROM_ANSWERS), withLatestFrom(this.store.select(getCounties)), filter(([, currentCounties]) => !!currentCounties === false), switchMap( - () => this.apiService.get(this.fetchCountiesURL).pipe( - map((counties: County[]) => fetchCountriesSuccess({ counties })), - catchError((err) => of(fetchCountriesFailure({ errorMessage: err.message })) + () => this.apiService.get(this.fetchCountiesURL).pipe( + map((counties: County[]) => new CountryAnswersSuccessAction(counties)), + catchError((err) => of(new CountryAnswersErrorAction(err.message)) ) ) ), ) ); - constructor ( - private actions$: Actions, - private apiService: ApiService, - private store: Store - ) { } + @Effect() getPollingStations$ = this.actions$.pipe( + ofType(CountryActionTypes.FETCH_COUNTRIES_FOR_POLLING_STATIONS), + switchMap( + () => this.apiService.get(this.fetchCountiesURL).pipe( + map((counties: County[]) => new CountryPollingStationSuccessAction(counties)), + catchError((err) => of(new CountryPollingStationErrorAction(err.message)) + ) + ) + ), + ) + + @Effect() pollingStationsDragAndDrop$ = this.actions$ + .pipe( + ofType(CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_DROP_AND_DROP_ORDER), + switchMap((counties: County[]) => + this.apiService.post(this.fetchCountiesURL + '/update-order', { counties: counties }).pipe( + map((counties: County[]) => new CountryPollingDragAndDropSuccessAction(counties)), + catchError((err) => of(new CountryPollingDragAndDropErrorAction(err.message))) + )) + ); + + @Effect() pollingStationsMoveToFirst$ = this.actions$ + .pipe( + ofType(CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_MOVE_TO_FIRST), + switchMap((county: County) => + this.apiService.post(this.fetchCountiesURL + '/move-to-first', { county: county }).pipe( + map((counties: County[]) => new CountryPollingDragAndDropSuccessAction(counties)), + catchError((err) => of(new CountryPollingDragAndDropErrorAction(err.message))) + )) + ); + + @Effect() pollingStationsDeleteCounty$ = this.actions$ + .pipe( + ofType(CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_DELETE), + switchMap((county: County) => + this.apiService.post(this.fetchCountiesURL + '/delete', { county: county }).pipe( + map((counties: County[]) => new CountryPollingDeleteSuccessAction(counties)), + catchError((err) => of(new CountryPollingDeleteErrorAction(err.message))) + )) + ); + } \ No newline at end of file diff --git a/src/app/store/county/county.reducer.ts b/src/app/store/county/county.reducer.ts index 4a1b9f65..4d180ba4 100644 --- a/src/app/store/county/county.reducer.ts +++ b/src/app/store/county/county.reducer.ts @@ -1,13 +1,26 @@ + import { CountyState } from "./county.state"; -import { createReducer, on } from "@ngrx/store"; -import { fetchCountriesFailure, fetchCountriesSuccess } from "./county.actions"; +import { CountryActions, CountryActionTypes } from "./county.actions"; + const initialCountyState: CountyState = { counties: undefined, }; -export const countyReducer = createReducer( - initialCountyState, - on(fetchCountriesSuccess, (state, action) => ({ ...state, counties: action.counties, })), - on(fetchCountriesFailure, (state, action) => ({ ...state, errorMessage: action.errorMessage, })), -); \ No newline at end of file +export function countyReducer(state = initialCountyState, $action: CountryActions) { + switch ($action.type) { + case CountryActionTypes.FETCH_COUNTRIES_SUCCESS: + case CountryActionTypes.FETCH_COUNTRIES_FOR_POLLING_STATIONS_SUCCESS: + case CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_DROP_AND_DROP_ORDER_SUCCESS: + case CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_MOVE_TO_FIRST_SUCCESS: + case CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_DELETE_SUCCESS: + return { ...state, counties: $action.countries }; + case CountryActionTypes.FETCH_COUNTRIES_FAILURE: + case CountryActionTypes.FETCH_COUNTRIES_FOR_POLLING_STATIONS_FAILURE: + case CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_DROP_AND_DROP_ORDER_FAILURE: + case CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_MOVE_TO_FIRST_FAILURE: + case CountryActionTypes.POST_COUNTRIES_FOR_POLLING_STATIONS_DELETE_FAILURE: + return { ...state, errorMessage: $action.errorMessage }; + + } +} \ No newline at end of file diff --git a/src/app/store/county/county.selectors.ts b/src/app/store/county/county.selectors.ts index ac4bb40f..c9ed9098 100644 --- a/src/app/store/county/county.selectors.ts +++ b/src/app/store/county/county.selectors.ts @@ -5,5 +5,5 @@ export const county = createFeatureSelector('county'); export const getCounties = createSelector( county, - (state: CountyState) => state.counties, + (state: CountyState) => state?.counties, ) \ No newline at end of file diff --git a/src/assets/forms/icon-first.png b/src/assets/forms/icon-first.png new file mode 100644 index 00000000..ed00e1a1 Binary files /dev/null and b/src/assets/forms/icon-first.png differ diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index ac98274c..14cd8dd0 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -178,7 +178,17 @@ "MESSAGE": "Message", "NOTIF_DETAILS_FOR": "Notification details for", "CHOOSE_COUNTY": "Choose county", + "ADD_COUNTRY": "Add country", "CLOSE": "Close", + "COUNTY_NAME": "Country name", + "CODE": "Code", + "STATIONS": "Stations", + "EXPORT_COUNTRY": "Export", + "IMPORT_COUNTRY": "Import", + "POLLING_STATION_FILTER_BY_NAME": "name", + "FILTER_BY_POLLING_NAME": "type the county name or code", + "NO_POLLING_STATIONS": "there are zero counties", + "MOVE_TO_TOP": "Set as first", "ANSWERS_POLLING_STATION": "Polling Station", "ANSWERS_NAME": "Name", diff --git a/src/assets/i18n/ro.json b/src/assets/i18n/ro.json index 9a8bf16e..e56c9e15 100644 --- a/src/assets/i18n/ro.json +++ b/src/assets/i18n/ro.json @@ -179,7 +179,18 @@ "MESSAGE": "Mesaj", "NOTIF_DETAILS_FOR": "Detaliile notificării pentru", "CHOOSE_COUNTY": "Alegeți județul", + "ADD_COUNTRY": "Adăugați țara", "CLOSE": "Închideți", + "COUNTY_NAME": "Numele țării", + "CODE": "Cod", + "STATIONS": "Staţii", + "EXPORT_COUNTRY": "Exportă", + "IMPORT_COUNTRY": "Importaţi", + "POLLING_STATION_FILTER_BY_NAME": "nume", + "FILTER_BY_POLLING_NAME": "tastați numele sau codul județului", + "NO_POLLING_STATIONS": "sunt zero județe", + "MOVE_TO_TOP": "Setați ca primul", + "ANSWERS_POLLING_STATION": "Secția de votare", "ANSWERS_NAME": "Nume",