From c90ca1185ba0fb0b2db62d56e925844db7fecbec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Developer=2EDave=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <44526601+Botosio@users.noreply.github.com> Date: Sat, 23 Oct 2021 11:00:52 -0400 Subject: [PATCH 1/7] #215 country view list completed --- src/app/answers/answers/answers.component.ts | 12 +- src/app/components/components.module.ts | 64 +++---- .../components/header/header.component.html | 65 ++----- .../polling-stations.component.html | 170 ++++++++++++++++++ .../polling-stations.component.scss | 43 +++++ .../polling-stations.component.ts | 75 ++++++++ src/app/routing/app.routes.ts | 8 +- src/app/store/county/county.actions.ts | 79 ++++++-- src/app/store/county/county.effects.ts | 39 ++-- src/app/store/county/county.reducer.ts | 23 ++- src/app/store/county/county.selectors.ts | 2 +- src/assets/i18n/en.json | 9 + src/assets/i18n/ro.json | 10 ++ 13 files changed, 480 insertions(+), 119 deletions(-) create mode 100644 src/app/components/polling-stations/polling-stations.component.html create mode 100644 src/app/components/polling-stations/polling-stations.component.scss create mode 100644 src/app/components/polling-stations/polling-stations.component.ts diff --git a/src/app/answers/answers/answers.component.ts b/src/app/answers/answers/answers.component.ts index 9c7fde06..8f12ed50 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 } 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'; @@ -84,10 +84,10 @@ export class AnswersComponent implements OnInit { ngOnInit() { this.formState = this.store.pipe(select(state => state.form)); - this.store.dispatch(fetchCountiesFromAnswers()); + this.store.dispatch(new CountryAnswersFetchAction()); } - requestFilteredData (filters) { + requestFilteredData(filters) { this.store.dispatch(updateFilters(filters)); this.store.dispatch(new LoadAnswerPreviewAction(1, undefined, true)); } @@ -102,7 +102,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 +121,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..9eb13f20 --- /dev/null +++ b/src/app/components/polling-stations/polling-stations.component.html @@ -0,0 +1,170 @@ +
+
+
+

{{ "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..259f4ee5 --- /dev/null +++ b/src/app/components/polling-stations/polling-stations.component.ts @@ -0,0 +1,75 @@ +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 { 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[] = []; + + 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.filteredCountryList = countyList), + takeUntil(this.destroy$) + ).subscribe(); + } + + onReorder(event: CdkDragDrop) { + moveItemInArray(this.countyList, event.previousIndex, event.currentIndex); + } + + public filterList(text: string): void { + 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.filteredCountryList = [...this.countyList]; + } +} 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..992e4c40 100644 --- a/src/app/store/county/county.actions.ts +++ b/src/app/store/county/county.actions.ts @@ -1,16 +1,71 @@ -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'); +} + +export type CountryActions = + CountryAnswersFetchAction | + CountryAnswersErrorAction | + CountryAnswersSuccessAction | + CountryPollingStationFetchAction | + CountryPollingStationErrorAction | + CountryPollingStationSuccessAction; + + +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[]; -export const fetchCountriesFailure = createAction( - '[County Effects] Counties Fetched Failure', - props<{ errorMessage: string }>() -); \ No newline at end of file + constructor(countries: County[]) { + this.countries = countries; + } +} diff --git a/src/app/store/county/county.effects.ts b/src/app/store/county/county.effects.ts index b68d5949..7c26eedc 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, 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,39 @@ 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), + // withLatestFrom(this.store.select(getCounties)), + switchMap( + () => this.apiService.get(this.fetchCountiesURL).pipe( + map((counties: County[]) => new CountryPollingStationSuccessAction(counties)), + catchError((err) => of(new CountryPollingStationErrorAction(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..a2d7bea7 100644 --- a/src/app/store/county/county.reducer.ts +++ b/src/app/store/county/county.reducer.ts @@ -1,13 +1,22 @@ + 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: + return { ...state, counties: $action.countries }; + case CountryActionTypes.FETCH_COUNTRIES_FAILURE: + return { ...state, errorMessage: $action.errorMessage }; + case CountryActionTypes.FETCH_COUNTRIES_FOR_POLLING_STATIONS_SUCCESS: + return { ...state, counties: $action.countries }; + case CountryActionTypes.FETCH_COUNTRIES_FOR_POLLING_STATIONS_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/i18n/en.json b/src/assets/i18n/en.json index ac98274c..c40ffcdd 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -178,7 +178,16 @@ "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_STATION": "there are zero counties", "ANSWERS_POLLING_STATION": "Polling Station", "ANSWERS_NAME": "Name", diff --git a/src/assets/i18n/ro.json b/src/assets/i18n/ro.json index 9a8bf16e..e2ecd292 100644 --- a/src/assets/i18n/ro.json +++ b/src/assets/i18n/ro.json @@ -179,7 +179,17 @@ "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_STATION": "sunt zero județe", + "ANSWERS_POLLING_STATION": "Secția de votare", "ANSWERS_NAME": "Nume", From e1d90efb97995473dfe6bcad3b67d62598cb2651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Developer=2EDave=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <44526601+Botosio@users.noreply.github.com> Date: Sat, 23 Oct 2021 11:03:42 -0400 Subject: [PATCH 2/7] missed an S --- src/assets/i18n/en.json | 2 +- src/assets/i18n/ro.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index c40ffcdd..278de6b1 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -187,7 +187,7 @@ "IMPORT_COUNTRY": "Import", "POLLING_STATION_FILTER_BY_NAME": "name", "FILTER_BY_POLLING_NAME": "type the county name or code", - "NO_POLLING_STATION": "there are zero counties", + "NO_POLLING_STATIONS": "there are zero counties", "ANSWERS_POLLING_STATION": "Polling Station", "ANSWERS_NAME": "Name", diff --git a/src/assets/i18n/ro.json b/src/assets/i18n/ro.json index e2ecd292..2b12670a 100644 --- a/src/assets/i18n/ro.json +++ b/src/assets/i18n/ro.json @@ -188,7 +188,7 @@ "IMPORT_COUNTRY": "Importaţi", "POLLING_STATION_FILTER_BY_NAME": "nume", "FILTER_BY_POLLING_NAME": "tastați numele sau codul județului", - "NO_POLLING_STATION": "sunt zero județe", + "NO_POLLING_STATIONS": "sunt zero județe", "ANSWERS_POLLING_STATION": "Secția de votare", From 2681d0398e1f26f77738d81ec6ba2e889d2e1f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Developer=2EDave=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <44526601+Botosio@users.noreply.github.com> Date: Sun, 24 Oct 2021 13:57:36 -0400 Subject: [PATCH 3/7] countries fixed on answers dropdown --- src/app/answers/answers/answers.component.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/app/answers/answers/answers.component.ts b/src/app/answers/answers/answers.component.ts index 8f12ed50..75b3e04a 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'; @@ -63,9 +63,14 @@ 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( + /* 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, @@ -84,7 +89,14 @@ export class AnswersComponent implements OnInit { ngOnInit() { this.formState = this.store.pipe(select(state => state.form)); - this.store.dispatch(new CountryAnswersFetchAction()); + //this.store.dispatch(new CountryAnswersFetchAction()); + this.store + .pipe( + select(s => s.county), + take(1), + map(_ => new CountryAnswersFetchAction()) + ) + .subscribe(action => this.store.dispatch(action)); } requestFilteredData(filters) { From 1ea2516eaa91e518c1edb634c6678b89a8faf0c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Developer=2EDave=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <44526601+Botosio@users.noreply.github.com> Date: Mon, 25 Oct 2021 17:03:34 -0400 Subject: [PATCH 4/7] Set order on list complete with new calls. back end isn't done yet --- .../polling-stations.component.html | 19 ++-- .../polling-stations.component.ts | 18 ++- src/app/store/county/county.actions.ts | 104 +++++++++++++++++- src/app/store/county/county.effects.ts | 32 +++++- src/app/store/county/county.reducer.ts | 10 +- src/assets/i18n/en.json | 1 + src/assets/i18n/ro.json | 1 + 7 files changed, 165 insertions(+), 20 deletions(-) diff --git a/src/app/components/polling-stations/polling-stations.component.html b/src/app/components/polling-stations/polling-stations.component.html index 9eb13f20..0b993e8b 100644 --- a/src/app/components/polling-stations/polling-stations.component.html +++ b/src/app/components/polling-stations/polling-stations.component.html @@ -119,26 +119,21 @@

{{ "FORMS" | translate }}

- + diff --git a/src/app/components/polling-stations/polling-stations.component.ts b/src/app/components/polling-stations/polling-stations.component.ts index 259f4ee5..3a6a5afe 100644 --- a/src/app/components/polling-stations/polling-stations.component.ts +++ b/src/app/components/polling-stations/polling-stations.component.ts @@ -3,7 +3,7 @@ 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 { CountryPollingStationFetchAction } from 'src/app/store/county/county.actions'; +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'; @@ -18,6 +18,7 @@ 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: '' @@ -52,16 +53,18 @@ export class PollingStationsComponent implements OnInit { private handleCountyData(): void { this.store.select(state => state.county).pipe( map(countyList => this.countyList = countyList?.counties), - tap(countyList => this.filteredCountryList = countyList), + tap(countyList => this.currentFilter ? this.filterList(this.currentFilter) : this.filteredCountryList = countyList), takeUntil(this.destroy$) ).subscribe(); } onReorder(event: CdkDragDrop) { - moveItemInArray(this.countyList, event.previousIndex, event.currentIndex); + moveItemInArray(this.filteredCountryList, event.previousIndex, event.currentIndex); + this.store.dispatch(new CountryPollingDragAndDropAction(this.countyList)) } public filterList(text: string): void { + this.currentFilter = text; const newFilter = [...this.countyList]; this.filteredCountryList = newFilter.filter(item => item.name.toLocaleLowerCase().includes(text.toLowerCase()) || @@ -70,6 +73,15 @@ export class PollingStationsComponent implements OnInit { } 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/store/county/county.actions.ts b/src/app/store/county/county.actions.ts index 992e4c40..46bd8b34 100644 --- a/src/app/store/county/county.actions.ts +++ b/src/app/store/county/county.actions.ts @@ -11,6 +11,15 @@ export class CountryActionTypes { 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 = @@ -19,7 +28,16 @@ export type CountryActions = CountryAnswersSuccessAction | CountryPollingStationFetchAction | CountryPollingStationErrorAction | - CountryPollingStationSuccessAction; + CountryPollingStationSuccessAction | + CountryPollingDragAndDropAction | + CountryPollingDragAndDropErrorAction | + CountryPollingDragAndDropSuccessAction | + CountryPollingMoveToFirstAction | + CountryPollingMoveToFirstErrorAction | + CountryPollingMoveToFirstSuccessAction | + CountryPollingDeleteAction | + CountryPollingDeleteErrorAction | + CountryPollingDeleteSuccessAction; export class CountryAnswersFetchAction implements Action { @@ -69,3 +87,87 @@ export class CountryPollingStationSuccessAction implements Action { 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; + } +} + diff --git a/src/app/store/county/county.effects.ts b/src/app/store/county/county.effects.ts index 7c26eedc..5c3bef93 100644 --- a/src/app/store/county/county.effects.ts +++ b/src/app/store/county/county.effects.ts @@ -1,4 +1,4 @@ -import { CountryActionTypes, CountryAnswersErrorAction, CountryAnswersSuccessAction, CountryPollingStationErrorAction, CountryPollingStationSuccessAction } from './county.actions'; +import { CountryActionTypes, CountryAnswersErrorAction, CountryAnswersSuccessAction, CountryPollingDeleteErrorAction, CountryPollingDeleteSuccessAction, CountryPollingDragAndDropErrorAction, CountryPollingDragAndDropSuccessAction, CountryPollingStationErrorAction, CountryPollingStationSuccessAction } from './county.actions'; import { Injectable } from '@angular/core'; import { Actions, createEffect, Effect, ofType } from '@ngrx/effects'; import { ApiService } from '../../core/apiService/api.service'; @@ -48,4 +48,34 @@ export class CountyEffects { ), ) + @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 a2d7bea7..4d180ba4 100644 --- a/src/app/store/county/county.reducer.ts +++ b/src/app/store/county/county.reducer.ts @@ -10,12 +10,16 @@ const initialCountyState: CountyState = { export function countyReducer(state = initialCountyState, $action: CountryActions) { switch ($action.type) { case CountryActionTypes.FETCH_COUNTRIES_SUCCESS: - return { ...state, counties: $action.countries }; - case CountryActionTypes.FETCH_COUNTRIES_FAILURE: - return { ...state, errorMessage: $action.errorMessage }; 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 }; } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 278de6b1..14cd8dd0 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -188,6 +188,7 @@ "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 2b12670a..e56c9e15 100644 --- a/src/assets/i18n/ro.json +++ b/src/assets/i18n/ro.json @@ -189,6 +189,7 @@ "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", From 495cd8d04e17538bdc604e4a224fe578f76ef40b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Developer=2EDave=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <44526601+Botosio@users.noreply.github.com> Date: Tue, 26 Oct 2021 05:02:22 -0400 Subject: [PATCH 5/7] remove commneted code out --- src/app/answers/answers/answers.component.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/app/answers/answers/answers.component.ts b/src/app/answers/answers/answers.component.ts index 75b3e04a..83d151ec 100644 --- a/src/app/answers/answers/answers.component.ts +++ b/src/app/answers/answers/answers.component.ts @@ -63,9 +63,7 @@ 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), @@ -88,8 +86,6 @@ export class AnswersComponent implements OnInit { ngOnInit() { this.formState = this.store.pipe(select(state => state.form)); - - //this.store.dispatch(new CountryAnswersFetchAction()); this.store .pipe( select(s => s.county), From bcc18ffce7a9ce0be50c46252272fbdf4a3c9bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Developer=2EDave=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <44526601+Botosio@users.noreply.github.com> Date: Tue, 26 Oct 2021 05:02:36 -0400 Subject: [PATCH 6/7] removed all commented code out --- .../polling-stations.component.html | 28 +------------------ src/app/store/county/county.effects.ts | 1 - 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/src/app/components/polling-stations/polling-stations.component.html b/src/app/components/polling-stations/polling-stations.component.html index 9eb13f20..576050e9 100644 --- a/src/app/components/polling-stations/polling-stations.component.html +++ b/src/app/components/polling-stations/polling-stations.component.html @@ -123,16 +123,6 @@

{{ "FORMS" | translate }}

{{ "DELETE" | translate }} - @@ -151,20 +141,4 @@

{{ "FORMS" | translate }}

- - - - - - - \ No newline at end of file + \ 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 7c26eedc..2d68b4a3 100644 --- a/src/app/store/county/county.effects.ts +++ b/src/app/store/county/county.effects.ts @@ -38,7 +38,6 @@ export class CountyEffects { @Effect() getPollingStations$ = this.actions$.pipe( ofType(CountryActionTypes.FETCH_COUNTRIES_FOR_POLLING_STATIONS), - // withLatestFrom(this.store.select(getCounties)), switchMap( () => this.apiService.get(this.fetchCountiesURL).pipe( map((counties: County[]) => new CountryPollingStationSuccessAction(counties)), From c28566fb4781bf6ec84d84f40859789cf703c070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Developer=2EDave=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <44526601+Botosio@users.noreply.github.com> Date: Tue, 26 Oct 2021 05:18:00 -0400 Subject: [PATCH 7/7] icon added, fixed passing incorrect order in --- .../polling-stations.component.html | 2 +- .../polling-stations.component.ts | 11 +++++++++++ src/assets/forms/icon-first.png | Bin 0 -> 273 bytes 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/assets/forms/icon-first.png diff --git a/src/app/components/polling-stations/polling-stations.component.html b/src/app/components/polling-stations/polling-stations.component.html index b50260c0..d6dfeb7c 100644 --- a/src/app/components/polling-stations/polling-stations.component.html +++ b/src/app/components/polling-stations/polling-stations.component.html @@ -124,7 +124,7 @@

{{ "FORMS" | translate }}

{{ "DELETE" | translate }} diff --git a/src/app/components/polling-stations/polling-stations.component.ts b/src/app/components/polling-stations/polling-stations.component.ts index 3a6a5afe..4ce5e066 100644 --- a/src/app/components/polling-stations/polling-stations.component.ts +++ b/src/app/components/polling-stations/polling-stations.component.ts @@ -60,9 +60,20 @@ export class PollingStationsComponent implements OnInit { 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]; diff --git a/src/assets/forms/icon-first.png b/src/assets/forms/icon-first.png new file mode 100644 index 0000000000000000000000000000000000000000..ed00e1a155acb743f1cac17aa4f5410ccdef006d GIT binary patch literal 273 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`j)FbFd;%$g$s6l5$8 za(7}_cTVOdki(Mh=E!H7!SLt=E$}Qn=siNN{Hhb|{=pJn606{OXctR=8cWa>DvjN!D*_3e3{K zR-ER3(-`;Slh9=MADdZ1StF|U>$Y$FJJa#^AKC2tZC)ExDun<3@?q}{NNxEZ`(+8x O#SEUVelF{r5}E+4wqDr) literal 0 HcmV?d00001