diff --git a/.editorconfig b/.editorconfig
index 6e87a00..2e8b0ef 100755
--- a/.editorconfig
+++ b/.editorconfig
@@ -3,8 +3,8 @@ root = true
[*]
charset = utf-8
-indent_style = space
-indent_size = 2
+indent_style = tab
+indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index f18045b..72047ad 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -15,15 +15,28 @@ jobs:
uses: actions/npm@master
with:
args: install
+ - name: Update environments file
+ env:
+ RVM_BASE_URL: ${{ secrets.RVM_BASE_URL }}
+ RVM_SERVER_URL: ${{ secrets.RVM_SERVER_URL }}
+ run: |
+ ENVIRONMENTS_FILE="$GITHUB_WORKSPACE/src/environments/environment.stage.ts"
+ sed -i "s#SERVER_URL#${RVM_SERVER_URL}#g" $ENVIRONMENTS_FILE
+ sed -i "s#SERVER_BASE_URL#${RVM_BASE_URL}#g" $ENVIRONMENTS_FILE
+ echo "$(cat $ENVIRONMENTS_FILE)"
- name: Build the solution
uses: actions/npm@master
with:
- args: "run-script build:prod"
+ args: "run-script build:stage"
- name: Deploy the solution
- uses: contention/rsync-deployments@master
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
RVM_MACHINE: ${{ secrets.RVM_MACHINE }}
RVM_USERNAME: ${{ secrets.RVM_USERNAME }}
- with:
- args: -arv --omit-dir-times --include="dist/a/*" $RVM_USERNAME@$RVM_MACHINE:/var/www/html/
+ run: |
+ DEPLOY_FOLDER="$GITHUB_WORKSPACE/dist/a/"
+ SSH_PATH="$HOME/.ssh"
+ mkdir "$SSH_PATH"
+ echo "$DEPLOY_KEY" > "$SSH_PATH/deploy_key"
+ chmod 600 "$SSH_PATH/deploy_key"
+ sh -c "rsync -rvz --omit-dir-times -e 'ssh -i $SSH_PATH/deploy_key -o StrictHostKeyChecking=no' $DEPLOY_FOLDER $RVM_USERNAME@$RVM_MACHINE:/var/www/html/"
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 0000000..63bc531
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,8 @@
+
+
+ src/app/core/validators/email-validation.ts
+
+ Methods+ |
+
+
|
+
+ + + + Static + emailValidation + + + + | +||||||
+
+ emailValidation(abstractControl: AbstractControl)
+ |
+ ||||||
+ + | +||||||
+ Email validator for reactive forms +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
import { AbstractControl } from '@angular/forms';
+
+export class EmailValidation {
+ /**
+ * Email validator for reactive forms
+ */
+ static emailValidation(abstractControl: AbstractControl): any {
+ const email = abstractControl.value;
+ const reg = new RegExp(/^[a-z0-9\._%+-]+@[a-z0-9\.-]+\.[a-z]{2,4}$/);
+
+ if (reg.test(email)) {
+ return null;
+ }
+
+ return { 'email': 'Adresa de email introdusă nu este validă (ex: email@email.com).' };
+ }
+}
+
+ +
+ src/app/core/validators/location-validation.ts
+
+ Methods+ |
+
+
|
+
+ + + + Static + locationValidation + + + + | +||||||
+
+ locationValidation(abstractControl: AbstractControl)
+ |
+ ||||||
+ + | +||||||
+ Location validator for reactive forms +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
import { AbstractControl } from '@angular/forms';
+
+export class LocationValidation {
+ /**
+ * Location validator for reactive forms
+ */
+ static locationValidation(abstractControl: AbstractControl): any {
+ const locationObject = abstractControl.value;
+
+ if ( locationObject.hasOwnProperty('_id')) {
+ return null;
+ }
+
+ return { 'obj' : true };
+ }
+}
+
+ +
+ src/app/core/validators/password-validation.ts
+
+ Methods+ |
+
+
|
+
+ + + + Static + MatchPassword + + + + | +||||||
+
+ MatchPassword(abstractControl: AbstractControl)
+ |
+ ||||||
+ + | +||||||
+ Match password validator for reactive forms +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + Static + passwordValidation + + + + | +||||||
+
+ passwordValidation(abstractControl: AbstractControl)
+ |
+ ||||||
+ + | +||||||
+ Password validator for reactive forms +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
import { AbstractControl } from '@angular/forms';
+
+export class PasswordValidation {
+ /**
+ * Match password validator for reactive forms
+ */
+ static MatchPassword(abstractControl: AbstractControl): any {
+ const password = abstractControl.get('password').value;
+ const confirmPassword = abstractControl.get('confirmPassword').value;
+
+ if (password !== confirmPassword) {
+ return {
+ password: 'Parolele trebuie să corespundă și să conțină minim 8 caractere, o literă mare, un număr și un caracter special.'
+ };
+ }
+
+ return null;
+ }
+
+ /**
+ * Password validator for reactive forms
+ */
+ static passwordValidation(abstractControl: AbstractControl): any {
+ const number = new RegExp('\\d');
+ const password = abstractControl.value;
+ const uppercase = new RegExp('[A-Z]');
+ const lowercase = new RegExp('[a-z]');
+ const special = new RegExp(/[!#$%&\‘\(\)\*?@\[\]^_\+\.`\{\|\}~]/);
+
+ if (!number.test(password)) {
+ return { password: 'Parolele trebuie să corespundă și să conțină minim 8 caractere, o literă mare, un număr și un caracter special.' };
+ }
+
+ if (!uppercase.test(password)) {
+ return { password: 'Parolele trebuie să corespundă și să conțină minim 8 caractere, o literă mare, un număr și un caracter special.' };
+ }
+
+ if (!lowercase.test(password)) {
+ return { password: 'Parolele trebuie să corespundă și să conțină minim 8 caractere, o literă mare, un număr și un caracter special.' };
+ }
+
+ if (!special.test(password)) {
+ return { password: 'Parolele trebuie să corespundă și să conțină minim 8 caractere, o literă mare, un număr și un caracter special.' };
+ }
+
+ if (password.length < 8) {
+ return { password: 'Parolele trebuie să corespundă și să conțină minim 8 caractere, o literă mare, un număr și un caracter special.' };
+ }
+
+ return null;
+ }
+}
+
+ +
+ src/app/core/validators/phone-validation.ts
+
+ Methods+ |
+
+
|
+
+ + + + Static + phoneValidation + + + + | +||||||
+
+ phoneValidation(abstractControl: AbstractControl)
+ |
+ ||||||
+ + | +||||||
+ Phone validator for reactive forms +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
import { AbstractControl } from '@angular/forms';
+
+export class PhoneValidation {
+ /**
+ * Phone validator for reactive forms
+ */
+ static phoneValidation(abstractControl: AbstractControl): any {
+ const phone = abstractControl.value;
+ const reg = new RegExp('^[+]{0,1}[0-9]+$');
+
+ if ((reg.test(phone) && phone.length >= 10)) {
+ if (phone.length === 12 && phone.indexOf('+') >= 0) {
+ return null;
+ }
+
+ if (phone.length === 10 && phone.indexOf('+') < 0) {
+ return null;
+ }
+ }
+
+ return { 'phone': 'Numărul de telefon introdus nu este valid (ex: +40722446688, 0733557799).' };
+ }
+}
+
+ +
+ src/app/core/validators/ssn-validation.ts
+
+ Methods+ |
+
+
|
+
+ + + + Static + ssnValidation + + + + | +||||||
+
+ ssnValidation(abstractControl: AbstractControl)
+ |
+ ||||||
+ Defined in src/app/core/validators/ssn-validation.ts:8
+ |
+ ||||||
+ CNP validator for reactive forms +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
import { AbstractControl } from '@angular/forms';
+import { map } from 'rxjs/operators';
+
+export class SsnValidation {
+ /**
+ * CNP validator for reactive forms
+ */
+ static ssnValidation(abstractControl: AbstractControl): any {
+ const ssn = parseInt(abstractControl.value, 10) || 0;
+
+ if (ssn.toString().length === 13) {
+ const cnp = Array.from(String(ssn), Number);
+ const coefs = [2, 7, 9, 1, 4, 6, 3, 5, 8, 2, 7, 9];
+ const idx = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
+ const s = idx.map((x) => coefs[x] * cnp[x]);
+ const res = s.reduce((a, b) => a + b, 0) % 11;
+
+ if ((res < 10 && res === cnp[12]) || (res === 10 && cnp[12] === 1) ) {
+ return null;
+ } else {
+ return {ssn: 'CNP-ul introdus nu este valid.'};
+ }
+
+ } else {
+ return {ssn: 'CNP-ul introdus nu este valid.'};
+ }
+ }
+}
+
+ +
+ src/app/core/validators/website-validation.ts
+
+ Methods+ |
+
+
|
+
+ + + + Static + websiteValidation + + + + | +||||||
+
+ websiteValidation(abstractControl: AbstractControl)
+ |
+ ||||||
+ + | +||||||
+ Website validator for reactive forms +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
import { AbstractControl } from '@angular/forms';
+
+export class WebsiteValidation {
+ /**
+ * Website validator for reactive forms
+ */
+ static websiteValidation(abstractControl: AbstractControl): any {
+ const website = abstractControl.value;
+ const reg = new RegExp('^(https?:\\/\\/)?' + // protocol
+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|' + // domain name
+ '((\\d{1,3}\\.){3}\\d{1,3}))' + // ip (v4) address
+ '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port
+ '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
+ '(\\#[-a-z\\d_]*)?$', 'i');
+ if (reg.test(website)) {
+ return null;
+ }
+
+ return { 'website': 'Adresa paginii web introdusă nu este validă (ex: www.website.com).' };
+ }
+}
+
+ +
+ src/app/pages/resources/resources/components/add-resource/add-resource.component.ts
+
+
+ OnInit
+
selector | +app-add-resource |
+
styleUrls | +./add-resource.component.scss |
+
templateUrl | +./add-resource.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(resourcesService: ResourcesService, route: ActivatedRoute, location: Location, router: Router, citiesandCounties: CitiesCountiesService, fb: FormBuilder, utilService: UtilService, filterService: FiltersService, authService: AuthenticationService)
+ |
+ ||||||||||||||||||||||||||||||
+ + | +||||||||||||||||||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + countykey + + + + | +||||||||
+countykey(event: any)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for editing the county field. When activated, disable the city form until enter is pressed or mouse selection +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + onSubmit + + + + | +
+onSubmit()
+ |
+
+ + | +
+ Process form values and send data to server. If success close page +
+ Returns :
+ void
+
+ |
+
+ + + + selectedCategory + + + + | +
+selectedCategory()
+ |
+
+ + | +
+ trigger for select category from category typeahead. will unlock the subcategory field +
+ Returns :
+ void
+
+ |
+
+ + + + selectedCity + + + + | +||||||||
+selectedCity(val: literal type)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select city from city typeahead +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + selectedCounty + + + + | +||||||||
+selectedCounty(val: any)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select county from county typeahead. will unlock the city field +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + selectedOrganisation + + + + | +||||||||
+selectedOrganisation(val: any)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select organisation from organisation typeahead. +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + Public + authService + + + | +
+ Type : AuthenticationService
+
+ |
+
+ + | +
+ + + + categories + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ + + + cities + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ list of items to select from. + |
+
+ + + + cityPlaceholder + + + | +
+ Type : string
+
+ |
+
+ Default value : 'Selectați mai întâi județul'
+ |
+
+ + | +
+ placeholders for HTML + |
+
+ + + + click$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click1$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click2$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click3$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click4$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + edit + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag -> if user is editing then method is PUT, else POST + |
+
+ + + + focus$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus1$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus2$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus3$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus4$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + form + + + | +
+ Type : FormGroup
+
+ |
+
+ + | +
+ form that holds data + |
+
+ + + + formatter + + + | +
+ Default value : () => {...}
+ |
+
+ + | +
+ formater to display only name from object + |
+
+ + + + instance1 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + instance2 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + instance3 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + instance4 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + loading + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag -> if information is beeing loaded show loader elements in frontend + |
+
+ + + + loadingCities + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + res + + + | +
+ Type : any
+
+ |
+
+ + | +
+ + + + resourcePlaceholder + + + | +
+ Type : string
+
+ |
+
+ Default value : 'Selectați mai întâi categoria'
+ |
+
+ + | +
+ + + + searchcounty + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for county typeahead. registers typing, focus, and click and searches the backend + |
+ ||||
+
+ Parameters :
+
+
|
+
+ + + + searchOrganisation + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for organisation typeahead. registers typing, focus, and click and searches the backend + |
+ ||||
+
+ Parameters :
+
+
|
+
+ + + + subCategories + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
import { Component, OnInit, Input, ViewChild } from '@angular/core';
+import {
+ FormGroup,
+ FormControl,
+ Validators,
+ FormBuilder
+} from '@angular/forms';
+import { Router, ActivatedRoute } from '@angular/router';
+import { CitiesCountiesService } from '../../../../../core/service/cities-counties.service';
+import { Subject } from 'rxjs/internal/Subject';
+import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
+import { Observable } from 'rxjs/internal/Observable';
+import {
+ debounceTime,
+ distinctUntilChanged,
+ filter,
+ map,
+ switchMap
+} from 'rxjs/operators';
+import { merge } from 'rxjs';
+import { ResourcesService } from '@app/pages/resources/resources.service';
+import { AuthenticationService, FiltersService, UtilService } from '@app/core';
+import { Location } from '@angular/common';
+import { LocationValidation } from '@app/core/validators/location-validation';
+
+@Component({
+ selector: 'app-add-resource',
+ templateUrl: './add-resource.component.html',
+ styleUrls: ['./add-resource.component.scss']
+})
+export class AddResourceComponent implements OnInit {
+ /**
+ * form that holds data
+ */
+ form: FormGroup;
+ /**
+ * flag -> if user is editing then method is PUT, else POST
+ */
+ edit = false;
+ res: any;
+ /**
+ * list of items to select from.
+ */
+ cities: any[] = [];
+ categories: any[] = [];
+ subCategories: any[] = [];
+ /**
+ * flag -> if information is beeing loaded show loader elements in frontend
+ */
+ loading = false;
+ loadingCities = false;
+
+ /**
+ * placeholders for HTML
+ */
+ cityPlaceholder = 'Selectați mai întâi județul';
+ resourcePlaceholder = 'Selectați mai întâi categoria';
+
+ /**
+ * references to NGBTypeahead for opening on focus or click
+ */
+ @ViewChild('instance', { static: true }) instance: NgbTypeahead;
+ focus$ = new Subject<string>();
+ click$ = new Subject<string>();
+ @ViewChild('instance', { static: true }) instance1: NgbTypeahead;
+ focus1$ = new Subject<string>();
+ click1$ = new Subject<string>();
+ @ViewChild('instance', { static: true }) instance2: NgbTypeahead;
+ focus2$ = new Subject<string>();
+ click2$ = new Subject<string>();
+ @ViewChild('instance', { static: true }) instance3: NgbTypeahead;
+ focus3$ = new Subject<string>();
+ click3$ = new Subject<string>();
+ @ViewChild('instance', { static: true }) instance4: NgbTypeahead;
+ focus4$ = new Subject<string>();
+ click4$ = new Subject<string>();
+
+ constructor(private resourcesService: ResourcesService,
+ private route: ActivatedRoute,
+ private location: Location, private router: Router,
+ private citiesandCounties: CitiesCountiesService,
+ private fb: FormBuilder, private utilService: UtilService,
+ private filterService: FiltersService,
+ public authService: AuthenticationService) {}
+
+ ngOnInit() {
+
+ const navigation = this.router.getCurrentNavigation();
+ let fixedOrg: any = null;
+ this.filterService.getSubCategories('0', '').subscribe((elem: any) => {
+ this.categories = elem;
+ });
+ if (navigation && navigation.extras && navigation.extras.state) {
+ fixedOrg = navigation.extras.state.ngo;
+ }
+
+ this.form = this.fb.group({
+ subCategory: [{value: '', disabled: true}],
+ name: ['', Validators.required],
+ address: '',
+ resource_type: ['', Validators.required],
+ category: ['', Validators.required],
+ organisation: this.authService.is('NGO') ?
+ [{value: {name: this.authService.user.organisation.name, _id: this.authService.user.organisation._id},
+ disabled: true }, Validators.required]
+ : fixedOrg ?
+ [{value: {name: fixedOrg.name, _id: fixedOrg._id},
+ disabled: false }, Validators.required]
+ : [{value: '' , disabled: false }, Validators.required],
+ quantity: ['', [Validators.required, Validators.min(1)]],
+ city: [{ value: '', disabled: true }, Validators.required],
+ county: ['', Validators.required],
+ comments: ''
+ });
+ }
+/**
+ * formater to display only name from object
+ */
+ formatter = (result: { name: string }) => result.name;
+ /**
+ * trigger for county typeahead. registers typing, focus, and click and searches the backend
+ * @param {Observable} text observable event with the filter text
+ */
+ searchcounty = (text$: Observable<string>) => {
+
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+ const clicksWithClosedPopup$ = this.click1$.pipe(
+ filter(() => !this.instance1.isPopupOpen())
+ );
+ const inputFocus$ = this.focus1$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ switchMap((term: string) => this.citiesandCounties.getCounties(term))
+ );
+ }
+/**
+ * trigger for county typeahead. registers typing, focus, and click and searches the backend
+ * @param {Observable} text observable event with the filter text
+ */
+ searchcity = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+ const clicksWithClosedPopup$ = this.click2$.pipe(
+ filter(() => !this.instance2.isPopupOpen())
+ );
+ const inputFocus$ = this.focus2$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ map((term: string) => {
+ if (term === '') {
+ return this.cities;
+ } else {
+ return this.cities.filter(v => {
+ const aux: String = this.utilService.removeDiacritics(v.name).toLowerCase();
+ return aux.indexOf(term.toLowerCase()) > -1;
+ }).slice(0, 20);
+ }
+ }));
+ }
+ /**
+ * trigger for organisation typeahead. registers typing, focus, and click and searches the backend
+ * @param {Observable} text observable event with the filter text
+ */
+ searchOrganisation = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+
+ const clicksWithClosedPopup$ = this.click$.pipe(
+ filter(() => !this.instance.isPopupOpen())
+ );
+
+ const inputFocus$ = this.focus$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ switchMap((term: string) => {
+ return this.filterService.getorganisationbyName(term);
+ }));
+ }
+/**
+ * trigger for select county from county typeahead. will unlock the city field
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedCounty(val: any) {
+ this.form.controls.county.markAsTouched();
+ if (val.item && val.item._id) {
+ this.form.patchValue({county: val.item});
+ this.loadingCities = true;
+ this.citiesandCounties.getCitiesbyCounty(val.item._id, '').subscribe((res: any) => {
+ this.cities = res;
+ this.loadingCities = false;
+ this.form.controls.city.enable();
+ });
+ this.cityPlaceholder = 'Alegeți Orașul';
+ } else if (this.form.controls.county.value.name && val !== this.form.controls.county.value.name) {
+ this.form.patchValue({county: '', city: ''});
+ }
+ }
+/**
+ * trigger for editing the county field. When activated, disable the city form until enter is pressed or mouse selection
+ * @param {any} event to be verified for which key has been pressed
+ */
+ countykey(event: any) {
+ this.form.controls.county.markAsTouched();
+ if (event.code !== 'Enter') {
+ this.cities = [];
+ this.form.controls.city.disable();
+ this.form.controls.city.reset('');
+ this.cityPlaceholder = 'Selectați mai întâi județul';
+ }
+ }
+/**
+ * trigger for select city from city typeahead
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedCity(val: { item: any }) {
+ this.form.controls.city.markAsTouched();
+ this.form.patchValue({city: val.item});
+ }
+ /**
+ * trigger for select organisation from organisation typeahead.
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedOrganisation(val: any) {
+ this.form.controls.organisation.markAsTouched();
+ if (val.item && val.item._id) {
+ this.form.patchValue({organisation: val.item});
+ } else if (this.form.controls.organisation.value.name && val !== this.form.controls.organisation.value.name) {
+ this.form.patchValue({organisation: ''});
+ }
+ }
+ /**
+ * trigger for select category from category typeahead. will unlock the subcategory field
+ */
+ selectedCategory() {
+ this.form.controls.category.markAsTouched();
+ if (this.form.value.category) {
+ this.filterService.getSubCategories(this.form.value.category, '').subscribe(resp => {
+ if (resp.length > 0) {
+ this.form.controls.subCategory.enable();
+ this.resourcePlaceholder = 'Alegeți Categoria';
+ this.subCategories = resp;
+ } else {
+ this.form.controls.subCategory.disable();
+ this.form.controls.subCategory.reset('');
+ this.resourcePlaceholder = 'Selectați mai întâi categoria';
+ }
+ });
+ }
+ }
+
+
+ /**
+ * Process form values and send data to server. If success close page
+ */
+ onSubmit() {
+ this.loading = true;
+ const resource = this.form.value;
+ resource.organisation_id = this.form.controls['organisation'].value._id;
+ resource.county = resource.county._id;
+ resource.city = resource.city._id;
+ resource.categories = [resource.category];
+ if (resource.subCategory) {
+ resource.categories.push(resource.subCategory);
+ }
+ this.resourcesService
+ .addResource(resource)
+ .subscribe((element: any) => {
+ this.loading = false;
+ this.location.back();
+ }, () => {
+ this.loading = false;
+ });
+ }
+
+}
+
+ <div class="container mt-5">
+ <nav class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <app-back-button></app-back-button>
+ </nav>
+ <p class="page-title">Adaugă o resursă:</p>
+ <div class="note col-md-8">
+ <span>
+ Vrei să adaugi mai multe resurse simultan?
+ <a [routerLink]="['../import']">Importă o listă</a>.
+ </span>
+ </div>
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
+ <div class="row">
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Nume resursă *</label>
+ <input formControlName="name"
+ [ngClass]="{ 'error': form.controls.name.invalid && form.controls.name.touched }"
+ class="form-control" type="text" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Categorie *</label>
+ <select class="form-control" (change)="selectedCategory()" formControlName="category">
+ <option *ngFor="let category of categories; let i=index" [value]="category._id">
+ {{category.name}}</option>
+ </select>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Subcategorie</label>
+ <select class="form-control" formControlName="subCategory">
+ <option *ngFor="let subCat of subCategories; let i=index" [value]="subCat._id">
+ {{subCat.name}}
+ </option>
+ </select>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Cantitate *</label>
+ <input formControlName="quantity"
+ [ngClass]="{ 'error': form.controls.quantity.invalid && form.controls.quantity.touched }"
+ class="form-control" type="number" />
+ <span class="error-message"
+ *ngIf="form.controls.quantity.invalid && form.controls.quantity.touched">* Cantitatea trebuie să
+ fie mai mare decât 0.</span>
+ </div>
+ </div>
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Tip *</label>
+ <select class="form-control" formControlName="resource_type"
+ [ngClass]="{ 'error': form.controls.resource_type.invalid && form.controls.resource_type.touched }"
+ class="form-control">
+ <option>Perisabil</option>
+ <option>Neperisabil</option>
+ </select>
+ <span class="error-message"
+ *ngIf="form.controls.resource_type.invalid && form.controls.resource_type.touched">* Trebuie sa
+ alegeți un tip.</span>
+ </div>
+ </div>
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Organizație *</label>
+ <input formControlName="organisation" type="text"
+ [ngClass]="{ 'error': form.controls.organisation.invalid && form.controls.organisation.touched }"
+ (selectItem)="selectedOrganisation($event)" class="form-control"
+ [ngbTypeahead]="searchOrganisation" [resultFormatter]="formatter" [inputFormatter]="formatter"
+ (focus)="focus$.next($event.target.value)" (click)="click$.next($event.target.value)"
+ #instance="ngbTypeahead" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Județ *</label>
+ <input type="text" formControlName="county" (selectItem)="selectedCounty($event)"
+ (blur)="selectedCounty($event.target.value)" class="form-control" [ngbTypeahead]="searchcounty"
+ (focus)="focus1$.next($event.target.value)" [inputFormatter]="formatter"
+ [resultFormatter]="formatter" (keyup)="countykey($event)"
+ [ngClass]="{ 'error': form.controls.county.invalid && form.controls.county.touched}"
+ (click)="click1$.next($event.target.value)" #instance="ngbTypeahead" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Localitate *</label>
+ <input type="text" formControlName="city" class="form-control" [ngbTypeahead]="searchcity"
+ (selectItem)="selectedCity($event)"
+ [ngClass]="{ 'error': form.controls.city.invalid && form.controls.city.touched }"
+ placeholder="{{ cityPlaceholder }}" autocomplete="new-password" [inputFormatter]="formatter"
+ [resultFormatter]="formatter" (focus)="focus2$.next($event.target.value)"
+ (click)="click2$.next($event.target.value)" #instance="ngbTypeahead" />
+ <div class="spinner-border text-grey input-loader" role="status" *ngIf="loadingCities">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Adresă</label>
+ <input formControlName="address" class="form-control"
+ [ngClass]="{ 'error': form.controls.address.invalid && form.controls.address.touched }"
+ type="text" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-12">
+ <div class="form-group">
+ <label>Comentarii</label>
+ <textarea rows="3" formControlName="comments"
+ [ngClass]="{ 'error': form.controls.comments.invalid && form.controls.comments.touched }"
+ class="form-control" type="text"></textarea>
+ </div>
+ </div>
+ </div>
+ <button *ngIf="!loading" [disabled]="form.invalid"
+ class="btn btn-info btn-rounded waves-effect waves-light mt-5 float-right" type="submit">
+ Adaugă
+ </button>
+ <div class="spinner-border text-green float-right" role="status" *ngIf="loading">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </form>
+</div>
+
+ ./add-resource.component.scss
+
+ +
+ src/app/pages/users/users/components/add-user/add-user.component.ts
+
+
+ OnInit
+
selector | +app-add-user |
+
styleUrls | +./add-user.component.scss |
+
templateUrl | +./add-user.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(fb: FormBuilder, router: Router, filterService: FiltersService, route: ActivatedRoute, authService: AuthenticationService, usersService: UsersService, location: Location)
+ |
+ ||||||||||||||||||||||||
+ + | +||||||||||||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + onSubmit + + + + | +
+onSubmit()
+ |
+
+ + | +
+ Process form values and send data to server. If success close page +
+ Returns :
+ void
+
+ |
+
+ + + + selectedInstitut + + + + | +||||||||
+selectedInstitut(val: literal type)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select county from county typeahead. will unlock the city field +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + setDuplicateEmailError + + + + | +
+setDuplicateEmailError()
+ |
+
+ + | +
+ Set error if email already exists +
+ Returns :
+ void
+
+ |
+
+ + + + setInstitutions + + + + | +
+setInstitutions()
+ |
+
+ + | +
+ set institution form if needed +
+ Returns :
+ void
+
+ |
+
+ + + + setOrganisations + + + + | +
+setOrganisations()
+ |
+
+ + | +
+ set organisation form if needed +
+ Returns :
+ void
+
+ |
+
+ + + + setPageByRoles + + + + | +
+setPageByRoles()
+ |
+
+ + | +
+ call function to complete form depending on the role of the user +
+ Returns :
+ void
+
+ |
+
+ + + + Public + authService + + + | +
+ Type : AuthenticationService
+
+ |
+
+ + | +
+ + + + click$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + displayInstitution + + + | +
+ Default value : false
+ |
+
+ + | +
+ FLAG form HTML to display form according to role + |
+
+ + + + displayOrganisation + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + focus$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + form + + + | +
+ Type : FormGroup
+
+ |
+
+ + | +
+ form that holds data + |
+
+ + + + instance + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ references to NGBTypeahead for opening on focus or click + |
+
+ + + + institutions + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ list of institutions or organisation to pe parsed + |
+
+ + + + loading + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for HTML to display loading animation + |
+
+ + + + organisations + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ + + + role + + + | +
+ Type : string
+
+ |
+
+ + | +
+ role of user that will be created + |
+
+ + + + Public + route + + + | +
+ Type : ActivatedRoute
+
+ |
+
+ + | +
+ + + + user + + + | +
+ Type : any
+
+ |
+
+ Default value : {}
+ |
+
+ + | +
+ user data + |
+
import { Component, OnInit, ViewChild } from '@angular/core';
+import { FormBuilder, Validators, FormGroup } from '@angular/forms';
+import { Router, ActivatedRoute } from '@angular/router';
+import { UsersService } from '@app/core/service/users.service';
+import { EmailValidation } from '@app/core/validators/email-validation';
+import { PhoneValidation } from '@app/core/validators/phone-validation';
+import { AuthenticationService, FiltersService } from '@app/core';
+import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
+import { Subject } from 'rxjs';
+import { Location } from '@angular/common';
+
+@Component({
+ selector: 'app-add-user',
+ templateUrl: './add-user.component.html',
+ styleUrls: ['./add-user.component.scss']
+})
+
+export class AddUserComponent implements OnInit {
+ /**
+ * form that holds data
+ */
+ form: FormGroup;
+ /**
+ * role of user that will be created
+ */
+ role: string;
+ /**
+ * user data
+ */
+ user: any = {};
+ /**
+ * flag for HTML to display loading animation
+ */
+ loading = false;
+ /**
+ * references to NGBTypeahead for opening on focus or click
+ */
+ @ViewChild('instance', { static: true }) instance: NgbTypeahead;
+ focus$ = new Subject<string>();
+ click$ = new Subject<string>();
+ /**
+ * list of institutions or organisation to pe parsed
+ */
+ institutions: any[] = [];
+ organisations: any[] = [];
+ /**
+ * FLAG form HTML to display form according to role
+ */
+ displayInstitution = false;
+ displayOrganisation = false;
+
+ constructor(private fb: FormBuilder,
+ private router: Router,
+ private filterService: FiltersService,
+ public route: ActivatedRoute,
+ public authService: AuthenticationService,
+ private usersService: UsersService,
+ private location: Location) { }
+
+ ngOnInit() {
+ this.form = this.fb.group({
+ name: ['', Validators.required],
+ email: ['', [ Validators.required, EmailValidation.emailValidation ]],
+ phone: ['', [ Validators.required, PhoneValidation.phoneValidation ]],
+ institution: [''],
+ organisation: ['']
+ });
+
+ if (this.route.snapshot.paramMap.get('role')) {
+ this.role = this.route.snapshot.paramMap.get('role');
+ this.setPageByRoles();
+ }
+ }
+/**
+ * call function to complete form depending on the role of the user
+ */
+ setPageByRoles() {
+ if ((this.role === '0' || this.role === '1') && this.authService.is('DSU')) {
+ this.setInstitutions();
+ }
+
+ if (this.role === '2' && this.authService.is('DSU')) {
+ this.setOrganisations();
+ }
+ }
+/**
+ * set organisation form if needed
+ */
+ setOrganisations() {
+ this.filterService.getorganisationbyName().subscribe(response => {
+ this.organisations = response;
+ });
+
+ this.displayOrganisation = true;
+ this.form.controls['organisation'].setValidators(Validators.required);
+ }
+/**
+ * set institution form if needed
+ *
+ */
+ setInstitutions() {
+ this.filterService.getInstitutionFilters().subscribe(response => {
+ this.institutions = response;
+ });
+
+ this.displayInstitution = true;
+ this.form.controls['institution'].setValidators(Validators.required);
+ }
+
+ /**
+ * trigger for select county from county typeahead. will unlock the city field
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedInstitut(val: { item: any }) {
+ this.form.controls.institution.markAsTouched();
+ this.form.patchValue({institution: val.item});
+ }
+
+ /**
+ * Process form values and send data to server. If success close page
+ */
+ onSubmit() {
+ this.loading = true;
+ this.user.name = this.form.value.name;
+ this.user.email = this.form.value.email;
+ this.user.phone = this.form.value.phone;
+
+ if (this.role) {
+ this.user.role = this.role;
+
+ if (this.role === '1' || this.role === '0') {
+ this.user.institution = this.form.value.institution;
+ }
+
+ if (this.role === '2') {
+ this.user.organisation = this.form.value.organisation;
+ }
+ }
+
+ this.usersService.addUser(this.user).subscribe((response) => {
+ this.loading = false;
+ this.location.back();
+ }, () => {
+ this.setDuplicateEmailError();
+ this.loading = false;
+ });
+ }
+ /**
+ * Set error if email already exists
+ */
+
+ setDuplicateEmailError() {
+ this.form.controls['email'].setErrors({'email': 'Adresa de email introdusă există deja în sistem.'});
+ }
+}
+
+ <div class="container mt-5">
+ <nav
+ class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <app-back-button></app-back-button>
+ </nav>
+ <p class="page-title">Adaugă un utilizator:</p>
+
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
+ <div class="row">
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Nume și Prenume *</label>
+ <input
+ [ngClass]="{ 'error': form.controls.name.invalid && form.controls.name.touched }"
+ formControlName="name"
+ class="form-control"
+ type="text"/>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Email *</label>
+ <input
+ [ngClass]="{ 'error': form.controls.email.invalid && form.controls.email.touched }"
+ formControlName="email"
+ class="form-control"
+ type="email"
+ />
+ <span class="error-message" *ngIf="form.controls.email.invalid && form.controls.email.touched">* {{form.controls.email.errors.email}}</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Telefon *</label>
+ <input
+ [ngClass]="{ 'error': form.controls.phone.invalid && form.controls.phone.touched }"
+ formControlName="phone"
+ class="form-control"
+ type="tel"
+ />
+ <span class="error-message" *ngIf="form.controls.phone.invalid && form.controls.phone.touched">* {{form.controls.phone.errors.phone}}</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4" [ngStyle]="{'display': displayInstitution ? 'initial' : 'none'}" >
+ <div class="form-group">
+ <label>Insituție *</label>
+ <select class="form-control" formControlName="institution">
+ <option *ngFor="let institution of institutions; let i=index" [value]="institution._id">
+ {{institution.name}}
+ </option>
+ </select>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4" [ngStyle]="{'display': displayOrganisation ? 'initial' : 'none'}" >
+ <div class="form-group">
+ <label>Organizație *</label>
+ <select class="form-control" formControlName="organisation">
+ <option *ngFor="let organisation of organisations; let i=index" [value]="organisation._id">
+ {{organisation.name}}
+ </option>
+ </select>
+ </div>
+ </div>
+
+ </div>
+ <button
+ *ngIf="!loading"
+ class="btn btn-info btn-rounded waves-effect waves-light mt-5 float-right"
+ type="submit"
+ [disabled]="form.invalid">
+ Adaugă
+ </button>
+ <div class="spinner-border text-green float-right" role="status" *ngIf="loading">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </form>
+</div>
+
+ ./add-user.component.scss
+
+ +
+ src/app/pages/volunteers/volunteers/components/add-volunteer/add-volunteer.component.ts
+
+
+ OnInit
+
providers | +
+ { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }
+ |
+
selector | +app-add-volunteer |
+
styleUrls | +./add-volunteer.component.scss |
+
templateUrl | +./add-volunteer.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+ Accessors+ |
+
+ + | +
+constructor(volunteerService: VolunteerService, filterService: FiltersService, router: Router, utilService: UtilService, route: ActivatedRoute, location: Location, fb: FormBuilder, citiesandCounties: CitiesCountiesService, authService: AuthenticationService)
+ |
+ ||||||||||||||||||||||||||||||
+ + | +||||||||||||||||||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + acreditorKey + + + + | +
+acreditorKey()
+ |
+
+ + | +
+ if error has appeared, when user changes input remove the error +
+ Returns :
+ void
+
+ |
+
+ + + + addCourse + + + + | +
+addCourse()
+ |
+
+ + | +
+ trigger for add course from course table footer. willbe added to form and displayed in table +
+ Returns :
+ void
+
+ |
+
+ + + + countykey + + + + | +||||||||
+countykey(event: any)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for editing the county field. When activated, disable the city form until enter is pressed or mouse selection +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + courseKey + + + + | +||||||||
+courseKey(event: any)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for editing the county field. When activated, disable the static acreditor +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + onSubmit + + + + | +
+onSubmit()
+ |
+
+ + | +
+ Process form values and send data to server. If success close page +
+ Returns :
+ void
+
+ |
+
+ + + + removeCourse + + + + | +||||||||
+removeCourse(index: number)
+ |
+ ||||||||
+ + | +||||||||
+ remove one of the courses from the table by index +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + selectedCity + + + + | +||||||||
+selectedCity(val: literal type)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select city from city typeahead +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + selectedCounty + + + + | +||||||||
+selectedCounty(val: any)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select county from county typeahead. will unlock the city field +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + selectedcourse + + + + | +||||||
+selectedcourse(obj: any)
+ |
+ ||||||
+ + | +||||||
+ trigger for select course from course typeahead +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + selectedorganisation + + + + | +||||||||
+selectedorganisation(val: literal type)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select organisation from organisation typeahead. +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + accreditedError + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + acreditedby + + + | +
+ Type : any
+
+ |
+
+ + | +
+ + + + Public + authService + + + | +
+ Type : AuthenticationService
+
+ |
+
+ + | +
+ + + + cities + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ list of cities to pe parsed. edited when the user selects a county or edits this NGO + |
+
+ + + + cityPlaceholder + + + | +
+ Type : string
+
+ |
+
+ Default value : 'Selectați mai întâi județul'
+ |
+
+ + | +
+ placeholder for disabled city field + |
+
+ + + + click$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click1$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click2$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click3$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click4$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + coursename + + + | +
+ Type : any
+
+ |
+
+ + | +
+ courses values and errors + |
+
+ + + + coursenameError + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + dateError + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + focus$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus1$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus2$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus3$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus4$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + form + + + | +
+ Type : FormGroup
+
+ |
+
+ + | +
+ form that holds data + |
+
+ + + + formatter + + + | +
+ Default value : () => {...}
+ |
+
+ + | +
+ formater to display only name from object + |
+
+ + + + instance + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ references to NGBTypeahead for opening on focus or click + |
+
+ + + + instance1 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + instance2 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + instance3 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + instance4 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + loading + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag -> if information is beeing loaded show loader elements in frontend + |
+
+ + + + loadingCities + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + now + + + | +
+ Type : any
+
+ |
+
+ + | +
+ + + + obtained + + + | +
+ Type : Date
+
+ |
+
+ + | +
+ + + + searchacreditedby + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for accredited by typeahead. registers typing, focus, and click and searches the backend + |
+ ||||
+
+ Parameters :
+
+
|
+
+ + + + searchcity + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for county typeahead. registers typing, focus, and click and searches the backend + |
+ ||||
+
+ Parameters :
+
+
|
+
+ + + + searchcounty + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for county typeahead. registers typing, focus, and click and searches the backend + |
+ ||||
+
+ Parameters :
+
+
|
+
+ + + + searchcourse + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for course typeahead. registers typing, focus, and click and searches the backend + |
+ ||||
+
+ Parameters :
+
+
|
+
+ + + + searchorganisation + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for organistion typeahead. registers typing, focus, and click and searches the backend + |
+ ||||
+
+ Parameters :
+
+
|
+
+ + + + static_accreditor + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + Public + volunteerService + + + | +
+ Type : VolunteerService
+
+ |
+
+ + | +
+ + f + | +
+ getf()
+ |
+
+ + | +
+ wrapper for the form' controls + |
+
+ + c + | +
+ getc()
+ |
+
+ + | +
+ wrapper for the form's controls courses array + |
+
import { Component, OnInit, ViewChild } from '@angular/core';
+import { FormGroup, Validators, FormBuilder, FormArray } from '@angular/forms';
+import { VolunteerService } from '../../../volunteers.service';
+import { Router, ActivatedRoute } from '@angular/router';
+import { Observable, merge, Subject } from 'rxjs';
+import {
+ debounceTime,
+ distinctUntilChanged,
+ map,
+ filter,
+ switchMap,
+} from 'rxjs/operators';
+import { NgbTypeahead, NgbDateAdapter, NgbDateNativeAdapter } from '@ng-bootstrap/ng-bootstrap';
+import { CitiesCountiesService } from '../../../../../core/service/cities-counties.service';
+import { AuthenticationService, FiltersService, UtilService } from '@app/core';
+import { EmailValidation } from '@app/core/validators/email-validation';
+import { PhoneValidation } from '@app/core/validators/phone-validation';
+import { Location } from '@angular/common';
+import { SsnValidation } from '@app/core/validators/ssn-validation';
+import * as moment from 'moment';
+import { TouchSequence } from 'selenium-webdriver';
+
+@Component({
+ selector: 'app-add-volunteer',
+ templateUrl: './add-volunteer.component.html',
+ styleUrls: ['./add-volunteer.component.scss'],
+ providers: [{provide: NgbDateAdapter, useClass: NgbDateNativeAdapter}]
+})
+
+export class AddVolunteerComponent implements OnInit {
+ /**
+ * form that holds data
+ */
+ form: FormGroup;
+ /**
+ * courses values and errors
+ */
+ coursename: any;
+ coursenameError = false;
+ acreditedby: any;
+ accreditedError = false;
+ obtained: Date;
+ dateError = false;
+
+/**
+ * placeholder for disabled city field
+ */
+ cityPlaceholder = 'Selectați mai întâi județul';
+/**
+ * references to NGBTypeahead for opening on focus or click
+ */
+ @ViewChild('instance', { static: true }) instance: NgbTypeahead;
+ focus$ = new Subject<string>();
+ click$ = new Subject<string>();
+
+ @ViewChild('instance', { static: true }) instance1: NgbTypeahead;
+ focus1$ = new Subject<string>();
+ click1$ = new Subject<string>();
+
+ @ViewChild('instance', { static: true }) instance2: NgbTypeahead;
+ focus2$ = new Subject<string>();
+ click2$ = new Subject<string>();
+
+ @ViewChild('instance', { static: true }) instance3: NgbTypeahead;
+ focus3$ = new Subject<string>();
+ click3$ = new Subject<string>();
+
+ @ViewChild('instance', { static: true }) instance4: NgbTypeahead;
+ focus4$ = new Subject<string>();
+ click4$ = new Subject<string>();
+ static_accreditor = false;
+ /**
+ * flag -> if information is beeing loaded show loader elements in frontend
+ */
+ loading = false;
+ loadingCities = false;
+ /**
+ * list of cities to pe parsed. edited when the user selects a county or edits this NGO
+ */
+ cities: any[] = [];
+ // /**
+ // * date object to force course acreditation date in the past
+ // */
+ now: any;
+
+ constructor(
+ public volunteerService: VolunteerService,
+ private filterService: FiltersService,
+ private router: Router, private utilService: UtilService,
+ private route: ActivatedRoute, private location: Location,
+ private fb: FormBuilder,
+ private citiesandCounties: CitiesCountiesService,
+ public authService: AuthenticationService) {
+ const dateObj = new Date();
+ const month = dateObj.getUTCMonth() + 1; // months from 1-12
+ const day = dateObj.getUTCDate();
+ const year = dateObj.getUTCFullYear();
+ this.now = {day: day, month: month, year: year};
+ }
+
+ ngOnInit() {
+ const navigation = this.router.getCurrentNavigation();
+
+ let fixedOrg: any;
+ if (navigation && navigation.extras && navigation.extras.state) {
+ fixedOrg = navigation.extras.state.ngo;
+ }
+ this.form = this.fb.group({
+ name: ['', Validators.required],
+ ssn: ['', [Validators.required, SsnValidation.ssnValidation]],
+ email: ['', [Validators.required, EmailValidation.emailValidation]],
+ phone: ['', [Validators.required, PhoneValidation.phoneValidation]],
+ address: [''],
+ job: [''],
+ county: ['', Validators.required],
+ city: [{ value: '', disabled: true }, Validators.required],
+ organisation: this.authService.is('NGO') ?
+ [{value: {name: this.authService.user.organisation.name, _id: this.authService.user.organisation._id},
+ disabled: true }, Validators.required]
+ : fixedOrg ?
+ [{value: {name: fixedOrg.name, _id: fixedOrg._id},
+ disabled: false }, Validators.required]
+ : [{value: '' , disabled: false }, Validators.required],
+ courses: this.fb.array([]),
+ comments: ['']
+ });
+ }
+ /**
+ * wrapper for the form' controls
+ */
+ get f() {
+ return this.form.controls;
+ }
+/**
+ * wrapper for the form's controls courses array
+ */
+ get c() {
+ return this.f.courses as FormArray;
+ }
+/**
+ * formater to display only name from object
+ */
+ formatter = (result: { name: string }) => result.name;
+ /**
+ * trigger for organistion typeahead. registers typing, focus, and click and searches the backend
+ * @param {Observable} text observable event with the filter text
+ */
+ searchorganisation = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+
+ const clicksWithClosedPopup$ = this.click$.pipe(
+ filter(() => !this.instance.isPopupOpen())
+ );
+
+ const inputFocus$ = this.focus$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ switchMap((term: string) => {
+ return this.filterService.getorganisationbyName(term);
+ }));
+ }
+ /**
+ * trigger for course typeahead. registers typing, focus, and click and searches the backend
+ * @param {Observable} text observable event with the filter text
+ */
+ searchcourse = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+
+ const clicksWithClosedPopup$ = this.click3$.pipe(
+ filter(() => !this.instance3.isPopupOpen())
+ );
+
+ const inputFocus$ = this.focus3$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ switchMap((term: string) => {
+ this.coursenameError = false;
+ return this.filterService.getSpecializationFilters(term);
+ }));
+ }
+ /**
+ * trigger for accredited by typeahead. registers typing, focus, and click and searches the backend
+ * @param {Observable} text observable event with the filter text
+ */
+ searchacreditedby = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+
+ const clicksWithClosedPopup$ = this.click4$.pipe(
+ filter(() => !this.instance.isPopupOpen())
+ );
+
+ const inputFocus$ = this.focus4$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ switchMap((term: string) => {
+ this.accreditedError = false;
+ return this.filterService.getAcreditedFilters(term);
+ }));
+ }
+/**
+ * trigger for county typeahead. registers typing, focus, and click and searches the backend
+ * @param {Observable} text observable event with the filter text
+ */
+ searchcounty = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+ const clicksWithClosedPopup$ = this.click1$.pipe(
+ filter(() => !this.instance1.isPopupOpen())
+ );
+ const inputFocus$ = this.focus1$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ switchMap((term: string) => this.citiesandCounties.getCounties(term))
+ );
+ }
+/**
+ * trigger for county typeahead. registers typing, focus, and click and searches the backend
+ * @param {Observable} text observable event with the filter text
+ */
+ searchcity = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(debounceTime(200), distinctUntilChanged());
+ const clicksWithClosedPopup$ = this.click2$.pipe(
+ filter(() => !this.instance2.isPopupOpen())
+ );
+ const inputFocus$ = this.focus2$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ map((term: string) => {
+ if (term === '') {
+ return this.cities;
+ } else {
+ return this.cities.filter(v => {
+ const aux: String = this.utilService.removeDiacritics(v.name).toLowerCase();
+ return aux.indexOf(term.toLowerCase()) > -1;
+ }).slice(0, 20);
+ }
+ }));
+ }
+ /**
+ * trigger for editing the county field. When activated, disable the static acreditor
+ * @param {any} event to be verified for which key has been pressed
+ */
+ courseKey(event: any) {
+ if (event.code !== 'Enter') {
+ this.coursenameError = true;
+ this.static_accreditor = false;
+ this.acreditedby = '';
+ }
+ }
+ /**
+ * if error has appeared, when user changes input remove the error
+ */
+ acreditorKey() {
+ this.accreditedError = false;
+ }
+/**
+ * trigger for select course from course typeahead
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedcourse(obj: any) {
+ if (obj.item.static_accreditor) {
+ this.acreditedby = obj.item.static_accreditor;
+ this.static_accreditor = true;
+ } else {
+ this.acreditedby = {
+ };
+ this.static_accreditor = false;
+ }
+ this.coursenameError = false;
+ }
+/**
+ * trigger for add course from course table footer. willbe added to form and displayed in table
+ */
+ addCourse() {
+ const now = new Date();
+ if (!this.acreditedby) {
+ this.accreditedError = true;
+ }
+ if (!this.coursename) {
+ this.coursenameError = true;
+ }
+ if (!this.obtained) {
+ this.dateError = true;
+ }
+ // if (this.obtained > now) {
+ if (!this.coursenameError && this.coursename && this.acreditedby) {
+ this.c.push(
+ this.fb.group({
+ course_name: this.coursename.name,
+ course_name_id: this.coursename._id,
+ obtained: moment(this.obtained).format('DD.MM.YYYY'),
+ accredited_by: this.acreditedby.hasOwnProperty('name') ? this.acreditedby.name : this.acreditedby
+ })
+ );
+ this.static_accreditor = false;
+ this.coursename = null;
+ this.acreditedby = null;
+ this.obtained = null;
+ this.dateError = false;
+ this.accreditedError = false;
+ this.coursenameError = false;
+ }
+ // } else {
+ // this.dateError = true;
+ // }
+ }
+ /**
+ * remove one of the courses from the table by index
+ * @param {number} index result object from typeahead that needs to be stored
+ */
+ removeCourse(index: number) {
+ const control = <FormArray>this.form.controls.courses;
+ control.removeAt(index);
+ }
+ /**
+ * trigger for select county from county typeahead. will unlock the city field
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedCounty(val: any) {
+ this.form.controls.county.markAsTouched();
+ if (val.item && val.item._id) {
+
+ this.form.patchValue({county: val.item});
+ this.loadingCities = true;
+ this.citiesandCounties.getCitiesbyCounty(val.item._id, '').subscribe((res: any) => {
+ this.cities = res;
+ this.loadingCities = false;
+ this.form.controls.city.enable();
+ });
+ this.cityPlaceholder = 'Alegeți Orașul';
+ } else if (this.form.controls.county.value.name && val !== this.form.controls.county.value.name) {
+ this.form.patchValue({county: '', city: ''});
+ }
+ }
+/**
+ * trigger for editing the county field. When activated, disable the city form until enter is pressed or mouse selection
+ * @param {any} event to be verified for which key has been pressed
+ */
+ countykey(event: any) {
+ this.form.controls.county.markAsTouched();
+ if (event.code !== 'Enter') {
+ this.form.controls.city.disable();
+ this.form.controls.city.reset('');
+ this.cityPlaceholder = 'Selectați mai întâi județul';
+ }
+ }
+/**
+ * trigger for select city from city typeahead
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedCity(val: { item: any }) {
+ this.form.controls.city.markAsTouched();
+ this.form.patchValue({city: val.item});
+ }
+ /**
+ * trigger for select organisation from organisation typeahead.
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedorganisation(val: { item: any }) {
+ this.form.controls.organisation.markAsTouched();
+ this.form.patchValue({organisation: val.item});
+ }
+/**
+ * Process form values and send data to server. If success close page
+ */
+ onSubmit() {
+ this.loading = true;
+ const volunteer = {...this.form.value};
+ volunteer.ssn = volunteer.ssn.toString();
+ volunteer.county = volunteer.county._id;
+ volunteer.city = volunteer.city._id;
+ volunteer.organisation_id = this.form.controls.organisation.value._id;
+ this.volunteerService.addVolunteer(volunteer).subscribe(() => {
+ this.loading = false;
+ this.form.controls['email'].setErrors({});
+ this.location.back();
+ }, (obj: any) => {
+ this.loading = false;
+ if (obj.error.errors) {
+ if (obj.error.errors[0].indexOf('CNP') !== -1) {
+ this.form.controls['ssn'].setErrors({'ssn': 'CNP-ul introdus există deja în sistem.'});
+ } else {
+ this.form.controls['email'].setErrors({'email': 'Adresa de email introdusă există deja în sistem.'});
+ }
+ }
+ });
+ }
+}
+
+ <div class="container wide-container mt-5">
+ <nav class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <app-back-button></app-back-button>
+ </nav>
+ <div>
+ <p class="page-title">Adaugă un voluntar:</p>
+ <div class="note col-md-8">
+ <span>
+ Vrei să adaugi mai mulți voluntari simultan?
+ <a [routerLink]="['../import']">Importă o listă</a>.
+ </span>
+ </div>
+ </div>
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
+ <div class="row">
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Nume și Prenume *</label>
+ <input
+ [ngClass]="{ 'error': form.controls.name.invalid && form.controls.name.touched }"
+ formControlName="name"
+ class="form-control"
+ type="text" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>CNP *</label>
+ <input
+ [ngClass]="{ 'error': form.controls.ssn.invalid && form.controls.ssn.touched }"
+ formControlName="ssn"
+ class="form-control"
+ type="number" />
+ <span class="error-message" *ngIf="form.controls.ssn.invalid && form.controls.ssn.touched">* {{form.controls.ssn.errors.ssn}}</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Email *</label>
+ <input
+ [ngClass]="{ 'error': form.controls.email.invalid && form.controls.email.touched }"
+ formControlName="email"
+ class="form-control"
+ type="email"/>
+ <span class="error-message" *ngIf="form.controls.email.invalid && form.controls.email.touched">* {{form.controls.email.errors.email}}</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Profesie</label>
+ <input
+ [ngClass]="{ 'error': form.controls.job.invalid && form.controls.job.touched }"
+ formControlName="job"
+ class="form-control"
+ type="text" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Telefon *</label>
+ <input
+ [ngClass]="{ 'error': form.controls.phone.invalid && form.controls.phone.touched }"
+ formControlName="phone"
+ class="form-control"
+ type="tel" />
+ <span class="error-message" *ngIf="form.controls.phone.invalid && form.controls.phone.touched">* {{form.controls.phone.errors.phone}}</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Organizație *</label>
+ <input formControlName="organisation"
+ type="text"
+ [ngClass]="{ 'error': form.controls.organisation.invalid && form.controls.organisation.touched }"
+ (selectItem)="selectedorganisation($event)"
+ class="form-control"
+ [ngbTypeahead]="searchorganisation"
+ [resultFormatter]="formatter"
+ [inputFormatter]="formatter"
+ (focus)="focus$.next($event.target.value)"
+ (click)="click$.next($event.target.value)"
+ #instance="ngbTypeahead"/>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Județ *</label>
+ <input type="text" formControlName="county" (selectItem)="selectedCounty($event)" (blur)="selectedCounty($event.target.value)"
+ class="form-control" [ngbTypeahead]="searchcounty" (focus)="focus1$.next($event.target.value)"
+ [inputFormatter]="formatter" [resultFormatter]="formatter" (keyup)="countykey($event)"
+ [ngClass]="{ 'error': form.controls.county.invalid && form.controls.county.touched}"
+ (click)="click1$.next($event.target.value)" #instance="ngbTypeahead" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Localitate *</label>
+ <input type="text" formControlName="city" class="form-control"
+ [ngbTypeahead]="searchcity" (selectItem)="selectedCity($event)"
+ [ngClass]="{ 'error': form.controls.city.invalid && form.controls.city.touched }"
+ placeholder="{{ cityPlaceholder }}" autocomplete="new-password" [inputFormatter]="formatter"
+ [resultFormatter]="formatter" (focus)="focus2$.next($event.target.value)"
+ (click)="click2$.next($event.target.value)" #instance="ngbTypeahead" />
+ <div class="spinner-border text-grey input-loader" role="status" *ngIf="loadingCities">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div>
+ <div class="form-group">
+ <label>Adresă</label>
+ <input
+ formControlName="address"
+ [ngClass]="{ 'error': form.controls.address.invalid && form.controls.address.touched }"
+ class="form-control"
+ type="text" />
+ </div>
+ </div>
+ </div>
+
+ <div class="col-md-12 mt-4">
+ <table class="w-100 table table-bordered table-hover">
+ <thead>
+ <tr>
+ <th>
+ Specializare
+ </th>
+ <th>
+ Data acreditării
+ </th>
+ <th>
+ Acreditat de
+ </th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let course of c.controls; let i = index"
+ [formGroup]="course"
+ class="mb-3">
+ <td style="padding-right:15px">
+ <div class="form-group">
+ <input
+ readonly
+ formControlName="course_name"
+ class="form-control"
+ type="text" />
+ </div>
+ </td>
+ <td style="padding-right:15px">
+ <div class="form-group">
+ <input formControlName="obtained"
+ readonly
+ class="form-control"
+ />
+ </div>
+ </td>
+ <td>
+ <div class="form-group">
+ <input formControlName="accredited_by"
+ class="form-control"
+ readonly
+ type="text" />
+ </div>
+ </td>
+ <td>
+ <button
+ placement="top" ngbTooltip="Șterge cursul"
+ (click)="removeCourse(i)"
+ class="btn-danger btn margin-auto-center"
+ type="button">
+ <span class="fa fa-trash"></span>
+ </button>
+ </td>
+ </tr>
+ <tr *ngIf="c.controls.length === 0">
+ <td colspan="4" class="text-center p-4">Nicio specializare adăugată. Vă rugăm completați formularul de mai jos și apăsați pe butonul "<span class="fa fa-plus-circle text-info"></span>" pentru a salva acreditarea.</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td style="padding-right:15px">
+ <div class="form-group">
+ <input
+ [(ngModel)]="coursename"
+ [ngModelOptions]="{ standalone: true }"
+ type="text"
+ class="form-control"
+ [ngClass]="{ 'error': coursenameError }"
+ (selectItem)="selectedcourse($event)"
+ (keyup)="courseKey($event)"
+ [ngbTypeahead]="searchcourse"
+ [resultFormatter]="formatter"
+ [inputFormatter]="formatter"
+ (focus)="focus3$.next($event.target.value)"
+ (click)="click3$.next($event.target.value)"
+ #instance="ngbTypeahead"/>
+ </div>
+ <span class="error-message" *ngIf="coursenameError">*Alegeți un curs din listă</span>
+ </td>
+ <td style="padding-right:15px">
+ <div class="form-group">
+ <div class="input-group datepicker">
+ <input class="form-control" placeholder="dd.mm.yyyy" [ngModelOptions]="{ standalone: true }"
+ readonly style="background: white;"
+ (focus)="d.open()"
+ name="dp" [(ngModel)]="obtained" ngbDatepicker [maxDate]="now" #d="ngbDatepicker">
+ <div class="input-group-append">
+ <button class="btn btn-outline-secondary calendar" (click)="d.toggle()" type="button"><i class="fa fa-calendar"></i></button>
+ </div>
+ </div>
+ </div>
+ <span class="error-message" *ngIf="dateError">*Data trebuie să fie anterioară</span>
+ </td>
+ <td>
+ <div class="form-group">
+ <input
+ [(ngModel)]="acreditedby"
+ [ngModelOptions]="{ standalone: true }"
+ type="text"
+ (keyup)="acreditorKey()"
+ [disabled]=static_accreditor
+ class="form-control"
+ [ngbTypeahead]="searchacreditedby"
+ [resultFormatter]="formatter"
+ [inputFormatter]="formatter"
+ (focus)="focus4$.next($event.target.value)"
+ (click)="click4$.next($event.target.value)"
+ #instance="ngbTypeahead"/>
+ </div>
+ <span class="error-message" *ngIf="accreditedError">*Acest câmp este obligatoriu</span>
+ </td>
+ <td>
+ <button placement="top" ngbTooltip="Adaugă cursul"
+ (click)="addCourse()"
+ class="btn-info btn margin-auto-center"
+ type="button">
+ <span class="fa fa-plus-circle"></span>
+ </button>
+ </td>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+
+ <div class="mt-4 col-md-12">
+ <div class="form-group">
+ <label>Comentarii</label>
+ <textarea
+ class="form-control"
+ rows="3"
+ [ngClass]="{ 'error': form.controls.comments.invalid && form.controls.comments.touched }"
+ formControlName="comments"
+ type="text">
+ </textarea>
+ </div>
+ </div>
+ </div>
+ <button
+ [disabled]="form.invalid"
+ *ngIf="!loading"
+ class=" btn btn-info btn-rounded waves-effect waves-light mt-5 float-right mb-5"
+ type="submit">
+ Salvează
+ </button>
+ <div class="spinner-border text-green float-right mt-5" role="status" *ngIf="loading">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </form>
+</div>
+
+
+ ./add-volunteer.component.scss
+
.form-group {
+ margin: 0;
+}
+
+.page-title {
+ margin-bottom: 1.5rem;
+}
+
+.mx--15 {
+ margin-left: -15px;
+ margin-right: -15px;
+}
+
+.w-100 {
+ width: 100%;
+}
+
+.margin-auto-center {
+ margin: 0 auto;
+ display: block;
+}
+
+.border-spacing {
+ border-collapse: separate;
+ border-spacing: 10px;
+ border-collapse: expression('separate', cellSpacing = '10px');
+}
+::ng-deep .form-group .datepicker .dropdown-menu.show{
+ max-height: initial;
+}
+ +
+ src/app/app.component.ts
+
selector | +app-root |
+
styleUrls | +./app.component.scss |
+
templateUrl | +./app.component.html |
+
+ Properties+ |
+
+
|
+
+constructor()
+ |
+
+ Defined in src/app/app.component.ts:11
+ |
+
+ Main application component + |
+
+ + + + title + + + | +
+ Type : string
+
+ |
+
+ Default value : 'app'
+ |
+
+ Defined in src/app/app.component.ts:11
+ |
+
import { Component } from '@angular/core';
+import { AuthenticationService } from './core';
+import { Router, ActivatedRoute } from '@angular/router';
+
+@Component({
+ selector: 'app-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.scss']
+})
+export class AppComponent {
+ title = 'app';
+
+ /**
+ * Main application component
+ */
+ constructor() {
+ }
+}
+
+ <app-top-bar></app-top-bar>
+<router-outlet >
+ <app-spinner></app-spinner>
+</router-outlet>
+
+
+ ./app.component.scss
+
router-outlet{
+ height: 100%;
+}
+ +
+ src/app/shared/back-button/back-button.component.ts
+
+
+ OnInit
+
selector | +app-back-button |
+
styleUrls | +./back-button.component.scss |
+
templateUrl | +./back-button.component.html |
+
+ Methods+ |
+
+ + | +
+constructor(location: Location)
+ |
+ ||||||
+ + | +||||||
+ reusable back button component +
+ Parameters :
+
+
|
+
+ + + + goBack + + + + | +
+goBack()
+ |
+
+ + | +
+ goes back in navigation history +
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+ Angular ng on init +
+ Returns :
+ void
+
+ |
+
import { Component, OnInit } from '@angular/core';
+import { Location } from '@angular/common';
+
+@Component({
+ selector: 'app-back-button',
+ templateUrl: './back-button.component.html',
+ styleUrls: ['./back-button.component.scss']
+})
+
+export class BackButtonComponent implements OnInit {
+
+ /**
+ * reusable back button component
+ */
+ constructor(private location: Location) { }
+
+ /**
+ * Angular ng on init
+ */
+ ngOnInit() { }
+
+ /**
+ * goes back in navigation history
+ */
+ goBack() {
+ this.location.back();
+ }
+
+}
+
+ <a class="mb-2" (click)="goBack()" href="javascript:void(0);"> << Înapoi </a>
+
+
+ ./back-button.component.scss
+
+ +
+ src/app/top-bar/components/current-profile/current-profile.component.ts
+
+
+ OnInit
+
selector | +app-current-profile |
+
styleUrls | +./current-profile.component.scss |
+
templateUrl | +./current-profile.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+ + | +
+constructor(authService: AuthenticationService)
+ |
+ ||||||
+ + | +||||||
+
+ Parameters :
+
+
|
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+ get current user from server +
+ Returns :
+ void
+
+ |
+
+ + + + parseRole + + + + | +||||||
+parseRole(roleNumber: number)
+ |
+ ||||||
+ + | +||||||
+ parse roleNumber in order to display role abreviation +
+ Parameters :
+
+
+
+
+ Returns :
+ string
+
+
+
+
+ |
+
+ + + + Public + authService + + + | +
+ Type : AuthenticationService
+
+ |
+
+ + | +
+ + + + user + + + | +
+ Type : any
+
+ |
+
+ Default value : {
+ 'email': 'no login'
+ }
+ |
+
+ + | +
+ user to be displayed + |
+
import { Component, OnInit } from '@angular/core';
+import { AuthenticationService } from '@app/core';
+
+@Component({
+ selector: 'app-current-profile',
+ templateUrl: './current-profile.component.html',
+ styleUrls: ['./current-profile.component.scss']
+})
+export class CurrentProfileComponent implements OnInit {
+/**
+ * user to be displayed
+ */
+ user: any = {
+ 'email': 'no login'
+ };
+
+ constructor(public authService: AuthenticationService) {}
+ /**
+ * get current user from server
+ */
+ ngOnInit() {
+ this.user = this.authService.user;
+ }
+/**
+ * parse roleNumber in order to display role abreviation
+ */
+ parseRole(roleNumber: number): string {
+ const roles = ['OFF', 'INS', 'NGO', 'DSU'];
+ return roles[roleNumber];
+ }
+}
+
+ <a class="current-profile d-inline-flex">
+ <div class="frame">
+ <span class="helper"></span>
+ <img src="./../../../../assets/images/DSU_logo.PNG">
+ </div>
+ <div class="name">
+ <span>{{ user.name }}</span>
+ <span>{{ parseRole(user.role) }}</span>
+ </div>
+</a>
+
+ ./current-profile.component.scss
+
.current-profile{
+ .frame {
+ white-space: nowrap;
+ text-align: center;
+ margin-right: 5px;
+ .helper {
+ display: inline-block;
+ height: 100%;
+ vertical-align: middle;
+ }
+
+ img {
+ border-radius: 50%;
+ vertical-align: middle;
+ max-height: 25px;
+ max-width: 160px;
+ }
+ }
+ .name{
+ span {
+ display: block;
+ line-height: 20px;
+ margin-bottom: 0;
+ max-height: 40px;
+ max-width: 200px;
+ overflow: hidden;
+ white-space: pre-wrap;
+ width: 100%;
+ word-break: break-word;
+ word-wrap: break-word;
+ }
+ }
+
+}
+
+ +
+ src/app/pages/resources/resources/components/edit-resource/edit-resource.component.ts
+
+
+ OnInit
+
selector | +app-edit-resource |
+
styleUrls | +./edit-resource.component.scss |
+
templateUrl | +./edit-resource.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(resourcesService: ResourcesService, route: ActivatedRoute, location: Location, router: Router, citiesandCounties: CitiesCountiesService, fb: FormBuilder, utilService: UtilService, filterService: FiltersService, authService: AuthenticationService)
+ |
+ ||||||||||||||||||||||||||||||
+ + | +||||||||||||||||||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + countykey + + + + | +||||||||
+countykey(event: any)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for editing the county field. When activated, disable the city form until enter is pressed or mouse selection +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + getResourceDetails + + + + | +||||||
+getResourceDetails(resId: string)
+ |
+ ||||||
+ + | +||||||
+ get the details of the resource when edititing +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + onSubmit + + + + | +
+onSubmit()
+ |
+
+ + | +
+ Process form values and send data to server. If success close page +
+ Returns :
+ void
+
+ |
+
+ + + + selectedCategory + + + + | +
+selectedCategory()
+ |
+
+ + | +
+ trigger for select category from category typeahead. will unlock the subcategory field +
+ Returns :
+ void
+
+ |
+
+ + + + selectedCity + + + + | +||||||||
+selectedCity(val: literal type)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select city from city typeahead +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + selectedCounty + + + + | +||||||||
+selectedCounty(val: any)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select county from county typeahead. will unlock the city field +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + selectedOrganisation + + + + | +||||||||
+selectedOrganisation(val: any)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select organisation from organisation typeahead. +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + Public + authService + + + | +
+ Type : AuthenticationService
+
+ |
+
+ + | +
+ + + + categories + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ + + + cities + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ list of items to select from. + |
+
+ + + + cityPlaceholder + + + | +
+ Type : string
+
+ |
+
+ Default value : 'Selectați mai întâi județul'
+ |
+
+ + | +
+ placeholders for HTML + |
+
+ + + + click$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click1$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click2$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click3$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click4$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus1$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus2$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus3$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus4$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + form + + + | +
+ Type : FormGroup
+
+ |
+
+ + | +
+ form that holds data + |
+
+ + + + formatter + + + | +
+ Default value : () => {...}
+ |
+
+ + | +
+ + + + instance + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ references to NGBTypeahead for opening on focus or click + |
+
+ + + + instance1 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + instance2 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + instance3 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + instance4 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + loading + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for HTML to display loading animation + |
+
+ + + + loadingCities + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + res + + + | +
+ Type : any
+
+ |
+
+ + | +
+ flag -> if user is editing then method is PUT, else POST + |
+
+ + + + resourcePlaceholder + + + | +
+ Type : string
+
+ |
+
+ Default value : 'Selectați mai întâi categoria'
+ |
+
+ + | +
+ + + + searchcity + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for city typeahead. registers typing, focus, and click and searches the stored list of cities + |
+ ||||
+
+ Parameters :
+
+
|
+
+ + + + searchcounty + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for county typeahead. registers typing, focus, and click and searches the backend + |
+ ||||
+
+ Parameters :
+
+
|
+
+ + + + searchOrganisation + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for city typeahead. registers typing, focus, and click and searches the backend + |
+ ||||
+
+ Parameters :
+
+
|
+
+ + + + subCategories + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
import { Component, OnInit, ViewChild } from '@angular/core';
+import { FormGroup, FormBuilder, Validators } from '@angular/forms';
+import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
+import { Subject, Observable, merge } from 'rxjs';
+import { ResourcesService } from '@app/pages/resources/resources.service';
+import { ActivatedRoute, Router } from '@angular/router';
+import { Location } from '@angular/common';
+import { CitiesCountiesService, UtilService, FiltersService, AuthenticationService } from '@app/core';
+import { LocationValidation } from '@app/core/validators/location-validation';
+import { debounceTime, distinctUntilChanged, filter, switchMap, map } from 'rxjs/operators';
+
+@Component({
+ selector: 'app-edit-resource',
+ templateUrl: './edit-resource.component.html',
+ styleUrls: ['./edit-resource.component.scss']
+})
+export class EditResourceComponent implements OnInit {
+ /**
+ * form that holds data
+ */
+ form: FormGroup;
+ /**
+ * flag -> if user is editing then method is PUT, else POST
+ */
+ res: any;
+ /**
+ * list of items to select from.
+ */
+ cities: any[] = [];
+ categories: any[] = [];
+ subCategories: any[] = [];
+ /**
+ * flag for HTML to display loading animation
+ */
+ loading = false;
+ loadingCities = false;
+
+ /**
+ * placeholders for HTML
+ */
+ cityPlaceholder = 'Selectați mai întâi județul';
+ resourcePlaceholder = 'Selectați mai întâi categoria';
+
+ /**
+ * references to NGBTypeahead for opening on focus or click
+ */
+ @ViewChild('instance', { static: true }) instance: NgbTypeahead;
+ focus$ = new Subject<string>();
+ click$ = new Subject<string>();
+ @ViewChild('instance', { static: true }) instance1: NgbTypeahead;
+ focus1$ = new Subject<string>();
+ click1$ = new Subject<string>();
+ @ViewChild('instance', { static: true }) instance2: NgbTypeahead;
+ focus2$ = new Subject<string>();
+ click2$ = new Subject<string>();
+ @ViewChild('instance', { static: true }) instance3: NgbTypeahead;
+ focus3$ = new Subject<string>();
+ click3$ = new Subject<string>();
+ @ViewChild('instance', { static: true }) instance4: NgbTypeahead;
+ focus4$ = new Subject<string>();
+ click4$ = new Subject<string>();
+
+ constructor(private resourcesService: ResourcesService,
+ private route: ActivatedRoute,
+ private location: Location, private router: Router,
+ private citiesandCounties: CitiesCountiesService,
+ private fb: FormBuilder, private utilService: UtilService,
+ private filterService: FiltersService,
+ public authService: AuthenticationService) {}
+
+ ngOnInit() {
+
+ const navigation = this.router.getCurrentNavigation();
+ let fixedOrg: any = null;
+ this.filterService.getSubCategories('0', '').subscribe((elem: any) => {
+ this.categories = elem;
+ });
+ if (navigation && navigation.extras && navigation.extras.state) {
+ fixedOrg = navigation.extras.state.ngo;
+ }
+
+ this.form = this.fb.group({
+ subCategory: [{value: '', disabled: true}],
+ name: ['', Validators.required],
+ address: '',
+ resource_type: ['', Validators.required],
+ category: ['', Validators.required],
+ organisation: this.authService.is('NGO') ?
+ [{value: {name: this.authService.user.organisation.name, _id: this.authService.user.organisation._id},
+ disabled: true }, Validators.required]
+ : fixedOrg ?
+ [{value: {name: fixedOrg.name, _id: fixedOrg._id},
+ disabled: false }, Validators.required]
+ : [{value: '' , disabled: false }, Validators.required],
+ quantity: ['', [Validators.required, Validators.min(1)]],
+ city: [{ value: '', disabled: true }, Validators.required],
+ county: ['', Validators.required],
+ comments: ''
+ });
+ if (this.route.snapshot.paramMap.get('id')) {
+ this.getResourceDetails(this.route.snapshot.paramMap.get('id'));
+ }
+ }
+ /**
+ * get the details of the resource when edititing
+ * @param {string} id of the edited NGO
+ */
+ getResourceDetails(resId: string) {
+ if (resId) {
+ this.resourcesService.getResource(resId).subscribe(data => {
+ this.res = data;
+ this.form = this.fb.group({
+ name: this.res.name,
+ subCategory: '',
+ address: this.res.address,
+ resource_type: [this.res.resource_type, Validators.required],
+ category: ['', Validators.required],
+ organisation: [{value: this.res.organisation, disabled: this.authService.is('NGO')} , Validators.required],
+ quantity: [this.res.quantity, [Validators.required, Validators.min(0)]],
+ city: ['', [Validators.required, LocationValidation.locationValidation]],
+ county: ['', [Validators.required, LocationValidation.locationValidation]],
+ comments: this.res.comments
+ });
+ if (this.res.categories && this.res.categories[0] && this.res.categories[0]._id) {
+ this.form.patchValue({category: this.res.categories[0]._id});
+ if (this.res.categories[1]) {
+ this.filterService.getSubCategories(this.res.categories[0]._id, '').subscribe(resp => {
+ this.form.controls.subCategory.enable();
+ this.subCategories = resp;
+ this.form.patchValue({subCategory: this.res.categories[1]._id});
+ });
+ }
+ }
+ this.selectedCounty({item: this.res.county});
+ this.selectedCity({item: this.res.city});
+ });
+ }
+ }
+
+ formatter = (result: { name: string }) => result.name;
+ /**
+ * trigger for county typeahead. registers typing, focus, and click and searches the backend
+ * @param {Observable} text observable event with the filter text
+ */
+ searchcounty = (text$: Observable<string>) => {
+
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+ const clicksWithClosedPopup$ = this.click1$.pipe(
+ filter(() => !this.instance1.isPopupOpen())
+ );
+ const inputFocus$ = this.focus1$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ switchMap((term: string) => this.citiesandCounties.getCounties(term))
+ );
+ }
+/**
+ * trigger for city typeahead. registers typing, focus, and click and searches the stored list of cities
+ * @param {Observable} text observable event with the filter text
+ */
+ searchcity = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+ const clicksWithClosedPopup$ = this.click2$.pipe(
+ filter(() => !this.instance2.isPopupOpen())
+ );
+ const inputFocus$ = this.focus2$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ map((term: string) => {
+ if (term === '') {
+ return this.cities;
+ } else {
+ return this.cities.filter(v => {
+ const aux: String = this.utilService.removeDiacritics(v.name).toLowerCase();
+ return aux.indexOf(term.toLowerCase()) > -1;
+ }).slice(0, 20);
+ }
+ }));
+ }
+ /**
+ * trigger for city typeahead. registers typing, focus, and click and searches the backend
+ * @param {Observable} text observable event with the filter text
+ */
+ searchOrganisation = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+
+ const clicksWithClosedPopup$ = this.click$.pipe(
+ filter(() => !this.instance.isPopupOpen())
+ );
+
+ const inputFocus$ = this.focus$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ switchMap((term: string) => {
+ return this.filterService.getorganisationbyName(term);
+ }));
+ }
+/**
+ * trigger for select county from county typeahead. will unlock the city field
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedCounty(val: any) {
+ this.form.controls.county.markAsTouched();
+ if (val.item && val.item._id) {
+ this.form.patchValue({county: val.item});
+ this.loadingCities = true;
+ this.citiesandCounties.getCitiesbyCounty(val.item._id, '').subscribe((res: any) => {
+ this.cities = res;
+ this.loadingCities = false;
+ this.form.controls.city.enable();
+ });
+ this.cityPlaceholder = 'Alegeți Orașul';
+ } else if (this.form.controls.county.value.name && val !== this.form.controls.county.value.name) {
+ this.form.patchValue({county: '', city: ''});
+ }
+ }
+/**
+ * trigger for editing the county field. When activated, disable the city form until enter is pressed or mouse selection
+ * @param {any} event to be verified for which key has been pressed
+ */
+ countykey(event: any) {
+ this.form.controls.county.markAsTouched();
+ if (event.code !== 'Enter') {
+ this.cities = [];
+ this.form.controls.city.disable();
+ this.form.controls.city.reset('');
+ this.cityPlaceholder = 'Selectați mai întâi județul';
+ }
+ }
+/**
+ * trigger for select city from city typeahead
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedCity(val: { item: any }) {
+ this.form.controls.city.markAsTouched();
+ this.form.patchValue({city: val.item});
+ }
+ /**
+ * trigger for select organisation from organisation typeahead.
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedOrganisation(val: any) {
+ this.form.controls.organisation.markAsTouched();
+ if (val.item && val.item._id) {
+ this.form.patchValue({organisation: val.item});
+ } else if (this.form.controls.organisation.value.name && val !== this.form.controls.organisation.value.name) {
+ this.form.patchValue({organisation: ''});
+ }
+ }
+ /**
+ * trigger for select category from category typeahead. will unlock the subcategory field
+ */
+ selectedCategory() {
+ this.form.controls.category.markAsTouched();
+ if (this.form.value.category) {
+ this.filterService.getSubCategories(this.form.value.category, '').subscribe(resp => {
+ if (resp.length > 0) {
+ this.form.controls.subCategory.enable();
+ this.resourcePlaceholder = 'Alegeți Categoria';
+ this.subCategories = resp;
+ } else {
+ this.form.controls.subCategory.disable();
+ this.form.controls.subCategory.reset('');
+ this.resourcePlaceholder = 'Selectați mai întâi categoria';
+ }
+ });
+ }
+ }
+
+
+ /**
+ * Process form values and send data to server. If success close page
+ */
+ onSubmit() {
+ this.loading = true;
+ const resource = this.form.value;
+ resource.organisation_id = this.form.controls['organisation'].value._id;
+ resource.county = resource.county._id;
+ resource.city = resource.city._id;
+ resource.categories = [resource.category];
+ if (resource.subCategory) {
+ resource.categories.push(resource.subCategory);
+ }
+ this.resourcesService.editResource(this.res._id, resource)
+ .subscribe((element: any) => {
+ this.loading = false;
+ this.location.back();
+ }, () => {
+ this.loading = false;
+ });
+ }
+
+}
+
+ <div class="container mt-5">
+ <nav class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <app-back-button></app-back-button>
+ </nav>
+ <p class="page-title">Modifică resursă:</p>
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
+ <div class="row">
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Nume resursă *</label>
+ <input formControlName="name"
+ [ngClass]="{ 'error': form.controls.name.invalid && form.controls.name.touched }"
+ class="form-control" type="text" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Categorie *</label>
+ <select class="form-control" (change)="selectedCategory()" formControlName="category">
+ <option *ngFor="let category of categories; let i=index" [value]="category._id">
+ {{category.name}}</option>
+ </select>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Subcategorie</label>
+ <select class="form-control" formControlName="subCategory">
+ <option *ngFor="let subCat of subCategories; let i=index" [value]="subCat._id">
+ {{subCat.name}}
+ </option>
+ </select>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Cantitate *</label>
+ <input formControlName="quantity"
+ [ngClass]="{ 'error': form.controls.quantity.invalid && form.controls.quantity.touched }"
+ class="form-control" type="number" />
+ <span class="error-message"
+ *ngIf="form.controls.quantity.invalid && form.controls.quantity.touched">* Cantitatea trebuie să
+ fie mai mare decât 0.</span>
+ </div>
+ </div>
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Tip *</label>
+ <select class="form-control" formControlName="resource_type"
+ [ngClass]="{ 'error': form.controls.resource_type.invalid && form.controls.resource_type.touched }"
+ class="form-control">
+ <option>Perisabil</option>
+ <option>Neperisabil</option>
+ </select>
+ <span class="error-message"
+ *ngIf="form.controls.resource_type.invalid && form.controls.resource_type.touched">* Trebuie sa
+ alegeți un tip.</span>
+ </div>
+ </div>
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Organizație *</label>
+ <input formControlName="organisation" type="text"
+ [ngClass]="{ 'error': form.controls.organisation.invalid && form.controls.organisation.touched }"
+ (selectItem)="selectedOrganisation($event)" class="form-control"
+ [ngbTypeahead]="searchOrganisation" [resultFormatter]="formatter" [inputFormatter]="formatter"
+ (focus)="focus$.next($event.target.value)" (click)="click$.next($event.target.value)"
+ #instance="ngbTypeahead" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Județ *</label>
+ <input type="text" formControlName="county" (selectItem)="selectedCounty($event)"
+ (blur)="selectedCounty($event.target.value)" class="form-control" [ngbTypeahead]="searchcounty"
+ (focus)="focus1$.next($event.target.value)" [inputFormatter]="formatter"
+ [resultFormatter]="formatter" (keyup)="countykey($event)"
+ [ngClass]="{ 'error': form.controls.county.invalid && form.controls.county.touched}"
+ (click)="click1$.next($event.target.value)" #instance="ngbTypeahead" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Localitate *</label>
+ <input type="text" formControlName="city" class="form-control" [ngbTypeahead]="searchcity"
+ (selectItem)="selectedCity($event)"
+ [ngClass]="{ 'error': form.controls.city.invalid && form.controls.city.touched }"
+ placeholder="{{ cityPlaceholder }}" autocomplete="new-password" [inputFormatter]="formatter"
+ [resultFormatter]="formatter" (focus)="focus2$.next($event.target.value)"
+ (click)="click2$.next($event.target.value)" #instance="ngbTypeahead" />
+ <div class="spinner-border text-grey input-loader" role="status" *ngIf="loadingCities">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Adresă</label>
+ <input formControlName="address" class="form-control"
+ [ngClass]="{ 'error': form.controls.address.invalid && form.controls.address.touched }"
+ type="text" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-12">
+ <div class="form-group">
+ <label>Comentarii</label>
+ <textarea rows="3" formControlName="comments"
+ [ngClass]="{ 'error': form.controls.comments.invalid && form.controls.comments.touched }"
+ class="form-control" type="text"></textarea>
+ </div>
+ </div>
+ </div>
+ <button *ngIf="!loading" [disabled]="form.invalid"
+ class="btn btn-info btn-rounded waves-effect waves-light mt-5 float-right" type="submit">
+ Salvează
+ </button>
+ <div class="spinner-border text-green float-right" role="status" *ngIf="loading">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </form>
+</div>
+
+ ./edit-resource.component.scss
+
+ +
+ src/app/pages/users/users/components/edit-user/edit-user.component.ts
+
+
+ OnInit
+
selector | +app-edit-user |
+
styleUrls | +./edit-user.component.scss |
+
templateUrl | +./edit-user.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(fb: FormBuilder, router: Router, filterService: FiltersService, route: ActivatedRoute, authService: AuthenticationService, usersService: UsersService, location: Location)
+ |
+ ||||||||||||||||||||||||
+ + | +||||||||||||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + editForm + + + + | +
+editForm()
+ |
+
+ + | +
+ add existing volunteer data in form for displaying +
+ Returns :
+ void
+
+ |
+
+ + + + getData + + + + | +||||||||
+getData(id: string)
+ |
+ ||||||||
+ + | +||||||||
+ get user data by id +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + onSubmit + + + + | +
+onSubmit()
+ |
+
+ + | +
+ Process form values and send data to server. If success close page +
+ Returns :
+ void
+
+ |
+
+ + + + selectedInstitut + + + + | +||||||||
+selectedInstitut(val: literal type)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select county from county typeahead. will unlock the city field +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + setDuplicateEmailError + + + + | +
+setDuplicateEmailError()
+ |
+
+ + | +
+ Set error if email already exists +
+ Returns :
+ void
+
+ |
+
+ + + + setInstitutions + + + + | +
+setInstitutions()
+ |
+
+ + | +
+ set institution form if needed +
+ Returns :
+ void
+
+ |
+
+ + + + setOrganisations + + + + | +
+setOrganisations()
+ |
+
+ + | +
+ set organisation form if needed +
+ Returns :
+ void
+
+ |
+
+ + + + setPageByRoles + + + + | +
+setPageByRoles()
+ |
+
+ + | +
+ call function to complete form depending on the role of the user +
+ Returns :
+ void
+
+ |
+
+ + + + Public + authService + + + | +
+ Type : AuthenticationService
+
+ |
+
+ + | +
+ + + + click$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + displayInstitution + + + | +
+ Default value : false
+ |
+
+ + | +
+ FLAG form HTML to display form according to role + |
+
+ + + + displayOrganisation + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + focus$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + form + + + | +
+ Type : FormGroup
+
+ |
+
+ + | +
+ form that holds data + |
+
+ + + + instance + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ references to NGBTypeahead for opening on focus or click + |
+
+ + + + institutions + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ list of institutions to pe parsed + |
+
+ + + + loading + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for HTML to display loading animation + |
+
+ + + + organisations + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ + + + role + + + | +
+ Type : string
+
+ |
+
+ + | +
+ role of user that will be created + |
+
+ + + + Public + route + + + | +
+ Type : ActivatedRoute
+
+ |
+
+ + | +
+ + + + user + + + | +
+ Type : any
+
+ |
+
+ Default value : {}
+ |
+
+ + | +
+ user data + |
+
import { Component, OnInit, ViewChild } from '@angular/core';
+import { FormBuilder, Validators, FormGroup } from '@angular/forms';
+import { Router, ActivatedRoute } from '@angular/router';
+import { UsersService } from '@app/core/service/users.service';
+import { EmailValidation } from '@app/core/validators/email-validation';
+import { PhoneValidation } from '@app/core/validators/phone-validation';
+import { AuthenticationService, FiltersService } from '@app/core';
+import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
+import { Subject } from 'rxjs';
+import { Location } from '@angular/common';
+
+@Component({
+ selector: 'app-edit-user',
+ templateUrl: './edit-user.component.html',
+ styleUrls: ['./edit-user.component.scss']
+})
+export class EditUserComponent implements OnInit {
+
+ /**
+ * form that holds data
+ */
+ form: FormGroup;
+ /**
+ * role of user that will be created
+ */
+ role: string;
+ /**
+ * user data
+ */
+ user: any = {};
+/**
+ * flag for HTML to display loading animation
+ */
+ loading = false;
+ /**
+ * references to NGBTypeahead for opening on focus or click
+ */
+ @ViewChild('instance', { static: true }) instance: NgbTypeahead;
+ focus$ = new Subject<string>();
+ click$ = new Subject<string>();
+ /**
+ * list of institutions to pe parsed
+ */
+ institutions: any[] = [];
+ organisations: any[] = [];
+ /**
+ * FLAG form HTML to display form according to role
+ */
+ displayInstitution = false;
+ displayOrganisation = false;
+
+ constructor(private fb: FormBuilder,
+ private router: Router,
+ private filterService: FiltersService,
+ public route: ActivatedRoute,
+ public authService: AuthenticationService,
+ private usersService: UsersService,
+ private location: Location) { }
+
+ ngOnInit() {
+ this.form = this.fb.group({
+ name: ['', Validators.required],
+ email: ['', [ Validators.required, EmailValidation.emailValidation ]],
+ phone: ['', [ Validators.required, PhoneValidation.phoneValidation ]],
+ institution: [''],
+ organisation: ['']
+ });
+
+ if (this.route.snapshot.paramMap.get('id')) {
+ this.getData(this.route.snapshot.paramMap.get('id'));
+ }
+ }
+/**
+ * call function to complete form depending on the role of the user
+ */
+ setPageByRoles() {
+ if (this.user.role === '1' || this.user.role === '0') {
+ this.form.controls['institution'].setValue(this.user.institution._id);
+
+ if (this.authService.is('INS')) {
+ this.form.controls['institution'].disable();
+ }
+
+ this.setInstitutions();
+ }
+
+ if (this.user.role === '2') {
+ this.form.controls['organisation'].setValue(this.user.organisation._id);
+
+ if (this.authService.is('NGO')) {
+ this.form.controls['organisation'].disable();
+ }
+
+ this.setOrganisations();
+ }
+ }
+/**
+ * set organisation form if needed
+ */
+ setOrganisations() {
+ this.filterService.getorganisationbyName().subscribe(response => {
+ this.organisations = response;
+ });
+
+ this.displayOrganisation = true;
+ this.form.controls['organisation'].setValidators(Validators.required);
+ }
+/**
+ * set institution form if needed
+ *
+ */
+ setInstitutions() {
+ this.filterService.getInstitutionFilters().subscribe(response => {
+ this.institutions = response;
+ });
+
+ this.displayInstitution = true;
+ this.form.controls['institution'].setValidators(Validators.required);
+ }
+/**
+ * get user data by id
+ * @param {string} id of the user to be edited
+ */
+ getData(id: string) {
+ this.usersService.getUser(id).subscribe(response => {
+ this.user = response;
+ this.role = this.user.role;
+ this.setPageByRoles();
+ this.editForm();
+ });
+ }
+
+ /**
+ * trigger for select county from county typeahead. will unlock the city field
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedInstitut(val: { item: any }) {
+ this.form.controls.institution.markAsTouched();
+ this.form.patchValue({institution: val.item});
+ }
+
+ /**
+ * add existing volunteer data in form for displaying
+ */
+ editForm() {
+ this.form.controls['name'].setValue(this.user.name);
+ this.form.controls['email'].setValue(this.user.email);
+ this.form.controls['phone'].setValue(this.user.phone);
+ }
+
+ /**
+ * Process form values and send data to server. If success close page
+ */
+ onSubmit() {
+ this.loading = true;
+ this.user.name = this.form.value.name;
+ this.user.email = this.form.value.email;
+ this.user.phone = this.form.value.phone;
+
+ if (this.role) {
+ this.user.role = this.role;
+
+ if (this.role === '1' || this.role === '0') {
+ this.user.institution = this.form.value.institution;
+ }
+
+ if (this.role === '2') {
+ this.user.organisation = this.form.value.organisation;
+ }
+ }
+
+ this.usersService.updateUser(this.user).subscribe((response) => {
+ this.loading = false;
+ this.location.back();
+ }, () => {
+ this.setDuplicateEmailError();
+ this.loading = false;
+ });
+ }
+ /**
+ * Set error if email already exists
+ */
+ setDuplicateEmailError() {
+ this.form.controls['email'].setErrors({'email': 'Adresa de email introdusă există deja în sistem.'});
+ }
+}
+
+
+<div class="container mt-5">
+ <nav
+ class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <app-back-button></app-back-button>
+ </nav>
+ <p class="page-title">Modifică utilizatorul:</p>
+
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
+ <div class="row">
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Nume și Prenume *</label>
+ <input
+ [ngClass]="{ 'error': form.controls.name.invalid && form.controls.name.touched }"
+ formControlName="name"
+ class="form-control"
+ type="text"/>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Email *</label>
+ <input
+ [ngClass]="{ 'error': form.controls.email.invalid && form.controls.email.touched }"
+ formControlName="email"
+ class="form-control"
+ type="email"
+ />
+ <span class="error-message" *ngIf="form.controls.email.invalid && form.controls.email.touched">* {{form.controls.email.errors.email}}</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Telefon *</label>
+ <input
+ [ngClass]="{ 'error': form.controls.phone.invalid && form.controls.phone.touched }"
+ formControlName="phone"
+ class="form-control"
+ type="tel"
+ />
+ <span class="error-message" *ngIf="form.controls.phone.invalid && form.controls.phone.touched">* {{form.controls.phone.errors.phone}}</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4" [ngStyle]="{'display': displayInstitution ? 'initial' : 'none'}" >
+ <div class="form-group">
+ <label>Insituție *</label>
+ <select class="form-control" formControlName="institution">
+ <option *ngFor="let institution of institutions; let i=index" [value]="institution._id">
+ {{institution.name}}
+ </option>
+ </select>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4" [ngStyle]="{'display': displayOrganisation ? 'initial' : 'none'}" >
+ <div class="form-group">
+ <label>Organizație *</label>
+ <select class="form-control" formControlName="organisation">
+ <option *ngFor="let organisation of organisations; let i=index" [value]="organisation._id">
+ {{organisation.name}}
+ </option>
+ </select>
+ </div>
+ </div>
+
+ </div>
+ <button
+ *ngIf="!loading"
+ class="btn btn-info btn-rounded waves-effect waves-light mt-5 float-right"
+ type="submit"
+ [disabled]="form.invalid">
+ Salvează
+ </button>
+ <div class="spinner-border text-green float-right" role="status" *ngIf="loading">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </form>
+ </div>
+
+ ./edit-user.component.scss
+
+ +
+ src/app/pages/volunteers/volunteers/components/edit-volunteer/edit-volunteer.component.ts
+
+
+ OnInit
+
selector | +app-edit-volunteer |
+
styleUrls | +./edit-volunteer.component.scss |
+
templateUrl | +./edit-volunteer.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+ Accessors+ |
+
+ + | +
+constructor(volunteerService: VolunteerService, filterService: FiltersService, router: Router, utilService: UtilService, route: ActivatedRoute, location: Location, fb: FormBuilder, citiesandCounties: CitiesCountiesService, authService: AuthenticationService)
+ |
+ ||||||||||||||||||||||||||||||
+ + | +||||||||||||||||||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + acreditorKey + + + + | +
+acreditorKey()
+ |
+
+ + | +
+ if error has appeared, when user changes input remove the error +
+ Returns :
+ void
+
+ |
+
+ + + + addCourse + + + + | +
+addCourse()
+ |
+
+ + | +
+ trigger for add course from course table footer. willbe added to form and displayed in table +
+ Returns :
+ void
+
+ |
+
+ + + + countykey + + + + | +||||||||
+countykey(event: any)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for editing the county field. When activated, disable the city form until enter is pressed or mouse selection +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + courseKey + + + + | +||||||||
+courseKey(event: any)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for editing the county field. When activated, disable the static acreditor +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + getVolunteerDetails + + + + | +||||||
+getVolunteerDetails(volId: string)
+ |
+ ||||||
+ + | +||||||
+ get the details of the volunteer when edititing +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + onSubmit + + + + | +
+onSubmit()
+ |
+
+ + | +
+ Process form values and send data to server. If success close page +
+ Returns :
+ void
+
+ |
+
+ + + + removeCourse + + + + | +||||||||
+removeCourse(index: number)
+ |
+ ||||||||
+ + | +||||||||
+ remove one of the courses from the table by index +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + selectedCity + + + + | +||||||||
+selectedCity(val: literal type)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select city from city typeahead +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + selectedCounty + + + + | +||||||||
+selectedCounty(val: any)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select county from county typeahead. will unlock the city field +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + selectedcourse + + + + | +||||||
+selectedcourse(obj: any)
+ |
+ ||||||
+ + | +||||||
+ trigger for select course from course typeahead +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + selectedorganisation + + + + | +||||||||
+selectedorganisation(val: literal type)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select organisation from organisation typeahead. +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + accreditedError + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + acreditedby + + + | +
+ Type : any
+
+ |
+
+ + | +
+ + + + Public + authService + + + | +
+ Type : AuthenticationService
+
+ |
+
+ + | +
+ + + + cities + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ list of cities to pe parsed. edited when the user selects a county or edits this NGO + |
+
+ + + + cityPlaceholder + + + | +
+ Type : string
+
+ |
+
+ Default value : 'Selectați mai întâi județul'
+ |
+
+ + | +
+ placeholder for disabled city field + |
+
+ + + + click$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click1$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click2$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click3$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click4$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + coursename + + + | +
+ Type : any
+
+ |
+
+ + | +
+ courses values and errors + |
+
+ + + + coursenameError + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + dateError + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + focus$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus1$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus2$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus3$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus4$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + form + + + | +
+ Type : FormGroup
+
+ |
+
+ + | +
+ form that holds data + |
+
+ + + + formatter + + + | +
+ Default value : () => {...}
+ |
+
+ + | +
+ formater to display only name from object + |
+
+ + + + instance + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ references to NGBTypeahead for opening on focus or click + |
+
+ + + + instance1 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + instance2 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + instance3 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + instance4 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + loading + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag -> if information is beeing loaded show loader elements in frontend + |
+
+ + + + loadingCities + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + now + + + | +
+ Type : any
+
+ |
+
+ + | +
+ date object to force course acreditation date in the past + |
+
+ + + + obtained + + + | +
+ Type : Date
+
+ |
+
+ + | +
+ + + + searchacreditedby + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for accredited by typeahead. registers typing, focus, and click and searches the backend + |
+ ||||
+
+ Parameters :
+
+
|
+
+ + + + searchcity + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for county typeahead. registers typing, focus, and click and searches the backend + |
+ ||||
+
+ Parameters :
+
+
|
+
+ + + + searchcounty + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for county typeahead. registers typing, focus, and click and searches the backend + |
+ ||||
+
+ Parameters :
+
+
|
+
+ + + + searchcourse + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for course typeahead. registers typing, focus, and click and searches the backend + |
+ ||||
+
+ Parameters :
+
+
|
+
+ + + + searchorganisation + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for organistion typeahead. registers typing, focus, and click and searches the backend + |
+ ||||
+
+ Parameters :
+
+
|
+
+ + + + static_accreditor + + + | +
+ Default value : false
+ |
+
+ + | +
+ date object to force course acreditation date in the past + |
+
+ + + + Public + volunteerService + + + | +
+ Type : VolunteerService
+
+ |
+
+ + | +
+ + f + | +
+ getf()
+ |
+
+ + | +
+ wrapper for the form' controls + |
+
+ + c + | +
+ getc()
+ |
+
+ + | +
+ wrapper for the form's controls courses array + |
+
import { Component, OnInit, ViewChild } from '@angular/core';
+import { FormGroup, Validators, FormBuilder, FormArray } from '@angular/forms';
+import { VolunteerService } from '../../../volunteers.service';
+import { Router, ActivatedRoute } from '@angular/router';
+import { Observable, merge, Subject } from 'rxjs';
+import {
+ debounceTime,
+ distinctUntilChanged,
+ map,
+ filter,
+ switchMap,
+} from 'rxjs/operators';
+import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
+import { CitiesCountiesService } from '../../../../../core/service/cities-counties.service';
+import { AuthenticationService, FiltersService, UtilService } from '@app/core';
+import { EmailValidation } from '@app/core/validators/email-validation';
+import { PhoneValidation } from '@app/core/validators/phone-validation';
+import { Location } from '@angular/common';
+import { SsnValidation } from '@app/core/validators/ssn-validation';
+import * as moment from 'moment';
+
+
+@Component({
+ selector: 'app-edit-volunteer',
+ templateUrl: './edit-volunteer.component.html',
+ styleUrls: ['./edit-volunteer.component.scss']
+})
+export class EditVolunteerComponent implements OnInit {
+ /**
+ * form that holds data
+ */
+ form: FormGroup;
+ /**
+ * courses values and errors
+ */
+ coursename: any;
+ coursenameError = false;
+ acreditedby: any;
+ accreditedError = false;
+ obtained: Date;
+ dateError = false;
+
+/**
+ * placeholder for disabled city field
+ */
+ cityPlaceholder = 'Selectați mai întâi județul';
+/**
+ * references to NGBTypeahead for opening on focus or click
+ */
+ @ViewChild('instance', { static: true }) instance: NgbTypeahead;
+ focus$ = new Subject<string>();
+ click$ = new Subject<string>();
+
+ @ViewChild('instance', { static: true }) instance1: NgbTypeahead;
+ focus1$ = new Subject<string>();
+ click1$ = new Subject<string>();
+
+ @ViewChild('instance', { static: true }) instance2: NgbTypeahead;
+ focus2$ = new Subject<string>();
+ click2$ = new Subject<string>();
+
+ @ViewChild('instance', { static: true }) instance3: NgbTypeahead;
+ focus3$ = new Subject<string>();
+ click3$ = new Subject<string>();
+
+ @ViewChild('instance', { static: true }) instance4: NgbTypeahead;
+ focus4$ = new Subject<string>();
+ click4$ = new Subject<string>();
+
+ /**
+ * flag -> if information is beeing loaded show loader elements in frontend
+ */
+ loading = false;
+ loadingCities = false;
+ /**
+ * list of cities to pe parsed. edited when the user selects a county or edits this NGO
+ */
+ cities: any[] = [];
+ /**
+ * date object to force course acreditation date in the past
+ */
+ now: any;
+ /**
+ * date object to force course acreditation date in the past
+ */
+ static_accreditor = false;
+ constructor(
+ public volunteerService: VolunteerService,
+ private filterService: FiltersService,
+ private router: Router, private utilService: UtilService,
+ private route: ActivatedRoute, private location: Location,
+ private fb: FormBuilder,
+ private citiesandCounties: CitiesCountiesService,
+ public authService: AuthenticationService) {
+ const dateObj = new Date();
+ const month = dateObj.getUTCMonth() + 1; // months from 1-12
+ const day = dateObj.getUTCDate();
+ const year = dateObj.getUTCFullYear();
+ this.now = {day: day, month: month, year: year};
+ }
+
+ ngOnInit() {
+ const navigation = this.router.getCurrentNavigation();
+
+ let fixedOrg: any;
+ if (navigation && navigation.extras && navigation.extras.state) {
+ fixedOrg = navigation.extras.state.ngo;
+ }
+ this.form = this.fb.group({
+ name: ['', Validators.required],
+ ssn: ['', [Validators.required, SsnValidation.ssnValidation]],
+ email: ['', [Validators.required, EmailValidation.emailValidation]],
+ phone: ['', [Validators.required, PhoneValidation.phoneValidation]],
+ address: [''],
+ job: [''],
+ county: ['', Validators.required],
+ city: [{ value: '', disabled: true }, Validators.required],
+ organisation: this.authService.is('NGO') ?
+ [{value: {name: this.authService.user.organisation.name, _id: this.authService.user.organisation._id},
+ disabled: true }, Validators.required]
+ : fixedOrg ?
+ [{value: {name: fixedOrg.name, _id: fixedOrg._id},
+ disabled: false }, Validators.required]
+ : [{value: '' , disabled: false }, Validators.required],
+ courses: this.fb.array([]),
+ comments: ['']
+ });
+
+ if (this.route.snapshot.paramMap.get('id')) {
+ this.getVolunteerDetails(this.route.snapshot.paramMap.get('id'));
+ }
+ }
+/**
+ * get the details of the volunteer when edititing
+ * @param {string} id of the edited volunteer
+ */
+ getVolunteerDetails(volId: string) {
+ if (volId) {
+ this.volunteerService.getVolunteer(volId).subscribe(data => {
+ const aux = data.courses.map((element: any) => {
+ return this.fb.group({
+ course_name: element.course_name.name,
+ course_name_id: element.course_name._id,
+ obtained: moment(element.obtained).format('DD.MM.YYYY'),
+ accredited_by: element.accredited.name
+ });
+ });
+ this.form = this.fb.group({
+ name: [data.name, Validators.required],
+ ssn: [data.ssn, [Validators.required, Validators.minLength(13), Validators.maxLength(13)]],
+ email: [data.email, [Validators.required, EmailValidation.emailValidation]],
+ phone: [data.phone, [Validators.required, PhoneValidation.phoneValidation]],
+ address: data.address,
+ job: data.job,
+ courses: this.fb.array(aux),
+ county: ['', Validators.required],
+ city: ['', Validators.required],
+ organisation: [{value: data.organisation, disabled: this.authService.is('NGO') }, Validators.required],
+ comments: data.comments
+ });
+ this.selectedCounty({item: data.county});
+ this.selectedCity({item: data.city});
+ });
+ }
+ }
+/**
+ * wrapper for the form' controls
+ */
+ get f() {
+ return this.form.controls;
+ }
+/**
+ * wrapper for the form's controls courses array
+ */
+ get c() {
+ return this.f.courses as FormArray;
+ }
+/**
+ * formater to display only name from object
+ */
+ formatter = (result: { name: string }) => result.name;
+/**
+ * trigger for organistion typeahead. registers typing, focus, and click and searches the backend
+ * @param {Observable} text observable event with the filter text
+ */
+ searchorganisation = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+
+ const clicksWithClosedPopup$ = this.click$.pipe(
+ filter(() => !this.instance.isPopupOpen())
+ );
+
+ const inputFocus$ = this.focus$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ switchMap((term: string) => {
+ return this.filterService.getorganisationbyName(term);
+ }));
+ }
+/**
+ * trigger for course typeahead. registers typing, focus, and click and searches the backend
+ * @param {Observable} text observable event with the filter text
+ */
+ searchcourse = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+
+ const clicksWithClosedPopup$ = this.click3$.pipe(
+ filter(() => !this.instance3.isPopupOpen())
+ );
+
+ const inputFocus$ = this.focus3$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ switchMap((term: string) => {
+ this.coursenameError = false;
+ return this.filterService.getSpecializationFilters(term);
+ }));
+ }
+/**
+ * trigger for accredited by typeahead. registers typing, focus, and click and searches the backend
+ * @param {Observable} text observable event with the filter text
+ */
+ searchacreditedby = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+
+ const clicksWithClosedPopup$ = this.click4$.pipe(
+ filter(() => !this.instance.isPopupOpen())
+ );
+
+ const inputFocus$ = this.focus4$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ switchMap((term: string) => {
+ this.accreditedError = false;
+ return this.filterService.getAcreditedFilters(term);
+ }));
+ }
+/**
+ * trigger for county typeahead. registers typing, focus, and click and searches the backend
+ * @param {Observable} text observable event with the filter text
+ */
+ searchcounty = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+ const clicksWithClosedPopup$ = this.click1$.pipe(
+ filter(() => !this.instance1.isPopupOpen())
+ );
+ const inputFocus$ = this.focus1$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ switchMap((term: string) => this.citiesandCounties.getCounties(term))
+ );
+ }
+/**
+ * trigger for county typeahead. registers typing, focus, and click and searches the backend
+ * @param {Observable} text observable event with the filter text
+ */
+ searchcity = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(debounceTime(200), distinctUntilChanged());
+ const clicksWithClosedPopup$ = this.click2$.pipe(
+ filter(() => !this.instance2.isPopupOpen())
+ );
+ const inputFocus$ = this.focus2$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ map((term: string) => {
+ if (term === '') {
+ return this.cities;
+ } else {
+ return this.cities.filter(v => {
+ const aux: String = this.utilService.removeDiacritics(v.name).toLowerCase();
+ return aux.indexOf(term.toLowerCase()) > -1;
+ }).slice(0, 20);
+ }
+ }));
+ }
+ /**
+ * trigger for editing the county field. When activated, disable the static acreditor
+ * @param {any} event to be verified for which key has been pressed
+ */
+ courseKey(event: any) {
+ if (event.code !== 'Enter') {
+ this.coursenameError = true;
+ this.static_accreditor = false;
+ this.acreditedby = '';
+ }
+ }
+ /**
+ * if error has appeared, when user changes input remove the error
+ */
+ acreditorKey() {
+ this.accreditedError = false;
+ }
+ /**
+ * trigger for select course from course typeahead
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedcourse(obj: any) {
+ if (obj.item.static_accreditor) {
+ this.acreditedby = obj.item.static_accreditor;
+ this.static_accreditor = true;
+ } else {
+ this.acreditedby = {
+ };
+ this.static_accreditor = false;
+ }
+ this.coursenameError = false;
+ }
+ /**
+ * trigger for add course from course table footer. willbe added to form and displayed in table
+ */
+ addCourse() {
+ const now = new Date();
+ if (!this.acreditedby) {
+ this.accreditedError = true;
+ }
+ if (!this.coursename) {
+ this.coursenameError = true;
+ }
+ if (!this.obtained) {
+ this.dateError = true;
+ }
+ // if (this.obtained > now) {
+ if (!this.coursenameError && this.coursename && this.acreditedby) {
+ this.c.push(
+ this.fb.group({
+ course_name: this.coursename.name,
+ course_name_id: this.coursename._id,
+ obtained: moment(this.obtained).format('DD.MM.YYYY'),
+ accredited_by: this.acreditedby.hasOwnProperty('name') ? this.acreditedby.name : this.acreditedby
+ })
+ );
+ this.static_accreditor = false;
+ this.coursename = null;
+ this.acreditedby = null;
+ this.obtained = null;
+ this.dateError = false;
+ this.accreditedError = false;
+ this.coursenameError = false;
+ }
+ // } else {
+ // this.dateError = true;
+ // }
+ }
+/**
+ * remove one of the courses from the table by index
+ * @param {number} index result object from typeahead that needs to be stored
+ */
+ removeCourse(index: number) {
+ const control = <FormArray>this.form.controls.courses;
+ control.removeAt(index);
+ }
+/**
+ * trigger for select county from county typeahead. will unlock the city field
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedCounty(val: any) {
+ this.form.controls.county.markAsTouched();
+ if (val.item && val.item._id) {
+
+ this.form.patchValue({county: val.item});
+ this.loadingCities = true;
+ this.citiesandCounties.getCitiesbyCounty(val.item._id, '').subscribe((res: any) => {
+ this.cities = res;
+ this.loadingCities = false;
+ this.form.controls.city.enable();
+ });
+ this.cityPlaceholder = 'Alegeți Orașul';
+ } else if (this.form.controls.county.value.name && val !== this.form.controls.county.value.name) {
+ this.form.patchValue({county: '', city: ''});
+ }
+ }
+/**
+ * trigger for editing the county field. When activated, disable the city form until enter is pressed or mouse selection
+ * @param {any} event to be verified for which key has been pressed
+ */
+ countykey(event: any) {
+ this.form.controls.county.markAsTouched();
+ if (event.code !== 'Enter') {
+ this.form.controls.city.disable();
+ this.form.controls.city.reset('');
+ this.cityPlaceholder = 'Selectați mai întâi județul';
+ }
+ }
+/**
+ * trigger for select city from city typeahead
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedCity(val: { item: any }) {
+ this.form.controls.city.markAsTouched();
+ this.form.patchValue({city: val.item});
+ }
+ /**
+ * trigger for select organisation from organisation typeahead.
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedorganisation(val: { item: any }) {
+ this.form.controls.organisation.markAsTouched();
+ this.form.patchValue({organisation: val.item});
+ }
+/**
+ * Process form values and send data to server. If success close page
+ */
+ onSubmit() {
+ this.loading = true;
+ const volunteer = {...this.form.value};
+ volunteer.ssn = volunteer.ssn.toString();
+ volunteer.county = volunteer.county._id;
+ volunteer.city = volunteer.city._id;
+ volunteer.organisation_id = this.form.controls.organisation.value._id;
+
+ this.volunteerService.editVolunteer(this.route.snapshot.paramMap.get('id'), volunteer).subscribe(() => {
+ this.loading = false;
+ this.location.back();
+ }, (obj: any) => {
+ this.loading = false;
+ if (obj.error.errors) {
+ if (obj.error.errors[0].indexOf('CNP') !== -1) {
+ this.form.controls['ssn'].setErrors({'ssn': 'CNP-ul introdus există deja în sistem.'});
+ } else {
+ this.form.controls['email'].setErrors({'email': 'Adresa de email introdusă există deja în sistem.'});
+ }
+ }
+ });
+ }
+}
+
+ <div class="container wide-container mt-5">
+ <nav class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <app-back-button></app-back-button>
+ </nav>
+ <p class="page-title">Modifică voluntarul:</p>
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
+ <div class="row">
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Nume și Prenume *</label>
+ <input
+ [ngClass]="{ 'error': form.controls.name.invalid && form.controls.name.touched }"
+ formControlName="name"
+ class="form-control"
+ type="text" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>CNP *</label>
+ <input
+ [ngClass]="{ 'error': form.controls.ssn.invalid && form.controls.ssn.touched }"
+ formControlName="ssn"
+ class="form-control"
+ type="number" />
+ <span class="error-message" *ngIf="form.controls.ssn.invalid && form.controls.ssn.touched">* {{form.controls.ssn.errors.ssn}}</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Email *</label>
+ <input
+ [ngClass]="{ 'error': form.controls.email.invalid && form.controls.email.touched }"
+ formControlName="email"
+ class="form-control"
+ type="email"/>
+ <span class="error-message" *ngIf="form.controls.email.invalid && form.controls.email.touched">* {{form.controls.email.errors.email}}</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Profesie</label>
+ <input
+ [ngClass]="{ 'error': form.controls.job.invalid && form.controls.job.touched }"
+ formControlName="job"
+ class="form-control"
+ type="text" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Telefon *</label>
+ <input
+ [ngClass]="{ 'error': form.controls.phone.invalid && form.controls.phone.touched }"
+ formControlName="phone"
+ class="form-control"
+ type="tel" />
+ <span class="error-message" *ngIf="form.controls.phone.invalid && form.controls.phone.touched">* {{form.controls.phone.errors.phone}}</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Organizație *</label>
+ <input formControlName="organisation"
+ type="text"
+ [ngClass]="{ 'error': form.controls.organisation.invalid && form.controls.organisation.touched }"
+ (selectItem)="selectedorganisation($event)"
+ class="form-control"
+ [ngbTypeahead]="searchorganisation"
+ [resultFormatter]="formatter"
+ [inputFormatter]="formatter"
+ (focus)="focus$.next($event.target.value)"
+ (click)="click$.next($event.target.value)"
+ #instance="ngbTypeahead"/>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Județ *</label>
+ <input type="text" formControlName="county" (selectItem)="selectedCounty($event)" (blur)="selectedCounty($event.target.value)"
+ class="form-control" [ngbTypeahead]="searchcounty" (focus)="focus1$.next($event.target.value)"
+ [inputFormatter]="formatter" [resultFormatter]="formatter" (keyup)="countykey($event)"
+ [ngClass]="{ 'error': form.controls.county.invalid && form.controls.county.touched}"
+ (click)="click1$.next($event.target.value)" #instance="ngbTypeahead" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Localitate *</label>
+ <input type="text" formControlName="city" class="form-control"
+ [ngbTypeahead]="searchcity" (selectItem)="selectedCity($event)"
+ [ngClass]="{ 'error': form.controls.city.invalid && form.controls.city.touched }"
+ placeholder="{{ cityPlaceholder }}" autocomplete="new-password" [inputFormatter]="formatter"
+ [resultFormatter]="formatter" (focus)="focus2$.next($event.target.value)"
+ (click)="click2$.next($event.target.value)" #instance="ngbTypeahead" />
+ <div class="spinner-border text-grey input-loader" role="status" *ngIf="loadingCities">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div>
+ <div class="form-group">
+ <label>Adresă</label>
+ <input
+ formControlName="address"
+ [ngClass]="{ 'error': form.controls.address.invalid && form.controls.address.touched }"
+ class="form-control"
+ type="text" />
+ </div>
+ </div>
+ </div>
+
+ <div class="col-md-12 mt-4">
+ <table class="w-100 table table-bordered table-hover">
+ <thead>
+ <tr>
+ <th>
+ Specializare
+ </th>
+ <th>
+ Data acreditării
+ </th>
+ <th>
+ Acreditat de
+ </th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let course of c.controls; let i = index"
+ [formGroup]="course"
+ class="mb-3">
+ <td style="padding-right:15px">
+ <div class="form-group">
+ <!-- [ngClass]="{ 'error': course.controls.name.invalid && course.controls.name.touched }" -->
+ <input
+ readonly
+ formControlName="course_name"
+ class="form-control"
+ type="text" />
+ </div>
+ </td>
+ <td style="padding-right:15px">
+ <div class="form-group">
+ <input formControlName="obtained"
+ readonly
+ class="form-control"
+ />
+ </div>
+ </td>
+ <td>
+ <div class="form-group">
+ <input formControlName="accredited_by"
+ class="form-control"
+ readonly
+ type="text" />
+ </div>
+ </td>
+ <td>
+ <button
+ placement="top" ngbTooltip="Șterge cursul"
+ (click)="removeCourse(i)"
+ class="btn-danger btn margin-auto-center"
+ type="button">
+ <span class="fa fa-trash"></span>
+ </button>
+ </td>
+ </tr>
+ <tr *ngIf="c.controls.length === 0">
+ <td colspan="4" class="text-center p-4">Nicio specializare adăugată. Vă rugăm completați formularul de mai jos și apăsați pe butonul "<span class="fa fa-plus-circle text-info"></span>" pentru a salva acreditarea.</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td style="padding-right:15px">
+ <div class="form-group">
+ <input
+ [(ngModel)]="coursename"
+ [ngModelOptions]="{ standalone: true }"
+ type="text"
+ class="form-control"
+ [ngClass]="{ 'error': coursenameError }"
+ (selectItem)="selectedcourse($event)"
+ (keyup)="courseKey($event)"
+ [ngbTypeahead]="searchcourse"
+ [resultFormatter]="formatter"
+ [inputFormatter]="formatter"
+ (focus)="focus3$.next($event.target.value)"
+ (click)="click3$.next($event.target.value)"
+ #instance="ngbTypeahead"/>
+ </div>
+ <span class="error-message" *ngIf="coursenameError">*Alegeți un curs din listă</span>
+ </td>
+ <td style="padding-right:15px">
+ <div class="form-group">
+ <div class="input-group datepicker">
+ <input class="form-control" placeholder="dd.mm.yyyy" [ngModelOptions]="{ standalone: true }"
+ readonly style="background: white;"
+ (focus)="d.open()"
+ name="dp" [(ngModel)]="obtained" ngbDatepicker [maxDate]="now" #d="ngbDatepicker">
+ <div class="input-group-append">
+ <button class="btn btn-outline-secondary calendar" (click)="d.toggle()" type="button"><i class="fa fa-calendar"></i></button>
+ </div>
+ </div>
+ </div>
+ <span class="error-message" *ngIf="dateError">*Data trebuie să fie anterioară</span>
+ </td>
+ <td>
+ <div class="form-group">
+ <input
+ [(ngModel)]="acreditedby"
+ [ngModelOptions]="{ standalone: true }"
+ type="text"
+ (keyup)="acreditorKey()"
+ [disabled]=static_accreditor
+ class="form-control"
+ [ngbTypeahead]="searchacreditedby"
+ [resultFormatter]="formatter"
+ [inputFormatter]="formatter"
+ (focus)="focus4$.next($event.target.value)"
+ (click)="click4$.next($event.target.value)"
+ #instance="ngbTypeahead"/>
+ </div>
+ <span class="error-message" *ngIf="accreditedError">*Acest câmp este obligatoriu</span>
+ </td>
+ <td>
+ <button placement="top" ngbTooltip="Adaugă cursul"
+ (click)="addCourse()"
+ class="btn-info btn margin-auto-center"
+ type="button">
+ <span class="fa fa-plus-circle"></span>
+ </button>
+ </td>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+
+ <div class="mt-4 col-md-12">
+ <div class="form-group">
+ <label>Comentarii</label>
+ <textarea
+ class="form-control"
+ rows="3"
+ [ngClass]="{ 'error': form.controls.comments.invalid && form.controls.comments.touched }"
+ formControlName="comments"
+ type="text">
+ </textarea>
+ </div>
+ </div>
+ </div>
+ <button
+ [disabled]="form.invalid"
+ *ngIf="!loading"
+ class=" btn btn-info btn-rounded waves-effect waves-light mt-5 float-right mb-5"
+ type="submit">
+ Salvează
+ </button>
+ <div class="spinner-border text-green float-right mt-5" role="status" *ngIf="loading">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </form>
+ </div>
+
+
+ ./edit-volunteer.component.scss
+
.form-group {
+ margin: 0;
+}
+
+.page-title {
+ margin-bottom: 1.5rem;
+}
+
+.mx--15 {
+ margin-left: -15px;
+ margin-right: -15px;
+}
+
+.w-100 {
+ width: 100%;
+}
+
+.margin-auto-center {
+ margin: 0 auto;
+ display: block;
+}
+
+.border-spacing {
+ border-collapse: separate;
+ border-spacing: 10px;
+ border-collapse: expression('separate', cellSpacing = '10px');
+}
+::ng-deep .form-group .datepicker .dropdown-menu.show{
+ max-height: initial;
+}
+ +
+ src/app/pages/resources/resources/components/import-resources/import-resources.component.ts
+
+
+ OnInit
+
selector | +app-import-resources |
+
styleUrls | +./import-resources.component.scss |
+
templateUrl | +./import-resources.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(resourceService: ResourcesService, filterService: FiltersService, router: Router, authService: AuthenticationService)
+ |
+ |||||||||||||||
+ + | +|||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + fileReset + + + + | +
+fileReset()
+ |
+
+ + | +
+ reset file input and response records +
+
+
+ Returns :
+ void
+
+
+
+ observable with response + + |
+
+ + + + isValidCSVFile + + + + | +||||||||
+isValidCSVFile(file: any)
+ |
+ ||||||||
+ + | +||||||||
+ check if file is ending with csv +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + uploadListener + + + + | +||||||
+uploadListener($event: any)
+ |
+ ||||||
+ + | +||||||
+ send file to service and upload to server +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + Public + authService + + + | +
+ Type : AuthenticationService
+
+ |
+
+ + | +
+ + + + csvReader + + + | +
+ Type : any
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('csvReader', {static: true})
+ |
+
+ + | +
+ csv reader input reference + |
+
+ + + + file + + + | +
+ Type : any
+
+ |
+
+ Default value : null
+ |
+
+ + | +
+ the file that will be extracted from file input + |
+
+ + + + loading + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for HTML to display loading animation + |
+
+ + + + NGOValues + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ list of all the NGO from which the DSU can select + |
+
+ + + + organisation_id + + + | +
+ Type : any
+
+ |
+
+ Default value : ''
+ |
+
+ + | +
+ id of the org to which the resources will be added + |
+
+ + + + Public + resp + + + | +
+ Type : any
+
+ |
+
+ Default value : {}
+ |
+
+ + | +
+ response from server. contains all errors + |
+
import { Component, OnInit, ViewChild } from '@angular/core';
+import { ResourcesService } from '@app/pages/resources/resources.service';
+import { FiltersService, AuthenticationService } from '@app/core';
+import { Location } from '@angular/common';
+import { Router } from '@angular/router';
+
+@Component({
+ selector: 'app-import-resources',
+ templateUrl: './import-resources.component.html',
+ styleUrls: ['./import-resources.component.scss']
+})
+export class ImportResourcesComponent implements OnInit {
+ /**
+ * csv reader input reference
+ */
+ @ViewChild('csvReader', { static: true }) csvReader: any;
+ /**
+ * the file that will be extracted from file input
+ */
+ file: any = null;
+ /**
+ * flag for HTML to display loading animation
+ */
+ loading = false;
+ /**
+ * id of the org to which the resources will be added
+ */
+ organisation_id: any = '';
+ /**
+ * list of all the NGO from which the DSU can select
+ */
+ NGOValues: any[] = [];
+ /**
+ * response from server. contains all errors
+ */
+ public resp: any = {};
+ constructor(private resourceService: ResourcesService,
+ private filterService: FiltersService,
+ private router: Router,
+ public authService: AuthenticationService) {
+ this.resp.has_errors = false;
+ if (authService.is('NGO')) {
+ this.organisation_id = this.authService.user.organisation._id;
+ }
+ }
+
+ ngOnInit() {
+ this.filterService.getorganisationbyName('').subscribe((data) => {
+ this.NGOValues = data;
+ });
+ }
+/**
+ * send file to service and upload to server
+ * @param {any} event contains the file
+ */
+ uploadListener($event: any): void {
+ const files = $event.srcElement.files;
+ this.loading = true;
+
+ if (this.isValidCSVFile(files[0])) {
+ const input = $event.target;
+ this.file = input.files[0];
+ this.resourceService.importCsv(this.file, this.organisation_id).subscribe((response: any) => {
+ this.resp = response;
+ if (!this.resp.has_errors) {
+ this.loading = false;
+ this.router.navigateByUrl('/resources');
+ } else {
+ this.loading = false;
+ }
+ }, error => {
+ this.loading = false;
+ });
+ } else {
+ alert('Vă rog introduceți un fișier CSV valid.');
+ this.fileReset();
+ }
+ }
+/**
+ * check if file is ending with csv
+ * @param {any} file that will be uploaded
+ * @returns {boolead}
+ */
+ isValidCSVFile(file: any) {
+ return file.name.endsWith('.csv');
+ }
+/**
+ * reset file input and response records
+ * @returns observable with response
+ */
+ fileReset() {
+ this.csvReader.nativeElement.value = '';
+ }
+}
+
+ <div class="container mt-5">
+ <nav class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <app-back-button></app-back-button>
+ </nav>
+ <p class="page-title">Adaugă resurse:</p>
+
+ <div class="note col-md-7">
+ <span>
+ Vrei să adaugi resurse individual?
+ <a ref="javascript:void(0)" [routerLink]="['../add']">Adaugă o resursa</a>.
+ </span>
+ </div>
+
+ <p class="m-2" *ngIf="!!file">{{ file.name }}</p>
+
+ <div *ngIf="!loading" class="col-md-7">
+ <select class="form-control mt-2" *ngIf="authService.is('DSU')" [(ngModel)]="organisation_id">
+ <option value="" selected disabled>Vă rugăm să selectați o organizație la care să adaugați</option>
+ <option *ngFor="let org of NGOValues; let i=index" [value]="org._id">
+ {{org.name}}
+ </option>
+ </select>
+ <div class="import-box" >
+ <button class="btn btn-info mt-4 btn-import" [disabled]="organisation_id==''">
+ Importați un fișier CSV
+ </button>
+ <input type="file" #csvReader name="Upload CSV" id="txtFileUpload" (change)="uploadListener($event)" [disabled]="organisation_id==''"
+ accept=".csv" class="import-csv" />
+ </div>
+ </div>
+ <div class="spinner-border text-green" role="status" *ngIf="loading">
+ <span class="sr-only">Loading...</span>
+ </div>
+ <div class="error-table" *ngIf="resp.has_errors">
+ <span>Au fost importate {{resp.rows_imported}} rânduri din {{resp.rows_discovered}}. Au apărut erori la unele rânduri:</span>
+ <div class="col-md-12" *ngFor="let err of resp.errors">
+ <span>Rândul {{err.line}}: </span>
+ <div *ngFor="let ex of err.error">
+ <span>{{ex.value}} - {{ex.error}}</span>
+ </div>
+ </div>
+ </div>
+</div>
+
+ ./import-resources.component.scss
+
.error-table {
+ margin-top: 100px;
+}
+ +
+ src/app/pages/volunteers/volunteers/components/import-volunteers/import-volunteers.component.ts
+
+
+ OnInit
+
selector | +app-import-volunteers |
+
styleUrls | +./import-volunteers.component.scss |
+
templateUrl | +./import-volunteers.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(volunteerService: VolunteerService, filterService: FiltersService, router: Router, authService: AuthenticationService)
+ |
+ |||||||||||||||
+ + | +|||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + fileReset + + + + | +
+fileReset()
+ |
+
+ + | +
+ reset file input and response records +
+
+
+ Returns :
+ void
+
+
+
+ observable with response + + |
+
+ + + + isValidCSVFile + + + + | +||||||||
+isValidCSVFile(file: any)
+ |
+ ||||||||
+ + | +||||||||
+ check if file is ending with csv +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + uploadListener + + + + | +||||||
+uploadListener($event: any)
+ |
+ ||||||
+ + | +||||||
+ send file to service and upload to server +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + Public + authService + + + | +
+ Type : AuthenticationService
+
+ |
+
+ + | +
+ + + + csvReader + + + | +
+ Type : any
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('csvReader', {static: true})
+ |
+
+ + | +
+ csv reader input reference + |
+
+ + + + file + + + | +
+ Type : any
+
+ |
+
+ Default value : null
+ |
+
+ + | +
+ the file that will be extracted from file input + |
+
+ + + + loading + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for HTML to display loading animation + |
+
+ + + + NGOValues + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ list of all the NGO from which the DSU can select + |
+
+ + + + organisation_id + + + | +
+ Type : any
+
+ |
+
+ Default value : ''
+ |
+
+ + | +
+ id of the org to which the resources will be added + |
+
+ + + + Public + resp + + + | +
+ Type : any
+
+ |
+
+ Default value : {}
+ |
+
+ + | +
+ response from server. contains all errors + |
+
import { Component, OnInit, ViewChild } from '@angular/core';
+import { VolunteerService } from '@app/pages/volunteers/volunteers.service';
+import { FiltersService, AuthenticationService } from '@app/core';
+import { Location } from '@angular/common';
+import { Router } from '@angular/router';
+
+@Component({
+ selector: 'app-import-volunteers',
+ templateUrl: './import-volunteers.component.html',
+ styleUrls: ['./import-volunteers.component.scss']
+})
+export class ImportVolunteersComponent implements OnInit {
+ /**
+ * csv reader input reference
+ */
+ @ViewChild('csvReader', { static: true }) csvReader: any;
+ /**
+ * the file that will be extracted from file input
+ */
+ file: any = null;
+ /**
+ * flag for HTML to display loading animation
+ */
+ loading = false;
+ /**
+ * id of the org to which the resources will be added
+ */
+ organisation_id: any = '';
+ /**
+ * list of all the NGO from which the DSU can select
+ */
+ NGOValues: any[] = [];
+ /**
+ * response from server. contains all errors
+ */
+ public resp: any = {};
+ constructor(private volunteerService: VolunteerService,
+ private filterService: FiltersService,
+ private router: Router,
+ public authService: AuthenticationService) {
+ this.resp.has_errors = false;
+ if (authService.is('NGO')) {
+ this.organisation_id = this.authService.user.organisation._id;
+ }
+ }
+
+ ngOnInit() {
+ this.filterService.getorganisationbyName('').subscribe((data) => {
+ this.NGOValues = data;
+ });
+ }
+/**
+ * send file to service and upload to server
+ * @param {any} event contains the file
+ */
+ uploadListener($event: any): void {
+ const files = $event.srcElement.files;
+ this.loading = true;
+ if (this.isValidCSVFile(files[0])) {
+ const input = $event.target;
+ this.file = input.files[0];
+ this.volunteerService.importCsv(this.file, this.organisation_id).subscribe((response: any) => {
+ this.resp = response;
+ if (!this.resp.has_errors) {
+ this.loading = false;
+ this.router.navigateByUrl('/volunteers');
+ } else {
+ this.loading = false;
+ }
+ }, error => {
+ this.loading = false;
+ });
+ } else {
+ alert('Vă rog introduceți un fișier CSV valid.');
+ this.fileReset();
+ this.loading = false;
+ }
+ }
+/**
+ * check if file is ending with csv
+ * @param {any} file that will be uploaded
+ * @returns {boolead}
+ */
+ isValidCSVFile(file: any) {
+ return file.name.endsWith('.csv');
+ }
+/**
+ * reset file input and response records
+ * @returns observable with response
+ */
+ fileReset() {
+ this.csvReader.nativeElement.value = '';
+ }
+}
+
+ <div class="container mt-5">
+ <nav class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <app-back-button></app-back-button>
+ </nav>
+ <p class="page-title">Adaugă voluntar:</p>
+
+ <div class="note col-md-7">
+ <span>
+ Vrei să adaugi voluntari individual?
+ <a href="javascript:void(0)" [routerLink]="['../add']">Adaugă un voluntar</a>.
+ </span>
+ </div>
+
+ <p class="m-2" *ngIf="!!file">{{ file.name }}</p>
+
+ <div *ngIf="!loading" class="col-md-7">
+ <select class="form-control mt-2" *ngIf="authService.is('DSU')" [(ngModel)]="organisation_id">
+ <option value="" selected disabled>Vă rugăm să selectați o organizație la care să adaugați</option>
+ <option *ngFor="let org of NGOValues; let i=index" [value]="org._id">
+ {{org.name}}</option>
+ </select>
+ <div class="import-box" >
+ <button class="btn btn-info mt-4 btn-import" [disabled]="organisation_id==''">
+ Importați un fișier CSV
+ </button>
+ <input type="file" #csvReader name="Upload CSV" id="txtFileUpload" (change)="uploadListener($event)" [disabled]="organisation_id==''"
+ accept=".csv" class="import-csv" />
+ </div>
+ </div>
+ <div class="spinner-border text-green" role="status" *ngIf="loading">
+ <span class="sr-only">Loading...</span>
+ </div>
+ <div class="error-table" *ngIf="resp.has_errors">
+ <span>Au fost importate {{resp.rows_imported}} rânduri din {{resp.rows_discovered}}. Au apărut erori la unele rânduri:</span>
+ <div class="col-md-12" *ngFor="let err of resp.errors">
+ <span>Rândul {{err.line}}: </span>
+ <div *ngFor="let ex of err.error">
+ <span>{{ex.value}} - {{ex.error}}</span>
+ </div>
+ </div>
+ </div>
+</div>
+
+ ./import-volunteers.component.scss
+
.error-table {
+ margin-top: 100px;
+}
+ +
+ src/app/pages/info/info/info.component.ts
+
+
+ OnInit
+
selector | +app-info |
+
styleUrls | +./info.component.scss |
+
templateUrl | +./info.component.html |
+
+ Methods+ |
+
+
|
+
+constructor()
+ |
+
+ Defined in src/app/pages/info/info/info.component.ts:9
+ |
+
+ Component to show on info page. All data is static + |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Defined in src/app/pages/info/info/info.component.ts:15
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'app-info',
+ templateUrl: './info.component.html',
+ styleUrls: ['./info.component.scss']
+})
+
+export class InfoComponent implements OnInit {
+ /**
+ * Component to show on info page. All data is static
+ */
+ constructor() { }
+
+ ngOnInit() {
+ }
+
+}
+
+ <div class="container">
+ <nav class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <h4 class="navtitle mb-2"> Despre Aplicație </h4>
+ </nav>
+
+ <div>
+ <p>
+ Resource and Volunteer Management este o aplicație menită să ofere sprijin în eventualitatea unei calamități majore care ar avea loc în România. RVM este un instrument de administrare de voluntari și resurse pe care le pune la dispoziție societatea civilă către Departamentul de Situații de Urgență în cazul unui seism major sau al unui alt dezastru natural. Aplicația permite managementul stocurilor de resurse disponibile, menținerea unei situații clare cu privire la cantități, tipuri de materiale și locurile în care acestea sunt depozitate, precum și statusul voluntarilor organizați pe specializări distincte.
+ </p>
+ <p>
+ RVM are două componente principale:
+ </p>
+ <ul>
+ <li>
+ Aplicația web care permite gestionarea resurselor și a voluntarilor specializați în oferirea de prim-ajutor, dar și de intervenție de orice alt tip
+ </li>
+ <li>
+ Aplicația mobilă care permite ofițerilor de intervenție din teren să mobilizeze voluntari sau să valideze identitatea unor voluntari deja existenți în platformă.
+ </li>
+ </ul>
+ <p>
+ Sistemul are patru tipuri de utilizatori:
+ </p>
+ <ul>
+ <li>
+ Administratorul DSU care are capacitatea de a urmări simplu care este nivelul de resurse disponibile din teren la orice moment și de a cere organizațiilor situații actualizate, poate da acces altor instituții în platformă și poate administra organizațiile, respectiv resursele centralizate în platformă.
+ </li>
+ <li>
+ Administratorul ONG care are un cont dedicat unde își poate ține evidența resurselor pe care le poate face disponibile în cazul în care situația o cere.
+ </li>
+ <li>
+ Administratorul instituțional care poate să listeze în platformă efectivele de intervenție ca aceștia să poată beneficia de aplicație pe teren în caz de dezastru.
+ </li>
+ <li>
+ Ofițerul de intervenție care poate fi un angajat DSU, al Poliției, Jandarmeriei sau al altei instituții care intervine în caz de urgență și are nevoie să mobilizeze voluntari sau să valideze identitatea unor voluntari deja existenți în platformă.
+ </li>
+ </ul>
+ <p>
+ Aplicația RVM este dezvoltată de Code for Romania, în parteneriat cu Departamentul pentru Situații de Urgență și cu sprijinul World Bank.
+ </p>
+
+ <div class="row justify-content-between logo-group">
+ <img src="../../../../assets/images/DSU_logo.PNG" class="col-md-3"/>
+ <img src="../../../../assets/images/code_4_romania_logo.svg" class="col-md-3"/>
+ <img src="../../../../assets/images/world_bank_logo.JPG" class="col-md-3"/>
+ <img src="../../../../assets/images/GFDPR_logo.jpg" class="col-md-3"/>
+ </div>
+ </div>
+
+</div>
+
+
+
+ ./info.component.scss
+
.logo-group img {
+ object-fit: contain;
+}
+ +
+ src/app/pages/authentication/login/login.component.ts
+
+
+ OnInit
+
selector | +app-login |
+
styleUrls | +./login.component.scss |
+
templateUrl | +./login.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(router: Router, authenticationService: AuthenticationService)
+ |
+ |||||||||
+ + | +|||||||||
+
+ Parameters :
+
+
|
+
+ + + + Public + changeErrorMessage + + + + | +
+
+ changeErrorMessage()
+ |
+
+ + | +
+ On error broadcast message to ngb-alert. +After DeboundeTime we set errorMessage to null +
+ Returns :
+ void
+
+ |
+
+ + + + login + + + + | +
+login()
+ |
+
+ + | +
+ Login with username and password obtained from form loginForm. +On success redirects to dashboard +
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + Private + _success + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + errorMessage + + + | +
+ Type : string
+
+ |
+
+ + | +
+ Message to be displaied on error + |
+
+ + + + isLoading + + + | +
+ Default value : false
+ |
+
+ + | +
+ Flag for html loader + |
+
+ + + + loginForm + + + | +
+ Type : FormGroup
+
+ |
+
+ + | +
+ Form that stores login data + |
+
+ + + + Public + router + + + | +
+ Type : Router
+
+ |
+
+ + | +
import { Component, OnInit } from '@angular/core';
+import { FormGroup, Validators, FormControl } from '@angular/forms';
+import { Router } from '@angular/router';
+import { AuthenticationService } from '@app/core';
+import { finalize, debounceTime } from 'rxjs/operators';
+import { Subject } from 'rxjs/internal/Subject';
+
+@Component({
+ selector: 'app-login',
+ templateUrl: './login.component.html',
+ styleUrls: ['./login.component.scss']
+})
+export class LoginComponent implements OnInit {
+ /**
+ * Form that stores login data
+ */
+ loginForm: FormGroup;
+ /**
+ * Flag for html loader
+ */
+ isLoading = false;
+ /**
+ * Message to be displaied on error
+ */
+ errorMessage: string;
+ constructor(
+ public router: Router,
+ private authenticationService: AuthenticationService) {
+ this.loginForm = new FormGroup({
+ email: new FormControl('', [Validators.required]),
+ password: new FormControl('', [Validators.required])
+ });
+ }
+ private _success = new Subject<string>();
+
+ ngOnInit() {
+ this._success.subscribe(message => (this.errorMessage = message));
+ this._success
+ .pipe(debounceTime(5000))
+ .subscribe(() => (this.errorMessage = null));
+ }
+ /**
+ * On error broadcast message to ngb-alert.
+ * After DeboundeTime we set errorMessage to null
+ */
+ public changeErrorMessage() {
+ this._success.next(
+ 'Nu s-a putut realiza autentificarea, vă rugăm verificați datele și reîncercați'
+ );
+ }
+ /**
+ * Login with username and password obtained from form {@link loginForm}.
+ *
+ * On success redirects to dashboard
+ */
+ login() {
+ this.router.navigate(['/'], { replaceUrl: true });
+ this.isLoading = true;
+ this.authenticationService
+ .login(this.loginForm.value)
+ .pipe(
+ finalize(() => {
+ this.loginForm.markAsPristine();
+ this.isLoading = false;
+ })
+ )
+ .subscribe(
+ (credentials: any) => {
+ if (credentials.user.role !== '0') {
+ this.router.navigate(['/'], {
+ replaceUrl: true
+ });
+ } else {
+ this.changeErrorMessage();
+ }
+ },
+ (error: any) => {
+ this.changeErrorMessage();
+ console.log('Login error: ', error);
+ }
+ );
+ }
+}
+
+ <section id="wrapper" class="login-register login-sidebar">
+ <!-- <div class="col-md-2 col-sm-1"> </div> -->
+ <div class="logo-container col-md-12 col-sm-12 col-xs-12">
+ <img src="./../../../../assets/images/DSU_logo.PNG" />
+ </div>
+ <!-- <div class="col-md-2 col-sm-1"> </div> -->
+ <div class="login-box col-md-12 col-sm-12 col-xs-12">
+ <div class="card-body">
+ <form autocomplete="off" [formGroup]="loginForm" (ngSubmit)="login()" class="form-horizontal form-material"
+ id="loginform" novalidate>
+ <div class="form-group mt-4">
+ <div class="col-xs-12">
+ <input formControlName="email" autocomplete="new-password" class="form-control" type="text"
+ placeholder="Nume Utilizator" />
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-xs-12">
+ <input autocomplete="new-password" formControlName="password" class="form-control"
+ type="password" placeholder="Parola" />
+ </div>
+ </div>
+ <ngb-alert *ngIf="errorMessage" class="error-message" type="success" (close)="successMessage = null">
+ {{ errorMessage }}</ngb-alert>
+ <div class="form-group text-center mt-4">
+ <div class="col-xs-12">
+ <button class="btn button-login btn-lg btn-block text-uppercase waves-effect waves-light"
+ type="submit" [disabled]="loginForm.invalid || isLoading">
+ <app-button-loader [isLoading]="isLoading" [label]="'Login'"></app-button-loader>
+ </button>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+
+ <div class="d-flex my-5">
+ <a [routerLink]="['/recover']" class="navigation">
+ Am uitat parola
+ </a>
+ </div>
+</section>
+
+ ./login.component.scss
+
:host ::ng-deep .help-block {
+ color: white;
+}
+ +
+ src/app/pages/map/map/map.component.ts
+
+
+ OnInit
+
selector | +app-map |
+
styleUrls | +./map.component.scss |
+
templateUrl | +./map.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+ HostListeners+ |
+
+
|
+
+constructor(mapservice: MapService)
+ |
+ ||||||
+ Defined in src/app/pages/map/map/map.component.ts:151
+ |
+ ||||||
+
+ Parameters :
+
+
|
+
+ + + + click + + + + | +
+ Arguments : '$event'
+ |
+
+click(event: any)
+ |
+
+ Defined in src/app/pages/map/map/map.component.ts:114
+ |
+
+ Click listener to check if user has clicked on map or outside of it + |
+
+ + + + deselectCountybyId + + + + | +||||||
+deselectCountybyId(e: any)
+ |
+ ||||||
+ Defined in src/app/pages/map/map/map.component.ts:275
+ |
+ ||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Defined in src/app/pages/map/map/map.component.ts:156
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + selectCountybyId + + + + | +|||||||||
+selectCountybyId(id: any, e: any)
+ |
+ |||||||||
+ Defined in src/app/pages/map/map/map.component.ts:217
+ |
+ |||||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + setIcon + + + + | +||||||
+setIcon(i: any)
+ |
+ ||||||
+ Defined in src/app/pages/map/map/map.component.ts:193
+ |
+ ||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + setIcons + + + + | +
+setIcons()
+ |
+
+ Defined in src/app/pages/map/map/map.component.ts:182
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + tooltip + + + + | +||||||||||||
+tooltip(x: any, y: any, txt: string)
+ |
+ ||||||||||||
+ Defined in src/app/pages/map/map/map.component.ts:236
+ |
+ ||||||||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + tooltipOut + + + + | +
+tooltipOut()
+ |
+
+ Defined in src/app/pages/map/map/map.component.ts:259
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + adjustments + + + | +
+ Type : any
+
+ |
+
+ Default value : {
+ 'Dambovita': {
+ vol_x: -15,
+ vol_y: 20,
+ res_x: -5,
+ res_y: -10
+ },
+ 'Tulcea': {
+ vol_x: -35,
+ vol_y: 0,
+ res_x: -35,
+ res_y: 0
+ },
+ 'Ialomita': {
+ vol_x: 0,
+ vol_y: -5,
+ res_x: 0,
+ res_y: -5
+ },
+ 'Constanta': {
+ vol_x: 15,
+ vol_y: 0,
+ res_x: 15,
+ res_y: 0
+ },
+ 'Alba': {
+ vol_x: 10,
+ vol_y: -20,
+ res_x: 10,
+ res_y: -20,
+ },
+ 'Timis': {
+ vol_x: 0,
+ vol_y: -10,
+ res_x: 0,
+ res_y: -10,
+ },
+ 'Cluj': {
+ vol_x: 0,
+ vol_y: 20,
+ res_x: 0,
+ res_y: 20,
+ },
+ 'Neamt': {
+ vol_x: -10,
+ vol_y: 0,
+ res_x: -10,
+ res_y: 0,
+ },
+ 'Brasov': {
+ vol_x: -20,
+ vol_y: 0,
+ res_x: -20,
+ res_y: 0,
+ },
+ 'Ilfov': {
+ vol_x: -15,
+ vol_y: 10,
+ res_x: 10,
+ res_y: -15
+ },
+ 'Harghita': {
+ vol_x: 0,
+ vol_y: 15,
+ res_x: 0,
+ res_y: 15
+ },
+ 'Mehedinti': {
+ vol_x: 0,
+ vol_y: 20,
+ res_x: 10,
+ res_y: -25
+ },
+ 'Salaj': {
+ vol_x: -10,
+ vol_y: 0,
+ res_x: -10,
+ res_y: 0
+ },
+ 'Olt': {
+ vol_x: 10,
+ vol_y: 0,
+ res_x: 10,
+ res_y: 0
+ },
+ }
+ |
+
+ Defined in src/app/pages/map/map/map.component.ts:17
+ |
+
+ Adjustments to align the icons so that they will fit within the county border + |
+
+ + + + hasclicked + + + | +
+ Default value : false
+ |
+
+ Defined in src/app/pages/map/map/map.component.ts:106
+ |
+
+ Flag to check if a county is selected + |
+
+ + + + ids + + + | +
+ Type : any[]
+
+ |
+
+ Defined in src/app/pages/map/map/map.component.ts:13
+ |
+
+ Array of counties and county data + |
+
+ + + + previous + + + | +
+ Type : any
+
+ |
+
+ Defined in src/app/pages/map/map/map.component.ts:110
+ |
+
+ Var with the previous selected county + |
+
import { Component, OnInit, AfterViewInit, HostListener } from '@angular/core';
+import { MapService } from '../map.service';
+
+@Component({
+ selector: 'app-map',
+ templateUrl: './map.component.html',
+ styleUrls: ['./map.component.scss']
+})
+export class MapComponent implements OnInit {
+ /**
+ * Array of counties and county data
+ */
+ ids: any[];
+ /**
+ * Adjustments to align the icons so that they will fit within the county border
+ */
+ adjustments: any = {
+ 'Dambovita': {
+ vol_x: -15,
+ vol_y: 20,
+ res_x: -5,
+ res_y: -10
+ },
+ 'Tulcea': {
+ vol_x: -35,
+ vol_y: 0,
+ res_x: -35,
+ res_y: 0
+ },
+ 'Ialomita': {
+ vol_x: 0,
+ vol_y: -5,
+ res_x: 0,
+ res_y: -5
+ },
+ 'Constanta': {
+ vol_x: 15,
+ vol_y: 0,
+ res_x: 15,
+ res_y: 0
+ },
+ 'Alba': {
+ vol_x: 10,
+ vol_y: -20,
+ res_x: 10,
+ res_y: -20,
+ },
+ 'Timis': {
+ vol_x: 0,
+ vol_y: -10,
+ res_x: 0,
+ res_y: -10,
+ },
+ 'Cluj': {
+ vol_x: 0,
+ vol_y: 20,
+ res_x: 0,
+ res_y: 20,
+ },
+ 'Neamt': {
+ vol_x: -10,
+ vol_y: 0,
+ res_x: -10,
+ res_y: 0,
+ },
+ 'Brasov': {
+ vol_x: -20,
+ vol_y: 0,
+ res_x: -20,
+ res_y: 0,
+ },
+ 'Ilfov': {
+ vol_x: -15,
+ vol_y: 10,
+ res_x: 10,
+ res_y: -15
+ },
+ 'Harghita': {
+ vol_x: 0,
+ vol_y: 15,
+ res_x: 0,
+ res_y: 15
+ },
+ 'Mehedinti': {
+ vol_x: 0,
+ vol_y: 20,
+ res_x: 10,
+ res_y: -25
+ },
+ 'Salaj': {
+ vol_x: -10,
+ vol_y: 0,
+ res_x: -10,
+ res_y: 0
+ },
+ 'Olt': {
+ vol_x: 10,
+ vol_y: 0,
+ res_x: 10,
+ res_y: 0
+ },
+ };
+ /**
+ * Flag to check if a county is selected
+ */
+ hasclicked = false;
+ /**
+ * Var with the previous selected county
+ */
+ previous: any;
+ /**
+ * Click listener to check if user has clicked on map or outside of it
+ */
+ @HostListener('click', ['$event']) onClick(event: any) {
+ /*
+ * If a county is already selected
+ */
+ if (this.hasclicked) {
+ /*
+ * if the user has clicked inside the rect of a county
+ */
+ if (event.target.nodeName === 'path') {
+ /*
+ * if there is a previously selected county, it must be deselected
+ */
+ if (this.previous && this.previous.event) {
+ this.deselectCountybyId(this.previous.event);
+ }
+ /*
+ * Select the new county
+ */
+ this.selectCountybyId(event.target.getAttribute('id'), event);
+ } else {
+ /*
+ * User has clicked ouside the map and there already is a selected county. must deslect the county
+ */
+ if (this.previous && this.previous.id) {
+ this.deselectCountybyId(this.previous.event);
+ }
+ this.hasclicked = false;
+ }
+ } else {
+ /*
+ * First time click. If inside a county select the county
+ */
+ if (event.target.nodeName === 'path') {
+ this.hasclicked = true;
+ this.selectCountybyId(event.target.getAttribute('id'), event);
+ }
+ }
+ }
+
+ constructor(private mapservice: MapService) {
+ }
+
+ ngOnInit() {
+ /*
+ * Get nr of resources and nr of volunteers per county
+ */
+ this.mapservice.getMapFilters().subscribe((res: any) => {
+ res.map((elem: any) => {
+
+ elem.id = elem._id;
+ elem.icons = [];
+ if (elem.nrResurse !== 0) {
+ elem.icons.push('res');
+ }
+ if (elem.nrVoluntari !== 0) {
+ elem.icons.push('vol');
+ }
+ return elem;
+ });
+ this.ids = res;
+ setTimeout(() => {
+ this.setIcons();
+ }, 0);
+ });
+ }
+ /*
+ * If there are volunteers or resources in a county, render icons over said county
+ */
+ setIcons() {
+ for (let i = 0; i < this.ids.length; i++) {
+ if (this.ids[i].name !== 'București' && this.ids[i].name !== 'Ilfov') {
+ this.setIcon(i);
+ }
+ }
+ }
+/*
+ * Add icons over a specific county
+ * @param {any} i the id of the county
+ */
+ setIcon(i: any) {
+ const p = (document.getElementById(this.ids[i].id) as any).getBBox();
+ const cx = p.x + p.width / 2;
+ const cy = p.y + p.height / 2;
+ const adjust = this.adjustments[this.ids[i].slug];
+ if (this.ids[i].icons.includes('res')) {
+ const res = document.getElementById('icon_res_' + this.ids[i].id);
+ res.setAttribute('x', String(cx - 20 + (adjust ? adjust.res_x : 0)));
+ res.setAttribute('y', String(cy - 10 + (adjust ? adjust.res_y : 0)));
+ res.setAttribute('width', '20');
+ res.setAttribute('height', '20');
+ }
+ if (this.ids[i].icons.includes('vol')) {
+ const vol = document.getElementById('icon_vol_' + this.ids[i].id);
+ vol.setAttribute('x', String(cx + 5 + (adjust ? adjust.vol_x : 0)));
+ vol.setAttribute('y', String(cy - 10 + (adjust ? adjust.vol_y : 0)));
+ vol.setAttribute('width', '20');
+ vol.setAttribute('height', '20');
+ }
+ }
+/*
+ * highlight county and add label over it
+ * @param {any} i the id of the county
+ */
+ selectCountybyId(id: any, e: any) {
+ if (e.target && e.target.nodeName === 'path') {
+ this.previous = {id: id, event: e};
+ const p = e.target.getBBox();
+ const cx = p.x + p.width / 2;
+ const cy = p.y + p.height / 2;
+ this.tooltip(cx, cy,
+ this.ids.find(x => x.id === id).name +
+ '<br/> Resurse: ' + this.ids.find(x => x.id === id).nrResurse +
+ '<br/> Voluntari: ' + this.ids.find(x => x.id === id).nrVoluntari);
+ e.target.setAttribute('fill', '#264998');
+ }
+ }
+/*
+ * add tooltip on the provided coordinates
+ * @param {any} x the x coord
+ * @param {any} y the y coord
+ * @param {any} txt the string to be shown
+ */
+ tooltip(x: any, y: any, txt: string) {
+ const text: any = document.getElementById('recttext');
+ text.innerHTML = txt;
+ const p = text.getBBox();
+ setTimeout(function() {
+ text.setAttribute('width', p.width - 110);
+ text.setAttribute('height', p.height + 110);
+ text.setAttribute('x', String(x - p.width / 2 - p.x));
+ text.setAttribute('y', String(y - p.height / 2 - p.y));
+ text.setAttribute('fill', '#000000');
+ const rect = document.getElementById('recttest');
+ rect.setAttribute('x', String(x - (p.width / 2) - 5));
+ rect.setAttribute('y', String(y - (p.height / 2) - 5));
+ rect.setAttribute('width', p.width + 10);
+ rect.setAttribute('height', p.height + 10);
+ rect.setAttribute('fill', '#ffffff');
+ }, 0);
+
+ }
+/*
+ * delete tooltip
+ *
+ */
+ tooltipOut() {
+ const text = document.getElementById('recttext');
+ text.innerHTML = '';
+ text.setAttribute('x', '0');
+ text.setAttribute('y', '0');
+
+ const rect = document.getElementById('recttest');
+ rect.setAttribute('x', '0');
+ rect.setAttribute('y', '0');
+ rect.setAttribute('width', '0');
+ rect.setAttribute('height', '0');
+ }
+/*
+ * dehighlight county by id
+ * @param {any} e the rect of the county
+ */
+ deselectCountybyId(e: any) {
+ if (e.target && e.target.nodeName === 'path') {
+ // const p = e.target.getBBox();
+ // const cx = p.x + p.width / 2;
+ // const cy = p.y + p.height / 2;
+
+ this.tooltipOut();
+
+ e.target.setAttribute('fill', '#f3d973');
+ }
+ }
+}
+
+ <div class="container container-ph mt-2">
+ <div class="svg-box">
+<svg
+ class="svg-box-content"
+ viewBox="0 0 1000 705"
+ preserveAspectRatio="none"
+ version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <path fill="#f3d973" stroke="#fff"
+ d="m373.8 546.1l0.8 3.5 0.1 1.2 0.5 1.3 0.8 1.1 1.9 1.1 1.6 0.7 3.5 2.2 1 0.9 1 1.3 2.1 7.3 1.3 2.3 1.7 2.6 3 3.1-4 4.7-1.4 2.6-0.1 2.2 0.4 1.1 0.9 1 4.2 2.5 1.2 1.2 2.8 4 0.8 1 2.3 0.6 1.3 0.7 1.5 1.6 1.4 2.9 1.2 1.9 1.3 1.4 2.4 1.9 1 1.2 1.8 3.9 1.1 1.6 1.1 1.3 3.8 2.5 2.7 1 1.2 0.9 2 2.1 0.6 1.4 0 1.2-0.9 2.1-0.2 1.9 0.2 2.2 0.8 3.6 2.2 4 0 0.9-0.7 0.3-3.6-0.7-1 0.1-1.1 1.1-0.1 2.5-1.1 5 0.2 0.9 0.6 0.9 1.8 0.7 3.3 0.2 1.4 0.3 1.4 1.2 0.5 1.4 0.2 1.5-0.2 1.3-0.4 0.9-2 1.2-1 0.7-0.5 1.3 0.2 1.2 1.5 2.5 0.3 1.2-0.2 1-1.3 4-0.6 1-1.5 1.1-0.3 0.7-0.3 1.7-0.5 0.8-1.9 1.5-0.7 1.1-5.8 12.3-36.9-9.3-6-3.6-2.4-0.4-8.9 2-4.6-0.8-11.4-6.3-16.8-0.9-9.6 1.4-3.9 2.1-3.7 0.8-3.2 1.4-22.4 2-3.3-0.8-2.6-2.3-1.3-2.8-0.1-3.3 2.6-11 1.1-3.3 2.1-1.4 2.2-0.3 4.2-1.4 2.3-0.3 3.7-2.1 1.8-4.5-1-4.5-2.3-1.1 8.6-9.1 3.3-0.9 1.1 0.2 0.8-0.5 0.4-1.8 0-1.7-0.3-1.8-0.7-2.4-0.2-1.9 0.3-1.7 2.2-4.8 0.3-1.2 0.5-3.7 0.5-2.6 0.6-1.9 0.9-1.3 5.6-3.2 1.4-0.2 2.1 0.4 0.6-0.2 0.2-1-0.3-0.8-1.4-1.1-3.3-0.4-1.2-0.3-0.8-0.6-0.4-1 0-1 0.8-3.2 0.2-1.2 0.4-0.8 0.8-0.8 2.1-0.7 1.6-0.8 1.4-1.6 1.4-2.1 2.3-2 0-0.6-2.4-0.9 0.4-0.5 1.4-0.7 6.6-0.4 1.3-0.9 1.7-0.7 2.6-0.6 5.8 0.3 1.5-1.7 9.7-6.8 10-3.2 5-0.3 0.9-0.6 0.5-0.8 0-2.2-0.4-2.7 0.4-2.2 0.1-1.7-0.5-3.8 0.3-2.2 0.5-0.6 1.3-0.1 1.4 0.6 1 1.1z" id="county_romania_dolj_0" name="Dolj">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m373.8 546.1l-1-1.1-1.4-0.6-1.3 0.1-0.5 0.6-0.3 2.2 0.5 3.8-0.1 1.7-0.4 2.2 0.4 2.7 0 2.2-0.5 0.8-0.9 0.6-5 0.3-10 3.2-9.7 6.8-1.7-3.6-1.1-1.5-5.9-5.9-6.8-4.7-2.5-1.1-18.3-5.3-2-1-2.4-1.7-8.1-7.7-9.4-10.7-0.7-1.7-0.3-1.6-0.4-3.2-1-4-0.2-2.7 0.5-2.5 0.5-1.3 0-1.3-0.5-1.3-1.3-1.3-2.7-1.7-1.7-1.6-1.2-1.4-0.8-1.3-1-1-1.3-0.6-2-0.3-5 0.6-1.6-0.4-1.9-1-4.6-5.9-4.1-1.9 0.6-3.2-0.6-1.2-0.7-0.9-1-0.3-4-0.2-1.3-0.5-0.9-0.7-0.4-1.2 0.2-1.5 0.9-2.1 3-4 7.2-6.4 22.4-8.2 1.1-0.2 1.4 0.7 0.7 1 1 0.8 1.2 0.4 4.6 0.3 0.9 0.4 1.7 1.6 1.1 0.4 1.6-0.1 6.8-2.9 2.6-0.2 1.6 0.2 3.2 1.5 1.3 0 1.7-0.4 5.7-3.5 1.7-0.6 4.7-1 3.6-1.8 2-0.6 2.7 0.1 1.6 0.4 1.5 0.8 1.9 1.4 0.9 0.1 1.1-0.4 1.8-1.5 6-3.3 6.6-2.2 3.2 1 5.1 0 1.2 0.4 2 1.1 1.6 0.3 1.9 0.1 6.5-0.7 1.8 0.4 1.8 1.4 0.8 1.2 0.5 1.7-1.4 5.8-0.2 2.9 0.8 4.1 1.1 2.7 0.5 2.1-0.2 1.8-1.7 5-0.4 2.2 0.4 5.1-0.1 2-2.9 11.7-1.2 3.3-0.5 2-0.2 2.9 1.5 17.1-0.4 7.9-1.5 8.6z" id="county_romania_gorj_32" name="Gorj">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m343.6 571.6l-1.5 1.7-5.8-0.3-2.6 0.6-1.7 0.7-1.3 0.9-6.6 0.4-1.4 0.7-0.4 0.5 2.4 0.9 0 0.6-2.3 2-1.4 2.1-1.4 1.6-1.6 0.8-2.1 0.7-0.8 0.8-0.4 0.8-0.2 1.2-0.8 3.2 0 1 0.4 1 0.8 0.6 1.2 0.3 3.3 0.4 1.4 1.1 0.3 0.8-0.2 1-0.6 0.2-2.1-0.4-1.4 0.2-5.6 3.2-0.9 1.3-0.6 1.9-0.5 2.6-0.5 3.7-0.3 1.2-2.2 4.8-0.3 1.7 0.2 1.9 0.7 2.4 0.3 1.8 0 1.7-0.4 1.8-0.8 0.5-1.1-0.2-3.3 0.9-8.6 9.1-2.2-0.9-4.8-0.7-3.8-1.7-22.8-15.6-0.5-2.1 0.4-7.1-0.8-2-2-1-4.4-0.6-4.1-1.8-3.5-3.1-2.8-3.8-1.9-4.3-0.1-1.1 0.3-2.4-0.1-1.1-0.5-1-2.2-2.1-0.3-1.2 0-1.9 0.3-2 0.4-1.4 0.8-0.6 0.9-0.3 3.8-0.2 1.3-0.6 0.9-1.5 0.3-2.5 0.6-2.5 1.6-1.5 2.1-0.6 2.2 0 2.2 0.9 3.8 2.6 2.3 0.6 2.1-0.4 2.3-1.1 1.9-1.9 0.6-2.7-5.3-5.9-1.6-1.2-8.3-1-3.6-1.7-3.6-3-7.3-8.1-1.6-0.9-1.9-0.5-2.6-0.1-1.1 0.9-3.7 4.1-2 1.2-4.4 1-1.6 1.2-0.6 2.3-12 21.8-1.4 1.4-2.5 0.7-2.3-0.3-2.4-1-1.8-1.8-1.1-4.3-1-1-1.2-0.7-1.1-1.1-0.6-1.1-0.5-2.9 5.7-1 1.4 0.2 2.6 0.7 2.5-0.1 1.1-0.6 0.9-1 0.5-1.9-0.1-1.8-0.5-2.1-0.8-2.5-0.1-1.9 0.2-1.9 0.4-1.8 0.7-2.2 1.1-2.5 2.4-4.1 0.9-2.4 0.6-2 0-1.2 0.3-1 0.6-0.8 1.6-0.3 1.1 0.2 1 0.5 3.6 3.7 1.5 0.9 1.1 0.1 3.4-0.1 0.9 0.2 1.5 1.1 1.2 2.3 0.8 0.4 1.1 0.1 1.6-0.3 1.1-0.9 1.1-1.7 1-3.3 1.6-7.1 2.6-6.7 4.5-7.4 1.1-3.5 0.6-2.4 0.5-3.5 0.4-1.8 0.8-2.2 3.4-5.4 4.5-4.8 4.1 1.9 4.6 5.9 1.9 1 1.6 0.4 5-0.6 2 0.3 1.3 0.6 1 1 0.8 1.3 1.2 1.4 1.7 1.6 2.7 1.7 1.3 1.3 0.5 1.3 0 1.3-0.5 1.3-0.5 2.5 0.2 2.7 1 4 0.4 3.2 0.3 1.6 0.7 1.7 9.4 10.7 8.1 7.7 2.4 1.7 2 1 18.3 5.3 2.5 1.1 6.8 4.7 5.9 5.9 1.1 1.5 1.7 3.6z" id="county_romania_mehedinti_36" name="mehedinti">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m443.7 527.1l1.2 3.6 0.7 0.6 1 0.3 0.9-0.4 4.3-3.6 0.7-0.9 1.4-3.6 1.2-1.7 0.9-0.6 1 0.2 0.5 0.8 0.3 1.2-0.3 2.7 0.1 1.7 0.8 2 4.7 9.9 1.2 1.6 2.1 2.1 1.7 0.5 1.7 0 1.6-0.4 1.9 0.2 0.8 0.6 0.5 0.8-0.6 0.9-1.9 0.8-0.9 0.7-0.9 1.6 0.3 1.4 2.6 4.3 0.8 2.3 0.5 3.3 0.2 1.8 1.1 3.8 1 6.7-0.1 1-0.6 1.9-0.1 2.6 1.3 4.9 0.4 6.5 0.6 2.3 0.7 1.2 1 0.7 5.1 0.4 2.3 1.6-1.4 3.4-0.2 3.8 0.9 6.5 0.3 7-0.3 3.3-1.2 5-0.1 3.9 0.2 2.5 0.5 2.2 0.2 2.8-0.7 1.3-1.3 0.9-3.6 0.7-1.4 0.5-1.2 0.9-3.1 3.4-1.1 0.6-1.5 0.4-1.8 0.1-1.8 0.6-1.9 1-2.2 3-0.6 2.1 0.1 2.8 3.2 8.4 3.8 7.6 4.5 7.6 0.6 2.2-0.2 1.1-0.9 0.8-3 1.5-1.4 1-1.2 1.2-2.3 3.1-17-6.4-3.6-0.5-3.7 1.2-5.9 4.5-1.8 0.5-21 1.1-1-0.3 5.8-12.3 0.7-1.1 1.9-1.5 0.5-0.8 0.3-1.7 0.3-0.7 1.5-1.1 0.6-1 1.3-4 0.2-1-0.3-1.2-1.5-2.5-0.2-1.2 0.5-1.3 1-0.7 2-1.2 0.4-0.9 0.2-1.3-0.2-1.5-0.5-1.4-1.4-1.2-1.4-0.3-3.3-0.2-1.8-0.7-0.6-0.9-0.2-0.9 1.1-5 0.1-2.5 1.1-1.1 1-0.1 3.6 0.7 0.7-0.3 0-0.9-2.2-4-0.8-3.6-0.2-2.2 0.2-1.9 0.9-2.1 0-1.2-0.6-1.4-2-2.1-1.2-0.9-2.7-1-3.8-2.5-1.1-1.3-1.1-1.6-1.8-3.9-1-1.2-2.4-1.9-1.3-1.4-1.2-1.9-1.4-2.9-1.5-1.6-1.3-0.7-2.3-0.6-0.8-1-2.8-4-1.2-1.2-4.2-2.5-0.9-1-0.4-1.1 0.1-2.2 1.4-2.6 4-4.7 4-2.5 2.5-0.6 1.2 0.2 2.1 1.2 0.8 1.1 1 1.7 0.8 0.7 0.8 0.1 1.3-0.6 3.3-4.8 1.1-1.2 1.3-0.7 1.1-0.2 1.4 0 2.5 0.7 2.1-0.2 3.1-1.8 0.8-1.1 0.7-3.1 0.5-1.3 1.3-1 1 0 0.9 0.9-0.2 2.5 0.8 2 0.8 1 1 0.5 1.6-1.4-0.3-2.7-0.8-3.4-1.2-2.8-1.2-1.9 0.4-2.5-1.4-6.4-0.5-3.2 0-1.3 2-2.1 1.7-2.6 0.9-1.9 0.6-1.6 0.8-3.3 0.2-2 0.9-3.5 3-5.3 1.6-2 1.1-0.6 1-0.1 1.1 0.3 1.3 2.3-0.2 6.9z" id="county_romania_olt_41" name="Olt">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m557.2 578.2l2.3 1.8 0.2 1 0 1.2-1.9 3.2-2.2 5.6-0.5 1.9 0.4 1.2 1.8 3.4 1.4 4 1.1 1.7 1.3 1.1 9.3 1.2 1.5 0.5 3 2.8 2.1 1.5 0.9 1 0.6 1.6 0.4 2.3 0.1 3.8-0.3 2.2 0.5 4.2 0 2.2-0.2 1.7-0.9 0.7-3.1 0.9-1 0.7-0.4 1.2-0.5 3.1-0.5 1.5-1.2 2.7-0.6 1.8-0.2 2 0 2.3 0.9 8.4 2.7 15.3 1.3 3.6 2.5 3.6 0.6 1.9 1.8 15-6.5 0.2-1.9 1.3-1.6 1.6-2.3 1.4-2.4 1-2 0.5-1.9 1-2.4 0.3-4.8-0.3-2.2 0.4-4.4 2-2.4 0.6-4.6-0.6-3.9-2.3-3.6-2.8-3.8-2.3-4.4-1-13.6-1.1-12.5-4.4-22.3 1.5-4.9-0.7-4.7-1.7 2.3-3.1 1.2-1.2 1.4-1 3-1.5 0.9-0.8 0.2-1.1-0.6-2.2-4.5-7.6-3.8-7.6-3.2-8.4-0.1-2.8 0.6-2.1 2.2-3 1.9-1 1.8-0.6 1.8-0.1 1.5-0.4 1.1-0.6 3.1-3.4 1.2-0.9 1.4-0.5 3.6-0.7 1.3-0.9 0.7-1.3-0.2-2.8-0.5-2.2-0.2-2.5 0.1-3.9 1.2-5 0.3-3.3-0.3-7-0.9-6.5 0.2-3.8 1.4-3.4 4.5-1.2 5.7 1.5 1.9-0.1 1.6-0.4 1.9-1.3 1.3-1.6 1.1-0.8 1-0.2 3.3 0.5 2.6-0.7 1.5-0.6 2.3-2.3 2.2-1.3 4.8-0.9 2.4 0 1.8 0.2 1.7 0.5 3.4 1.7 2.3 0.9 1.2-0.1 1.8-0.6 2.1-2 1.1-1.6 1.6-2.8 2.1-2.6 0.5-0.8 0-0.9-0.8-1.5-0.1-0.9 0.6-0.5 1.5-0.3 2.4 0.5 5.9 2.8 2.6 0.2z" id="county_romania_teleorman_11" name="Teleorman">
+</path>
+<path fill="#f3d973" stroke="#fff" id="county_romania_bucuresti_8" name="Bucharest"
+ d="m630.8 588.8l-1.6 4.7-5-1.6-0.8 2.3 3.3 8.1-2.8 2.5-5.1-6.7-5.8-1.3-7-7.5 0-4.7 7-0.6 0-4.7-5.6-2.4 5.1-8.9 7.1-2.6 0.9 9.9 8.3 2-2.6 7.6 4.6 3.9z" >
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m822 600.6l-3 7.4-2.8 3.5-3.5 2.2-15.3 3.8-3.8 3.1-2.1 1.3-4.4 0.8-8.8-0.1-4.1 1.4-5.6 3.4-1 1.1-0.8 1.4-1.8 1.5-2.2 1.1-1.7 0.6-16.1-2.6-2.9 2.5-1.3 1.9-2.8 0.2-2.2-1.3-11.1-2.2-7.7-4.8-2.7 1.7-12.4 1.3-10 6-3.8 1.1-4.7 0-1.2 0.3-2.1 1.3-1.1 0.3-2.1 0.3-3.5 1.2-20.9 3.1-0.2-2.1 0.3-1.5 0.8-1.8 3-4.4 0.7-2 0.3-2.4-0.3-3.6-0.9-3.2-1.4-2.9-2.4-2.6-9.4-5.6-4.9-5.8 1.2-3.9 2.7-4.3 2.7-3.6 1.8-1.8 2-1.4 6.3-3 1.2-1.3 1.2-2.4-0.1-0.8-0.6-0.6-2-0.7-1.5-0.9-1-1.1 0.1-1.9 0.4-0.9 1.9-1.3 8.4-4.4 1.7-0.3 1.5 0.4 1.5 0.8 3.3 2.5 1.3 0.5 1.5 0.2 1.8-0.3 2.6-0.9 1.9-1.4 1.6-2.1 0.7-0.6 1-0.3 0.9 0.4 3.4 3.4 1.8 1.1 2 0.6 2.6 0 1.7-0.3 1.6-0.1 1.2 0.4 3.1 1.8 2 0.4 6.1-1.1 2.1 0.2 2.4 0.5 7.8 3.3 2.5 0.6 3.2-0.1 6.1-1.1 18.8 0.4 1.9-0.5 3-1.2 2.7-0.4 3.2 0.1 14.7 2.8 3 1.5 5.9 1.3 1.3 0.6 1.7 1.5 2.2 2.5 4.3 3 9.7 4.1 15.9 5.2z" id="county_romania_calarasi_23" name="Calarasi">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m611.6 543.8l-2.6 3.1-1.3 1-0.7 1.5-0.4 1.5-0.6 1-2.3 1.7-0.4 0.9 0 0.9 0.4 0.9 3.6 1.2 0.4 1.2-0.5 1.3-1 1.4-5 6-2.4 1.9-32.5 2-3 0.8-2.1 1.1-1.1 1.3-2.9 3.7-2.6-0.2-5.9-2.8-2.4-0.5-1.5 0.3-0.6 0.5 0.1 0.9 0.8 1.5 0 0.9-0.5 0.8-2.1 2.6-1.6 2.8-1.1 1.6-2.1 2-1.8 0.6-1.2 0.1-2.3-0.9 2.3-4.2 0.3-2.7-1-3.7-0.1-2 0.6-1.5 1.9-3.6 0.1-1.3-0.4-1.1-1.8-1.1-2.4-0.4-1.1-0.4-0.9-0.6-2.9-2.9-1.1-1.5-2.7-5.3-0.3-1.8 0.4-1.2 2.4-1.5 0.4-0.7-2.1-2-1.5-1.8-0.2-1.7 0.3-1.7 1.6-2.9 0.5-1.3 0.2-1.6 0.9-12.2-0.3-2.3-1.7-8.1-0.2-2.9 0.2-2.5 0.6-1.3 3-3.5 0.5-1.3 0-1.4-0.5-1.9-1.2-2.9-3.3-3.8-1.5-2.5-1.1-5.9 0.1-2.9 0.5-2 0.5-0.9 1.1-2.7 1.1-1.1 0.7 0.1 0.7 0.7 1.2 2 0.8 0.7 0.9 0.3 1-0.2 0.9-0.7 1-1.1 0.8-1.6 1-2.4 0.8-3.1 0.2-3.5 0.5-2.4 0.6-2 0.8-1.6 1.6-2.7 0.7-1.6 0.5-2.5 0-1.8-0.4-1.5-0.6-1.4-1.6-2.9 6.4-1.7 2-2.5 0.5-1.6 0.9-1.9 0.8-0.9 2.1-1 1.9 9.4 0.7 6.7 0.5 1.8 0.7 1.3 3.2 2.4 1.1 1.2 1.2 2 0.7 1.8 0.3 1.5-0.3 4.1 0.1 2.1 1.6 2.8 2.1 2.3 1.2 1.8 5.8 11.3 4.4 4.9 0.9 1.6 0.4 1.6 0.7 6.4 0.8 3.7 0.8 1.9 0.9 1.6 1.1 1 2.3 1 1.2 0 2.9-0.8 0.8 0.4 1.1 1.2 1.4 2.4 0.5 2.1 0.1 2.1-0.6 4.3 0.4 2.7 0.8 1.4 1.2 0.9 5 0.7 1.6 0.6 1.8 1 7.3 9 1.3 1.1 2.1 1.1 1.2 0.4z" id="county_romania_dambovita_4" name="Dâmbovita">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m638.1 605.5l4.9 5.8 9.4 5.6 2.4 2.6 1.4 2.9 0.9 3.2 0.3 3.6-0.3 2.4-0.7 2-3 4.4-0.8 1.8-0.3 1.5 0.2 2.1-11.2 1.7-8.3 3.7-8.5 2.2-3.7 2-3.9 4.3-1.8 2.9-0.8 2.2-12.7 9.4-1 1.7-0.8 2.1-5 6.4-3.2 1.8-3.5 3.6-0.2 0.6-2.4 4-4.4 1.9-0.7 0.1-1.8-15-0.6-1.9-2.5-3.6-1.3-3.6-2.7-15.3-0.9-8.4 0-2.3 0.2-2 0.6-1.8 1.2-2.7 0.5-1.5 0.5-3.1 0.4-1.2 1-0.7 3.1-0.9 0.9-0.7 0.2-1.7 0-2.2-0.5-4.2 0.3-2.2-0.1-3.8-0.4-2.3-0.6-1.6-0.9-1-2.1-1.5-3-2.8-1.5-0.5-9.3-1.2-1.3-1.1-1.1-1.7-1.4-4-1.8-3.4-0.4-1.2 0.5-1.9 2.2-5.6 1.9-3.2 0-1.2-0.2-1-2.3-1.8 2.9-3.7 1.1-1.3 2.1-1.1 3-0.8 32.5-2 2.6 5.7-0.4 4.3-0.5 1.6-1.7 3.3-1 2.9-1 6.7 0.7 2.8 1.1 1.8 2.8 1.4 2 0.7 1.5 1 0.8 1.8 0.8 0.8 0.9 0.3 2.4 0 1.2 0.6 8 7.2 1.8 1.1 1.6 0.8 1.7 0.4 1.6 0.3 1.4-0.1 1.4-0.3 1.3-0.8 2.6-2.1 5.7-6z" id="county_romania_giurgiu_21" name="Giurgiu">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m735.3 531.7l8.7 3.6 1.7 0.2 2.1-0.1 2.4-1.2 1.8-0.6 1.4 0.2 1.3 0.7 3 1.2 28.8 0.8 4.1-0.9 2.4-0.8 1.8-1 2.4-0.8 4.9-0.5 2.2 0.4 1.9 1.2 0.8 1.1 3.2 3-1.7 1-1.1 1.6-0.8 2.1 0.8 2.7 1.5 2.6 1.9 2.1 2 0.8 2.6 0.4 1.9 1 5.6 6.3 0.4 0.9-0.1 1.3-0.6 3.5-0.7 1.6 0 5.2-0.7 1.4-1.1 0.5 0.3 1.2 1.4 1.4 2.3 1.3 2.6 0.9 2 0.4 0.8 1.2 0.9 2.7 0.7 3 0.2 2.3-1.2 1.8-4.4 3.6-1 1.1-2.7 6.5-15.9-5.2-9.7-4.1-4.3-3-2.2-2.5-1.7-1.5-1.3-0.6-5.9-1.3-3-1.5-14.7-2.8-3.2-0.1-2.7 0.4-3 1.2-1.9 0.5-18.8-0.4-6.1 1.1-3.2 0.1-2.5-0.6-7.8-3.3-2.4-0.5-2.1-0.2-6.1 1.1-2-0.4-3.1-1.8-1.2-0.4-1.6 0.1-1.7 0.3-2.6 0-2-0.6-1.8-1.1-3.4-3.4-0.9-0.4-1 0.3-0.7 0.6-1.6 2.1-1.9 1.4-2.6 0.9-1.8 0.3-1.5-0.2-1.3-0.5-3.3-2.5-1.5-0.8-1.5-0.4-1.7 0.3-8.4 4.4-2.5-1.9-3.3-1.7-0.9-1.3-0.3-1.2 0.5-2.5 1.2-2.3 0.9-1 4.1-2.9 0.7-0.8 0.5-1.2-0.3-1-0.9-1.1-6.5-3.6-1.1-1.4-0.2-2.4-0.3-1.2-0.8-1.5-0.3-1.2 0-2.5-0.6-0.7-1.1-0.4-4.2-0.6-2-0.6 3.8-3.6 3.6-1.7 1.5-0.1 1.2 0.3 2.5 1.4 2.3 0.2 1.2-0.5 1.8-2.1 1.5-1.2 1.4-0.3 5.7 0.5 1.5-0.4 1.4-0.8 1.9-1.6 1.3-2.4 2.7-3.4 2.4 1.2 3.5 3.1 1.9 0.5 2.5 0.2 7-0.5 6.3 1.1 2.4 0.8 1.5 0 3.7-0.7 2.1 0.1 2.5 0.9 3.1 2 1.4 0.6 1.5 0 1.5-0.9 1.1-1.2 1.3-0.8 1.6-0.2 2.5 0.7 1.7 0.9 1.4 0.9 1 1 0.6 1 0.1 1.1-0.2 1.9 0.1 1.5 0.4 0.4 1.5-0.1 3-1.2 2.4-0.6 1-0.5 0.5-0.7-0.5-0.8-1.9-1.5-0.7-0.7 0.5-1.5 2.3-1.2z" id="county_romania_ialomita_29" name="Ialomita">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m902.5 552.5l-0.4 2.1 0.3 2.4 0.9 0.6 5.6-1 2.5-1.5 2.3-2.3 2.2-2.8-1.5-3.1 7.3 2.4 1.7 1.2 0.5 2.6-0.9 3-1.8 2.3-2.5 0.8 1.9-2.5 1.7-2.8 0.3-2.5-2.4-1.4 0 1.1 0.7 1-0.2 0.5-2 0.5-1.5-1-1.1 0.3-1 1.7-2 1.6-3.3 3.5-7 3.7-1.5 1.3 1.9-3.5 0.3-1.5-1.6-0.2-1.3 0.3-1 1-0.4 1.9 1.4 0-0.7 1.5 0.4 1.2 1.1 0.8 1.4 0.6-4.2 1.3-1.6 1.4 0 2.3 1.5-2 1.5 0.9 1.3 1.1-0.9 2-1.2 3.8-0.9 4 0.5 2.9 0.9 0.2 0.8-1.1 0.6-1.6 0.7-3.5 1-1.2 2.1-1.4 3.2-4.3 1.8-2 1.9-0.9 2-0.5 1.5-1.6 0.9-2 0.4 0-1 2.5-6.9 13.4-5 3.8-9.5 12.9-1.1 3.3-0.3 1.8-1.4-1-1.6 0.7-1.4 1.2-1.1 1.7-0.6 1.8-0.3 2.1-0.2 5.6 0.5 1.8 1.7 3.2 1.4 4.6 0.4 2.2-0.4 1.3-2 0.5-1 1.1-0.6 1.5 0.7 2.7 1.8 11.9 1.8 7.6-0.2 2.1-1 1.4-0.5 2.7-1.5 4.2-0.4 2-2.5 5.1-0.4 1.4-1.7 8-0.6 1.4-0.4 1.4 0.5 1.4-0.4 1.3-0.4 3 0.2 1.6-0.5 1.4-15.2 0.9-22.5-3.9-21.8-10-3.6-2.8-4.7-16.9-2.5-4.2-5.9 0.7-7.3 4.1-6.9 1.7-4.8-6.6-2.1-4.4-2.4-0.9-6.3 2-20.1 0.2-1.1-0.9-2.1-3.6-1.2-1.1-6-2.9-2.2-2.5 0.5-3.3-1.7-1.4 1.3-1.9 2.9-2.5 16.1 2.6 1.7-0.6 2.2-1.1 1.8-1.5 0.8-1.4 1-1.1 5.6-3.4 4.1-1.4 8.8 0.1 4.4-0.8 2.1-1.3 3.8-3.1 15.3-3.8 3.5-2.2 2.8-3.5 3-7.4 2.7-6.5 1-1.1 4.4-3.6 1.2-1.8-0.2-2.3-0.7-3-0.9-2.7-0.8-1.2-2-0.4-2.6-0.9-2.3-1.3-1.4-1.4-0.3-1.2 1.1-0.5 0.7-1.4 0-5.2 0.7-1.6 0.6-3.5 0.1-1.3-0.4-0.9-5.6-6.3-1.9-1-2.6-0.4-2-0.8-1.9-2.1-1.5-2.6-0.8-2.7 0.8-2.1 1.1-1.6 1.7-1 1.7-0.3 2-0.1 2.1 0.5 3.1 2.1 2 0.4 2.2-0.8 2.1-2 9.6-0.8 5.3 2.1 9.1 8.6 4.6-1.1 2.2-6.4 6.1 0 5.3 6.4 7.6 3.2 8.4 0 6.8-2.1 6.1 3.2 6 1.4z" id="county_romania_constanta_33" name="Constanta">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m261 297.9l5.1 5.3-6.9 5.2-1.8 4-1 2.5-2.4 4-2.2 2-2 1.1-2.5 0.4-2-0.2-5.9-1.4-1.9 0.2-1.4 0.9-2.5 2.2-0.9 1.5-0.5 1.7 0.3 5-0.2 2.2-0.5 2.3-1.5 3.1-0.6 1.9-0.3 1.5 1.1 3.6 0.3 1.5-0.1 1.9-0.7 2.5-1.2 2.6-4.9 6.4-1.9 0.9-0.9 1.1-0.7 2.2-1.1 0.9-1.8 0.1-3.9 1-0.9 0.4-2.1 2-0.9 0-2.6-1.5-1.5-0.3-2.1 0.3-1.6 0-1.6-0.4-2.7-2.6-1.8-0.8-0.8-0.6-0.7-1.8-0.6-0.8-1.1-0.6-4.1-0.3-1.9-0.6-1.3-1.2-0.9-1.1-1.1-0.8-1.6-0.5-2.5 0-1.3 0.3-0.8 0.4-0.7 1.7-0.7 3.4-1 1.3-3.2 0.2-1-0.4-1.7-1.4-0.6 0-0.5 0.9-0.2 2.3 0.1 2.1-0.1 0.8-1.1-0.1-0.9-1.1-1.3-2.1-2.1-1.7-3.4-1.6-1.5 0-0.9 0.5-0.8 1-1.4 2.5-1.1 2.4-0.6 0.8-0.8 0.1-2-2.7-1.8-1.5-0.1-0.6 0.7-2.4-0.4-1.1-0.9-1.2-2.2-1.8-2.3-2.5-1.1-0.6-1.2-0.2-2.9 0.4-1-0.4-0.8-1.1-0.2-1.2-0.1-2.2-0.9-0.9-1.6-0.5-3.5-0.4-1.8 0.2-1.1 0.6-0.2 0.8-0.2 2.1-0.4 1-0.6 0.7-2.8 2.3-2.4 2.4-1.4 0.1-1.5-1.8-1.1-0.6-1.8 0-1.5 0.3-1.2 0.5-2.4 2.1-1.5 0-1.8-1-2.8-2.8-0.8-1.8-0.4-2-0.8-0.3-1.6 0.2-1 0.6-1 0.9-0.6 1.7-0.1 1.7-0.6 1.6-0.7 0.4-1.1 0.1-1.6-0.3-1.4-0.7-1.3-1.9-0.3-1 0.3-1.8-0.5-0.7-1.1-0.7-2.2-0.6-1.3 0.1-2 0.5-0.9 0-6.6-1.2-1.5-0.5-1.3-0.9-1.4-2.1-1.1-3-3.7-3.8-15-10.5-1.5-0.6 0.1-1.9 0.5-1.1 0.8-0.3 2 0.5 0.9-0.8 0-1.3-1-3.4-0.1-1.4 0.5-0.8 4.1-3.5 2.2-1.1 2.2-0.7 2.1 0.1 1 0.5 1.9 1.7 0.8 0.4 1.1-0.1 2.2-1.1 1.9 0.4 4 1.7 2.1-0.1 0.9-0.4 0.9 0 0.8 0.4 0.8 0.9 2 1.8 1.9-0.7 5.1-6.2 0.7-0.3 3 0 1.1-0.8 1.2-2.3 0.9-3 0.4-6.8 1.1-3.3 1.8-2.1 2-0.7 4.5-0.2 2.4-1.9-0.6-3.4-3.1-5.9 0.2-3.1 1.5-2.4 1.8-2.4 1.4-2.8 0.4-5.9 0.6-2.9 1.6-1.9 2.2-0.6 3.9 0.3 2.3-1.2 2.2-2.9 0.7-2 2.3 0.8 4.9 1.2 35.3-2 2.7 0.5 1.5 1.8 1.5 0.8 1.3 0.1 2 0 1.4 0.4 1.7 0.9 1.4 1.4 2.6 1 9.3 0.4 2.8 0.6 2 0.9 1.1 1.1 2.4 1.7 0.8 1 0.8 1.8 2.3 6.2 1.2 2.5 1.6 2 2.1 1.4 2.7 0.9 1.7 0.7 5 4.4 7.9 3 0.9 1 0.8 2.9 1.2 1.7 1.6 0.9 1.6 0.4 9.6 0.2 8 1.7 5 0.4 4.4-0.3z" id="county_romania_arad_28" name="Arad">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m247.7 150.9l-1.1 4.3-0.6 1.3-1.2 1.9-0.5 1.3-0.3 2.6-0.3 1.2-0.6 1.2-2.7 4-1.8 1.9 0.1 2.1 1.3 3.3 4.5 7.1 3.1 6.1 0.4 1.3 0.8 1.2 1.1 1.3 6.6 3.8 4.1 4.1 1.8 3.1-0.8 2.1-2.3 4.5-0.4 1.5 0.2 1 0.9 0.7 5.3 2.1 1.4 1.1 0.5 1.6-0.4 2.9-1.1 3.5-1.3 2.3-0.9 0.7-3.9 1.8-1.2 0.8-0.9 1.1-0.6 1.1-0.2 1.3 0.3 1.4 1.3 1.8 4.2 3.8 1.4 2.2 1.4 3.2 1.8 7.2 1 1.9 2.1 1.7 1 0.9 0.5 1.3-0.5 3.8 0 5.8-1.6-1-2.4-0.6-2.7 0.3-1.2 0.7-1.3 1.6-1.4 2.8-1.1 3.9-0.6 3.6 0.2 9.4 1.9 8.1-4.4 0.3-5-0.4-8-1.7-9.6-0.2-1.6-0.4-1.6-0.9-1.2-1.7-0.8-2.9-0.9-1-7.9-3-5-4.4-1.7-0.7-2.7-0.9-2.1-1.4-1.6-2-1.2-2.5-2.3-6.2-0.8-1.8-0.8-1-2.4-1.7-1.1-1.1-2-0.9-2.8-0.6-9.3-0.4-2.6-1-1.4-1.4-1.7-0.9-1.4-0.4-2 0-1.3-0.1-1.5-0.8-1.5-1.8-2.7-0.5-35.3 2-4.9-1.2-2.3-0.8 1.3-2.4 2.9-0.5 2.1-1.2 2-2.9 0.3-3-2.8-2-0.8-1.1 0.3-1.8 0.8-1.5 2.3-6.3 1.3-2.5 2.2-2 3.9-1.1 1.1-0.9 0.9-2-0.1-1.7-0.3-1.6-0.1-1.9 0.1-1.8 0.6-0.6 4.9-4 0.8-1.1 2.1-7.7 0.4-0.1-1.1-2-0.7-0.6-1.9-0.6-0.4-1.4 4.1-4.9 2.4-2.3 5.2-3.4 2.1-2.1 0.7-1.4 0.5-2.8 0.5-1.4 1-1.4 2.3-2.3 0.9-1.2 0.7-1.9-0.3-2.9 0-1.8 0.5-1.7 1.2-2.3 0.5-1.4 1.3-5.6 0.6-1.8 4.1-6 1.9-2.2 1.9-1.1 4.7-1.4 2.1-4.3-0.1-5.2-1-5.4-0.2-4.9 2-3.8 3.1-3.5 6.5-4.9 5.2-1.3 1.4-1.1 0.6-1.3 2.7 4.1 1.3 3.1 0.9 1.5 1.2 1.2 4.3 3.3 1.6 2.2 1.7 3.1 3 4.7 0.9 1.8 1.2 3.3 0.6 1.2 1 0.7 1.8 0.4 1.9-0.5 1.9 0 1.6 0.3 1-0.2 2.5-1.4 1.1-0.3 1.1 0.3 1.5 1 1.7 1.5 3.1 1.8 1.3 1.1 1 1.8 0.7 3.9 1 2.8z" id="county_romania_bihor_24" name="Bihor">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m257.1 468.8l-7.2 6.4-3 4-0.9 2.1-0.2 1.5 0.4 1.2 0.9 0.7 1.3 0.5 4 0.2 1 0.3 0.7 0.9 0.6 1.2-0.6 3.2-4.5 4.8-3.4 5.4-0.8 2.2-0.4 1.8-0.5 3.5-0.6 2.4-1.1 3.5-4.5 7.4-2.6 6.7-1.6 7.1-1 3.3-1.1 1.7-1.1 0.9-1.6 0.3-1.1-0.1-0.8-0.4-1.2-2.3-1.5-1.1-0.9-0.2-3.4 0.1-1.1-0.1-1.5-0.9-3.6-3.7-1-0.5-1.1-0.2-1.6 0.3-0.6 0.8-0.3 1 0 1.2-0.6 2-0.9 2.4-2.4 4.1-1.1 2.5-0.7 2.2-0.4 1.8-0.2 1.9 0.1 1.9 0.8 2.5 0.5 2.1 0.1 1.8-0.5 1.9-0.9 1-1.1 0.6-2.5 0.1-2.6-0.7-1.4-0.2-5.7 1-0.3-1-2.9-7.2-1.1-1-3.3-0.6-9.6-5-1.7-0.3-1.8 0.5-3.9 1.6-4.7 1-5.5 0-5.1-1.5-3.9-3.9-1-2.7-0.6-2.7-0.9-2.4-1.9-1.7-2.1-0.6-6.4 0.5-9-1-1.7-0.8-1.8-3.9-1.9-1.5-1.9-0.8 0.4-2 0.9-1.6 1.4-1.3 2.8-1 6.2 0.3 2.9-0.4 4.3-1.3 1.5-1.2 0.3-2.9-0.9-2.4-1.5-1.4-3.7-1.4-2.6-1.3-5.1-0.9-2.4-1.7 0.2-1.8-0.4-0.7-1.8-0.1-1.4-0.4-0.2-1.3 0.5-1.5 0.8-1.2 1-0.5 2.7-0.2 1.1-0.4 1.3-1.1 0.8-3.9 1.5-2.3 0.1-1.9-0.2-1.1 0.8-1.2 0.9-1 1.1-0.5 1.3-0.1 1.4-1.2 0.3-1.8-0.4-2-1-1.2 2.1-1.1 1.9-2.6 1.1-2.6 0-1.5-0.6-4.1 0-1.6 0.3-1.2 2.1-2.6 0.5-1.8-0.4-2.3 0-2-0.9-2.1-0.8-1-2.1-1.7-3.6-3.8-0.7-1.1-0.4-2.6 0.3-1.2 1.1-2.1 0.4-1.4 0-1.3-0.2-2.8 1-1.4 1.9-1.6 4.5-2.5 1.4-1.6 0.8-1.5 0.2-3.2 0.5-1.6 1-1.6 1.8-1.2 3-1.1 3.3 0 1.7 0.5 2 1.2 2.4 0.7 2.3 1.3 1.3 1.7 0.8 0.7 1.9 0 2.6-0.7 4.8-2.2 3.8-2.4 1-0.2 1.6 0.5 1 0.8 0.8 1.2 1 2.5 1.3 2.2 0.9 0.7 1.1 0.6 1.4 0.2 2.1-0.3 2.1-0.8 2.6-1.9 0.7-0.9-0.1-0.6-1.4-0.1-1-0.5-0.2-0.7-0.5-2.8 0.2-0.8 0.6-0.5 3.7 0 2.5-0.4 2.2-0.8 2.6-1.7 2.7-2.4 1.1-0.5 1.4 0.3 0.8 0.6 1.3 1.5 1.6 0.6 1.5-0.2 2.6 0.3 0.7-0.5 2.9-6.1 3.9-2.8 11.6-2.2 0.9 3.5 3.2 4.3 1.4 1 1.5 0.5 2.8 0.6 0.5 0.7-0.2 2.2 0.2 1.2 0.6 0.4 1.8-0.2 1.1 0.3 0.7 0.7 0.7 1.2 1.3 1.1 2 1.3 3.5 2.7 1.6 2.1 1 2.1 1.3 4.4 0.3 1.8 0.1 3.3 0.4 2.5-0.2 1.4-0.5 1.5-1.3 2-0.4 0.9 0.2 1.4 0.8 1.4 1.4 2 0.6 1.6 0.3 1.4-0.2 2.3-0.6 1.3-2.3 3.4-0.8 2.4 0 2.9z" id="county_romania_caras-severin_30" name="Caras-Severin">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m49.7 328.5l1.5 0.6 15 10.5 3.7 3.8 1.1 3 1.4 2.1 1.3 0.9 1.5 0.5 6.6 1.2 0.9 0 2-0.5 1.3-0.1 2.2 0.6 1.1 0.7 0.5 0.7-0.3 1.8 0.3 1 1.3 1.9 1.4 0.7 1.6 0.3 1.1-0.1 0.7-0.4 0.6-1.6 0.1-1.7 0.6-1.7 1-0.9 1-0.6 1.6-0.2 0.8 0.3 0.4 2 0.8 1.8 2.8 2.8 1.8 1 1.5 0 2.4-2.1 1.2-0.5 1.5-0.3 1.8 0 1.1 0.6 1.5 1.8 1.4-0.1 2.4-2.4 2.8-2.3 0.6-0.7 0.4-1 0.2-2.1 0.2-0.8 1.1-0.6 1.8-0.2 3.5 0.4 1.6 0.5 0.9 0.9 0.1 2.2 0.2 1.2 0.8 1.1 1 0.4 2.9-0.4 1.2 0.2 1.1 0.6 2.3 2.5 2.2 1.8 0.9 1.2 0.4 1.1-0.7 2.4 0.1 0.6 1.8 1.5 2 2.7 0.8-0.1 0.6-0.8 1.1-2.4 1.4-2.5 0.8-1 0.9-0.5 1.5 0 3.4 1.6 2.1 1.7 1.3 2.1 0.9 1.1 1.1 0.1 0.1-0.8-0.1-2.1 0.2-2.3 0.5-0.9 0.6 0 1.7 1.4 1 0.4 3.2-0.2 1-1.3 0.7-3.4 0.7-1.7 0.8-0.4 1.3-0.3 2.5 0 1.6 0.5 1.1 0.8 0.9 1.1 1.3 1.2 1.9 0.6 4.1 0.3 1.1 0.6 0.6 0.8 0.7 1.8 0.8 0.6 1.8 0.8 2.7 2.6 1.6 0.4 1.6 0 2.1-0.3 1.5 0.3 2.6 1.5 0.9 0 2.1-2 0.9-0.4 3.9-1 1.8-0.1 1.1-0.9 0.7-2.2 0.9-1.1 1.9-0.9 3.6 3.8 1 2.5 0.3 2.8 1.2 4.1 1 1.5 2.9 2.3 0.8 1 1.1 1.9 1.9 4.9 0.9 1.3 0.9 0.5 1.3-0.7 1.6-0.2 1.1 0.7 0.3 1-0.3 1.1-0.8 1.2-4.3 3.9-1.2 1.3-0.8 1.6-0.5 1.4-0.8 3.9-0.8 1.2-0.9 0.4-11.6 2.2-3.9 2.8-2.9 6.1-0.7 0.5-2.6-0.3-1.5 0.2-1.6-0.6-1.3-1.5-0.8-0.6-1.4-0.3-1.1 0.5-2.7 2.4-2.6 1.7-2.2 0.8-2.5 0.4-3.7 0-0.6 0.5-0.2 0.8 0.5 2.8 0.2 0.7 1 0.5 1.4 0.1 0.1 0.6-0.7 0.9-2.6 1.9-2.1 0.8-2.1 0.3-1.4-0.2-1.1-0.6-0.9-0.7-1.3-2.2-1-2.5-0.8-1.2-1-0.8-1.6-0.5-1 0.2-3.8 2.4-4.8 2.2-2.6 0.7-1.9 0-0.8-0.7-1.3-1.7-2.3-1.3-2.4-0.7-2-1.2-1.7-0.5-3.3 0-3 1.1-1.8 1.2-1 1.6-0.5 1.6-0.2 3.2-0.8 1.5-1.4 1.6-4.5 2.5-1.9 1.6-1 1.4 0.2 2.8 0 1.3-0.4 1.4-1.1 2.1-0.3 1.2 0.4 2.6 0.7 1.1 3.6 3.8 2.1 1.7 0.8 1 0.9 2.1 0 2 0.4 2.3-0.5 1.8-2.1 2.6-0.3 1.2 0 1.6 0.6 4.1 0 1.5-1.1 2.6-1.9 2.6-2.1 1.1-2.6-3.2-2.7-2.2-3-1.6-11.2-3.5-4.5-0.2-1.8-0.8-3.5-2.4-1.8-2.1-3.6-5.3-1.7-1.3-1.1 0.3-1.7 1.9-1 0.4-1.2-0.3-1.9-1.8-1-0.9-8.7-4-1.7-1.3-4-5.4-6.8-6.2-3.5-5.1-1.5-1.5-1.8-0.9-1.8-0.6-1.6-1-0.6-2.1 2.3-1.9 1.5-1.6 0.3-2.1-1.3-3.5-3.1-5.4-0.4-2.4 0.8-3.8 1.2-2.8 0.4-1.3 0.3-2.1-0.1-7.9 0.7-3 0-1.3-0.9-1.5-1.3-0.7-1.1 0.5-1 1.3-0.6 1.7-1.3 1.1-1.4 0.5-1.4-0.3-1.3-1.2-1-2.1-2.4-3.1-1.1-1.7-0.3-1.5-0.3-3.1-0.3-1.2-0.8-1-1.8-1.2-0.7-0.7-3.5-6.3-1.7-1.6-1.9-0.8-4.1-0.5-1.9-0.9-5.5-5.2-2-1.3-4.2-1.9-1.8-1.3-1.6-2.5-2.2-6.9-1.3-2.3-6.6-8.3 4.3-5.4 16.9-0.5 2.6-4.2 4.3 1 4.2 1.8 3.1 2.8 1 0.7 1.3 0.5 0.8 0 5.9-1.2 2.1-1.1 1.6-1.8 0.6-1.8z" id="county_romania_timis_14" name="Timis">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m759.6 111.6l-5.8 2.8-4.3-0.1-1.7-0.4-1.3 0.1-1.2 0.8-1.8 2-1.8 2-0.6 1.1-0.4 2.7-0.9 1.1-1.2 0.7-2.9 0-1.4-0.4-1.8-1-1.9-0.8-3-0.6-1.7-1.4-0.8-0.3-1.2 0.2-1.2 0.9-0.7 1.1-0.4 1.1 0.2 1.4 1.9 4.2 0.1 1.3-0.5 1.1-1.1 0.9-2.2 0.8-1.6 0.3-2-0.6-1.3-1-1.1-1.2-1.6-2-1.8-1.4-1-0.4-17.7 0-1.7-0.4-3.1-2.1-1.1-0.1-0.9 0.5-0.7 0.9-0.6 1.7-1.5 3.2-4.2 4.1-4.4-0.6-2.6-2.1-2.6-3.7-0.6-1.6-0.2-1.8 0.3-4.1-0.1-1.9-1.7-8.2-0.9-3.4-1.1-1.9-3.4-3.5-1.3-2.2-7-8.7-2-3.5-12-15.4-2.5-2.5-5.4-3-4.3-3.7-2.8-1.8-1.6-1.5-0.7-1.8-1.8-9.6 2.4 0 5-2.3 1.1-1.6 2.3-5.4 1.3-1.7 2.7-2.4 1.3-1.4 1.6-3.4 1.8-4.9 1.1-5-0.2-3.6 0.8-5.2 0.9 0.3 1.9 0.2 1.8-0.6 3.5-1.7 1.8-0.5 5-0.2 15.1-3.5 8.2-3.9 2.4-0.1 2.5 2.1 1.1 0.3 1.1-1.8 1.2 2.5 2.1 0.4 4.3-0.9 1.8 0.9 2.4 3.1 1.2-0.7 4.4 4.5 0.7 1.2 0.4 2.7 1-0.5 1.2-1.8 1-0.9 1.3 1.2 0.5 1.8 0.8 1.7 3.7 1.4 0 1.3-1.2 1.2-2.1 0.1 0 1 2.9 1.8 1.9 0.6 1.4-1.1 0.9 0.5 1.5 1.6-0.1 0.9-1.1 1.4-0.3 0.9 0.8 3.9 1.9 3 2.5 2.3 2.7 1.6 0 1.2-2.1 1 0.7 2.2 2.7-1.2 1.4 2 1 2.2 1.4-0.9 1.2 1.5 0.1 1.4-0.7 1.4-1.3 1.1 2.3 1.8 0.6 0.4 0 1-1.8 2.5 1.1 1.5 2.4 1.3 1.9 2.3-0.1 0.7 0.2 4.9-0.1 0.9 1.1 0.9 2.4 0.9 0.8 1.4-3.6 2.2 1.3 1.1 1.4 2.2 2.1 4.4 2.4 2 0.4 1.5-0.1 1.7 0.5 1.1 0 1-2.4 0.5 1 3.5 2.4 4.1 6.9 9.1 2.9 3.1 3.3 1.2 0.3 1.3z" id="county_romania_botosani_6" name="Botosani">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m271.2 269.1l3.2 2.8 14.8 0.4 2.4 0.7 1.5 1 1.5 3 1 1 2.2 1.8 0.5 0.8 0.4 2.8 0.7 1.1 0.7 0.1 0.8-0.4 1.2-1.7 0.8-0.8 4.3-2.7 1-0.3 2-0.1 4.8 1.6 1.8 0.2 1.3-0.2 1-0.6 1.1-1 3.8-2.9 10.6-2.1 1.8 0.5 1.5 0.8 5.9 4.3 5.2 2.2 2.3 0.4 1.9 0.1 3.4-0.3 1.2 0.4 0.7 0.9 0 1.2-1.7 2.8-0.5 1.2 0.1 1.3 0.8 1.5 2.2 1.5 1.7 0.6 1.5 0.1 0.7-0.5 0.2-0.9-0.4-2 0-1 0.6-0.7 1.2-0.3 5-0.1 1.2-0.2 2-1 1.1-0.3 4.7 0.2 1.6-0.2 3.9-1 2.1 0.3 6.6 2.1 1.4 0 1.7-0.8 0.8-1.2 3.3 0.2 1.6 1.1 1.2 1.4 0.6 1.8-0.4 1.4-0.8 1.2-1.3 1.2-0.9 1.1-0.5 1.8 0.5 1.2 3.3 3.6 1 0.7 5.8 2.5 2.2 1.8 1.1 2.3 0.8 4.8 1.6 2.2 1.7 1.1 0.5 10.1-0.1 1.6-0.4 1.2-1.2 1.2-2.1 1.3-1.6 1.8-2.3 2.1-8.8 9.5-1.2 1.9-0.7 2.2-0.5 2.8-0.4 0.8-0.9 0.4-0.9-0.2-0.9-0.7-1.9-1.9-1.9-1.2-1.2 0-7.3 1.6-1.3 0.9-0.7 1.4-0.1 2.5 0.5 1.5 0.7 1.4 0.4 1.4-0.3 1.8-1.6 2-1.4 1.2-1.6 1-1.3 0.2-2.5-0.1-1.7 0.5-1-0.2-2.9-1.7-2-0.2-0.8 0.4-0.5 0.8 0.5 5 0.7 1 1 0.5 2.7 0.7 1.2 0.7 0.7 0.8-0.2 1.3-3.6 5.6-1.4 2.7-1.4 1.4-1 0.1-2.4-0.3-1.5 0.9-1.5 2-0.7 1.6-2.4 7.7-0.3 2.7 0.7 3.9 0.8 2.2 0.7 2.5 0.2 2.8-0.9 4.5-0.8 2.6-0.3 2.4 1.1 3.3 2.6 4.5 0.4 2 0.1 1.4-2.9 4.3-4.4 0.7 0.2-5-0.6-2.6-0.7-0.9-5.2-5.1-2.3-2.6-1.8-1.3-4.1-2-2.5-2-1.3-1.9-1.1-2.6-0.4-1.5-0.1-1.3 1-3.3-0.4-1.4-2.2-2.7-0.6-1.1-0.2-1.3 0.3-2.8-0.4-2.3-0.9-3.2-2.3-6.3-2.7-11.7-0.9-1.7-2.8-3.4-1.7-2.8-4.1-10.7-1.2-1-2.1-0.3-2.5-1.2-10.2-13.5-1-2.3 0.6-1.1 0.7-0.2 1.5 0.5 0.5-0.6-0.1-1.5-1.7-4.5-1.3-2.4-1.4-1.9-1.1-0.8-1.3-0.6-4.8-0.7-1.2-0.5-0.7-0.9-1.8-6.7-1.7-4.4-1.1-2.2-1-1.3-1-0.3-9.7 1-0.9-0.3-5.2-3.3-5.1-5.3-1.9-8.1-0.2-9.4 0.6-3.6 1.1-3.9 1.4-2.8 1.3-1.6 1.2-0.7 2.7-0.3 2.4 0.6 1.6 1z" id="county_romania_alba_20" name="Alba">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m498.4 110.1l1.2 1.4 8.3 6.8 2.8 3.3 1.2 2.4 0.6 1.6 0.1 1.8-0.7 2.2-1.2 1.3-2.7 1.5-0.8 0.7-0.2 0.8 1.4 2.3 0.1 1-0.3 1.4-1.1 0.7-1.6-0.2-1 0.1-0.7 0.6-0.5 0.9 0.2 1.1 0.9 1.7 2.1 2.7 0.8 1.3 1.3 2.8 0.4 1.5 0.1 1.8-0.8 2.7-1.4 2.4-0.8 2.1-0.1 1.6 1.2 1.6 1.2 0.3 2-0.2 0.5 0.3 0.2 0.9-0.4 2.4-0.4 1.6-0.6 4.7 0.6 10.8-8.2 3.3-6.9 1.3-2.8 1-3.4 2.3-5.4 4.7-0.8 1.1-1.4 3.8-0.6 1.2-2.6 4-0.9 1.9-0.6 2.1-0.8 4.4-0.7 1.2-1.3 0.6-1.9-0.2-1.7-0.8-3.1-2-1.3-0.5-1.2-0.3-1.3 0.2-1.9 0.9-2.6 2.1-2.2 2.9-1 1.8-1 2.9-0.6 1.1-2 2.5-0.7 1.3-0.8 4.9-0.6 1.2-1.4 1-2.1 0.7-3.7 0.5-4.8 2-1.7 0.3-2.3-0.1-7-1.8-8.7-4.3 2.3-4.7 0.6-1.9 0.7-6.4 0.6-2.2 0.2-2.5-0.5-2.1-2-2.1-3.7-2.1-1.3-1-0.7-1.3-0.2-1.8 0.4-2.3-0.2-1-1.8-1.3-0.9-1 0.1-2.3 0.3-2 0.5-1.9 0.1-2-0.4-1.9-1.5-2.2-1.5-1.1-2.5-1.2-1.1-0.7-2.2-2.1-1.6-1-6-1.7-1.3-0.7-1.2-1.3-0.7-1.6-0.5-1.9-1.1-8.6-0.4-1.6 0-1.9 0.6-1.5 3.4-2.9 1.4-1.6 1.1-1.8 1-4.4 4.1-4 6.4-9.5 0.7-1.5 0.4-1.5 0.4-3.3 0.5-0.8 1.5-0.7 8.9-1.9 2.2-1.4 1-1.3 2.1-4.8 0.8-1.4 1.5-1.2 7.3-3.3 1.9-0.5 19.8 4.7 19.8-0.7 2.2-0.7 1.7-1.2 4.3-4.1 3.6-1.3 9 0.1z" id="county_romania_bistrita-nasaud_3" name="Bistrita-Nasaud">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m398.3 150.4l-1 4.4-1.1 1.8-1.4 1.6-3.4 2.9-0.6 1.5 0 1.9 0.4 1.6 1.1 8.6 0.5 1.9 0.7 1.6 1.2 1.3 1.3 0.7 6 1.7 1.6 1 2.2 2.1 1.1 0.7 2.5 1.2 1.5 1.1 1.5 2.2 0.4 1.9-0.1 2-0.5 1.9-0.3 2-0.1 2.3 0.9 1 1.8 1.3 0.2 1-0.4 2.3 0.2 1.8 0.7 1.3 1.3 1 3.7 2.1 2 2.1 0.5 2.1-0.2 2.5-0.6 2.2-0.7 6.4-0.6 1.9-2.3 4.7-3.2 0.4-2.1 2.5-4 7.6-0.4 1.7 0 1.4 1.5 2.3 0.7 1.5 0.2 2.6-0.7 1.7-1.1 1-3.8 0.7-1.3 0.6-1.8 1.7-1 2.2-1.1 3.2-0.2 1.8 0.2 1.4 0.9 2.5-0.1 1.7-0.7 0.9-2.3 1.4-1.4 1.5-2.1 3.2-0.4 1.9 0.5 1.7 2.4 2.4-0.8 1.2-1.7 0.8-1.4 0-6.6-2.1-2.1-0.3-3.9 1-1.6 0.2-4.7-0.2-1.1 0.3-2 1-1.2 0.2-5 0.1-1.2 0.3-0.6 0.7 0 1 0.4 2-0.2 0.9-0.7 0.5-1.5-0.1-1.7-0.6-2.2-1.5-0.8-1.5-0.1-1.3 0.5-1.2 1.7-2.8 0-1.2-0.7-0.9-1.2-0.4-3.4 0.3-1.9-0.1-2.3-0.4-5.2-2.2-5.9-4.3-1.5-0.8-1.8-0.5-10.6 2.1-3.8 2.9-1.1 1-1 0.6-1.3 0.2-1.8-0.2-4.8-1.6-2 0.1-1 0.3-4.3 2.7-0.8 0.8-1.2 1.7-0.8 0.4-0.7-0.1-0.7-1.1-0.4-2.8-0.5-0.8-2.2-1.8-1-1-1.5-3-1.5-1-2.4-0.7-14.8-0.4-3.2-2.8 0-5.8 0.5-3.8-0.5-1.3-1-0.9-2.1-1.7-1-1.9-1.8-7.2-1.4-3.2-1.4-2.2-4.2-3.8-1.3-1.8-0.3-1.4 0.2-1.3 0.6-1.1 0.9-1.1 1.2-0.8 3.9-1.8 0.9-0.7 1.3-2.3 1.1-3.5 0.4-2.9-0.5-1.6-1.4-1.1-5.3-2.1-0.9-0.7-0.2-1 0.4-1.5 2.3-4.5 0.8-2.1 1.2 0.4 12.6 0.3 1.9 0.3 1.1 0.6 1.1 0.9 0.8 1.5 1.8 4.6 1.1 2 1.1 1.3 1.3 0.5 3.9 0.4 1 0.4 0.8 0.6 1 1.6 0.8 0.6 1 0 2.3-0.4 1.3-0.1 3.9 1.1 1.8-0.3 2.1-0.8 1.5-0.3 1.2 0.2 2.4 0.9 1.3-0.1 1-0.6 0.3-1.3-0.2-2.5 0.3-1.1 1.5-2.8 0.7-1.1 1.2-0.2 0.9 0.6 0.7 1.2 0.7 2.3 0.6 0.7 0.9 0.3 2.2-0.8 1.1 0 0.9 0.3 2.1 2.4 1-0.1 1-0.7 0.9-2.2 0-1.4-0.5-1.4-2-2.3-0.4-1.4 0.5-1 1.5-0.7 1.1 0 1.8 0.7 0.6-0.3 0.4-2 0.5-0.9 1.2-0.7 1.3-0.2 2.3 0.1 1.4-0.2 1.6-0.4 1-0.9 0.4-1.5-0.1-1.2-1-3.4-0.5-1.1-1.4-1.8-0.4-1 0.1-1 0.6-0.8 2.2-0.6 0.6-0.5 0.9-2.6 0.8-1.4 0.4-2.6 0.3-1 4.9-2 2.4-1.6 1.3-0.4 1.2 0.1 1.1 0.5 1 0 0.9-0.7 0.7-1.9 1-1.9 1.6-1.9 3.7-2 3.5-1.1 6.2-0.7 1.6-0.6 0.9-1.4-0.1-3 0.5-3 0.5-1.8 0.7-1.4 0.1-2.2-1.6-2.5 3-2.6 17.5 1.4z" id="county_romania_cluj_19" name="Cluj">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m353.6 437.7l-0.3 2.3 0.2 1.4 1.5 4.5 0.3 1.7-1.5 4.4-6.6 2.2-6 3.3-1.8 1.5-1.1 0.4-0.9-0.1-1.9-1.4-1.5-0.8-1.6-0.4-2.7-0.1-2 0.6-3.6 1.8-4.7 1-1.7 0.6-5.7 3.5-1.7 0.4-1.3 0-3.2-1.5-1.6-0.2-2.6 0.2-6.8 2.9-1.6 0.1-1.1-0.4-1.7-1.6-0.9-0.4-4.6-0.3-1.2-0.4-1-0.8-0.7-1-1.4-0.7-1.1 0.2-22.4 8.2 0-2.9 0.8-2.4 2.3-3.4 0.6-1.3 0.2-2.3-0.3-1.4-0.6-1.6-1.4-2-0.8-1.4-0.2-1.4 0.4-0.9 1.3-2 0.5-1.5 0.2-1.4-0.4-2.5-0.1-3.3-0.3-1.8-1.3-4.4-1-2.1-1.6-2.1-3.5-2.7-2-1.3-1.3-1.1-0.7-1.2-0.7-0.7-1.1-0.3-1.8 0.2-0.6-0.4-0.2-1.2 0.2-2.2-0.5-0.7-2.8-0.6-1.5-0.5-1.4-1-3.2-4.3-0.9-3.5 0.9-0.4 0.8-1.2 0.8-3.9 0.5-1.4 0.8-1.6 1.2-1.3 4.3-3.9 0.8-1.2 0.3-1.1-0.3-1-1.1-0.7-1.6 0.2-1.3 0.7-0.9-0.5-0.9-1.3-1.9-4.9-1.1-1.9-0.8-1-2.9-2.3-1-1.5-1.2-4.1-0.3-2.8-1-2.5-3.6-3.8 4.9-6.4 1.2-2.6 0.7-2.5 0.1-1.9-0.3-1.5-1.1-3.6 0.3-1.5 0.6-1.9 1.5-3.1 0.5-2.3 0.2-2.2-0.3-5 0.5-1.7 0.9-1.5 2.5-2.2 1.4-0.9 1.9-0.2 5.9 1.4 2 0.2 2.5-0.4 2-1.1 2.2-2 2.4-4 1-2.5 1.8-4 6.9-5.2 5.2 3.3 0.9 0.3 9.7-1 1 0.3 1 1.3 1.1 2.2 1.7 4.4 1.8 6.7 0.7 0.9 1.2 0.5 4.8 0.7 1.3 0.6 1.1 0.8 1.4 1.9 1.3 2.4 1.7 4.5 0.1 1.5-0.5 0.6-1.5-0.5-0.7 0.2-0.6 1.1 1 2.3 10.2 13.5 2.5 1.2 2.1 0.3 1.2 1 4.1 10.7 1.7 2.8 2.8 3.4 0.9 1.7 2.7 11.7 2.3 6.3 0.9 3.2 0.4 2.3-0.3 2.8 0.2 1.3 0.6 1.1 2.2 2.7 0.4 1.4-1 3.3 0.1 1.3 0.4 1.5 1.1 2.6 1.3 1.9 2.5 2 4.1 2 1.8 1.3 2.3 2.6 5.2 5.1 0.7 0.9 0.6 2.6-0.2 5z" id="county_romania_hunedoara_10" name="Hunedoara">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m497 89.2l1.3 4.3 2.5 4 4.5 4.8 0.9 1.4 0.2 1.7-0.8 1.5-0.9 1-6.3 2.2-9-0.1-3.6 1.3-4.3 4.1-1.7 1.2-2.2 0.7-19.8 0.7-19.8-4.7-1.9 0.5-7.3 3.3-1.5 1.2-0.8 1.4-2.1 4.8-1 1.3-2.2 1.4-8.9 1.9-1.5 0.7-0.5 0.8-0.4 3.3-0.4 1.5-0.7 1.5-6.4 9.5-4.1 4-17.5-1.4-3 2.6-2.7 1.4-1.2 0.4-1.5 0-1.3-0.3-1.4-0.9-1.8-1.8-1.3-0.8-2.1-1-1-1.2-0.6-1.2-0.2-1.2-1.4-0.9-2.5-0.4-16.2 0.7-2.4-0.4-13.2-4.5-4.2-2.2-2.1-0.8-2.8-0.5-11.1 0-5.3 1.9-9.7-1.8-1.8-1-1.4-1.3-0.2-1.1 0.3-1 0.8-0.9 3.9-2.6 1.8-2 0.6-1.1 1.4-4.7 0.9-1.5 6.2-7.9 1-0.6 1 0 0.8 0.5 1.8 2.3 1.2 1.1 4.8 3.2 1.6 0.7 1.8 0 2-1.2 1.6-1.8 1.9-2.8 2.3-2.7 1.8-4 0.7-2 1.3-3 0.4-1.9-0.2-2.1-0.8-1.2-1.2-1.2-1.6-0.9-7.2-2-1.2-0.8-0.7-1.1-0.1-1.3 0.4-2.2 1.2-1.3 1.8-0.8 7.5 0.1 4.1 1.3 1.6 0.8 1.8 0.5 1.8 0.1 2.4-0.5 2.6-1.3 15-11.5 0.9-1.7 0.1-1.7-1.1-2-1.4-1.4-3.5-2.2-1.5-1.2-1-1.2-5.1-7.2-1.7-6.3 0.9-1 1-1.9 1.2-1 1.1-0.3 4-0.7 2 0.6 6.8 0.8 4.3 1.5 2.5 0.3 7.3-0.4 1.7 0.9 5.5 5.1 0.7 2.4 2.3 0 10.6-4.4 3.3 0.2 1.8 1.3 5.2 1.4 2.1 1 3.8 3.7 2.1 0.2 6.2 2.4 2.3 0.1 7-3.6 5.2-0.1 4.2-3.6 2.4-1.4 2 0 6.1 1.4 5.9-0.1 2.1 0.6 4.9 2.9 2.7 2.1 1.6 2.1 0.7 2.5 0.1 2.1 0.5 1.9 1.9 2.2 3.5 2.2 8.6 3.4 1.5 1.3 1.3 1.9 3.6 6.4 2.4 3.8 2 1.4 4.9-0.9z" id="county_romania_maramures_35" name="maramures">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m524.6 188.9l0.7 5.9 1.5 3 2.4 2.4 1 1.4 0.7 1.9 0.4 2.4 0.3 9.4-1.7 10.4 0.6 3.5 2.3 6.4 0.8 2.5-0.1 2.1-0.6 3.4-0.3 2.9 0.3 4.1-0.2 1.5-0.9 1.5-4.6 3.8-1.5 1.7-3.8 5.9-1.3 1.4-1.8 1.2-4.5 1.9-1.4 0.9-6.2 6.5-0.8 1.4-0.5 1.7-0.5 5.8-0.5 1.2-1.1 1.1-6.6 2.7-0.8 1 0 1 2.9 2.1 0.8 1 0 1.6-0.6 1.1-1.2 0.6-2.6 0.6-2.6 2-2.3 1-0.8 0.8-0.1 1 0.6 1.3 8.4 10.2 1.9 1.8 2 0.7 5-0.2 1.7 0.7 5.6 4.3 8.3 8.9-5.3 1.2-9.7 0.4-1.5 0.5-1.5 0.8-2.3 2-1.1 1.2-1 2.9 0.6 3.2-5-2.9-1.1-1-1.7-1.2-1.4-0.6-2.3-0.3-1.5 0.2-3.4 0.9-3.3 0.2-0.8 0.3-1.2 1-0.8 0.4-2.4 0-1.2 0.4-1.4 0-1.4-0.7-1.9-2.3-1.1-3.4-0.6-3.9-0.5-8.8-0.7-4.1 0.1-1.4 0.5-1.3 0.7-1 0.3-0.9-0.4-0.5-0.9-0.1-2.1 1.1-1.5 0.4-2-0.2-2.8-0.9-1.4 0-1.3 0.9-1.5 1.8-1.3 0.7-2.1 0.1-7.4-0.5-3.3 0.7-1.7 0.7-2.1 0.5-2-0.3-5.8-2.4-3.8 0.8-7.8 0-1.7-1.1-1.6-2.2-0.8-4.8-1.1-2.3-2.2-1.8-5.8-2.5-1-0.7-3.3-3.6-0.5-1.2 0.5-1.8 0.9-1.1 1.3-1.2 0.8-1.2 0.4-1.4-0.6-1.8-1.2-1.4-1.6-1.1-3.3-0.2-2.4-2.4-0.5-1.7 0.4-1.9 2.1-3.2 1.4-1.5 2.3-1.4 0.7-0.9 0.1-1.7-0.9-2.5-0.2-1.4 0.2-1.8 1.1-3.2 1-2.2 1.8-1.7 1.3-0.6 3.8-0.7 1.1-1 0.7-1.7-0.2-2.6-0.7-1.5-1.5-2.3 0-1.4 0.4-1.7 4-7.6 2.1-2.5 3.2-0.4 8.7 4.3 7 1.8 2.3 0.1 1.7-0.3 4.8-2 3.7-0.5 2.1-0.7 1.4-1 0.6-1.2 0.8-4.9 0.7-1.3 2-2.5 0.6-1.1 1-2.9 1-1.8 2.2-2.9 2.6-2.1 1.9-0.9 1.3-0.2 1.2 0.3 1.3 0.5 3.1 2 1.7 0.8 1.9 0.2 1.3-0.6 0.7-1.2 0.8-4.4 0.6-2.1 0.9-1.9 2.6-4 0.6-1.2 1.4-3.8 0.8-1.1 5.4-4.7 3.4-2.3 2.8-1 6.9-1.3 8.2-3.3 9.5 1.8 4.8 2.3z" id="county_romania_mures_39" name="mures">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m377.8 151.6l1.6 2.5-0.1 2.2-0.7 1.4-0.5 1.8-0.5 3 0.1 3-0.9 1.4-1.6 0.6-6.2 0.7-3.5 1.1-3.7 2-1.6 1.9-1 1.9-0.7 1.9-0.9 0.7-1 0-1.1-0.5-1.2-0.1-1.3 0.4-2.4 1.6-4.9 2-0.3 1-0.4 2.6-0.8 1.4-0.9 2.6-0.6 0.5-2.2 0.6-0.6 0.8-0.1 1 0.4 1 1.4 1.8 0.5 1.1 1 3.4 0.1 1.2-0.4 1.5-1 0.9-1.6 0.4-1.4 0.2-2.3-0.1-1.3 0.2-1.2 0.7-0.5 0.9-0.4 2-0.6 0.3-1.8-0.7-1.1 0-1.5 0.7-0.5 1 0.4 1.4 2 2.3 0.5 1.4 0 1.4-0.9 2.2-1 0.7-1 0.1-2.1-2.4-0.9-0.3-1.1 0-2.2 0.8-0.9-0.3-0.6-0.7-0.7-2.3-0.7-1.2-0.9-0.6-1.2 0.2-0.7 1.1-1.5 2.8-0.3 1.1 0.2 2.5-0.3 1.3-1 0.6-1.3 0.1-2.4-0.9-1.2-0.2-1.5 0.3-2.1 0.8-1.8 0.3-3.9-1.1-1.3 0.1-2.3 0.4-1 0-0.8-0.6-1-1.6-0.8-0.6-1-0.4-3.9-0.4-1.3-0.5-1.1-1.3-1.1-2-1.8-4.6-0.8-1.5-1.1-0.9-1.1-0.6-1.9-0.3-12.6-0.3-1.2-0.4-1.8-3.1-4.1-4.1-6.6-3.8-1.1-1.3-0.8-1.2-0.4-1.3-3.1-6.1-4.5-7.1-1.3-3.3-0.1-2.1 1.8-1.9 2.7-4 0.6-1.2 0.3-1.2 0.3-2.6 0.5-1.3 1.2-1.9 0.6-1.3 1.1-4.3 4.5-3.7 13.9-6.7 2.9-0.8 2.1 0 11.6 3.3 1.4 0.9 2.5 2.5 1.6 0.9 4.4 1.1 1.1 0.6 1.8 2.1 1.3 0.9 0.6-0.3 0.9-1.8 0.7-0.9 4.1-3.4 0.5-1 0.2-1.2-0.2-1.2-0.9-2.1-1.2-2.2 5.3-1.9 11.1 0 2.8 0.5 2.1 0.8 4.2 2.2 13.2 4.5 2.4 0.4 16.2-0.7 2.5 0.4 1.4 0.9 0.2 1.2 0.6 1.2 1 1.2 2.1 1 1.3 0.8 1.8 1.8 1.4 0.9 1.3 0.3 1.5 0 1.2-0.4 2.7-1.4z" id="county_romania_salaj_37" name="Salaj">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m343.2 48.5l1.7 6.3 5.1 7.2 1 1.2 1.5 1.2 3.5 2.2 1.4 1.4 1.1 2-0.1 1.7-0.9 1.7-15 11.5-2.6 1.3-2.4 0.5-1.8-0.1-1.8-0.5-1.6-0.8-4.1-1.3-7.5-0.1-1.8 0.8-1.2 1.3-0.4 2.2 0.1 1.3 0.7 1.1 1.2 0.8 7.2 2 1.6 0.9 1.2 1.2 0.8 1.2 0.2 2.1-0.4 1.9-1.3 3-0.7 2-1.8 4-2.3 2.7-1.9 2.8-1.6 1.8-2 1.2-1.8 0-1.6-0.7-4.8-3.2-1.2-1.1-1.8-2.3-0.8-0.5-1 0-1 0.6-6.2 7.9-0.9 1.5-1.4 4.7-0.6 1.1-1.8 2-3.9 2.6-0.8 0.9-0.3 1 0.2 1.1 1.4 1.3 1.8 1 9.7 1.8 1.2 2.2 0.9 2.1 0.2 1.2-0.2 1.2-0.5 1-4.1 3.4-0.7 0.9-0.9 1.8-0.6 0.3-1.3-0.9-1.8-2.1-1.1-0.6-4.4-1.1-1.6-0.9-2.5-2.5-1.4-0.9-11.6-3.3-2.1 0-2.9 0.8-13.9 6.7-4.5 3.7-1-2.8-0.7-3.9-1-1.8-1.3-1.1-3.1-1.8-1.7-1.5-1.5-1-1.1-0.3-1.1 0.3-2.5 1.4-1 0.2-1.6-0.3-1.9 0-1.9 0.5-1.8-0.4-1-0.7-0.6-1.2-1.2-3.3-0.9-1.8-3-4.7-1.7-3.1-1.6-2.2-4.3-3.3-1.2-1.2-0.9-1.5-1.3-3.1-2.7-4.1 0.3-2.2 0.3-1.1 2.6-3.7 0.3-1.4 0.4-2.8 0.4-1.2 0.8-1 1.7-1.3 0.8-0.8 2.4-3.5 1.2-1.3 1.9-1 1.9-0.7 1.3-0.2 4.9 0.8 1.5-0.1 1.4-0.6 1.2-1.2 1.8-6.2 3.2-0.7 7.8 4.1 3.7 0.6 4-0.6 3.8-1.7 3.1-2.7 2.6-3.4 1.3-1 2.2-1 2.2-0.3 0.7-0.4 0.9-1.7-0.2-1.2-0.6-1 0-1.4 1.1-2.1 1.7-1.2 4.2-1.5 1.8-1.6 2.6-5 1.8-2 2.1-0.7 1.9-1.3 0.9-2.1-0.9-3.2 0.9-1.9 1.6-0.1 1.8 0.9 3.3 2.1 1.7 0.6 1.7-0.3 4.5-3.6 1.5-2.7 2.3-7.4 2.1-3.2 2.2-1 2.3 0.3 7.4 2.6 1.8 1.3 4.4 5.2 5 4.4 2.4 2.8 0.7 0.3 1.6 0 1.5-0.3 6.8 3.4 2.2-0.1 0.4-0.4z" id="county_romania_satumare_34" name="Satu mare">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m535.7 446.6l1.6 2.9 0.6 1.4 0.4 1.5 0 1.8-0.5 2.5-0.7 1.6-1.6 2.7-0.8 1.6-0.6 2-0.5 2.4-0.2 3.5-0.8 3.1-1 2.4-0.8 1.6-1 1.1-0.9 0.7-1 0.2-0.9-0.3-0.8-0.7-1.2-2-0.7-0.7-0.7-0.1-1.1 1.1-1.1 2.7-0.5 0.9-0.5 2-0.1 2.9 1.1 5.9 1.5 2.5 3.3 3.8 1.2 2.9 0.5 1.9 0 1.4-0.5 1.3-3 3.5-0.6 1.3-0.2 2.5 0.2 2.9 1.7 8.1 0.3 2.3-0.9 12.2-0.2 1.6-0.5 1.3-1.6 2.9-0.3 1.7 0.2 1.7 1.5 1.8 2.1 2-0.4 0.7-2.4 1.5-0.4 1.2 0.3 1.8 2.7 5.3 1.1 1.5 2.9 2.9 0.9 0.6 1.1 0.4 2.4 0.4 1.8 1.1 0.4 1.1-0.1 1.3-1.9 3.6-0.6 1.5 0.1 2 1 3.7-0.3 2.7-2.3 4.2-3.4-1.7-1.7-0.5-1.8-0.2-2.4 0-4.8 0.9-2.2 1.3-2.3 2.3-1.5 0.6-2.6 0.7-3.3-0.5-1 0.2-1.1 0.8-1.3 1.6-1.9 1.3-1.6 0.4-1.9 0.1-5.7-1.5-4.5 1.2-2.3-1.6-5.1-0.4-1-0.7-0.7-1.2-0.6-2.3-0.4-6.5-1.3-4.9 0.1-2.6 0.6-1.9 0.1-1-1-6.7-1.1-3.8-0.2-1.8-0.5-3.3-0.8-2.3-2.6-4.3-0.3-1.4 0.9-1.6 0.9-0.7 1.9-0.8 0.6-0.9-0.5-0.8-0.8-0.6-1.9-0.2-1.6 0.4-1.7 0-1.7-0.5-2.1-2.1-1.2-1.6-4.7-9.9-0.8-2-0.1-1.7 0.3-2.7-0.3-1.2-0.5-0.8-1-0.2-0.9 0.6-1.2 1.7-1.4 3.6-0.7 0.9-4.3 3.6-0.9 0.4-1-0.3-0.7-0.6-1.2-3.6 2.7-2 4.1-5 0.7-1.3 0.3-1.2-0.1-1.3-0.7-1.8-1.6-2.6-0.1-1.2 0.8-3.1 1.2-9.1 3.2-14.1-0.2-3.5-0.6-2.2-0.8-1.8-0.7-1-1.6-1.9-0.5-1.5-0.2-1.9 0.4-3.1 0.3-3.7-1-3.6-0.1-1.6 0.2-1.4 0.6-1.2 1.7-2.2 0.3-1.4-0.2-1.8-2.2-9.4 0-1.7 0.3-1.6 3.1-6 1.4-6.6-1.3-4.8 19.2-5.6 6.7-0.6 1.6 0.4 1.4 0.8 1.7 1.8 1.5 2.5 0.8 0.5 1.3-0.4 1.9-1.2 1.2-0.5 1.3-0.1 3.1 0.2 6.5-0.6 11.8 1.6 2.5 1.5 4 3.5 1.8 1.2 1.7 0.6 1.7 1.2 1.5 2 5.9 13.1 3.5 4.2z" id="county_romania_arges_22" name="Arges">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m501.1 344.5l-2.2 3-6.3 5.2-5.9 3.2-0.6 0.7-0.3 1 0.7 2 0.1 1.1-0.3 1.5-0.9 0.5-0.7-0.1-1.7-0.7-1.2 0.2-1.8 1-1.6 1.7-4.7 2.5-0.7 0.9-0.1 1.2 0.4 0.9 1.5 1.7 0.4 1.2-0.2 2.9 0.3 1 0.6 0.8 1.5 0.9 0.3 1-0.6 1.6-1.4 1.1-1.8 0.8-2 0.5-3.8 1.6-1.6 1.3-0.5 1.3 0.1 1.5 3.3 7 1.1 3.7 0.9 5 0.4 4.3 0.3 2.3 0.2 3.1-19.2 5.6-21.6-0.2-14.7 3.4-1.5 0.5-10.6 6.1-2 0.1-1.6-0.3-3.7-1.7-4-0.5-17.6 0.7-1.7 0.5-1.7 2-1 0.8-1.3 0.3-1.8 0.1-1.4 0.5-3.3 2.6-2.1 1.3-1.3 0.5-2.2-0.2 2.9-4.3-0.1-1.4-0.4-2-2.6-4.5-1.1-3.3 0.3-2.4 0.8-2.6 0.9-4.5-0.2-2.8-0.7-2.5-0.8-2.2-0.7-3.9 0.3-2.7 2.4-7.7 0.7-1.6 1.5-2 1.5-0.9 2.4 0.3 1-0.1 1.4-1.4 1.4-2.7 3.6-5.6 0.2-1.3-0.7-0.8-1.2-0.7-2.7-0.7-1-0.5-0.7-1-0.5-5 0.5-0.8 0.8-0.4 2 0.2 2.9 1.7 1 0.2 1.7-0.5 2.5 0.1 1.3-0.2 1.6-1 1.4-1.2 1.6-2 0.3-1.8-0.4-1.4-0.7-1.4-0.5-1.5 0.1-2.5 0.7-1.4 1.3-0.9 7.3-1.6 1.2 0 1.9 1.2 1.9 1.9 0.9 0.7 0.9 0.2 0.9-0.4 0.4-0.8 0.5-2.8 0.7-2.2 1.2-1.9 8.8-9.5 2.3-2.1 1.6-1.8 2.1-1.3 1.2-1.2 0.4-1.2 0.1-1.6-0.5-10.1 7.8 0 3.8-0.8 5.8 2.4 2 0.3 2.1-0.5 1.7-0.7 3.3-0.7 7.4 0.5 2.1-0.1 1.3-0.7 1.5-1.8 1.3-0.9 1.4 0 2.8 0.9 2 0.2 1.5-0.4 2.1-1.1 0.9 0.1 0.4 0.5-0.3 0.9-0.7 1-0.5 1.3-0.1 1.4 0.7 4.1 0.5 8.8 0.6 3.9 1.1 3.4 1.9 2.3 1.4 0.7 1.4 0 1.2-0.4 2.4 0 0.8-0.4 1.2-1 0.8-0.3 3.3-0.2 3.4-0.9 1.5-0.2 2.3 0.3 1.4 0.6 1.7 1.2 1.1 1 5 2.9z" id="county_romania_sibiu_40" name="Sibiu">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m453.1 420.5l1.3 4.8-1.4 6.6-3.1 6-0.3 1.6 0 1.7 2.2 9.4 0.2 1.8-0.3 1.4-1.7 2.2-0.6 1.2-0.2 1.4 0.1 1.6 1 3.6-0.3 3.7-0.4 3.1 0.2 1.9 0.5 1.5 1.6 1.9 0.7 1 0.8 1.8 0.6 2.2 0.2 3.5-3.2 14.1-1.2 9.1-0.8 3.1 0.1 1.2 1.6 2.6 0.7 1.8 0.1 1.3-0.3 1.2-0.7 1.3-4.1 5-2.7 2 0.2-6.9-1.3-2.3-1.1-0.3-1 0.1-1.1 0.6-1.6 2-3 5.3-0.9 3.5-0.2 2-0.8 3.3-0.6 1.6-0.9 1.9-1.7 2.6-2 2.1 0 1.3 0.5 3.2 1.4 6.4-0.4 2.5 1.2 1.9 1.2 2.8 0.8 3.4 0.3 2.7-1.6 1.4-1-0.5-0.8-1-0.8-2 0.2-2.5-0.9-0.9-1 0-1.3 1-0.5 1.3-0.7 3.1-0.8 1.1-3.1 1.8-2.1 0.2-2.5-0.7-1.4 0-1.1 0.2-1.3 0.7-1.1 1.2-3.3 4.8-1.3 0.6-0.8-0.1-0.8-0.7-1-1.7-0.8-1.1-2.1-1.2-1.2-0.2-2.5 0.6-4 2.5-3-3.1-1.7-2.6-1.3-2.3-2.1-7.3-1-1.3-1-0.9-3.5-2.2-1.6-0.7-1.9-1.1-0.8-1.1-0.5-1.3-0.1-1.2-0.8-3.5 1.5-8.6 0.4-7.9-1.5-17.1 0.2-2.9 0.5-2 1.2-3.3 2.9-11.7 0.1-2-0.4-5.1 0.4-2.2 1.7-5 0.2-1.8-0.5-2.1-1.1-2.7-0.8-4.1 0.2-2.9 1.4-5.8-0.5-1.7-0.8-1.2-1.8-1.4-1.8-0.4-6.5 0.7-1.9-0.1-1.6-0.3-2-1.1-1.2-0.4-5.1 0-3.2-1 1.5-4.4-0.3-1.7-1.5-4.5-0.2-1.4 0.3-2.3 4.4-0.7 2.2 0.2 1.3-0.5 2.1-1.3 3.3-2.6 1.4-0.5 1.8-0.1 1.3-0.3 1-0.8 1.7-2 1.7-0.5 17.6-0.7 4 0.5 3.7 1.7 1.6 0.3 2-0.1 10.6-6.1 1.5-0.5 14.7-3.4 21.6 0.2z" id="county_romania_valcea_26" name="Vâlcea">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m551 338l-0.7 2.1 0.4 1.4 0.9 1.6 3.4 2.6 1.4 1.9 0.9-0.9 2.1-0.1 2.2 0.6 1.3 1 0.4 2.7-1.3 5.1 0.3 2.6-0.7 2.1-0.2 3.3 0.1 9.9 0.4 2.3 1 2.1 7.4 5 3.3 3.4 0.3 5.1 1.9 0 1.6-1.1 1.5 1 1.5-0.1 1.5-0.6 1.6-0.3 0.7-1 1.6 2.5 1.9 1 9.8 3.7 2.6 1.9 3.1 3.2 1.7 2.6 1.6 3.3 1.1 0.9 2 0.9 2 1.1 2.1 1.8 3.2 5.1 2.3 2.4-5.8 12.4-4.9-1-5.6-3.3-2.3-0.3-1.4 0.6-0.9 1.2-0.7 3.4-0.5 1.9-1 1.8-2 1.8-1.8 0.5-1.5-0.3-0.9-0.8-1.4-2.4-0.7-1-1.3-0.8-1.4 0.1-2.9 1-1.5-0.3-4.5-1.7-4.5-0.9-2.4 0.1-17.8 3.2-1.4 0.5-1.7 1.2-2.1 1-0.8 0.9-0.9 1.9-0.5 1.6-2 2.5-6.4 1.7-3.5-4.2-5.9-13.1-1.5-2-1.7-1.2-1.7-0.6-1.8-1.2-4-3.5-2.5-1.5-11.8-1.6-6.5 0.6-3.1-0.2-1.3 0.1-1.2 0.5-1.9 1.2-1.3 0.4-0.8-0.5-1.5-2.5-1.7-1.8-1.4-0.8-1.6-0.4-6.7 0.6-0.2-3.1-0.3-2.3-0.4-4.3-0.9-5-1.1-3.7-3.3-7-0.1-1.5 0.5-1.3 1.6-1.3 3.8-1.6 2-0.5 1.8-0.8 1.4-1.1 0.6-1.6-0.3-1-1.5-0.9-0.6-0.8-0.3-1 0.2-2.9-0.4-1.2-1.5-1.7-0.4-0.9 0.1-1.2 0.7-0.9 4.7-2.5 1.6-1.7 1.8-1 1.2-0.2 1.7 0.7 0.7 0.1 0.9-0.5 0.3-1.5-0.1-1.1-0.7-2 0.3-1 0.6-0.7 5.9-3.2 6.3-5.2 2.2-3-0.6-3.2 1-2.9 1.1-1.2 2.3-2 1.5-0.8 1.5-0.5 9.7-0.4 5.3-1.2 5.6-0.9 5.8-2.7 1.8 0 1.8 0.4 2.2 1.1 1.7 1.7 1.6 1.2 4.7 1.7 2.9 3.2z" id="county_romania_brasov_7" name="Brasov">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m635.1 331.3l4.4-1.5 3.4-3 0.9-0.1 0.8 0.4 1.5 1.8 0.8 0.8 3.9 2.4 1 1.3 0.6 1.3-0.3 1-1.4 1.6-0.3 1.1 0.6 1.4 2 1 1.2 0.9 0.9 1.1 0.5 1.3 1 3.3 1.4 2.6 1.7 0.9-6.7 10.8-1.7 5-0.2 2.2-1.5 7.7-0.9 7.4 1 3.2 0.5 1.4 0.3 1.7-0.2 1.8-2 4.2-0.4 1.8-0.2 1.7-0.4 2-0.9 2-1.7 2.6-0.6 2.1-0.8 3.5-1 1.4-1.9 1-1.8-0.1-1.2-0.6-1.9-1.9-1.1-0.7-1.1-0.2-1 0.4-1 1.6-0.4 1.5-0.4 2.8-0.7 0.9-1.3 0.5-5.2 0.2-4.1 1.3-2.3-2.4-3.2-5.1-2.1-1.8-2-1.1-2-0.9-1.1-0.9-1.6-3.3-1.7-2.6-3.1-3.2-2.6-1.9-9.8-3.7-1.9-1-1.6-2.5-0.7 1-1.6 0.3-1.5 0.6-1.5 0.1-1.5-1-1.6 1.1-1.9 0-0.3-5.1-3.3-3.4-7.4-5-1-2.1-0.4-2.3-0.1-9.9 0.2-3.3 0.7-2.1-0.3-2.6 1.3-5.1-0.4-2.7-1.3-1-2.2-0.6-2.1 0.1-0.9 0.9-1.4-1.9-3.4-2.6-0.9-1.6-0.4-1.4 0.7-2.1 2.6-5.2 1.6-1.5-0.1-1.4 0.4-0.9 3.8-4.2 2.2-1.6 1.6-1.8 3.8-5.5 1.7-1.6 3.5-2.3 1.3-1.3 1.2-0.9 1.4-0.3 1.6 0.9 2.8 2.5 1.2 1.2 0.7 1.3 0.8 4.4 3.9 7.6 4.2 3.4 1 1.2 0.9 1.5 1.8 1.5 3 1.5 9.8 0.8 27.4-6z" id="county_romania_covasna_12" name="Covasna">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m573.9 191.8l2.4 0.6 2.9 1.3 1.6 1 1.4 1.8 1.3 2.3 1.8 4.6 1.3 2.6 1.9 2.5 3.5 2.7 1.5 1.5 0.9 1.5 0.6 2.6-0.1 1.2-0.4 0.7-0.7 0.1-1.9-0.4-1.1 0.1-1.3 1.1-0.2 1.3-0.1 1.6 1.2 25.6 0.5 1.9 0.9 1.7 1.6 1.9 3.5 3.5 1.2 0.7 1.1 0.1 0.4-0.9-0.1-1.4-0.7-3.3 0-1.4 1.1-0.7 1-0.3 8.6 2.1 0.3 11.4 1.8 7 1.4 2.5 1.5 2.3 1.4 2.8 0.1 1.8-1.1 1-1.4 0.6-1.5 1.3-0.9 2-0.5 4.3 0.6 2.2 1.5 1.4 4.3 1.2 5.6 0.8 1.5 0.9 1.6 1.7 0.5 1.8 0.1 1.8-0.8 3.6-0.1 2.1 0.9 1 1.5 0 2.7-0.8 4.2-2.3 1.5-0.3 1.5 0.2 1.8 1.1 0.6 1.3 0.2 1.4-1.5 4-0.2 1.3-0.5 6.2-0.4 1.6-1.3 2.7-0.5 1.6-0.8 5.8-27.4 6-9.8-0.8-3-1.5-1.8-1.5-0.9-1.5-1-1.2-4.2-3.4-3.9-7.6-0.8-4.4-0.7-1.3-1.2-1.2-2.8-2.5-1.6-0.9-1.4 0.3-1.2 0.9-1.3 1.3-3.5 2.3-1.7 1.6-3.8 5.5-1.6 1.8-2.2 1.6-3.8 4.2-0.4 0.9 0.1 1.4-1.6 1.5-2.6 5.2-2.9-3.2-4.7-1.7-1.6-1.2-1.7-1.7-2.2-1.1-1.8-0.4-1.8 0-5.8 2.7-5.6 0.9-8.3-8.9-5.6-4.3-1.7-0.7-5 0.2-2-0.7-1.9-1.8-8.4-10.2-0.6-1.3 0.1-1 0.8-0.8 2.3-1 2.6-2 2.6-0.6 1.2-0.6 0.6-1.1 0-1.6-0.8-1-2.9-2.1 0-1 0.8-1 6.6-2.7 1.1-1.1 0.5-1.2 0.5-5.8 0.5-1.7 0.8-1.4 6.2-6.5 1.4-0.9 4.5-1.9 1.8-1.2 1.3-1.4 3.8-5.9 1.5-1.7 4.6-3.8 0.9-1.5 0.2-1.5-0.3-4.1 0.3-2.9 0.6-3.4 0.1-2.1-0.8-2.5-2.3-6.4-0.6-3.5 1.7-10.4-0.3-9.4-0.4-2.4-0.7-1.9-1-1.4-2.4-2.4-1.5-3-0.7-5.9 4 1.6 2.9 0 1.4-0.6 0.9-1 0.6-1.3 0.2-2.7 0.3-1.4 1-1.8 1.8-1.2 2.1-0.9 2.6-0.6 1.8 0.1 1.8 0.8 11.8 8.1 1.2 1.2 1 1.5 1.3 1.4 1.7 1.1 1.6 0.2 2.2-0.3 1.5-0.5 5.6-0.8z" id="county_romania_harghita_2" name="Harghita">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m832.6 225.1l-0.8 0.5-5.4 0.7-1.8 1.1-1.7 1.3-1.2 0-1.3-0.6-1.9-1.5-0.9-0.1-1.3 0.8-0.4 1-0.9 0.7-1.5 0-3.9-1.3-3.4-2.2-1.1-0.3-1.7 0.2-1.4-0.1-1.1-0.9-0.1-1.1 1.7-5.6 0.1-1.7-0.4-1.5-0.9-0.9-1.3-0.2-1.4 1-0.5 1.4 0 2.9-0.3 0.9-0.7 0-1.2-0.8-4-5.3-2.9-3-6.3-5-1.4-0.7-1.2 0-0.8 1-0.2 1.1 0.8 2.3 0.1 2.5-0.5 2.3 0.2 0.7 0.7 0.5 1.2 0.1 1.9-0.3 1 0.7-0.2 1.8-0.5 1-1.3 0.7-2.1 0.3-3.9-0.7-1.9-0.8-1.5 0-0.7 1-1.2 2.4-1.1 1-3.9 1.3-1.9 0.1-2.6-0.6-1.9-0.7-2-0.6-2 0-7.9 1.4-12.2-0.8-0.5-2.1-0.6-1.3-1.9-3.3-2.2-3-1.2-1.9-0.1-1.6 0.7-1.5 1.4-1.4 1.8-1 1.9-0.8 3.1-0.8 0.9-0.7 0.4-1.1-0.6-1.3-1.4-1.7-2.4-2.1-1.3-1.5-1-1.5-0.8-2.9-0.9-1.2-1.3-0.7-2.4-0.2-1.7 0.4-1.4 0.8-3.6 4.3-1.1 1-1.3 0.5-1.9-0.2-5-1.1-1.5 0.3-0.9 0.9-0.9 2.6-0.6 1-0.9 0.5-1.6 0.1-4.3-1-2.7-1.5-6.2-4.8-4.1-2.3-3-0.8-2.4-0.1-2 0.3-1.8-0.1-1.4-0.8-0.9-2.1-0.8-2.4-3.1-7.9-5.4-7.9-7.3-7.2 8.1-1.9 1.1-0.8 0.9-1.2-0.6-1.1-6.5-6-1.1-2.6 0.4-1.4 1.8-0.5 11 1.3 2.3-0.4 0.9-0.9 0.5-1.4 0.1-1.9 1.1-2.9 0.4-1.7-0.3-1.4-1.5-2.2 4.2-4.1 1.5-3.2 0.6-1.7 0.7-0.9 0.9-0.5 1.1 0.1 3.1 2.1 1.7 0.4 17.7 0 1 0.4 1.8 1.4 1.6 2 1.1 1.2 1.3 1 2 0.6 1.6-0.3 2.2-0.8 1.1-0.9 0.5-1.1-0.1-1.3-1.9-4.2-0.2-1.4 0.4-1.1 0.7-1.1 1.2-0.9 1.2-0.2 0.8 0.3 1.7 1.4 3 0.6 1.9 0.8 1.8 1 1.4 0.4 2.9 0 1.2-0.7 0.9-1.1 0.4-2.7 0.6-1.1 1.8-2 1.8-2 1.2-0.8 1.3-0.1 1.7 0.4 4.3 0.1 5.8-2.8 1 3.6-0.1 1.4 0.7 0.9 1.4 0.3 0.8 4.2 0.6 2.3 1.2 1 0.9 0.1 1.5 1 2.8 0.1 1.6 0.5 1.5 0.9 1.4 1.3 0.7 1.5-0.8 0.7-0.6 1.2-0.7 3.4 1.8 1.7-0.8 4.8 1.5 1 1.4 1.2 2.6 6.1 1.3 2.3 0.8 0.5 2.9 0.6 2.8 1.9 2.6 0.7 1.1 1.2 2.1 3.7-0.1 2 1.1 1.9 2.7 3.5 1.3 4.2 0.7 2.3-0.1 0.6-1.2 0.4 1.2 1.7 4.6 2.5 0 1.1-0.6 1.1 2.4 1.5 3.2 3.6 3 2 1.3 1.2 0.1 2.2 2.5 0.5 2.5 1.6 2.3 1.1 2.1-1 0.9 2.5 3.4 4.3 1.5 2.7 2 5.6 0.4 2.7-0.9 2.3 1.7 1.2 0.1 1.8-0.1 1.8 1.2 1.5-0.2 3z" id="county_romania_iasi_25" name="Iasi">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m659.5 161.4l7.3 7.2 5.4 7.9 3.1 7.9 0.8 2.4 0.9 2.1 1.4 0.8 1.8 0.1 2-0.3 2.4 0.1 3 0.8 4.1 2.3 6.2 4.8 2.7 1.5 4.3 1 1.6-0.1 0.9-0.5 0.6-1 0.9-2.6 0.9-0.9 1.5-0.3 5 1.1 1.9 0.2 1.3-0.5 1.1-1 3.6-4.3 1.4-0.8 1.7-0.4 2.4 0.2 1.3 0.7 0.9 1.2 0.8 2.9 1 1.5 1.3 1.5 2.4 2.1 1.4 1.7 0.6 1.3-0.4 1.1-0.9 0.7-3.1 0.8-1.9 0.8-1.8 1-1.4 1.4-0.7 1.5 0.1 1.6 1.2 1.9 2.2 3 1.9 3.3 0.6 1.3 0.5 2.1 0.6 4.8 2.4 6.5 0.4 2.5 0 1.7-1.2 2.5-4.2 2.1-3-0.9-1.1-0.6-8.5-2-2.6 0.1-7.4 1.1-2.1-1.5-1.5-1.6-0.6-1.5 0.1-2.1-0.1-1.1-0.6-1.3-2.2-1.6-1.1-0.1-0.6 0.6 0.2 2.3-0.2 1.3-0.7 1-2.8 2.4-0.5 1-0.1 1.2 0.2 1.3 0 1.2-0.6 1.2-1.2 1-1.7 0.8-1.7 0-1.3-0.7-3.2-2.4-2.8-1-2 0.2-1.3 0.8-0.9 2.1-0.9 1.1-6.5 2.8-1.5 1-2 2.6-0.9 0.8-1.4 0.2-4.3-0.9-3.8 0.9-4 0-1.1 0.4-1 0.9-1.8 2.6-1 0.9-1.8 0.2-8.4 0.1-1.2-0.3-0.3-0.6 0.5-3-0.3-1.1-1.3-2.2-1.2-2.4-0.7-0.8-1.5-0.6-1.3 0.2-1.2 0.7-6.5 4.7-2.7 1.2-2 0.3-1.4-0.3-3.1-1.7-2.4-0.7-1.4 0.1-3.9 1.1-8.6-2.1-1 0.3-1.1 0.7 0 1.4 0.7 3.3 0.1 1.4-0.4 0.9-1.1-0.1-1.2-0.7-3.5-3.5-1.6-1.9-0.9-1.7-0.5-1.9-1.2-25.6 0.1-1.6 0.2-1.3 1.3-1.1 1.1-0.1 1.9 0.4 0.7-0.1 0.4-0.7 0.1-1.2-0.6-2.6-0.9-1.5-1.5-1.5-3.5-2.7-1.9-2.5-1.3-2.6-1.8-4.6-1.3-2.3-1.4-1.8-1.6-1-2.9-1.3-2.4-0.6 0.1-3.4 1.2-4 1.3-2.7 4.7-5.7 1.6-1.5 4.1-2.7 1.8-1.6 0.8-1.5 0.2-1.5-0.2-1.3-0.8-1.1-2.1-2-1.8-2.6-0.3-1.1 0.4-0.9 0.8-0.5 1.2-0.1 1.3 0.5 1.4 1 4.8 4.5 1.3 1 1.4 0.2 1.9-0.3 7.7-3.7 1.5-0.1 1.5 0.5 1.3 0.9 1.8 0.6 2 0.1 5.4-1.5 11.9 0.1 3.8-1 9.9-5.2 2.4-0.6 2.1 0.1 2.1 0.4 1.8 0.8 1.6 1.2 3.7 4.3z" id="county_romania_neamt_18" name="Neamt">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m613.4 432.5l6.4 7.6 4.3 3.2 1.4 1.4 1.4 2.2 3.7 7.9 0.5 2.3-0.1 1.4-0.7 1-0.2 1.3 0.5 1.6 3 3.1 1.1 1.5 0.4 3 0.4 1.1 0.7 1 1.5 0.9 1.5 0.3 4.6-0.4 1.2 0.4 1.1 0.9 0.5 1.9 1.1 1 0.9 0.2 2.5-2.2 0.9-0.3 0.5 0.3 0.1 1.1-0.4 2.7 0.3 1.7 1.5 1.7 2.5 2 0.9 1.4 0.7 1.5 0.7 4.4 0.2 5.7 0.5 2.2 0.7 1.6 1.8 1 2.9 0.9 0.9 0.4 2.4 2.3 0.8 1.1 0.6 1.3-0.2 2.1-0.9 1.2-1.9 1.9-0.7 1.1 0 1.4 0.4 1.5 4 4 2 3.6-2.7 3.4-1.3 2.4-1.9 1.6-1.4 0.8-1.5 0.4-5.7-0.5-1.4 0.3-1.5 1.2-1.8 2.1-1.2 0.5-2.3-0.2-2.5-1.4-1.2-0.3-1.5 0.1-3.6 1.7-3.8 3.6-4.4-0.3-1.5 0.2-2.8-1.1-2.4-0.1-6.4 2.1-6.5 0.1-0.8 1.7-0.6 0.6-1.2-0.4-2.1-1.1-1.3-1.1-7.3-9-1.8-1-1.6-0.6-5-0.7-1.2-0.9-0.8-1.4-0.4-2.7 0.6-4.3-0.1-2.1-0.5-2.1-1.4-2.4-1.1-1.2-0.8-0.4-2.9 0.8-1.2 0-2.3-1-1.1-1-0.9-1.6-0.8-1.9-0.8-3.7-0.7-6.4-0.4-1.6-0.9-1.6-4.4-4.9-5.8-11.3-1.2-1.8-2.1-2.3-1.6-2.8-0.1-2.1 0.3-4.1-0.3-1.5-0.7-1.8-1.2-2-1.1-1.2-3.2-2.4-0.7-1.3-0.5-1.8-0.7-6.7-1.9-9.4 1.7-1.2 1.4-0.5 17.8-3.2 2.4-0.1 4.5 0.9 4.5 1.7 1.5 0.3 2.9-1 1.4-0.1 1.3 0.8 0.7 1 1.4 2.4 0.9 0.8 1.5 0.3 1.8-0.5 2-1.8 1-1.8 0.5-1.9 0.7-3.4 0.9-1.2 1.4-0.6 2.3 0.3 5.6 3.3 4.9 1z" id="county_romania_prahova_16" name="Prahova">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m678.1 134.4l1.5 2.2 0.3 1.4-0.4 1.7-1.1 2.9-0.1 1.9-0.5 1.4-0.9 0.9-2.3 0.4-11-1.3-1.8 0.5-0.4 1.4 1.1 2.6 6.5 6 0.6 1.1-0.9 1.2-1.1 0.8-8.1 1.9-3.7-4.3-1.6-1.2-1.8-0.8-2.1-0.4-2.1-0.1-2.4 0.6-9.9 5.2-3.8 1-11.9-0.1-5.4 1.5-2-0.1-1.8-0.6-1.3-0.9-1.5-0.5-1.5 0.1-7.7 3.7-1.9 0.3-1.4-0.2-1.3-1-4.8-4.5-1.4-1-1.3-0.5-1.2 0.1-0.8 0.5-0.4 0.9 0.3 1.1 1.8 2.6 2.1 2 0.8 1.1 0.2 1.3-0.2 1.5-0.8 1.5-1.8 1.6-4.1 2.7-1.6 1.5-4.7 5.7-1.3 2.7-1.2 4-0.1 3.4-5.6 0.8-1.5 0.5-2.2 0.3-1.6-0.2-1.7-1.1-1.3-1.4-1-1.5-1.2-1.2-11.8-8.1-1.8-0.8-1.8-0.1-2.6 0.6-2.1 0.9-1.8 1.2-1 1.8-0.3 1.4-0.2 2.7-0.6 1.3-0.9 1-1.4 0.6-2.9 0-4-1.6-4.8-2.3-9.5-1.8-0.6-10.8 0.6-4.7 0.4-1.6 0.4-2.4-0.2-0.9-0.5-0.3-2 0.2-1.2-0.3-1.2-1.6 0.1-1.6 0.8-2.1 1.4-2.4 0.8-2.7-0.1-1.8-0.4-1.5-1.3-2.8-0.8-1.3-2.1-2.7-0.9-1.7-0.2-1.1 0.5-0.9 0.7-0.6 1-0.1 1.6 0.2 1.1-0.7 0.3-1.4-0.1-1-1.4-2.3 0.2-0.8 0.8-0.7 2.7-1.5 1.2-1.3 0.7-2.2-0.1-1.8-0.6-1.6-1.2-2.4-2.8-3.3-8.3-6.8-1.2-1.4 6.3-2.2 0.9-1 0.8-1.5-0.2-1.7-0.9-1.4-4.5-4.8-2.5-4-1.3-4.3 7.9-1.4 6.6-2.9 4.4-4.3 10.2-17 4.6-3.1 51.8-5.7 6.9-2.8 5.6-0.7 3.2-1.5 1.7-0.3 5.1 0.5 6.7-2 7.7-0.1 1.8 9.6 0.7 1.8 1.6 1.5 2.8 1.8 4.3 3.7 5.4 3 2.5 2.5 12 15.4 2 3.5 7 8.7 1.3 2.2 3.4 3.5 1.1 1.9 0.9 3.4 1.7 8.2 0.1 1.9-0.3 4.1 0.2 1.8 0.6 1.6 2.6 3.7 2.6 2.1 4.4 0.6z" id="county_romania_suceava_5" name="Suceava">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m737.9 240.5l2.1 2.9 1.5 1.6 4.8 4.1 2 2.2 1.2 2.7 0.1 3.8-0.3 3.5 0.4 3.5 11 19.3 4 9.4 2.4 9.5 1.1 7.1 0.8 19.3-3.2-0.4-3.7-1.1-1 0.1-1.4 1.9-0.8 0.8-1.2 0.3-1.9-0.2-4.5-2.8-2.1-0.4-2.7 0.1-7.8 1.6-2.6 1.5-1.2 1.3-1.8 2.5-1.1 1.1-2.9 1.9-1 1.2-1.2 2.3-0.9 0.7-1.4 0.3-5.5-2-1.4-0.3-1.6 0.2-1.8 0.9-0.9 1-0.8 2.2-0.4 0.6-1 0.2-4.1-1.7-1.6-0.2-1.8 0-2.1 0.5-1.7-0.2-2.6-0.7-4.6-0.2-3.2-0.6-10.2 0.7-1.7 0.5-1.2 1.1-0.7 1.3-0.3 1.4-0.3 4.4-3.2 1.5-12.2-1.8-1.7-0.9-1.4-2.6-1-3.3-0.5-1.3-0.9-1.1-1.2-0.9-2-1-0.6-1.4 0.3-1.1 1.4-1.6 0.3-1-0.6-1.3-1-1.3-3.9-2.4-0.8-0.8-1.5-1.8-0.8-0.4-0.9 0.1-3.4 3-4.4 1.5 0.8-5.8 0.5-1.6 1.3-2.7 0.4-1.6 0.5-6.2 0.2-1.3 1.5-4-0.2-1.4-0.6-1.3-1.8-1.1-1.5-0.2-1.5 0.3-4.2 2.3-2.7 0.8-1.5 0-0.9-1 0.1-2.1 0.8-3.6-0.1-1.8-0.5-1.8-1.6-1.7-1.5-0.9-5.6-0.8-4.3-1.2-1.5-1.4-0.6-2.2 0.5-4.3 0.9-2 1.5-1.3 1.4-0.6 1.1-1-0.1-1.8-1.4-2.8-1.5-2.3-1.4-2.5-1.8-7-0.3-11.4 3.9-1.1 1.4-0.1 2.4 0.7 3.1 1.7 1.4 0.3 2-0.3 2.7-1.2 6.5-4.7 1.2-0.7 1.3-0.2 1.5 0.6 0.7 0.8 1.2 2.4 1.3 2.2 0.3 1.1-0.5 3 0.3 0.6 1.2 0.3 8.4-0.1 1.8-0.2 1-0.9 1.8-2.6 1-0.9 1.1-0.4 4 0 3.8-0.9 4.3 0.9 1.4-0.2 0.9-0.8 2-2.6 1.5-1 6.5-2.8 0.9-1.1 0.9-2.1 1.3-0.8 2-0.2 2.8 1 3.2 2.4 1.3 0.7 1.7 0 1.7-0.8 1.2-1 0.6-1.2 0-1.2-0.2-1.3 0.1-1.2 0.5-1 2.8-2.4 0.7-1 0.2-1.3-0.2-2.3 0.6-0.6 1.1 0.1 2.2 1.6 0.6 1.3 0.1 1.1-0.1 2.1 0.6 1.5 1.5 1.6 2.1 1.5 7.4-1.1 2.6-0.1 8.5 2 1.1 0.6 3 0.9 4.2-2.1z" id="county_romania_bacau_1" name="Bacau">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m822.8 445.5l-0.6 2.7-0.3 4.4-0.2 1.1-1.1 1.9-0.5 2.3-1.9 3.1 5.8 5.1 3.6 1.5 3.7-1.5 1.3 1.3 0.8 1.8-1.3 2-1.6 3.1-0.5 2.9 1.6 1.3 2 0.6 2.1 1.6 1.8 2 0.9 1.9-0.4 2-1.2 2-1.6 1.5-1.4 0.7-0.3 0.7 0.7 4.8 0.6 1.2-0.1 1.8-1 1.6-0.3 1.1 1.1 5.4 0.1 3.4-1.5 1.5-0.4 0.7 0.1 7.9-0.8 1.5-2.8 3.8-1.1 2.3-0.4 3.6-0.7 3.1-1.6 2.8-2.1 2-2.2 0.8-2-0.4-3.1-2.1-2.1-0.5-2 0.1-1.7 0.3-3.2-3-0.8-1.1-1.9-1.2-2.2-0.4-4.9 0.5-2.4 0.8-1.8 1-2.4 0.8-4.1 0.9-28.8-0.8-3-1.2-1.3-0.7-1.4-0.2-1.8 0.6-2.4 1.2-2.1 0.1-1.7-0.2-8.7-3.6-0.4-1.1 0.7-6.9-0.7-1.8-2.1-2.2-0.3-1.4 0.4-1.4 1.4-1.3 1.1-0.6 3.8-1 0.9-1.4 0.8-2.6 1.1-5.5 0.5-3.2 0.1-2.4-0.2-1.4-0.5-0.9-1-0.4-2.5 0.1-1.4-0.2-0.8-0.5-0.4-1.1 0.1-4.2-0.1-1.5-0.7-1.2-0.8-0.7-2.3-0.9-0.6-1-0.1-1.6 0.1-3-0.1-1.3-0.4-1.1-0.7-0.8-0.9-0.5-1-0.1-0.9 0.4-0.7 0.6-0.1 2-0.3 1-1 0.9-1.2 0.3-1-0.3-0.7-0.6-1.6-4.1 0.1-2.2 0.9-1.2 1.5-1.3 8.4-2.9 5 0.7 1.3 0 4.2-1.3 1.4 0 2.7 1.1 0.6-0.6 0.4-2.2-0.5-3.4 0-1.6 0.7-1.2 2-0.9 1.6 0 1.6 0.3 1.3-0.3 1-1.2 0.8-3.4 0.7-2.1 1.9-3.3 0.4-2.9 1-3.1 2.6-4.1 2.7-3.5 1.9-1.7 2.1-1.4 6.2-2.3 6.1 2.7 3.7-0.2 1.9-0.4 1.3 0.3 0.9 0.9 2.1 2.6 3.6 3.1 5.8 3.2 3.7 0.7 6.9-0.3 2.5 0.4 2.6 0.9 6.4 0.8z" id="county_romania_braila_9" name="Braila">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m758.8 446.9l-0.4 2.9-1.9 3.3-0.7 2.1-0.8 3.4-1 1.2-1.3 0.3-1.6-0.3-1.6 0-2 0.9-0.7 1.2 0 1.6 0.5 3.4-0.4 2.2-0.6 0.6-2.7-1.1-1.4 0-4.2 1.3-1.3 0-5-0.7-8.4 2.9-1.5 1.3-0.9 1.2-0.1 2.2 1.6 4.1 0.7 0.6 1 0.3 1.2-0.3 1-0.9 0.3-1 0.1-2 0.7-0.6 0.9-0.4 1 0.1 0.9 0.5 0.7 0.8 0.4 1.1 0.1 1.3-0.1 3 0.1 1.6 0.6 1 2.3 0.9 0.8 0.7 0.7 1.2 0.1 1.5-0.1 4.2 0.4 1.1 0.8 0.5 1.4 0.2 2.5-0.1 1 0.4 0.5 0.9 0.2 1.4-0.1 2.4-0.5 3.2-1.1 5.5-0.8 2.6-0.9 1.4-3.8 1-1.1 0.6-1.4 1.3-0.4 1.4 0.3 1.4 2.1 2.2 0.7 1.8-0.7 6.9 0.4 1.1-2.3 1.2-0.5 1.5 0.7 0.7 1.9 1.5 0.5 0.8-0.5 0.7-1 0.5-2.4 0.6-3 1.2-1.5 0.1-0.4-0.4-0.1-1.5 0.2-1.9-0.1-1.1-0.6-1-1-1-1.4-0.9-1.7-0.9-2.5-0.7-1.6 0.2-1.3 0.8-1.1 1.2-1.5 0.9-1.5 0-1.4-0.6-3.1-2-2.5-0.9-2.1-0.1-3.7 0.7-1.5 0-2.4-0.8-6.3-1.1-7 0.5-2.5-0.2-1.9-0.5-3.5-3.1-2.4-1.2-2-3.6-4-4-0.4-1.5 0-1.4 0.7-1.1 1.9-1.9 0.9-1.2 0.2-2.1-0.6-1.3-0.8-1.1-2.4-2.3-0.9-0.4-2.9-0.9-1.8-1-0.7-1.6-0.5-2.2-0.2-5.7-0.7-4.4-0.7-1.5-0.9-1.4-2.5-2-1.5-1.7-0.3-1.7 0.4-2.7-0.1-1.1-0.5-0.3-0.9 0.3-2.5 2.2-0.9-0.2-1.1-1-0.5-1.9-1.1-0.9-1.2-0.4-4.6 0.4-1.5-0.3-1.5-0.9-0.7-1-0.4-1.1-0.4-3-1.1-1.5-3-3.1-0.5-1.6 0.2-1.3 0.7-1 0.1-1.4-0.5-2.3-3.7-7.9-1.4-2.2-1.4-1.4-4.3-3.2-6.4-7.6 5.8-12.4 4.1-1.3 5.2-0.2 1.3-0.5 0.7-0.9 0.4-2.8 0.4-1.5 1-1.6 1-0.4 1.1 0.2 1.1 0.7 1.9 1.9 1.2 0.6 1.8 0.1 1.9-1 1-1.4 0.8-3.5 0.6-2.1 1.7-2.6 0.9-2 0.4-2 0.2-1.7 0.4-1.8 2-4.2 0.2-1.8-0.3-1.7-0.5-1.4-1-3.2 4.7 4.5 4.4 6.4 6.9 12.2 2.1 2.4 1.8 1.5 3 0.4 1.5-0.1 2.4-0.8 1.3 0 2.4 1 6.7 5.8 2.3 2.9 3.6 2.7 4.5 2.6 1.3 0.5 2.1 0.3 1.5 0.6 1.8 0.1 0.7-1.6-0.4-1.3 1-1.1 1.6-0.1 2.5 0.5 3.2 1.4 1.8 1.4 0.6 1.3 0 3.1 0.8 1.9 2.7 2.9 1.9 1.3 1.4 0.5 1.4-0.1 1.2-0.5 2.6-1.5 1.2-0.4 1.5-0.1 0.6 0.6 0.2 1-0.3 2.7 0.4 1.4 0.9 0.2 1-0.4 1.8-1.9 1-0.8 1-0.3 1.7 0.3 1.2 0.7 1 1 0.9 0.3 1.8-0.1 1.3 0.3 0.8 0.7 0.4 1 0.4 2.4 0.7 1.1 1.1 0.6 5 1.4 2.4 0.3 4.8-0.2z" id="county_romania_buzau_15" name="Buzau">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m831 339.5l-0.7 2.2-0.9 0.5-0.2 1.6 0.6 0.4 0.1 0.9-1.3 5.3-0.2 1.5 0.3 2.2 2.6 7.6 0.5 2.6 1 2.2-0.6 2.3-0.2 1.2 0.3 1.2 1.2 2.5-0.3 0.7-1.4 1-0.5 0.8 0.3 4.4 1.6 4.6 1.9 3.6 0.9 1.5 0.9 15.1-0.2 2.5 0.7 1.9-1.5 0.8-3.5-0.1-1.4 0.5-1.7 1.3-1.8 1.7-1.3 1.7 5.9 3.1 2.4 1.9 2.6 4.8 0 5.1 0.8 1.5 4.2 5.2 0.2 0-1.6 2-0.9 3-1 0.6-1.1-0.2-3-1.5-1.9-0.6-2.1 0.2-4.6 1.6-1.1 0.2-0.7 0.5-1.5 2.4-6.4-0.8-2.6-0.9-2.5-0.4-6.9 0.3-3.7-0.7-5.8-3.2-3.6-3.1-2.1-2.6-0.9-0.9-1.3-0.3-1.9 0.4-3.7 0.2-6.1-2.7-2.8-3.1-1-1.7-1.9-5.3-0.4-1.1 0-3.2-0.8-3.3-0.9-2.1-1.1-1.8-2.2-2.2-1.8-3.1-1.1-0.7-2.9-1.5-1-1-2.7-3.9-1.4-3.3-0.6-2.2-11.2-28.4-1-4.1 0.1-3.3 0.3-1.7 0.8-1.5 0.9-1.2 1.5-1.6 2.6-3.3 2.3-2 1.9-1.2 3-0.6 3.7-0.2 1.7-0.5 1.8-1.6 1-1.4 2.2-1.9 6.1-0.4 3.6 5 1.3 2.6 1 4.4 1.3 3.8 0.9 1.7 1.1 0.8 1.3-0.6 0.6-1.6 0-2.3 0.6-4.1 0-1.4-0.9-2.8-2.1-4.1-0.2-1.2 0.4-1 1.4-0.9 18.1 2.3 1.7 0.6 0.8 0.9 0.3 2.3 0.7 0.9 1.1 0 0.9-0.6 1.7-3.1 0.8-1.3 1-0.8 1.7-0.8 1.5 0 7.9 2.6 3.9 2.1 2.7 0.7 1.6 0.1 3.5-0.9 0.4-0.2z" id="county_romania_galati_31" name="Galati">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m832.6 225.1l-0.1 1.3 1.7 4.3 4.2 7.3 0 2.9 5.9 11.9 1.1 4.2 0.3 2.2 0 2.1-0.7 1.7-1 1.9-0.6 2.2 0.9 2.5-0.9 0.7-0.4 1.2-0.2 5.7 0.2 1.2 1.3 2.8 1.3 0.4 0.1 1.2 0 4.8-0.1 1.3-0.6 1.1-1.5 0.8 0.8 2.1-0.5 0.8-1 0.4-0.7 0.8-0.6 4.6-0.5 0.7-1.3 0.4-0.2 1.1 0.4 3.1 0.1 1.9-1.6 3.7-1.3 1.3-3.2 1.4-0.4 1.1 0.3 2.6-0.2 0.8-2.5 0.9 0.3 1.3 1.9 3.2 1.3 1.9 0.3 1.3-1.2 3.6-0.6 3.7-2.1 6-0.4 0.2-3.5 0.9-1.6-0.1-2.7-0.7-3.9-2.1-7.9-2.6-1.5 0-1.7 0.8-1 0.8-0.8 1.3-1.7 3.1-0.9 0.6-1.1 0-0.7-0.9-0.3-2.3-0.8-0.9-1.7-0.6-18.1-2.3-1.4 0.9-0.4 1 0.2 1.2 2.1 4.1 0.9 2.8 0 1.4-0.6 4.1 0 2.3-0.6 1.6-1.3 0.6-1.1-0.8-0.9-1.7-1.3-3.8-1-4.4-1.3-2.6-3.6-5-1.4-7-0.8-19.3-1.1-7.1-2.4-9.5-4-9.4-11-19.3-0.4-3.5 0.3-3.5-0.1-3.8-1.2-2.7-2-2.2-4.8-4.1-1.5-1.6-2.1-2.9 1.2-2.5 0-1.7-0.4-2.5-2.4-6.5-0.6-4.8 12.2 0.8 7.9-1.4 2 0 2 0.6 1.9 0.7 2.6 0.6 1.9-0.1 3.9-1.3 1.1-1 1.2-2.4 0.7-1 1.5 0 1.9 0.8 3.9 0.7 2.1-0.3 1.3-0.7 0.5-1 0.2-1.8-1-0.7-1.9 0.3-1.2-0.1-0.7-0.5-0.2-0.7 0.5-2.3-0.1-2.5-0.8-2.3 0.2-1.1 0.8-1 1.2 0 1.4 0.7 6.3 5 2.9 3 4 5.3 1.2 0.8 0.7 0 0.3-0.9 0-2.9 0.5-1.4 1.4-1 1.3 0.2 0.9 0.9 0.4 1.5-0.1 1.7-1.7 5.6 0.1 1.1 1.1 0.9 1.4 0.1 1.7-0.2 1.1 0.3 3.4 2.2 3.9 1.3 1.5 0 0.9-0.7 0.4-1 1.3-0.8 0.9 0.1 1.9 1.5 1.3 0.6 1.2 0 1.7-1.3 1.8-1.1 5.4-0.7 0.8-0.5z" id="county_romania_vaslui_38" name="Vaslui">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m769 329.4l1.4 7-6.1 0.4-2.2 1.9-1 1.4-1.8 1.6-1.7 0.5-3.7 0.2-3 0.6-1.9 1.2-2.3 2-2.6 3.3-1.5 1.6-0.9 1.2-0.8 1.5-0.3 1.7-0.1 3.3 1 4.1 11.2 28.4 0.6 2.2 1.4 3.3 2.7 3.9 1 1 2.9 1.5 1.1 0.7 1.8 3.1 2.2 2.2 1.1 1.8 0.9 2.1 0.8 3.3 0 3.2 0.4 1.1 1.9 5.3 1 1.7 2.8 3.1-6.2 2.3-2.1 1.4-1.9 1.7-2.7 3.5-2.6 4.1-1 3.1-4.8 0.2-2.4-0.3-5-1.4-1.1-0.6-0.7-1.1-0.4-2.4-0.4-1-0.8-0.7-1.3-0.3-1.8 0.1-0.9-0.3-1-1-1.2-0.7-1.7-0.3-1 0.3-1 0.8-1.8 1.9-1 0.4-0.9-0.2-0.4-1.4 0.3-2.7-0.2-1-0.6-0.6-1.5 0.1-1.2 0.4-2.6 1.5-1.2 0.5-1.4 0.1-1.4-0.5-1.9-1.3-2.7-2.9-0.8-1.9 0-3.1-0.6-1.3-1.8-1.4-3.2-1.4-2.5-0.5-1.6 0.1-1 1.1 0.4 1.3-0.7 1.6-1.8-0.1-1.5-0.6-2.1-0.3-1.3-0.5-4.5-2.6-3.6-2.7-2.3-2.9-6.7-5.8-2.4-1-1.3 0-2.4 0.8-1.5 0.1-3-0.4-1.8-1.5-2.1-2.4-6.9-12.2-4.4-6.4-4.7-4.5 0.9-7.4 1.5-7.7 0.2-2.2 1.7-5 6.7-10.8 12.2 1.8 3.2-1.5 0.3-4.4 0.3-1.4 0.7-1.3 1.2-1.1 1.7-0.5 10.2-0.7 3.2 0.6 4.6 0.2 2.6 0.7 1.7 0.2 2.1-0.5 1.8 0 1.6 0.2 4.1 1.7 1-0.2 0.4-0.6 0.8-2.2 0.9-1 1.8-0.9 1.6-0.2 1.4 0.3 5.5 2 1.4-0.3 0.9-0.7 1.2-2.3 1-1.2 2.9-1.9 1.1-1.1 1.8-2.5 1.2-1.3 2.6-1.5 7.8-1.6 2.7-0.1 2.1 0.4 4.5 2.8 1.9 0.2 1.2-0.3 0.8-0.8 1.4-1.9 1-0.1 3.7 1.1 3.2 0.4z" id="county_romania_vrancea_27" name="Vrancea">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m637 540.6l2 0.6 4.2 0.6 1.1 0.4 0.6 0.7 0 2.5 0.3 1.2 0.8 1.5 0.3 1.2 0.2 2.4 1.1 1.4 6.5 3.6 0.9 1.1 0.3 1-0.5 1.2-0.7 0.8-4.1 2.9-0.9 1-1.2 2.3-0.5 2.5 0.3 1.2 0.9 1.3 3.3 1.7 2.5 1.9-1.9 1.3-0.4 0.9-0.1 1.9 1 1.1 1.5 0.9 2 0.7 0.6 0.6 0.1 0.8-1.2 2.4-1.2 1.3-6.3 3-2 1.4-1.8 1.8-2.7 3.6-2.7 4.3-1.2 3.9-5.7 6-2.6 2.1-1.3 0.8-1.4 0.3-1.4 0.1-1.6-0.3-1.7-0.4-1.6-0.8-1.8-1.1-8-7.2-1.2-0.6-2.4 0-0.9-0.3-0.8-0.8-0.8-1.8-1.5-1-2-0.7-2.8-1.4-1.1-1.8-0.7-2.8 1-6.7 1-2.9 1.7-3.3 0.5-1.6 0.4-4.3-2.6-5.7 2.4-1.9 5-6 1-1.4 0.5-1.3-0.4-1.2-3.6-1.2-0.4-0.9 0-0.9 0.4-0.9 2.3-1.7 0.6-1 0.4-1.5 0.7-1.5 1.3-1 2.6-3.1 0.6-0.6 0.8-1.7 6.5-0.1 6.4-2.1 2.4 0.1 2.8 1.1 1.5-0.2 4.4 0.3z m-6.2 48.2l-4.6-3.9 2.6-7.6-8.3-2-0.9-9.9-7.1 2.6-5.1 8.9 5.6 2.4 0 4.7-7 0.6 0 4.7 7 7.5 5.8 1.3 5.1 6.7 2.8-2.5-3.3-8.1 0.8-2.3 5 1.6 1.6-4.7z" id="county_romania_ilfov_17" name="Ilfov">
+</path>
+<path fill="#f3d973" stroke="#fff"
+ d="m902.5 552.5l-6-1.4-6.1-3.2-6.8 2.1-8.4 0-7.6-3.2-5.3-6.4-6.1 0-2.2 6.4-4.6 1.1-9.1-8.6-5.3-2.1-9.6 0.8 1.6-2.8 0.7-3.1 0.4-3.6 1.1-2.3 2.8-3.8 0.8-1.5-0.1-7.9 0.4-0.7 1.5-1.5-0.1-3.4-1.1-5.4 0.3-1.1 1-1.6 0.1-1.8-0.6-1.2-0.7-4.8 0.3-0.7 1.4-0.7 1.6-1.5 1.2-2 0.4-2-0.9-1.9-1.8-2-2.1-1.6-2-0.6-1.6-1.3 0.5-2.9 1.6-3.1 1.3-2-0.8-1.8-1.3-1.3-3.7 1.5-3.6-1.5-5.8-5.1 1.9-3.1 0.5-2.3 1.1-1.9 0.2-1.1 0.3-4.4 0.6-2.7 1.5-2.4 0.7-0.5 1.1-0.2 4.6-1.6 2.1-0.2 1.9 0.6 3 1.5 1.1 0.2 1-0.6 0.9-3 1.6-2 2.4-0.3 3.1 1.7 2.1 2.9-0.6 3 3.1 8.1 2.1 3.7 2.4 1.6 1.8 0.5 3.7 2.1 9.4 2.4 8.7 4.6 14.1 3.2 3.9-0.5 3.3-0.1 1.4-0.6 1.1-1.4-2.9-1.3-0.8-0.7-0.8-2.5 0.2-1.1 3-1.6-0.1-2.2 0.1-2.2 2.8-0.7 1.6 0.6 2.8 2 2.4 0.4 1.3 1 1.8 1.9 2.1 1.2 2.4-0.9 0.5-1 0.3-2.9 0.6-1.3 1.7-0.9 2.4-0.1 1.4-0.7 5.4-4.5 4.3-1.2 1.8-0.8 1.5-1.6 0.9-0.4 2.6-0.2 0.5-0.4 0.4-1.1 0.9-1.1 1.6-1.4 2.3-1.2 1.7-0.1 1.7 0.3 2.2 0 1.9-1 1.4-1.6 1-0.7 1.1 0.3 2.2 0.9 8.1 0.9 14.7 5.3 4.9 4.4 1.3 0.7 2.3 2.2 1.8 5.2 0.5 5.8-1.4 4.3 0.7 1 0.1 1.1-0.8 2.2-0.9-0.2-2.8 0.1 0 0.9 1.6 2.2 1.1 2.7 1.9 2 3.4 0.4-2.4 2.5-1.3 4.2-0.9 13.1-0.6 2.6-1.3 4.2-0.3 2.1-0.2 2.4-0.2 2.2-0.8 1.4 0.7 3.2-2.7 4.2 1.4 2.7-0.7 1.8-0.9 0.6-1 0.2-1 0.6-1.5 2.3-0.8 0.7-2.1-1.2-1.2 0.3-2.1 0.8-20.6 3.3-13.1 0.8-3.8 1.3-10.5 7.3-3.1 3.1-2.7 3.8-0.8-1.1 0.1-2.2-1.4-2.7-0.7-2.3 2.4-1 2.5-0.5 3.9-2 4.2-1.1 2.4-1.6 1.9-2.6 0.6-3.4-0.6-1.9-1.3-1.9-3.2-3.4 0.6 2.4 0.2 2.8-1.6-0.4-1.6 1.1-1.4 0.5-1.2-2.2-0.5-2.8-0.2-8.9 1.1-2.9 4.9-1.8 1.2-2-0.9-1.5-4.6-3.1-1.4-0.6-3.2 0-3.3-1.1-1.1 0.2-0.7 2.2-0.6 1.3-1.2 0.6-2.3 0.2-1.1 0.4-3.9 3.7-1.1 1.7-0.6 3.2 0.1 0.8 1 0.8 0.3 1-0.3 0.8-1.1 1.1 0.3 1.4 0.7 1.1 0.9 0.8 2.3 1 5.2 5.4-1.4 1.5-1.5 0.5 1.4 1 0 1.2-1.3 0.8-0.1 1.4 0.8 1.5 1.4 1.2-1 0.8-1.4 0.4-4.7 0.1-1.1 0.5-6.3 3.9-1.1 1-1 2.1-0.2 1.9 0.7 0.8 1.5-1.2-0.4 1.8-2 0.7z" id="county_romania_tulcea_13" name="Tulcea">
+</path>
+ <ng-container *ngFor="let county of ids; let i = index">
+ <image xlink:href="assets/images/medkit.svg" height="0" width="0" id="icon_res_{{county.id}}" style="pointer-events:none"/>
+ </ng-container>
+ <ng-container *ngFor="let county of ids; let i = index">
+ <image xlink:href="assets/images/volunteers.svg" height="0" width="0" id="icon_vol_{{county.id}}" style="pointer-events:none"/>
+ </ng-container>
+
+ <rect x="0" y="0" width="0" height="0" fill="#000" id="recttest" style="pointer-events: none"></rect>
+ <text x="0" y="0" fill="#ffffff" id="recttext" style="pointer-events:none"></text>
+</svg>
+</div>
+</div>
+
+
+ ./map.component.scss
+
.svg-box {
+ width: 100%;
+ height: 0;
+ padding-top: 100%; /* Aspect ratio */
+ position: relative;
+}
+
+.svg-box-content {
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+ +
+ src/app/pages/organisations/organisations/components/organisation-details/organisation-details.component.ts
+
+
+ OnInit
+ AfterContentChecked
+
selector | +app-organisation-details |
+
styleUrls | +./organisation-details.component.scss |
+
templateUrl | +./organisation-details.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(route: ActivatedRoute, router: Router, resourceService: ResourcesService, authService: AuthenticationService, organisationService: OrganisationService, filterService: FiltersService, location: Location, userService: UsersService, citiesandcounties: CitiesCountiesService)
+ |
+ ||||||||||||||||||||||||||||||
+ + | +||||||||||||||||||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + addresource + + + + | +
+addresource()
+ |
+
+ + | +
+ navigate to add resource with ngo data +
+ Returns :
+ void
+
+ |
+
+ + + + addvolunteer + + + + | +
+addvolunteer()
+ |
+
+ + | +
+ navigate to add volunteer with ngo data +
+ Returns :
+ void
+
+ |
+
+ + + + canEdit + + + + | +
+canEdit()
+ |
+
+ + | +
+
+
+ Returns :
+ any
+
+ |
+
+ + + + close + + + + | +
+close()
+ |
+
+ + | +
+ manual close for message send popup +
+ Returns :
+ void
+
+ |
+
+ + + + deleteSelf + + + + | +
+deleteSelf()
+ |
+
+ + | +
+ delete NGO +
+ Returns :
+ void
+
+ |
+
+ + + + getData + + + + | +
+getData()
+ |
+
+ + | +
+ get org data +
+ Returns :
+ void
+
+ |
+
+ + + + getResources + + + + | +
+getResources()
+ |
+
+ + | +
+ get resourcesData +
+ Returns :
+ void
+
+ |
+
+ + + + getVolunteers + + + + | +
+getVolunteers()
+ |
+
+ + | +
+ get volunteers data +
+ Returns :
+ void
+
+ |
+
+ + + + ngAfterContentChecked + + + + | +
+ngAfterContentChecked()
+ |
+
+ + | +
+ switch tab of necessary +
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + openMenu + + + + | +||||||||||||
+openMenu(volunteerId: string, status: boolean)
+ |
+ ||||||||||||
+ + | +||||||||||||
+ expand volunteer specialization row for a specific volunteer +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + resourcefilterChanged + + + + | +||||||||
+resourcefilterChanged(id: number)
+ |
+ ||||||||
+ + | +||||||||
+ resource filter callback. Filters added to pager and then a request is made +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + resourceSortChanged + + + + | +||||||
+resourceSortChanged(pager: any)
+ |
+ ||||||
+ + | +||||||
+ sort callback for resource table +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + searchChanged + + + + | +||||||
+searchChanged(pager: any)
+ |
+ ||||||
+ + | +||||||
+ search callback for both tabels +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + sendNotification + + + + | +
+sendNotification()
+ |
+
+ + | +
+ send manual notification and trigger popup +
+ Returns :
+ void
+
+ |
+
+ + + + validateinfo + + + + | +
+validateinfo()
+ |
+
+ + | +
+ send info updated and trigger popup +
+ Returns :
+ void
+
+ |
+
+ + + + volunteerfilterChanged + + + + | +||||||||
+volunteerfilterChanged(id: number)
+ |
+ ||||||||
+ + | +||||||||
+ volunteer filter callback. Filters added to pager and then a request is made +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + volunteerSortChanged + + + + | +||||||
+volunteerSortChanged(pager: any)
+ |
+ ||||||
+ + | +||||||
+ sort callback for volunteers table +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + Public + authService + + + | +
+ Type : AuthenticationService
+
+ |
+
+ + | +
+ + + + categoryFilterValues + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ + + + currentVolunteerId + + + | +
+ Type : string
+
+ |
+
+ Default value : ''
+ |
+
+ + | +
+ store the current voluneer that has courses open + |
+
+ + + + data + + + | +
+ Type : any
+
+ |
+
+ + | +
+ + + + hasResources + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + hasVolunteers + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for ngtemplate in HTML + |
+
+ + + + loading + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for HTML to display loading animation + |
+
+ + + + locationFilterValues + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ + + + messageSent + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for toast message + |
+
+ + + + navigationExtras + + + | +
+ Type : NavigationExtras
+
+ |
+
+ + | +
+ var for data to send when adding new resource. Only for DSU + |
+
+ + + + needupdate + + + | +
+ Default value : false
+ |
+
+ + | +
+ var that holds data about NGO, resources and volunteers + |
+
+ + + + ngoid + + + | +
+ Type : string
+
+ |
+
+ + | +
+ flag used to get ID from link and pass it to get method + |
+
+ + + + nrvol + + + | +
+ Type : number
+
+ |
+
+ Default value : 0
+ |
+
+ + | +
+ + + + propertyMap + + + | +
+ Type : object
+
+ |
+
+ Default value : {
+ '_id': 'id',
+ 'parent_id': 'parent_id'
+ }
+ |
+
+ + | +
+ mapping of object keys to filter recognizable keys + |
+
+ + + + resourceData + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ + + + resourceFiltersSelected + + + | +
+ Default value : Array(2)
+ |
+
+ + | +
+ + + + resourcePager + + + | +
+ Type : any
+
+ |
+
+ Default value : {}
+ |
+
+ + | +
+ var that holds pager and filters for resources and volunteers + |
+
+ + + + selectedTab + + + | +
+ Type : string
+
+ |
+
+ Default value : 'volunteers'
+ |
+
+ + | +
+ + + + specializationFilterValues + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ + + + tabRef + + + | +
+ Type : NgbTabset
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('tabRef', {static: true})
+ |
+
+ + | +
+ Tabs reference for vefifing which is open + |
+
+ + + + tabsInitialized + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + updateSent + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + volunteerFiltersSelected + + + | +
+ Default value : Array(2)
+ |
+
+ + | +
+ + + + volunteerPager + + + | +
+ Type : any
+
+ |
+
+ Default value : {}
+ |
+
+ + | +
+ + + + volunteersData + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ + + + volunteerTypeFilterValues + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ Fliterable values + |
+
import {
+ Component,
+ OnInit,
+ ViewChild,
+ AfterContentChecked,
+} from '@angular/core';
+import { ActivatedRoute, Router, NavigationExtras, ParamMap } from '@angular/router';
+import { OrganisationService } from '../../../organisations.service';
+import { NgbModal, NgbTabset } from '@ng-bootstrap/ng-bootstrap';
+import {
+ FormGroup,
+ Validators,
+ FormBuilder
+} from '@angular/forms';
+import { AuthenticationService } from '../../../../../core/authentication/authentication.service';
+
+import { CitiesCountiesService } from '../../../../../core/service/cities-counties.service';
+import { ResourcesService } from '@app/pages/resources/resources.service';
+import { Location } from '@angular/common';
+import { FiltersService, UsersService } from '@app/core';
+
+/**
+ * Alert message interface
+ */
+interface Alert {
+ type: string;
+ message: string;
+}
+
+@Component({
+ selector: 'app-organisation-details',
+ templateUrl: './organisation-details.component.html',
+ styleUrls: ['./organisation-details.component.scss']
+})
+export class NgodetailsComponent implements OnInit, AfterContentChecked {
+
+ /**
+ * var that holds data about NGO, resources and volunteers
+ */
+ needupdate = false;
+ data: any;
+ resourceData: any[] = [];
+ volunteersData: any[] = [];
+ /**
+ * var that holds pager and filters for resources and volunteers
+ */
+ resourcePager: any = {};
+ resourceFiltersSelected = Array(2);
+ volunteerPager: any = {};
+ volunteerFiltersSelected = Array(2);
+ /**
+ * flag for ngtemplate in HTML
+ */
+ hasVolunteers = false;
+ hasResources = false;
+
+ nrvol = 0;
+ /**
+ * flag used to get ID from link and pass it to get method
+ */
+ ngoid: string;
+ /**
+ * var for data to send when adding new resource. Only for DSU
+ */
+ navigationExtras: NavigationExtras;
+ /**
+ * Fliterable values
+ */
+ volunteerTypeFilterValues: any[] = [];
+ categoryFilterValues: any[] = [];
+ specializationFilterValues: any[] = [];
+ locationFilterValues: any[] = [];
+ /**
+ * Tabs reference for vefifing which is open
+ */
+ @ViewChild('tabRef', { static: true}) tabRef: NgbTabset;
+ tabsInitialized = false;
+ selectedTab = 'volunteers';
+
+ /**
+ * flag for toast message
+ */
+ messageSent = false;
+ updateSent = false;
+ /**
+ * flag for HTML to display loading animation
+ */
+ loading = false;
+ /**
+ * mapping of object keys to filter recognizable keys
+ */
+ propertyMap = {
+ '_id': 'id',
+ 'parent_id': 'parent_id'
+ };
+ /**
+ * store the current voluneer that has courses open
+ */
+ currentVolunteerId = '';
+
+ constructor(
+ private route: ActivatedRoute,
+ private router: Router,
+ private resourceService: ResourcesService,
+ public authService: AuthenticationService,
+ private organisationService: OrganisationService,
+ private filterService: FiltersService,
+ private location: Location,
+ private userService: UsersService,
+ private citiesandcounties: CitiesCountiesService,
+ ) {
+ if (this.router.url.indexOf('validate') > -1) {
+ this.needupdate = true;
+ }
+ /**
+ * set a specific open tab if necessary
+ */
+ const navigation = this.router.getCurrentNavigation();
+
+ if (navigation && navigation.extras && navigation.extras.state) {
+ this.selectedTab = navigation.extras.state.tabName;
+ }
+ }
+
+ ngOnInit() {
+ /**
+ * get values that can be queried for the filters
+ */
+ this.citiesandcounties.getCounties().subscribe((response: any) => {
+ const aux = response;
+ aux.map((elem: { id: any; _id: any; }) => elem.id = elem._id);
+ this.locationFilterValues = aux;
+ });
+ this.filterService.getCategoryFilters().subscribe((data) => {
+ this.categoryFilterValues = data.map((x: any) => {
+ const parent = data.find((y: any) => y._id === x.parent_id);
+ return {
+ id: x._id,
+ name: x.name,
+ parent_id: x.parent_id,
+ pp: x.parent_id === '0' ? x.name : ( parent ? parent.name : null),
+ level: x.parent_id === '0' ? 0 : 1
+ };
+ });
+ });
+ this.filterService.getSpecializationFilters().subscribe((data) => {
+ this.specializationFilterValues = data.map((elem: any) => {
+ return {id: elem._id, name: elem.name};
+ });
+ });
+ /**
+ * get current id, init pager, and get all data with the id
+ */
+ this.ngoid = this.route.snapshot.paramMap.get('id');
+ this.volunteerPager = this.organisationService.getVolunteerPager();
+ this.resourcePager = this.organisationService.getResourcePager();
+ this.getData();
+ this.getResources();
+ this.getVolunteers();
+ }
+ /**
+ * switch tab of necessary
+ */
+ ngAfterContentChecked() {
+ if (this.tabRef.tabs) {
+ this.tabRef.select(this.selectedTab);
+ }
+ }
+ /**
+ * get org data
+ */
+ getData() {
+ this.organisationService.getorganisation(this.ngoid).subscribe(data => {
+ this.data = data;
+ this.navigationExtras = {
+ state: {
+ ngo: {
+ name: data.name,
+ _id: this.ngoid
+ }
+ }
+ };
+ });
+ }
+ /**
+ * get volunteers data
+ */
+ getVolunteers() {
+ this.organisationService.getVolunteersbyorganisation(this.ngoid, this.volunteerPager).subscribe(data => {
+ this.volunteerPager.total = data.pager.total;
+ this.volunteersData = data.data;
+ if (!!data.data.courses) {
+ data.data.courses = data.data.courses.reverse();
+ }
+ if (Object.entries(this.volunteerPager.filters).length === 0 && this.volunteerPager.filters.constructor === Object) {
+ if (this.volunteersData.length === 0) {
+ this.hasVolunteers = false;
+ } else {
+ this.hasVolunteers = true;
+ this.nrvol = data.pager.total;
+ }
+ } else {
+ this.hasVolunteers = true;
+ }
+ });
+ }
+ /**
+ * get resourcesData
+ */
+ getResources() {
+ this.organisationService.getResourcesbyorganisation(this.ngoid, this.resourcePager).subscribe(data => {
+ this.resourcePager.total = data.pager.total;
+ this.resourceData = data.data;
+ if (Object.entries(this.volunteerPager.filters).length === 0 &&
+ this.volunteerPager.filters.constructor === Object &&
+ this.resourceData.length === 0) {
+
+ this.hasResources = false;
+ } else {
+ this.hasResources = true;
+ }
+ });
+ }
+ /**
+ * resource filter callback. Filters added to pager and then a request is made
+ * @param {number} id the index in the pager filters and filters Selected array
+ */
+ resourcefilterChanged(id: number) {
+ this.resourcePager.filters[id] = this.resourceFiltersSelected[id].map((elem: any) => elem.id).join(',');
+ this.getResources();
+ }
+/**
+ * volunteer filter callback. Filters added to pager and then a request is made
+ * @param {number} id the index in the pager filters and filters Selected array
+ */
+ volunteerfilterChanged(id: number) {
+ this.volunteerPager.filters[id] = this.volunteerFiltersSelected[id].map((elem: any) => elem.id).join(',');
+ this.getVolunteers();
+ }
+ // deleteRes(id: string) {
+ // this.resourceService.deleteResource(id).subscribe(resp => {
+ // this.getResources();
+ // });
+ // }
+ /**
+ * delete NGO
+ */
+ deleteSelf() {
+ if (this.authService.user._id === this.data._id) {
+ if (confirm('Sunteți sigur că doriți să vă ștergeți contul?')) {
+ this.loading = true;
+ this.organisationService.deleteorganisation(this.ngoid).subscribe(data => {
+ this.loading = false;
+ this.authService.setCredentials();
+ this.router.navigateByUrl('/login');
+ }, () => {
+ this.location.back();
+ });
+ }
+ } else {
+ if (confirm('Sunteți sigur că doriți să ștergeți această intrare? Odată ștearsă nu va mai putea fi recuperată.')) {
+ this.loading = true;
+ this.organisationService.deleteorganisation(this.ngoid).subscribe(data => {
+ this.loading = false;
+ this.router.navigateByUrl('/organisations');
+ }, () => {
+ this.loading = false;
+ });
+ }
+ }
+ }
+ /**
+ * navigate to add resource with ngo data
+ */
+ addresource() {
+ this.router.navigateByUrl('/resources/add', this.navigationExtras);
+ }
+ /**
+ * navigate to add volunteer with ngo data
+ */
+ addvolunteer() {
+ this.router.navigateByUrl('/volunteers/add', this.navigationExtras);
+ }
+ /**
+ * sort callback for volunteers table
+ */
+ volunteerSortChanged(pager: any) {
+ this.volunteerPager = pager;
+ this.getVolunteers();
+ }
+ /**
+ * sort callback for resource table
+ */
+ resourceSortChanged(pager: any) {
+ this.resourcePager = pager;
+ this.getResources();
+ }
+/**
+ * search callback for both tabels
+ */
+ searchChanged(pager: any) {
+ if (pager.search !== '') {
+ if (this.selectedTab === 'volunteers') {
+ this.volunteerPager = pager;
+ this.getVolunteers();
+ } else {
+ this.resourcePager = pager;
+ this.getResources();
+ }
+ }
+ }
+ /**
+ * send manual notification and trigger popup
+ */
+ sendNotification() {
+ this.organisationService.sendUpdateDataEmail(this.ngoid).subscribe(() => {
+ this.messageSent = true;
+ setTimeout(() => this.close(), 5000);
+ });
+ }
+ /**
+ * send info updated and trigger popup
+ */
+ validateinfo() {
+ this.organisationService.updated(this.ngoid).subscribe(() => {
+ this.updateSent = true;
+ this.needupdate = false;
+ setTimeout(() => this.close(), 5000);
+ });
+ }
+ /**
+ * manual close for message send popup
+ */
+ close() {
+ this.messageSent = false;
+ this.updateSent = false;
+ }
+ /**
+ * expand volunteer specialization row for a specific volunteer
+ * @param {string} volunteerId of the current voluneer that is referenced
+ * @param {boolean} status is open or is cloed
+ */
+ openMenu(volunteerId: string, status: boolean) {
+ if (status) {
+ this.currentVolunteerId = volunteerId;
+ } else {
+ this.currentVolunteerId = null;
+ }
+ }
+
+ canEdit() {
+ if (this.data) {
+ return this.authService.is('DSU') || (this.authService.is('NGO') && this.data._id === this.authService.user.organisation._id);
+ } else {
+ return false;
+ }
+ }
+}
+
+ <div class="container">
+ <nav class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <app-back-button *ngIf="authService.is('DSU')"></app-back-button>
+ <div class="form-inline ml-auto">
+ <app-table-search *ngIf="selectedTab === 'volunteers'; else resource" id=2 [pager]="volunteerPager" (searchChanged)="searchChanged($event)"></app-table-search>
+ <ng-template #resource>
+ <app-table-search [pager]="resourcePager" id=2 (searchChanged)="searchChanged($event)"></app-table-search>
+ </ng-template>
+ <button placement="bottom" ngbTooltip="Șterge organizația" *ngIf="authService.is('DSU') && !loading" (click)="deleteSelf()" class="btn btn-danger mx-2 my-sm-0">
+ <span class="fa fa-trash-o"></span>
+ <span class="d-none d-xl-inline"> Șterge Organizația</span>
+ </button>
+ <div class="spinner-border text-danger" role="status" *ngIf="authService.is('DSU') && loading">
+ <span class="sr-only">Loading...</span>
+ </div>
+ <button placement="bottom" ngbTooltip="Modifică organizația" *ngIf="canEdit() && !loading" [routerLink]="['/organisations/edit/',data?._id]" class="btn btn-info mx-2 my-sm-0">
+ <span class="fa fa-edit"></span>
+ <span class="d-none d-xl-inline"> Modifică Organizația</span>
+ </button>
+ <div *ngIf="hasResources && selectedTab === 'resources'">
+ <button placement="bottom" ngbTooltip="Adaugă o resursă" (click)="addresource()" class="btn btn-info m-2 my-sm-0 d-block">
+ <span class="fa fa-plus-circle"></span>
+ <span class="d-none d-xl-inline"> Adaugă Resursă</span>
+ </button>
+ </div>
+ <div *ngIf="hasVolunteers && selectedTab === 'volunteers'">
+ <button placement="bottom" ngbTooltip="Adaugă un voluntar" (click)="addvolunteer()" class="btn btn-info m-2 my-sm-0 d-block">
+ <span class="fa fa-plus-circle"></span>
+ <span class="d-none d-xl-inline"> Adaugă Voluntar</span>
+ </button>
+ </div>
+ </div>
+ </nav>
+ <ngb-alert *ngIf="messageSent" type="success" (close)="close()">Notificarea a fost trimisă</ngb-alert>
+ <ngb-alert *ngIf="updateSent" type="success" (close)="close()">Vă mulțumim că v-ați actualizat înregistrările.</ngb-alert>
+ <div class="ngo-details row">
+ <h3 class="col-md-12">Profil organizație: {{data?.name}}</h3>
+ <h5 class="col-md-12" style="margin-top:5px; font-weight: 500">Persoană de contact: {{data?.contact_person?.name}}
+ </h5>
+ <span class="col-md-12"><span class="fa fa-envelope"></span> {{data?.contact_person?.email}}</span>
+ <span class="col-md-12"><span class="fa fa-phone"></span> {{data?.contact_person?.phone}}</span>
+ <span class="col-md-12"><b>Status organizație:</b> {{data?.status || 'activ' }}</span>
+ <span class="col-md-12"><b>Total voluntari:</b> {{nrvol}}</span>
+ <span class="col-md-12"><b>Acoperire:</b> {{data?.cover || 'Națională' }}</span>
+ <div class="col-md-12">
+ <span><b>Data ultimului update: </b> {{ (data?.updated_at | date: 'dd.MM.yyyy') || 'Nu există' }}</span>
+ <button *ngIf="authService.is('DSU')" class="btn mx-3 btn-info" (click)="sendNotification()"><span
+ class="fa fa-plus-circle"></span> Trimite Notificare</button>
+ </div>
+ </div>
+ <div class="resource-list">
+ <ngb-tabset type="pills" #tabRef>
+ <ngb-tab id="volunteers">
+ <ng-template ngbTabTitle>
+ <a (click)="selectedTab = 'volunteers'">Listă Voluntari</a>
+ </ng-template>
+ <ng-template ngbTabContent>
+ <div *ngIf="hasVolunteers; else noVolunteers">
+ <div class="row">
+ <div class="filters col-md-9 col-sm-9 col-xs-9">
+ <span class="padding-rem">Total: {{volunteerPager.total}}</span>
+ <span class="padding-rem"> </span>
+ <span class="padding-rem">Filtrează după: </span>
+ <ngx-multiselect
+ class="location"
+ [(ngModel)]="volunteerFiltersSelected[0]"
+ showMaxLables = "1"
+ [options]="locationFilterValues"
+ (onItemClick)="volunteerfilterChanged(0)"
+ (onSelectNone)="volunteerfilterChanged(0)"
+ (onSelectAll)="volunteerfilterChanged(0)">
+ </ngx-multiselect>
+ <span class="vertical-bar"> </span>
+ <ngx-multiselect
+ class="specialization"
+ [(ngModel)]="volunteerFiltersSelected[1]"
+ showMaxLables = "1"
+ [options]="specializationFilterValues"
+ (onItemClick)="volunteerfilterChanged(1)"
+ (onSelectNone)="volunteerfilterChanged(1)"
+ (onSelectAll)="volunteerfilterChanged(1)">
+ </ngx-multiselect>
+ </div>
+ <div class="col-md-3 col-sm-3 col-xs-3">
+ <button *ngIf = "authService.is('NGO') && needupdate" class="d-none d-md-inline pull-right btn btn-info" (click)="validateinfo()"><span
+ class="fa fa-check-square"></span> Valideaza datele</button>
+ <button *ngIf = "authService.is('NGO') && needupdate" class="d-md-none pull-right btn btn-info" (click)="validateinfo()"><span
+ class="fa fa-check-square"></span></button>
+ </div>
+ </div>
+ <div class="table-responsive-md">
+ <table class="table customTable table-sm">
+ <thead>
+ <tr>
+ <th scope="col" appTableSort [pager]="volunteerPager" [value]="1"
+ (sortChanged)="volunteerSortChanged($event)">Nume</th>
+ <th scope="col" appTableSort [pager]="volunteerPager" [value]="3"
+ (sortChanged)="volunteerSortChanged($event)">Localizare</th>
+ <th scope="col" class="non-clickable">Specializare</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let res of volunteersData">
+
+ <td>{{res.name}}</td>
+ <td>{{res.city.name}}, {{res.county.name}}</td>
+ <td>
+ <table class="no-borders">
+ <tr *ngFor="let course of res.courses; let i = index; ">
+ <td *ngIf="currentVolunteerId === res._id || i===0">
+ {{ course?.course_name?.name }}
+ </td>
+ <td *ngIf="currentVolunteerId === res._id || i===0">
+ {{ course.obtained | date: 'dd.MM.yyyy' }}
+ </td>
+ </tr>
+ <!-- <td *ngIf="currentVolunteerId !== res._id">
+ {{ res.courses[0]?.course_name?.name}}
+ </td>
+ <td *ngIf="currentVolunteerId === res._id">
+ <p *ngFor="let course of res.courses">
+ {{ course.obtained | date: 'dd.MM.yyyy' }}
+ </p>
+ </td>
+
+ <td *ngIf="currentVolunteerId !== res._id">
+ {{ res.courses[0]?.obtained | date: 'dd.MM.yyyy'}}
+ </td> -->
+
+ </table>
+ </td>
+ <td>
+ <div class="d-flex text-right">
+ <button class="btn btn-info m-1 d-md-none"
+ [routerLink]="['/volunteers/id',res._id]">
+ <span class="fa fa-eye"></span></button>
+ <button class="btn btn-info m-1 d-none d-md-block"
+ [routerLink]="['/volunteers/id',res._id]">Vezi detalii</button>
+ <i class="fa fa-chevron-down clickable m-auto" [ngClass]="{'show' : res.courses.length > 1, 'hide' : res.courses.length <= 1}" (click)="openMenu(res._id, true)" *ngIf="currentVolunteerId !== res._id"></i>
+ <i class="fa fa-chevron-up clickable m-auto" [ngClass]="{'show' : res.courses.length > 1, 'hide' : res.courses.length <= 1}" (click)="openMenu(res._id, false)" *ngIf="currentVolunteerId === res._id && res.courses.length > 1"></i>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <div class = "row">
+ <div class="col-md-9 col-sm-9 col-xs-9">
+ <ngb-pagination
+ [maxSize]="5" [(page)]="volunteerPager.page" [pageSize]="volunteerPager.size"
+ (pageChange)="getVolunteers()" [collectionSize]="volunteerPager.total">
+ </ngb-pagination>
+ </div>
+ <div class="col-md-3 col-sm-3 col-xs-3">
+ <button *ngIf = "authService.is('NGO') && needupdate" class="d-none d-md-inline pull-right btn btn-info" (click)="validateinfo()"><span
+ class="fa fa-check-square"></span> Valideaza datele</button>
+ <button *ngIf = "authService.is('NGO') && needupdate" class="d-md-none pull-right btn btn-info" (click)="validateinfo()"><span
+ class="fa fa-check-square"></span></button>
+ </div>
+
+ </div>
+ </div>
+ <ng-template #noVolunteers>
+ <span>Această organizație nu are niciun voluntar adaugăt în sistem </span><button
+ (click)="addvolunteer()" class="btn btn-info d-block"><span
+ class="fa fa-plus-circle"></span> Adaugă Voluntar</button>
+ </ng-template>
+ </ng-template>
+ </ngb-tab>
+ <ngb-tab title="|" [disabled]="true"></ngb-tab>
+ <ngb-tab id="resources">
+ <ng-template ngbTabTitle>
+ <a (click)="selectedTab ='resources'">Listă Resurse</a></ng-template>
+ <ng-template ngbTabContent>
+ <div *ngIf="hasResources; else noResources">
+ <div class="row">
+ <div class="filters col-md-9 col-xs-9 col-sm-9">
+ <span class="padding-rem">Total: {{resourcePager.total}}</span>
+ <span class="padding-rem"> </span>
+ <span class="padding-rem">Filtrează după: </span>
+ <ngx-multiselect class="category"
+ [(ngModel)]="resourceFiltersSelected[0]"
+ [options]="categoryFilterValues"
+ (showMaxLables) = "1"
+ (onItemClick)="resourcefilterChanged(0)"
+ (onSelectNone)="resourcefilterChanged(0)"
+ (onSelectAll)="resourcefilterChanged(0)"
+ [propertyMap]="propertyMap" >
+ <ng-template let-option="option">
+ <span [ngClass]="{'pl-3': option.parent_id!=='0'}">
+ {{option.name}}
+ </span>
+ </ng-template>
+ </ngx-multiselect>
+ <span class="vertical-bar"> </span>
+ <ngx-multiselect
+ class="location"
+ [(ngModel)]="resourceFiltersSelected[1]"
+ showMaxLables = "1"
+ [options]="locationFilterValues"
+ (onItemClick)="resourcefilterChanged(1)"
+ (onSelectNone)="resourcefilterChanged(1)"
+ (onSelectAll)="resourcefilterChanged(1)">
+ </ngx-multiselect>
+ </div>
+ <div class="col-md-3 col-sm-3 col-xs-3">
+ <button *ngIf = "authService.is('NGO') && needupdate" class="d-none d-md-inline pull-right btn btn-info" (click)="validateinfo()"><span
+ class="fa fa-check-square"></span> Valideaza datele</button>
+ <button *ngIf = "authService.is('NGO') && needupdate" class="d-md-none pull-right btn btn-info" (click)="validateinfo()"><span
+ class="fa fa-check-square"></span></button>
+ </div>
+ </div>
+
+ <div class="table-responsive-md">
+ <table class="table customTable table-sm">
+ <thead>
+ <tr>
+ <!-- sortable="name" (sort)="onSort($event)" -->
+ <th scope="col" appTableSort [pager]="resourcePager" [value]="1"
+ (sortChanged)="resourceSortChanged($event)">Nume Resursă</th>
+ <th scope="col" appTableSort [pager]="resourcePager" [value]="2"
+ (sortChanged)="resourceSortChanged($event)">Categorie</th>
+ <th scope="col" appTableSort [pager]="resourcePager" [value]="3"
+ (sortChanged)="resourceSortChanged($event)">Cantitate</th>
+ <th scope="col" appTableSort [pager]="resourcePager" [value]="4"
+ (sortChanged)="resourceSortChanged($event)">Localizare</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let res of resourceData">
+ <td>{{res.name}}</td>
+ <td><div *ngFor="let elem of res?.categories">
+ {{elem.name}}
+ </div></td>
+ <td>{{res.quantity}}</td>
+ <td>{{res.city.name}}, {{res.county.name}}</td>
+ <td class="text-right">
+ <!-- <button class="btn btn-info m-1 d-none d-md-block"
+ [routerLink]="['/resources/edit/', res._id]">
+ <span class="fa fa-edit"></span> Editează</button>
+ <button class="d-md-none pull-right btn btn-info"
+ [routerLink]="['/resources/edit/', res._id]">
+ <span class="fa fa-edit"></span></button> -->
+ <button class="btn btn-danger m-1 d-none d-md-block" (click)="deleteRes(res._id)">
+ <span class="fa fa-trash-o"></span> Șterge</button>
+ <button class="d-md-none pull-right btn btn-danger" (click)="deleteRes(res._id)"><span
+ class="fa fa-trash-o"></span></button>
+ <button *ngIf="authService.is('DSU')" class="btn btn-info m-1 d-md-none"
+ [routerLink]="['/resources/name',res.slug]">
+ <span class="fa fa-eye"></span></button>
+ <button *ngIf="authService.is('DSU')" class="btn btn-info m-1 d-none d-md-block"
+ [routerLink]="['/resources/name',res.slug]">Edit</button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <div class="row">
+ <div class="col-md-9 col-xs-9 col-sm-9">
+ <ngb-pagination [maxSize]="5" [(page)]="resourcePager.page" [pageSize]="resourcePager.size"
+ (pageChange)="getResources()" [collectionSize]="resourcePager.total">
+ </ngb-pagination>
+ </div>
+ <div class="col-md-3 col-sm-3 col-xs-3">
+ <button *ngIf = "authService.is('NGO') && needupdate" class="d-none d-md-inline pull-right btn btn-info" (click)="validateinfo()"><span
+ class="fa fa-check-square"></span> Valideaza datele</button>
+ <button *ngIf = "authService.is('NGO') && needupdate" class="d-md-none pull-right btn btn-info" (click)="validateinfo()"><span
+ class="fa fa-check-square"></span></button>
+ </div>
+ </div>
+
+ </div>
+ </div>
+ <ng-template #noResources>
+ <span>Această organizație nu are nici o resursă adaugată în sistem</span>
+ <button class="btn btn-info d-block" (click)="addresource()">
+ <span class="fa fa-plus-circle"></span>
+ Adaugă Resursă
+ </button>
+ </ng-template>
+ </ng-template>
+ </ngb-tab>
+ </ngb-tabset>
+ </div>
+</div>
+
+ ./organisation-details.component.scss
+
::ng-deep .category{
+ .none-selected:before {
+ content: 'Categorie' !important;
+ }
+}
+::ng-deep .specialization{
+ .none-selected:before{
+ content:'Specializări' !important;
+ }
+}
+
+.hide {
+ visibility: hidden;
+}
+
+.show {
+ visibility: unset;
+}
+button{
+ word-break: break-word;
+}
+td{
+ p{
+ word-break: break-word;
+ }
+}
+.no-borders{
+ td{
+ border:none;
+ padding: 5px;
+ word-break: normal;
+ }
+}
+.non-clickable{
+ cursor: default !important;
+}
+ +
+ src/app/pages/404/not-found.component.ts
+
+
+ AfterViewInit
+
selector | +app-not-found |
+
styleUrls | +not-found.component.scss |
+
templateUrl | +./not-found.component.html |
+
+ Methods+ |
+
+
|
+
+ + + + ngAfterViewInit + + + + | +
+ngAfterViewInit()
+ |
+
+ Defined in src/app/pages/404/not-found.component.ts:14
+ |
+
+ Component to show when a page is not found. Only displays 404 message +
+ Returns :
+ void
+
+ |
+
import { Component, AfterViewInit } from '@angular/core';
+
+@Component({
+ selector: 'app-not-found',
+ templateUrl: './not-found.component.html',
+ styleUrls: ['not-found.component.scss']
+})
+
+export class NotFoundComponent implements AfterViewInit {
+ /**
+ * Component to show when a page is not found. Only displays 404 message
+ */
+
+ ngAfterViewInit() {
+
+ }
+}
+
+ <div class="error-box">
+ <div class="error-body text-center">
+ <h1>404</h1>
+ <h3 class="text-uppercase">Pagină inexistentă!</h3>
+ <p class="text-muted m-t-30 m-b-30">Pari pierdut ... căutați drumul către pagina de start?</p>
+ <a class="btn btn-info btn-rounded waves-effect waves-light m-b-40" routerLink="/" >Înapoi la pagina principală</a> </div>
+</div>
+
+ not-found.component.scss
+
.error-box {
+ height: 100%;
+ position: fixed;
+ // background: url(../../../assets/images/background/error-bg.jpg) no-repeat center center #fff;
+ width: 100%; }
+ .error-box .footer {
+ width: 100%;
+ left: 0px;
+ right: 0px; }
+
+.error-body {
+ padding-top: 5%; }
+ .error-body h1 {
+ font-size: 210px;
+ font-weight: 900;
+ text-shadow: 4px 4px 0 #ffffff, 6px 6px 0 #263238;
+ line-height: 210px; }
+
+@media(max-width:767px) {
+ .error-box {
+ position: relative;
+ padding-bottom: 60px;
+ }
+ .error-body {
+ padding-top: 10%;
+ }
+ .error-body h1 {
+ font-size: 100px;
+ font-weight: 600;
+ line-height: 100px;
+ }
+}
+ +
+ src/app/pages/organisations/organisations/components/organisation-edit/organisation-edit.component.ts
+
+
+ OnInit
+
selector | +app-organisation-edit |
+
styleUrls | +./organisation-edit.component.scss |
+
templateUrl | +./organisation-edit.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(route: ActivatedRoute, organisationService: OrganisationService, utilService: UtilService, userService: UsersService, location: Location, citiesandCounties: CitiesCountiesService, fb: FormBuilder)
+ |
+ ||||||||||||||||||||||||
+ + | +||||||||||||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + countykey + + + + | +||||||||
+countykey(event: any)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for editing the county field. When activated, disable the city form until enter is pressed or mouse selection +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + getOrganisationDetails + + + + | +||||||
+getOrganisationDetails(ngoId: string)
+ |
+ ||||||
+ + | +||||||
+ get the details of the organisation when edititing +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + onSubmit + + + + | +
+onSubmit()
+ |
+
+ + | +
+ Process form values and send data to server. If success close page +
+ Returns :
+ void
+
+ |
+
+ + + + selectedCity + + + + | +||||||||
+selectedCity(val: literal type)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select city from city typeahead +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + selectedCounty + + + + | +||||||||
+selectedCounty(val: any)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select county from county typeahead. will unlock the city field +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + cities + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ list of cities to pe parsed. edited when the user selects a county or edits this NGO + |
+
+ + + + cityPlaceholder + + + | +
+ Type : string
+
+ |
+
+ Default value : 'Selectați mai întâi județul'
+ |
+
+ + | +
+ placeholder for disabled city field + |
+
+ + + + click1$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click2$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus1$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus2$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + form + + + | +
+ Type : FormGroup
+
+ |
+
+ + | +
+ form that holds data + |
+
+ + + + formatter + + + | +
+ Default value : () => {...}
+ |
+
+ + | +
+ formatter to extract name from object and display in input + |
+
+ + + + instance1 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ references to NGBTypeahead for opening on focus or click + |
+
+ + + + instance2 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + loading + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for HTML to display loading animation + |
+
+ + + + loadingCities + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + searchcity + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for city typeahead. registers typing, focus, and click and searches the stored list of cities + |
+ ||||
+
+ Parameters :
+
+
|
+
+ + + + searchcounty + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for county typeahead. registers typing, focus, and click and searches the backend + |
+ ||||
+
+ Parameters :
+
+
|
+
import { Component, OnInit, ViewChild } from '@angular/core';
+import {
+ FormGroup,
+ Validators,
+ FormBuilder
+} from '@angular/forms';
+import { OrganisationService } from '../../../organisations.service';
+import { Router, ActivatedRoute } from '@angular/router';
+import { CitiesCountiesService } from '../../../../../core/service/cities-counties.service';
+import { Subject } from 'rxjs/internal/Subject';
+import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
+import { Observable } from 'rxjs/internal/Observable';
+import {
+ debounceTime,
+ distinctUntilChanged,
+ filter,
+ map,
+ switchMap
+} from 'rxjs/operators';
+import { merge } from 'rxjs';
+import { EmailValidation } from '@app/core/validators/email-validation';
+import { PhoneValidation } from '@app/core/validators/phone-validation';
+import { WebsiteValidation } from '@app/core/validators/website-validation';
+import { Location } from '@angular/common';
+import { LocationValidation } from '@app/core/validators/location-validation';
+import { UtilService, UsersService } from '@app/core';
+
+@Component({
+ selector: 'app-organisation-edit',
+ templateUrl: './organisation-edit.component.html',
+ styleUrls: ['./organisation-edit.component.scss']
+})
+
+export class OrganisationEditComponent implements OnInit {
+ /**
+ * form that holds data
+ */
+ form: FormGroup;
+
+ /**
+ * placeholder for disabled city field
+ */
+ cityPlaceholder = 'Selectați mai întâi județul';
+
+ /**
+ * references to NGBTypeahead for opening on focus or click
+ */
+ @ViewChild('instance', { static: true }) instance1: NgbTypeahead;
+ focus1$ = new Subject<string>();
+ click1$ = new Subject<string>();
+
+ @ViewChild('instance', { static: true }) instance2: NgbTypeahead;
+ focus2$ = new Subject<string>();
+ click2$ = new Subject<string>();
+ /**
+ * flag for HTML to display loading animation
+ */
+ loading = false;
+ loadingCities = false;
+ /**
+ * list of cities to pe parsed. edited when the user selects a county or edits this NGO
+ */
+ cities: any[] = [];
+
+ constructor(
+ private route: ActivatedRoute,
+ private organisationService: OrganisationService,
+ private utilService: UtilService,
+ private userService: UsersService,
+ private location: Location,
+ private citiesandCounties: CitiesCountiesService,
+ private fb: FormBuilder) { }
+
+ ngOnInit() {
+ /**
+ * build form because otherwise the frontend will crash before the {@link getOrganisationDetails} ends
+ */
+ this.form = this.fb.group({
+ name: ['', [Validators.required]],
+ website: ['', [Validators.required, WebsiteValidation.websiteValidation]],
+ contact_person: ['', Validators.required],
+ phone: ['', [Validators.required, PhoneValidation.phoneValidation]],
+ address: [''],
+ cover: [''],
+ email: ['', [Validators.required, EmailValidation.emailValidation]],
+ county: ['', [Validators.required, LocationValidation.locationValidation]],
+ city: [{value: '', disabled: true }, [Validators.required, ]],
+ comments: ['']
+ });
+ this.getOrganisationDetails(this.route.snapshot.paramMap.get('id'));
+ }
+ /**
+ * formatter to extract name from object and display in input
+ */
+ formatter = (result: { name: string }) => result.name;
+ /**
+ * get the details of the organisation when edititing
+ * @param {string} id of the edited NGO
+ */
+ getOrganisationDetails(ngoId: string) {
+ if (ngoId) {
+ this.organisationService.getorganisation(ngoId).subscribe(data => {
+ this.form = this.fb.group({
+ name: [data.name ],
+ cover: [data.cover],
+ website: [data.website, [Validators.required, WebsiteValidation.websiteValidation]],
+ contact_person: [data.contact_person.name, Validators.required],
+ phone: [data.contact_person.phone, [Validators.required, PhoneValidation.phoneValidation]],
+ address: [data.address],
+ email: [data.contact_person.email, [Validators.required, EmailValidation.emailValidation]],
+ county: ['', [Validators.required, LocationValidation.locationValidation]],
+ city: ['', [Validators.required]],
+ comments: [data.comments]
+ });
+ this.selectedCounty({item: data.county});
+ this.selectedCity({item: data.city});
+ });
+ }
+ }
+ /**
+ * trigger for county typeahead. registers typing, focus, and click and searches the backend
+ * @param {Observable} text observable event with the filter text
+ */
+ searchcounty = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+ const clicksWithClosedPopup$ = this.click1$.pipe(
+ filter(() => !this.instance1.isPopupOpen())
+ );
+ const inputFocus$ = this.focus1$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ switchMap((term: string) => this.citiesandCounties.getCounties(term))
+ );
+ }
+ /**
+ * trigger for city typeahead. registers typing, focus, and click and searches the stored list of cities
+ * @param {Observable} text observable event with the filter text
+ */
+ searchcity = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+ const clicksWithClosedPopup$ = this.click2$.pipe(
+ filter(() => !this.instance2.isPopupOpen())
+ );
+ const inputFocus$ = this.focus2$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ map((term: string) => {
+ if (term === '') {
+ return this.cities;
+ } else {
+ return this.cities.filter(v => {
+ const aux: String = this.utilService.removeDiacritics(v.name).toLowerCase();
+ return aux.indexOf(term.toLowerCase()) > -1;
+ }).slice(0, 20);
+ }
+ }));
+ }
+ /**
+ * trigger for select county from county typeahead. will unlock the city field
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedCounty(val: any) {
+ this.form.controls.county.markAsTouched();
+ if (val.item && val.item._id) {
+ this.form.patchValue({county: val.item});
+ this.loadingCities = true;
+ this.citiesandCounties.getCitiesbyCounty(val.item._id, '').subscribe((res: any) => {
+ this.cities = res;
+ this.cityPlaceholder = 'Alegeți Orașul';
+ this.loadingCities = false;
+ this.form.controls.city.enable();
+ });
+ } else if (this.form.controls.county.value.name && val !== this.form.controls.county.value.name) {
+ this.form.patchValue({county: '', city: ''});
+ }
+ }
+
+ /**
+ * trigger for editing the county field. When activated, disable the city form until enter is pressed or mouse selection
+ * @param {any} event to be verified for which key has been pressed
+ */
+ countykey(event: any) {
+ this.form.controls.county.markAsTouched();
+ if (event.code !== 'Enter') {
+ this.cities = [];
+ this.form.controls.city.disable();
+ this.form.controls.city.reset('');
+ this.cityPlaceholder = 'Selectați mai întâi județul';
+ }
+ }
+ /**
+ * trigger for select city from city typeahead
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedCity(val: { item: any }) {
+ this.form.controls.city.markAsTouched();
+ this.form.patchValue({city: val.item});
+ }
+
+ /**
+ * Process form values and send data to server. If success close page
+ */
+ onSubmit() {
+ const ngoid = this.route.snapshot.paramMap.get('id');
+ this.loading = true;
+ const ngo = this.form.value;
+ ngo.city = ngo.city._id;
+ ngo.county = ngo.county._id;
+
+ this.organisationService.editOrganisation(ngoid, this.form.value).subscribe(() => {
+ this.loading = false;
+ this.location.back();
+ }, () => {
+ this.loading = false;
+ });
+ }
+}
+
+ <div class="container wide-container mt-5">
+ <nav
+ class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <app-back-button></app-back-button>
+ </nav>
+ <p class="page-title">Modifică organizația:</p>
+
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
+ <div class="row">
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Nume Organizație *</label>
+ <input formControlName="name" class="form-control" type="text"
+ [ngClass]="{ 'error': form.controls.name.invalid && form.controls.name.touched }" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Website Organizație *</label>
+ <input formControlName="website" class="form-control" type="text"
+ [ngClass]="{ 'error': form.controls.website.invalid && form.controls.website.touched }" />
+ <span class="error-message" *ngIf="form.controls.website.invalid && form.controls.website.touched">* {{form.controls.website.errors.website}}</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Acoperire *</label>
+ <select class="form-control" formControlName="cover"
+ [ngClass]="{ 'error': form.controls.cover.invalid && form.controls.cover.touched }"
+ class="form-control">
+ <option>Națională</option>
+ <option>Locală</option>
+ </select>
+ <span class="error-message" *ngIf="form.controls.cover.invalid && form.controls.cover.touched">* Trebuie să alegeți un tip.</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Persoană de contact *</label>
+ <input formControlName="contact_person" class="form-control" type="text"
+ [ngClass]="{ 'error': form.controls.contact_person.invalid && form.controls.contact_person.touched }" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Email *</label>
+ <input formControlName="email" class="form-control" type="email"
+ [ngClass]="{ 'error': form.controls.email.invalid && form.controls.email.touched }" />
+ <span class="error-message" *ngIf="form.controls.email.invalid && form.controls.email.touched">* {{form.controls.email.errors.email}}</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Număr de Telefon *</label>
+ <input formControlName="phone" class="form-control" type="tel"
+ [ngClass]="{ 'error': form.controls.phone.invalid && form.controls.phone.touched }" />
+ <span class="error-message" *ngIf="form.controls.phone.invalid && form.controls.phone.touched">* {{form.controls.phone.errors.phone}}</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Județ *</label>
+ <input type="text" formControlName="county" (selectItem)="selectedCounty($event)" (blur)="selectedCounty($event.target.value)"
+ class="form-control" [ngbTypeahead]="searchcounty" (focus)="focus1$.next($event.target.value)"
+ (keyup)="countykey($event)"
+ [inputFormatter]="formatter" [resultFormatter]="formatter"
+ [ngClass]="{ 'error': form.controls.county.invalid && form.controls.county.touched}"
+ (click)="click1$.next($event.target.value)" #instance="ngbTypeahead" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Localitate *</label>
+ <input type="text" formControlName="city" class="form-control"
+ [ngbTypeahead]="searchcity" (selectItem)="selectedCity($event)"
+ [ngClass]="{ 'error': form.controls.city.invalid && form.controls.city.touched }"
+ placeholder="{{ cityPlaceholder }}" autocomplete="new-password" [inputFormatter]="formatter"
+ [resultFormatter]="formatter" (focus)="focus2$.next($event.target.value)"
+ (click)="click2$.next($event.target.value)" #instance="ngbTypeahead" />
+ <div class="spinner-border text-grey input-loader" role="status" *ngIf="loadingCities">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Adresă</label>
+ <input formControlName="address" class="form-control" type="text"
+ [ngClass]="{ 'error': form.controls.address.invalid && form.controls.address.touched}" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-12">
+ <div class="form-group">
+ <label>Comentarii</label>
+ <textarea rows="3" formControlName="comments" class="form-control" type="text"
+ [ngClass]="{ 'error': form.controls.comments.invalid && form.controls.comments.touched}"></textarea>
+ </div>
+ </div>
+ </div>
+
+ <div class="text-right">
+ <button *ngIf="!loading" class="btn btn-info btn-rounded waves-effect waves-light mt-5" [disabled]="form.invalid"
+ type="submit">
+ Salvează
+ </button>
+ <div class="spinner-border text-green" role="status" *ngIf="loading">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </div>
+ </form>
+</div>
+
+ ./organisation-edit.component.scss
+
.form-group{
+ margin:0;
+}
+ +
+ src/app/pages/organisations/organisations/components/organisation-add/organisation-add.component.ts
+
+
+ OnInit
+
selector | +app-organisation-add |
+
styleUrls | +./organisation-add.component.scss |
+
templateUrl | +./organisation-add.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(route: ActivatedRoute, organisationService: OrganisationService, utilService: UtilService, location: Location, citiesandCounties: CitiesCountiesService, fb: FormBuilder)
+ |
+ |||||||||||||||||||||
+ + | +|||||||||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + countykey + + + + | +||||||||
+countykey(event: any)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for editing the county field. When activated, disable the city form until enter is pressed or mouse selection +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + onSubmit + + + + | +
+onSubmit()
+ |
+
+ + | +
+ Process form values and send data to server. If success close page +
+ Returns :
+ void
+
+ |
+
+ + + + selectedCity + + + + | +||||||||
+selectedCity(val: literal type)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select city from city typeahead +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + selectedCounty + + + + | +||||||||
+selectedCounty(val: any)
+ |
+ ||||||||
+ + | +||||||||
+ trigger for select county from county typeahead. will unlock the city field +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + cities + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ list of cities to pe parsed. edited when the user selects a county or edits this NGO + |
+
+ + + + cityPlaceholder + + + | +
+ Type : string
+
+ |
+
+ Default value : 'Selectați mai întâi județul'
+ |
+
+ + | +
+ placeholder for disabled city field + |
+
+ + + + click1$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + click2$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus1$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + focus2$ + + + | +
+ Default value : new Subject<string>()
+ |
+
+ + | +
+ + + + form + + + | +
+ Type : FormGroup
+
+ |
+
+ + | +
+ form that holds data + |
+
+ + + + formatter + + + | +
+ Default value : () => {...}
+ |
+
+ + | +
+ + + + instance1 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ references to NGBTypeahead for opening on focus or click + |
+
+ + + + instance2 + + + | +
+ Type : NgbTypeahead
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('instance', {static: true})
+ |
+
+ + | +
+ + + + loading + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for HTML to display loading animation + |
+
+ + + + loadingCities + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + searchcity + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for city typeahead. registers typing, focus, and click and searches the stored list of cities + |
+ ||||
+
+ Parameters :
+
+
|
+
+ + + + searchcounty + + + | +||||
+ Default value : () => {...}
+ |
+ ||||
+ + | +||||
+ trigger for county typeahead. registers typing, focus, and click and searches the backend + |
+ ||||
+
+ Parameters :
+
+
|
+
import { Component, OnInit, ViewChild } from '@angular/core';
+import {
+ FormGroup,
+ Validators,
+ FormBuilder
+} from '@angular/forms';
+import { OrganisationService } from '../../../organisations.service';
+import { Router, ActivatedRoute } from '@angular/router';
+import { CitiesCountiesService } from '../../../../../core/service/cities-counties.service';
+import { Subject } from 'rxjs/internal/Subject';
+import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
+import { Observable } from 'rxjs/internal/Observable';
+import {
+ debounceTime,
+ distinctUntilChanged,
+ filter,
+ map,
+ switchMap
+} from 'rxjs/operators';
+import { merge } from 'rxjs';
+import { EmailValidation } from '@app/core/validators/email-validation';
+import { PhoneValidation } from '@app/core/validators/phone-validation';
+import { WebsiteValidation } from '@app/core/validators/website-validation';
+import { Location } from '@angular/common';
+import { LocationValidation } from '@app/core/validators/location-validation';
+import { UtilService } from '@app/core';
+
+@Component({
+ selector: 'app-organisation-add',
+ templateUrl: './organisation-add.component.html',
+ styleUrls: ['./organisation-add.component.scss']
+})
+
+export class OrganisationaddComponent implements OnInit {
+ /**
+ * form that holds data
+ */
+ form: FormGroup;
+
+ /**
+ * placeholder for disabled city field
+ */
+ cityPlaceholder = 'Selectați mai întâi județul';
+
+ /**
+ * references to NGBTypeahead for opening on focus or click
+ */
+ @ViewChild('instance', { static: true }) instance1: NgbTypeahead;
+ focus1$ = new Subject<string>();
+ click1$ = new Subject<string>();
+
+ @ViewChild('instance', { static: true }) instance2: NgbTypeahead;
+ focus2$ = new Subject<string>();
+ click2$ = new Subject<string>();
+ /**
+ * flag for HTML to display loading animation
+ */
+ loading = false;
+ loadingCities = false;
+ /**
+ * list of cities to pe parsed. edited when the user selects a county or edits this NGO
+ */
+ cities: any[] = [];
+
+ constructor(
+ private route: ActivatedRoute,
+ private organisationService: OrganisationService,
+ private utilService: UtilService,
+ private location: Location,
+ private citiesandCounties: CitiesCountiesService,
+ private fb: FormBuilder) { }
+
+ ngOnInit() {
+ /**
+ * init form with empty values
+ */
+ this.form = this.fb.group({
+ name: ['', [Validators.required]],
+ website: ['', [Validators.required, WebsiteValidation.websiteValidation]],
+ contact_person: ['', Validators.required],
+ phone: ['', [Validators.required, PhoneValidation.phoneValidation]],
+ address: [''],
+ cover: [''],
+ email: ['', [Validators.required, EmailValidation.emailValidation]],
+ county: ['', [Validators.required, LocationValidation.locationValidation]],
+ city: [{value: '', disabled: true }, [Validators.required, ]],
+ comments: ['']
+ });
+ }
+
+ formatter = (result: { name: string }) => result.name;
+ /**
+ * trigger for county typeahead. registers typing, focus, and click and searches the backend
+ * @param {Observable} text observable event with the filter text
+ */
+ searchcounty = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+ const clicksWithClosedPopup$ = this.click1$.pipe(
+ filter(() => !this.instance1.isPopupOpen())
+ );
+ const inputFocus$ = this.focus1$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ switchMap((term: string) => this.citiesandCounties.getCounties(term))
+ );
+ }
+ /**
+ * trigger for city typeahead. registers typing, focus, and click and searches the stored list of cities
+ * @param {Observable} text observable event with the filter text
+ */
+ searchcity = (text$: Observable<string>) => {
+ const debouncedText$ = text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ );
+ const clicksWithClosedPopup$ = this.click2$.pipe(
+ filter(() => !this.instance2.isPopupOpen())
+ );
+ const inputFocus$ = this.focus2$;
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
+ map((term: string) => {
+ if (term === '') {
+ return this.cities;
+ } else {
+ return this.cities.filter(v => {
+ const aux: String = this.utilService.removeDiacritics(v.name).toLowerCase();
+ return aux.indexOf(term.toLowerCase()) > -1;
+ }).slice(0, 20);
+ }
+ }));
+ }
+ /**
+ * trigger for select county from county typeahead. will unlock the city field
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedCounty(val: any) {
+ this.form.controls.county.markAsTouched();
+ if (val.item && val.item._id) {
+ this.form.patchValue({county: val.item});
+ this.loadingCities = true;
+ this.citiesandCounties.getCitiesbyCounty(val.item._id, '').subscribe((res: any) => {
+ this.cities = res;
+ this.cityPlaceholder = 'Alegeți Orașul';
+ this.loadingCities = false;
+ this.form.controls.city.enable();
+ });
+ } else if (this.form.controls.county.value.name && val !== this.form.controls.county.value.name) {
+ this.form.patchValue({county: '', city: ''});
+ }
+ }
+
+ /**
+ * trigger for editing the county field. When activated, disable the city form until enter is pressed or mouse selection
+ * @param {any} event to be verified for which key has been pressed
+ */
+ countykey(event: any) {
+ this.form.controls.county.markAsTouched();
+ if (event.code !== 'Enter') {
+ this.cities = [];
+ this.form.controls.city.disable();
+ this.form.controls.city.reset('');
+ this.cityPlaceholder = 'Selectați mai întâi județul';
+ }
+ }
+ /**
+ * trigger for select city from city typeahead
+ * @param {any} val result object from typeahead that needs to be stored
+ */
+ selectedCity(val: { item: any }) {
+ this.form.controls.city.markAsTouched();
+ this.form.patchValue({city: val.item});
+ }
+
+ /**
+ * Process form values and send data to server. If success close page
+ */
+ onSubmit() {
+ this.loading = true;
+ const ngo = this.form.value;
+ ngo.city = ngo.city._id;
+ ngo.county = ngo.county._id;
+ this.organisationService
+ .addorganisation(ngo)
+ .subscribe(() => {
+ this.loading = false;
+ this.location.back();
+ }, () => {
+ this.loading = false;
+ });
+ }
+}
+
+ <div class="container wide-container mt-5">
+ <nav
+ class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <app-back-button></app-back-button>
+ </nav>
+
+ <p class="page-title">Adaugă o organizație:</p>
+
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
+ <div class="row">
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Nume Organizație *</label>
+ <input formControlName="name" class="form-control" type="text"
+ [ngClass]="{ 'error': form.controls.name.invalid && form.controls.name.touched }" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Website Organizație *</label>
+ <input formControlName="website" class="form-control" type="text"
+ [ngClass]="{ 'error': form.controls.website.invalid && form.controls.website.touched }" />
+ <span class="error-message" *ngIf="form.controls.website.invalid && form.controls.website.touched">* {{form.controls.website.errors.website}}</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Acoperire *</label>
+ <select class="form-control" formControlName="cover"
+ [ngClass]="{ 'error': form.controls.cover.invalid && form.controls.cover.touched }"
+ class="form-control">
+ <option>Națională</option>
+ <option>Locală</option>
+ </select>
+ <span class="error-message" *ngIf="form.controls.cover.invalid && form.controls.cover.touched">* Trebuie să alegeți un tip.</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Persoană de contact *</label>
+ <input formControlName="contact_person" class="form-control" type="text"
+ [ngClass]="{ 'error': form.controls.contact_person.invalid && form.controls.contact_person.touched }" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Email *</label>
+ <input formControlName="email" class="form-control" type="email"
+ [ngClass]="{ 'error': form.controls.email.invalid && form.controls.email.touched }" />
+ <span class="error-message" *ngIf="form.controls.email.invalid && form.controls.email.touched">* {{form.controls.email.errors.email}}</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Număr de Telefon *</label>
+ <input formControlName="phone" class="form-control" type="tel"
+ [ngClass]="{ 'error': form.controls.phone.invalid && form.controls.phone.touched }" />
+ <span class="error-message" *ngIf="form.controls.phone.invalid && form.controls.phone.touched">* {{form.controls.phone.errors.phone}}</span>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Județ *</label>
+ <input type="text" formControlName="county" (selectItem)="selectedCounty($event)" (blur)="selectedCounty($event.target.value)"
+ class="form-control" [ngbTypeahead]="searchcounty" (focus)="focus1$.next($event.target.value)"
+ (keyup)="countykey($event)"
+ [inputFormatter]="formatter" [resultFormatter]="formatter"
+ [ngClass]="{ 'error': form.controls.county.invalid && form.controls.county.touched}"
+ (click)="click1$.next($event.target.value)" #instance="ngbTypeahead" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Localitate *</label>
+ <input type="text" formControlName="city" class="form-control"
+ [ngbTypeahead]="searchcity" (selectItem)="selectedCity($event)"
+ [ngClass]="{ 'error': form.controls.city.invalid && form.controls.city.touched }"
+ placeholder="{{ cityPlaceholder }}" autocomplete="new-password" [inputFormatter]="formatter"
+ [resultFormatter]="formatter" (focus)="focus2$.next($event.target.value)"
+ (click)="click2$.next($event.target.value)" #instance="ngbTypeahead" />
+ <div class="spinner-border text-grey input-loader" role="status" *ngIf="loadingCities">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-4 col-sm-12">
+ <div class="form-group">
+ <label>Adresă</label>
+ <input formControlName="address" class="form-control" type="text"
+ [ngClass]="{ 'error': form.controls.address.invalid && form.controls.address.touched}" />
+ </div>
+ </div>
+
+ <div class="mt-4 col-md-12">
+ <div class="form-group">
+ <label>Comentarii</label>
+ <textarea rows="3" formControlName="comments" class="form-control" type="text"
+ [ngClass]="{ 'error': form.controls.comments.invalid && form.controls.comments.touched}"></textarea>
+ </div>
+ </div>
+ </div>
+
+ <div class="text-right">
+ <button *ngIf="!loading" class="btn btn-info btn-rounded waves-effect waves-light mt-5" [disabled]="form.invalid"
+ type="submit">
+ Salvează
+ </button>
+ <div class="spinner-border text-green" role="status" *ngIf="loading">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </div>
+ </form>
+</div>
+
+ ./organisation-add.component.scss
+
// .expandable-span{
+// min-width: 520px;
+// max-width: 920px;
+// display: block;
+// width: 100%;
+// height: calc(1.5em + 0.75rem + 2px);
+// padding: 0.375rem 0.75rem;
+// font-size: 1rem;
+// font-weight: 400;
+// line-height: 1.5;
+// color: #495057;
+// background-color: #ffffff;
+// background-clip: padding-box;
+// border: 1px solid #ced4da;
+// border-radius: 0.25rem;
+// transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+// }
+// [contenteditable="true"].expandable-span {
+// white-space: nowrap;
+// overflow: hidden;
+// }
+// [contenteditable="true"].expandable-span br {
+// display:none;
+
+// }
+// [contenteditable="true"].expandable-span * {
+// display:inline;
+// white-space:nowrap;
+// }
+
+.form-group{
+ margin:0;
+}
+ +
+ src/app/pages/organisations/organisations/organisations.component.ts
+
+
+ OnInit
+
selector | +app-organisations |
+
styleUrls | +./organisations.component.scss |
+
templateUrl | +./organisations.component.html |
+
+ Methods+ |
+
+
|
+
+constructor()
+ |
+
+ + | +
+ wrapper for the organisations pages. acts as frame + |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'app-organisations',
+ templateUrl: './organisations.component.html',
+ styleUrls: ['./organisations.component.scss']
+})
+
+export class OrganisationsComponent implements OnInit {
+/**
+ * wrapper for the organisations pages. acts as frame
+ */
+ constructor() { }
+
+ ngOnInit() {
+ }
+
+}
+
+ <router-outlet></router-outlet>
+
+ ./organisations.component.scss
+
+ +
+ src/app/pages/organisations/organisations/components/organisations-dashboard/organisations-dashboard.component.ts
+
+
+ OnInit
+
selector | +app-organisations-dashboard |
+
styleUrls | +./organisations-dashboard.component.scss |
+
templateUrl | +./organisations-dashboard.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(organisationService: OrganisationService, breakpointObserver: BreakpointObserver, filterService: FiltersService, citiesandcounties: CitiesCountiesService, router: Router)
+ |
+ ||||||||||||||||||
+ + | +||||||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + filterChanged + + + + | +||||||||
+filterChanged(id?: number)
+ |
+ ||||||||
+ + | +||||||||
+ muliselect filter callback +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + getData + + + + | +
+getData()
+ |
+
+ + | +
+ get ngo list with filters in pager +
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + searchChanged + + + + | +||||||
+searchChanged(pager: any)
+ |
+ ||||||
+ + | +||||||
+ search callback +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + showOrganisationDetails + + + + | +||||||||||||||||
+showOrganisationDetails(id: string, property: string, e: any)
+ |
+ ||||||||||||||||
+ + | +||||||||||||||||
+ navigate to NGO details page with specific tab open +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + singleFilterChanged + + + + | +||||||||
+singleFilterChanged(id?: number)
+ |
+ ||||||||
+ + | +||||||||
+ single select filter callback +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + sortChanged + + + + | +||||||
+sortChanged(pager: any)
+ |
+ ||||||
+ + | +||||||
+ sort callback +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + switchtoblock + + + + | +
+switchtoblock()
+ |
+
+ + | +
+ set class of display element with grid view +
+ Returns :
+ void
+
+ |
+
+ + + + switchtolist + + + + | +
+switchtolist()
+ |
+
+ + | +
+ set class of display element with list view +
+ Returns :
+ void
+
+ |
+
+ + + + Public + breakpointObserver + + + | +
+ Type : BreakpointObserver
+
+ |
+
+ + | +
+ + + + categoryFilterValues + + + | +
+ Type : any[]
+
+ |
+
+ + | +
+ values to be displayed in filter menus + |
+
+ + + + displayBlock + + + | +
+ Default value : true
+ |
+
+ + | +
+ flag to indicate use of list or grid display + |
+
+ + + + locationFilterValues + + + | +
+ Type : any[]
+
+ |
+
+ + | +
+ + + + ngosData + + + | +
+ Type : any
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ + + + pager + + + | +
+ Type : any
+
+ |
+
+ Default value : {}
+ |
+
+ + | +
+ + + + propertyMap + + + | +
+ Type : object
+
+ |
+
+ Default value : {
+ '_id': 'id',
+ 'parent_id': 'parent_id'
+ }
+ |
+
+ + | +
+ mapping of object keys to filter recognizable keys + |
+
+ + + + selected + + + | +
+ Default value : Array(4)
+ |
+
+ + | +
+ selected values in the filters. Array of array of {id, name} objects + |
+
+ + + + specializationFilterValues + + + | +
+ Type : any[]
+
+ |
+
+ + | +
+ + + + typeFilterValues + + + | +
+ Type : []
+
+ |
+
+ Default value : [{id: 'Națională', name: 'Națională'}, {id: 'Locală', name: 'Locală'}]
+ |
+
+ + | +
import { Component, OnInit } from '@angular/core';
+import { OrganisationService } from '../../../organisations.service';
+import { BreakpointObserver } from '@angular/cdk/layout';
+import { FiltersService, CitiesCountiesService } from '@app/core';
+import { Router, NavigationExtras } from '@angular/router';
+@Component({
+ selector: 'app-organisations-dashboard',
+ templateUrl: './organisations-dashboard.component.html',
+ styleUrls: ['./organisations-dashboard.component.scss']
+})
+export class OrganisationsDashboardComponent implements OnInit {
+ ngosData: any = [];
+ pager: any = {};
+ /**
+ * flag to indicate use of list or grid display
+ */
+ displayBlock = true;
+ /**
+ * selected values in the filters. Array of array of {id, name} objects
+ */
+ selected = Array(4);
+
+ /**
+ * values to be displayed in filter menus
+ */
+ categoryFilterValues: any[];
+ typeFilterValues = [{id: 'Națională', name: 'Națională'}, {id: 'Locală', name: 'Locală'}];
+ specializationFilterValues: any[];
+ locationFilterValues: any[];
+ /**
+ * mapping of object keys to filter recognizable keys
+ */
+ propertyMap = {
+ '_id': 'id',
+ 'parent_id': 'parent_id'
+ };
+ constructor(
+ private organisationService: OrganisationService,
+ public breakpointObserver: BreakpointObserver,
+ private filterService: FiltersService,
+ private citiesandcounties: CitiesCountiesService,
+ private router: Router
+ ) {}
+
+
+ ngOnInit() {
+ this.organisationService.setPager();
+ /**
+ * get and store filter values
+ */
+ this.citiesandcounties.getCounties('').subscribe((response: any) => {
+ const aux = response;
+ aux.map((elem: { id: any; _id: any; }) => elem.id = elem._id);
+ this.locationFilterValues = aux;
+ });
+
+ this.filterService.getSpecializationFilters().subscribe((data) => {
+ this.specializationFilterValues = data.map((elem: any) => {
+ return {id: elem._id, name: elem.name};
+ });
+ });
+ this.filterService.getCategoryFilters().subscribe((data) => {
+ this.categoryFilterValues = data.map((x: any) => {
+ const parent = data.find((y: any) => y._id === x.parent_id);
+ return {
+ id: x._id,
+ name: x.name,
+ parent_id: x.parent_id,
+ pp: x.parent_id === '0' ? x.name : ( parent ? parent.name : null),
+ level: x.parent_id === '0' ? 0 : 1
+ };
+ });
+ });
+ this.pager = this.organisationService.getPager();
+
+ this.getData();
+
+ /**
+ * subscribe to screen size in order to use list instead of grid for display
+ */
+ this.breakpointObserver
+ .observe(['(max-width: 768px)'])
+ .subscribe(result => {
+ if (result.matches) {
+ this.switchtoblock();
+ }
+ });
+ }
+ /**
+ * sort callback
+ * @param {any} Pager pager is modified by external componet and passed as param
+ */
+ sortChanged(pager: any) {
+ this.pager = pager;
+ this.getData();
+ }
+ /**
+ * search callback
+ * @param {any} Pager pager is modified by external componet and passed as param
+ */
+ searchChanged(pager: any) {
+ this.pager = pager;
+ this.getData();
+ }
+ /**
+ * get ngo list with filters in pager
+ */
+ getData() {
+ this.organisationService.getorganisations(this.pager).subscribe(element => {
+ this.ngosData = element.data.map((elem: any) => {
+ elem.nr_vol = 0;
+ elem.nr_res = 0;
+ return elem;
+ });
+ this.pager.total = element.pager.total;
+ });
+ }
+ /**
+ * muliselect filter callback
+ * @param {number} id the index in the pager filters and filters selected array
+ */
+ filterChanged(id?: number) {
+ this.pager.filters[id] = this.selected[id].map((elem: any) => elem.id).join(',');
+ this.getData();
+ }
+ /**
+ * single select filter callback
+ * @param {number} id the index in the pager filters and filters selected array
+ */
+ singleFilterChanged(id?: number) {
+
+ if (this.selected[id]) {
+ this.pager.filters[id] = this.selected[id].id;
+ } else {
+ this.pager.filters[id] = null;
+ }
+ console.log(this.pager.filters[id]);
+ this.getData();
+ }
+
+ /**
+ * set class of display element with list view
+ */
+ switchtolist() {
+ this.displayBlock = false;
+ }
+
+ /**
+ * set class of display element with grid view
+ */
+ switchtoblock() {
+ this.displayBlock = true;
+ }
+ /**
+ * navigate to NGO details page with specific tab open
+ * @param {string} id the id of the organization to be show
+ * @param {string} property the tab that needs to be open on page load
+ * @param {number} e suppresed default event
+ */
+ showOrganisationDetails(id: string, property: string, e: any) {
+ e.preventDefault();
+ const navigationExtras: NavigationExtras = {
+ state: {
+ tabName: property
+ }
+ };
+ this.router.navigateByUrl('/organisations/id/' + id, navigationExtras);
+ }
+}
+
+ <div class="container">
+ <nav class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <h4 class="navtitle mb-2"> Organizații </h4>
+ <div class="form-inline ml-auto">
+ <app-table-search [pager]="pager" id=2 (searchChanged)="searchChanged($event)"></app-table-search>
+ <div class="btn-group btn-group-toggle m-2 hidden-radio" ngbRadioGroup name="radioBasic"
+ [(ngModel)]="displayBlock">
+ <label ngbButtonLabel (click)="switchtoblock()" class="btn-primary btn">
+ <input ngbButton type="radio" [value]="true"><span class="fa fa-th-large"></span>
+ </label>
+ <label ngbButtonLabel (click)="switchtolist()" class="btn btn-primary">
+ <input ngbButton type="radio" [value]="false"><span class="fa fa-bars"></span>
+ </label>
+ </div>
+ <button [routerLink]="['/organisations/add']" style="white-space: normal;"
+ class="btn-adjusting btn btn-info btn-rounded m-2"><span
+ class="fa fa-plus-circle"></span> Adaugă Organizație</button>
+ </div>
+ </nav>
+ <div class="filters my-3 mx-30px row">
+ <span class='padding-rem'>Total: {{pager.total}}</span>
+ <span class='padding-rem'> </span>
+ <span class='padding-rem'>Filtrează după: </span>
+ <ngx-multiselect class="type"
+ [(ngModel)]="selected[0]"
+ showMaxLables = "1"
+ [options]="typeFilterValues"
+ [showSearchFilter]="false"
+ [multiple]="false"
+ [showHelperElements]="false"
+ (onClear)="singleFilterChanged(0)"
+ (onItemClick)="singleFilterChanged(0)"
+ (onSelectNone)="singleFilterChanged(0)"
+ (onSelectAll)="singleFilterChanged(0)">
+ </ngx-multiselect>
+ <span class="vertical-bar"> </span>
+ <ngx-multiselect
+ [(ngModel)]="selected[1]"
+ showMaxLables = "1"
+ [options]="locationFilterValues"
+ (onItemClick)="filterChanged(1)"
+ (onSelectNone)="filterChanged(1)"
+ (onSelectAll)="filterChanged(1)">
+ </ngx-multiselect>
+ <span class="vertical-bar"> </span>
+ <ngx-multiselect class="specialization"
+ [(ngModel)]="selected[3]"
+ showMaxLables = "1"
+ [options]="specializationFilterValues"
+ [multiple]="false"
+ [showHelperElements]="false"
+ (onClear)="singleFilterChanged(3)"
+ (onItemClick)="singleFilterChanged(3)"
+ (onSelectNone)="singleFilterChanged(3)"
+ (onSelectAll)="singleFilterChanged(3)">
+ </ngx-multiselect>
+ <span class="vertical-bar"> </span>
+ <ngx-multiselect class="category"
+ [(ngModel)]="selected[4]"
+ showMaxLables = "1"
+ [options]="categoryFilterValues"
+ [multiple]="false"
+ [showHelperElements]="false"
+ [propertyMap]="propertyMap"
+ (onClear)="singleFilterChanged(4)"
+ (onItemClick)="singleFilterChanged(4)"
+ (onSelectNone)="singleFilterChanged(4)"
+ (onSelectAll)="singleFilterChanged(4)">
+ <ng-template let-option="option">
+ <span [ngClass]="{'pl-3': option.parent_id!=='0'}">
+ {{option.name}}
+ </span>
+ </ng-template>
+ </ngx-multiselect>
+ </div>
+
+ <div *ngIf="displayBlock; else displayList" class="cardlist row mt-5">
+ <div *ngFor="let elem of ngosData" class="d-flex col-md-6 col-sm-12 col-lg-4">
+ <div class="card mb-2" style="flex-grow: 1" [routerLink]="['id',elem._id]">
+ <div class="card-body">
+ <a class="btn btn-fix text-left">
+ <h4 class="card-title">{{elem.name}}</h4>
+ <div class="card-text row">
+ <span class="col-md-12">
+ <i class="fa fa-envelope"></i>
+ {{elem.contact_person?.email}}
+ </span>
+ <span class="col-md-12">
+ <i class="fa fa-phone"></i>
+ {{elem.contact_person?.phone}}
+ </span>
+ </div>
+ </a>
+ <div class="btn btn-fix text-left">
+ Resurse:<br>
+ <a (click)="showOrganisationDetails(elem._id, 'volunteers', $event)" href="javascript:void(0);"
+ class="card-link">voluntari: {{elem.volunteers}}</a>
+ ⋅
+ <a (click)="showOrganisationDetails(elem._id, 'resources', $event)" href="javascript:void(0);"
+ class="card-link">resurse: {{elem.resources}}</a>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-12 mx-30px">
+ <ngb-pagination
+ [maxSize]="5"
+ [(page)]="pager.page"
+ [pageSize]="pager.size"
+ (pageChange)="getData()"
+ [collectionSize]="pager.total">
+ </ngb-pagination>
+ </div>
+ </div>
+
+ <ng-template #displayList>
+ <div class="table-responsive">
+ <table class="table customTable table-sm">
+ <thead>
+ <tr>
+ <!-- sortable="name" (sort)="onSort($event)" -->
+ <th scope="col" appTableSort [pager]="pager" [value]="1" (sortChanged)="sortChanged($event)">
+ Nume</th>
+ <th scope="col" appTableSort [pager]="pager" [value]="2" (sortChanged)="sortChanged($event)">
+ Locație</th>
+ <th scope="col">
+ Nr. Voluntari</th>
+ <th scope="col">
+ Nr. Resurse</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let res of ngosData">
+ <!-- <tr> -->
+ <td>{{res.name}}</td>
+ <td>{{res.city?.name}}, {{res.county?.name}}</td>
+ <td>{{res.volunteers}}</td>
+ <td>{{res.resources}}</td>
+ <!-- <td>{{res.updated_at}}</td> -->
+ <td class="text-right"><button class="btn btn-info" [routerLink]="['id',res._id]">Vezi
+ detalii</button></td>
+ </tr>
+
+ </tbody>
+ </table>
+
+ <ngb-pagination
+ [maxSize]="5" [(page)]="pager.page" [pageSize]="pager.size" (pageChange)="getData()"
+ [collectionSize]="pager.total">
+ </ngb-pagination>
+ </div>
+ </ng-template>
+</div>
+
+ ./organisations-dashboard.component.scss
+
.card{
+ background-color: transparent;
+ margin: 10px;
+}
+
+.card-body{
+ padding: 0.25rem;
+ background-color:white;
+}
+
+@media (max-width: 425px) {
+ .container {
+ width: 100%;
+ max-width: none;
+ }
+ }
+
+@media (max-width: 1199px){
+ .btn-adjusting{
+ width: min-content;
+ }
+}
+
+
+.filters {
+ .padding-rem {
+ padding: 0.375rem 0.75rem
+ }
+
+ .vertical-bar {
+ background-color: #67757c;
+ width: 2px;
+ margin-left: 10px;
+ margin-right: 10px;
+ }
+}
+::ng-deep .specialization{
+ .none-selected:before{
+ content:'Specializări' !important;
+ }
+}
+::ng-deep .category{
+ .none-selected:before {
+ content: 'Categorie' !important;
+ }
+}
+::ng-deep .type{
+ .none-selected:before {
+ content: 'Acoperire' !important;
+ }
+}
+
+ +
+ src/app/pages/authentication/recover-password/recover-password.component.ts
+
+
+ OnInit
+
selector | +app-recover-password |
+
styleUrls | +./recover-password.component.scss |
+
templateUrl | +./recover-password.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(router: Router, authenticationService: AuthenticationService)
+ |
+ |||||||||
+ + | +|||||||||
+
+ Parameters :
+
+
|
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + resetPassword + + + + | +
+resetPassword()
+ |
+
+ + | +
+ Login with username and password obtained from form resetPasswordForm. +On success redirects to dashboard +
+ Returns :
+ void
+
+ |
+
+ + + + errorMessage + + + | +
+ Type : string
+
+ |
+
+ Default value : null
+ |
+
+ + | +
+ Message to be displaied on error + |
+
+ + + + loading + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for HTML to display loading animation + |
+
+ + + + resetPasswordForm + + + | +
+ Type : FormGroup
+
+ |
+
+ + | +
+ Form holds data to be completed + |
+
+ + + + Public + router + + + | +
+ Type : Router
+
+ |
+
+ + | +
import { Component, OnInit } from '@angular/core';
+import { FormGroup, Validators, FormControl } from '@angular/forms';
+import { Router } from '@angular/router';
+import { AuthenticationService } from '@app/core';
+import { EmailValidation } from '@app/core/validators/email-validation';
+
+@Component({
+ selector: 'app-recover-password',
+ templateUrl: './recover-password.component.html',
+ styleUrls: ['./recover-password.component.scss']
+})
+export class RecoverPasswordComponent implements OnInit {
+ /**
+ * Form holds data to be completed
+ */
+ resetPasswordForm: FormGroup;
+/**
+ * flag for HTML to display loading animation
+ */
+ loading = false;
+ /**
+ * Message to be displaied on error
+ */
+ errorMessage: string = null;
+
+ constructor(
+ public router: Router,
+ private authenticationService: AuthenticationService
+ ) {}
+
+ ngOnInit() {
+ this.resetPasswordForm = new FormGroup({
+ email: new FormControl('', [Validators.required, EmailValidation.emailValidation])
+ });
+ }
+ /**
+ * Login with username and password obtained from form {@link resetPasswordForm}.
+ *
+ * On success redirects to dashboard
+ */
+ resetPassword() {
+ this.loading = true;
+ this.authenticationService.recoverPassword(this.resetPasswordForm.value.email).subscribe(response => {
+ this.loading = false;
+ this.router.navigate(['/login']);
+ }, error => {
+ this.loading = false;
+ this.errorMessage = 'Adresa de email specificată nu există. Te rugăm să verifici și să încerci din nou.';
+ });
+ }
+}
+
+ <section id="wrapper" class="login-register login-sidebar">
+ <div class="logo-container col-md-12 col-sm-12 col-xs-12">
+ <img src="../../../assets/images/DSU_logo.PNG" />
+ </div>
+
+ <div class="login-box col-md-12 col-sm-12 col-xs-12">
+ <div class="card-body">
+ <form [formGroup]="resetPasswordForm" (ngSubmit)="resetPassword()" class="form-horizontal form-material"
+ id="resetPasswordForm">
+ <div class="form-group mt-4">
+ <div class="col-xs-12">
+ <input formControlName="email" class="form-control" type="email"
+ placeholder="Adresă de email" />
+ </div>
+ </div>
+
+ <ngb-alert [dismissible]="false" *ngIf="(resetPasswordForm.invalid && resetPasswordForm.touched) || !!errorMessage"
+ class="error-message" type="success">
+ {{ resetPasswordForm.controls.email.errors?.email || errorMessage }}
+ </ngb-alert>
+
+ <div class="form-group text-center mt-4">
+ <div class="col-xs-12">
+ <button class="btn button-login btn-lg btn-block text-uppercase waves-effect waves-light"
+ *ngIf="!loading"
+ type="submit" [disabled]="resetPasswordForm.invalid">
+ <app-button-loader [label]="'Trimite'"></app-button-loader>
+ </button>
+ <div class="spinner-border text-white" role="status" *ngIf="loading">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+
+ <div class="d-flex my-5">
+ <a [routerLink]="['/login']" class="navigation">
+ Autentificare
+ </a>
+ </div>
+</section>
+
+ ./recover-password.component.scss
+
+ +
+ src/app/pages/authentication/reset-password/reset-password.component.ts
+
+
+ OnInit
+
selector | +app-reset-password |
+
styleUrls | +./reset-password.component.scss |
+
templateUrl | +./reset-password.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(router: Router, authenticationService: AuthenticationService, formBuilder: FormBuilder, route: ActivatedRoute)
+ |
+ |||||||||||||||
+ + | +|||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + resetPassword + + + + | +
+resetPassword()
+ |
+
+ + | +
+ function to call on form submit +
+ Returns :
+ void
+
+ |
+
+ + + + errorMessage + + + | +
+ Type : string
+
+ |
+
+ Default value : null
+ |
+
+ + | +
+ error message to display in html + |
+
+ + + + loading + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for HTML to display a loading animation + |
+
+ + + + resetPasswordForm + + + | +
+ Type : FormGroup
+
+ |
+
+ + | +
+ Form holds data to be completed + |
+
+ + + + Public + router + + + | +
+ Type : Router
+
+ |
+
+ + | +
+ + + + token + + + | +
+ Type : string
+
+ |
+
+ + | +
+ reset password token received from reset link + |
+
import { Component, OnInit } from '@angular/core';
+import {
+ FormGroup,
+ Validators,
+ FormBuilder
+} from '@angular/forms';
+import { Router, ActivatedRoute } from '@angular/router';
+import { AuthenticationService } from '@app/core';
+import { PasswordValidation } from '@app/core/validators/password-validation';
+
+@Component({
+ selector: 'app-reset-password',
+ templateUrl: './reset-password.component.html',
+ styleUrls: ['./reset-password.component.scss']
+})
+
+export class ResetPasswordComponent implements OnInit {
+ /**
+ * Form holds data to be completed
+ */
+ resetPasswordForm: FormGroup;
+ /**
+ * reset password token received from reset link
+ */
+ token: string;
+ /**
+ * error message to display in html
+ */
+ errorMessage: string = null;
+ /**
+ * flag for HTML to display a loading animation
+ */
+ loading = false;
+
+ constructor(
+ public router: Router,
+ private authenticationService: AuthenticationService,
+ private formBuilder: FormBuilder,
+ private route: ActivatedRoute
+ ) {}
+
+ ngOnInit() {
+ /**
+ * Check if user is logged in. if true, redirect to login
+ */
+ if (!!this.authenticationService.isAuthenticated()) {
+ this.authenticationService.logout().subscribe(
+ (didlogout: Boolean) => {
+ if (didlogout) {
+ this.router.navigate(['/login']);
+ }
+ },
+ (error: any) => {
+ console.log('logout error: ', error);
+ });
+ }
+
+ /**
+ * get token from email link
+ */
+ this.route.params.subscribe(params => {
+ this.token = params['token'];
+ });
+ /**
+ * build form that holds data to be completed
+ */
+ this.resetPasswordForm = this.formBuilder.group(
+ {
+ password: ['',
+ [
+ Validators.required,
+ Validators.minLength(8),
+ PasswordValidation.passwordValidation
+ ]
+ ],
+ confirmPassword: ['',
+ [
+ Validators.required,
+ Validators.minLength(8),
+ PasswordValidation.passwordValidation
+ ]
+ ]
+ },
+ {
+ validator: PasswordValidation.MatchPassword
+ }
+ );
+ }
+ /**
+ * function to call on form submit
+ */
+ resetPassword() {
+ this.loading = true;
+ this.authenticationService.resetPassword(this.resetPasswordForm.value.password, this.token).subscribe(response => {
+ this.loading = false;
+ this.errorMessage = null;
+ this.router.navigate(['/login']);
+ }, error => {
+ this.loading = false;
+
+ // tslint:disable-next-line: max-line-length
+ this.errorMessage = 'Token-ul de resetare al parolei nu este valid, te rugăm să reîncerci din email-ul primit. Dacă problema persistă, te rugăm să ceri din nou schimbarea parolei.';
+ });
+ }
+}
+
+ <section id="wrapper" class="login-register login-sidebar">
+ <div class="logo-container col-md-12 col-sm-12 col-xs-12">
+ <img src="../../../assets/images/DSU_logo.PNG" />
+ </div>
+
+ <div class="login-box col-md-12 col-sm-12 col-xs-12">
+ <div class="card-body">
+ <form [formGroup]="resetPasswordForm" (ngSubmit)="resetPassword()" class="form-horizontal form-material"
+ id="resetPasswordForm">
+ <div class="form-group mt-4">
+ <div class="col-xs-12">
+ <input formControlName="password" class="form-control" type="password" placeholder="Parolă" />
+ </div>
+ </div>
+
+ <div class="form-group mt-4">
+ <div class="col-xs-12">
+ <input formControlName="confirmPassword" class="form-control" type="password"
+ placeholder="Confirmă Parola" />
+ </div>
+ </div>
+
+ <ngb-alert [dismissible]="false" *ngIf="(resetPasswordForm.invalid && resetPasswordForm.touched) || !!errorMessage"
+ class="error-message" type="success">
+ {{ resetPasswordForm.controls.password.errors?.password || resetPasswordForm.errors?.password || errorMessage }}
+ </ngb-alert>
+
+ <div class="form-group text-center mt-4">
+ <div class="col-xs-12">
+ <button *ngIf="!loading" class="btn button-login btn-lg btn-block text-uppercase waves-effect waves-light"
+ type="submit" [disabled]="resetPasswordForm.invalid">
+ <app-button-loader [label]="'Trimite'"></app-button-loader>
+ </button>
+ <div class="spinner-border text-white" role="status" *ngIf="loading">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </div>
+ </div>
+
+ </form>
+ </div>
+ </div>
+
+ <div class="d-flex my-5">
+ <a [routerLink]="['/login']" class="navigation">
+ Autentificare
+ </a>
+ </div>
+</section>
+
+ ./reset-password.component.scss
+
+ +
+ src/app/pages/resources/resources/components/resource-list/resource-list.component.ts
+
+
+ OnInit
+
selector | +app-resource-list |
+
styleUrls | +./resource-list.component.scss |
+
templateUrl | +./resource-list.component.html |
+
+ Properties+ |
+
+ + | +
+ Methods+ |
+
+
|
+
+constructor(resourceService: ResourcesService, route: ActivatedRoute, authService: AuthenticationService, router: Router)
+ |
+ |||||||||||||||
+ + | +|||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + deleteSelf + + + + | +||||||||
+deleteSelf(resId: string)
+ |
+ ||||||||
+ + | +||||||||
+ delete the selected res +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + getData + + + + | +
+getData()
+ |
+
+ + | +
+ get the resourses with the same slug from server +
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + sortChanged + + + + | +||||||
+sortChanged(pager: any)
+ |
+ ||||||
+ + | +||||||
+ when sorting we will set the pager and get data from server with the new pager +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + Public + authService + + + | +
+ Type : AuthenticationService
+
+ |
+
+ + | +
+ + + + data + + + | +
+ Type : any
+
+ |
+
+ + | +
+ data holds info about current resource + |
+
+ + + + loading + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for HTML to display loading animation + |
+
+ + + + pager + + + | +
+ Type : any
+
+ |
+
+ Default value : {}
+ |
+
+ + | +
+ pager for table + |
+
+ + + + resources + + + | +
+ Type : any[]
+
+ |
+
+ Default value : null
+ |
+
+ + | +
+ list of resources with same slug + |
+
+ + + + resslug + + + | +
+ Type : string
+
+ |
+
+ + | +
+ var holds slug of the resource + |
+
import { Component, OnInit } from '@angular/core';
+import { ResourcesService } from '@app/pages/resources/resources.service';
+import { ActivatedRoute, Router } from '@angular/router';
+import { AuthenticationService } from '@app/core';
+
+@Component({
+ selector: 'app-resource-list',
+ templateUrl: './resource-list.component.html',
+ styleUrls: ['./resource-list.component.scss']
+})
+export class ResourceListComponent implements OnInit {
+/**
+ * data holds info about current resource
+ */
+ data: any;
+ /**
+ * list of resources with same slug
+ */
+ resources: any[] = null;
+ /**
+ * var holds slug of the resource
+ */
+ resslug: string;
+ /**
+ * pager for table
+ */
+ pager: any = {};
+ /**
+ * flag for HTML to display loading animation
+ */
+ loading = false;
+
+ constructor(private resourceService: ResourcesService,
+ private route: ActivatedRoute,
+ public authService: AuthenticationService,
+ private router: Router) {
+ this.resslug = this.route.snapshot.paramMap.get('id');
+ }
+
+ ngOnInit() {
+ this.pager = this.resourceService.getPager();
+ this.getData();
+ }
+ /**
+ * delete the selected res
+ * @param {string} resId of the resource that will be deleted
+ */
+ deleteSelf(resId: string) {
+ if (confirm('Sunteți sigur că doriți să ștergeți această intrare? Odată ștearsă nu va mai putea fi recuperată.')) {
+ this.loading = true;
+ this.resourceService.deleteResource(resId).subscribe((data) => {
+ this.loading = false;
+ this.router.navigateByUrl('/resources');
+ }, () => {
+ this.loading = false;
+ });
+ }
+ }
+ /**
+ * when sorting we will set the pager and get data from server with the new pager
+ */
+ sortChanged(pager: any) {
+ this.pager = pager;
+ this.getData();
+ }
+/**
+ * get the resourses with the same slug from server
+ */
+ getData() {
+ this.resourceService.getResourceBySlug(this.resslug, this.pager).subscribe((response: any) => {
+ this.data = response.data[0];
+ this.resources = response.data;
+ this.pager.total = response.pager.total;
+ });
+ }
+
+}
+
+ <div class="container">
+ <nav class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <app-back-button></app-back-button>
+ </nav>
+ <div class="ngo-details row">
+ <h3 class="col-md-12">Denumire resursă: {{data?.name}}</h3>
+ </div>
+ <div class="resource-list">
+
+ <div class="table-responsive-md">
+ <table class="table customTable table-sm">
+ <thead>
+ <tr>
+ <!-- sortable="name" (sort)="onSort($event)" -->
+ <th scope="col" appTableSort [pager]="pager" [value]="1" (sortChanged)="sortChanged($event)">
+ Organizație</th>
+ <th scope="col" appTableSort [pager]="pager" [value]="2" (sortChanged)="sortChanged($event)">
+ Cantitate</th>
+ <th scope="col">
+ Adresă</th>
+ <th scope="col" appTableSort [pager]="pager" [value]="4" (sortChanged)="sortChanged($event)">
+ Județ</th>
+ <th scope="col" appTableSort [pager]="pager" [value]="5" (sortChanged)="sortChanged($event)">
+ Data ultimului update</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let res of resources">
+ <td>{{res.organisation?.name ? res.organisation?.name : ''}}</td>
+ <td>{{res.quantity}}</td>
+ <td>{{res.address}}</td>
+ <td>{{res.city?.name}}, {{res.county?.name}}</td>
+ <td>{{res.updated_at | date: 'dd.MM.yyyy'}}</td>
+ <td class="text-right">
+ <div>
+ <button *ngIf="res.organisation && res.organisation._id" class="btn btn-info mb-1" [routerLink]="['/organisations/id',res.organisation._id]">Detalii</button>
+ <button *ngIf="authService.is('DSU','NGO') && !loading" (click)="deleteSelf(res._id)"
+ class="btn btn-danger mb-1 ml-1">
+ <span class="fa fa-trash-o"></span> Șterge
+ </button>
+ <div class="spinner-border text-danger m-1" role="status"
+ *ngIf="authService.is('DSU','NGO') && loading">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <ngb-pagination
+ [maxSize]="5" [(page)]="pager.page" [pageSize]="pager.size" (pageChange)="getData()"
+ [collectionSize]="pager.total">
+ </ngb-pagination>
+ </div>
+ </div>
+
+ ./resource-list.component.scss
+
+ +
+ src/app/pages/resources/resources/components/resource-details/resource-details.component.ts
+
+
+ OnInit
+
selector | +app-resource-details |
+
styleUrls | +./resource-details.component.scss |
+
templateUrl | +./resource-details.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(resourceService: ResourcesService, route: ActivatedRoute, authService: AuthenticationService, router: Router, location: Location)
+ |
+ ||||||||||||||||||
+ + | +||||||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + deleteSelf + + + + | +
+deleteSelf()
+ |
+
+ + | +
+ delete this resource +
+ Returns :
+ void
+
+ |
+
+ + + + edit + + + + | +
+edit()
+ |
+
+ + | +
+ edit this resource +
+ Returns :
+ void
+
+ |
+
+ + + + getData + + + + | +
+getData()
+ |
+
+ + | +
+ get resource data from server +
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + Public + authService + + + | +
+ Type : AuthenticationService
+
+ |
+
+ + | +
+ + + + canEdit + + + | +
+ Default value : true
+ |
+
+ + | +
+ flag for HTML to display edit button + |
+
+ + + + Public + data + + + | +
+ Type : any
+
+ |
+
+ + | +
+ store resource data + |
+
+ + + + loading + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for HTML to display loading animation + |
+
import { Component, OnInit } from '@angular/core';
+import { ResourcesService } from '@app/pages/resources/resources.service';
+import { ActivatedRoute, Router } from '@angular/router';
+import { AuthenticationService } from '@app/core';
+import { Location } from '@angular/common';
+
+
+@Component({
+ selector: 'app-resource-details',
+ templateUrl: './resource-details.component.html',
+ styleUrls: ['./resource-details.component.scss']
+})
+export class ResourcedetailsComponent implements OnInit {
+ /**
+ * store resource data
+ */
+ public data: any;
+ /**
+ * flag for HTML to display edit button
+ */
+ canEdit = true;
+ /**
+ * flag for HTML to display loading animation
+ */
+ loading = false;
+ constructor(private resourceService: ResourcesService,
+ private route: ActivatedRoute,
+ public authService: AuthenticationService,
+ private router: Router,
+ private location: Location) { }
+
+ ngOnInit() {
+ this.getData();
+ }
+/**
+ * edit this resource
+ */
+ edit() {
+ this.router.navigateByUrl(`/resources/edit/${this.data._id}`);
+ }
+/**
+ * delete this resource
+ */
+ deleteSelf() {
+ if (confirm('Sunteți sigur că doriți să ștergeți această intrare? Odată ștearsă nu va mai putea fi recuperată.')) {
+ this.loading = true;
+ this.resourceService.deleteResource(this.data._id).subscribe(() => {
+ this.loading = false;
+ this.location.back();
+ }, () => {
+ this.loading = false;
+ });
+ }
+ }
+/**
+ * get resource data from server
+ */
+
+ getData() {
+ this.resourceService.getResource(this.route.snapshot.paramMap.get('id')).subscribe((data) => {
+ this.data = data;
+
+ this.canEdit = this.authService.is('DSU') ||
+ (this.authService.is('NGO') && (this.data.organisation._id === this.authService.user.organisation._id));
+ });
+ }
+
+}
+
+ <div class="container">
+ <nav class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <app-back-button></app-back-button>
+ <div class="mx-2 my-sm-0">
+ <button *ngIf="canEdit && !loading" (click)="deleteSelf()" class="btn btn-danger mx-2 my-sm-0"><span
+ class="fa fa-trash-o"></span> Șterge Resursa</button>
+ <div class="spinner-border text-danger" role="status" *ngIf="canEdit && loading">
+ <span class="sr-only">Loading...</span>
+ </div>
+ <button (click)="edit()" class="mx-2 btn btn-info" *ngIf="canEdit"><span class="fa fa-edit"></span> Modifică</button>
+ <!-- <button></button> -->
+ </div>
+ </nav>
+ <div class="ngo-details row">
+ <h3 class="col-md-12">Nume resursa: {{data?.name}}</h3>
+
+ <span class="col-md-12"><b>Organizație: </b> {{data?.organisation?.name || 'Neafiliat'}}</span>
+ <span class="col-md-12"><b>Localizare: </b>{{data?.city.name}}, {{data?.county.name}}</span>
+ <span *ngIf="data?.address" class="col-md-12"><b>Adresa: </b> {{data?.address }}</span>
+ <span class="col-md-12"><b>Tip Resursa: </b> {{data?.resource_type }}</span>
+ <span class="col-md-12"><b>Comentarii: </b> {{data?.comments}}</span>
+ <div class="col-md-12">
+ <span><b>Data ultimului update: </b> {{ (data?.updated_at | date: 'dd.MM.yyyy') || 'Nu există' }}</span>
+ </div>
+ <div class="col-md-12 mt-3 ml-3">
+ <h4> Categorii: </h4>
+ <span *ngFor = "let elem of data?.categories">
+ {{elem.name}}
+ </span>
+ </div>
+ </div>
+</div>
+
+ ./resource-details.component.scss
+
+
+ +
+ src/app/pages/resources/resources/resources.component.ts
+
+
+ OnInit
+
selector | +app-resources |
+
styleUrls | +./resources.component.scss |
+
templateUrl | +./resources.component.html |
+
+ Methods+ |
+
+
|
+
+constructor()
+ |
+
+ + | +
+ wrapper for the resources pages. acts as frame + |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'app-resources',
+ templateUrl: './resources.component.html',
+ styleUrls: ['./resources.component.scss']
+})
+
+export class ResourcesComponent implements OnInit {
+ /**
+ * wrapper for the resources pages. acts as frame
+ */
+ constructor() { }
+
+ ngOnInit() {
+ }
+
+}
+
+ <router-outlet></router-outlet>
+
+ ./resources.component.scss
+
+ +
+ src/app/pages/resources/resources/components/resources-dashboard/resources-dashboard.component.ts
+
+
+ OnInit
+
selector | +app-resources-dashboard |
+
styleUrls | +./resources-dashboard.component.scss |
+
templateUrl | +./resources-dashboard.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(resourceService: ResourcesService, filterService: FiltersService, citiesandCounties: CitiesCountiesService, breakpointObserver: BreakpointObserver, authService: AuthenticationService, router: Router)
+ |
+ |||||||||||||||||||||
+ + | +|||||||||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + addresource + + + + | +
+addresource()
+ |
+
+ + | +
+ send user to add resource. if is NGO the ngo id is static. +
+ Returns :
+ void
+
+ |
+
+ + + + filterChanged + + + + | +||||||||
+filterChanged(id?: number)
+ |
+ ||||||||
+ + | +||||||||
+ filter callback. Filters added to pager and then a request is made +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + getData + + + + | +
+getData()
+ |
+
+ + | +
+ get data from server and store localy +
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + searchChanged + + + + | +||||||||
+searchChanged(pager: any)
+ |
+ ||||||||
+ + | +||||||||
+ search callback. Filters added to pager and then a request is made +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + sortChanged + + + + | +||||||||
+sortChanged(pager: any)
+ |
+ ||||||||
+ + | +||||||||
+ sort callback. Filters added to pager and then a request is made +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + switchtoblock + + + + | +
+switchtoblock()
+ |
+
+ + | +
+ set flag for HTML to grid view +
+ Returns :
+ void
+
+ |
+
+ + + + switchtolist + + + + | +
+switchtolist()
+ |
+
+ + | +
+ set flag for HTML to list view +
+ Returns :
+ void
+
+ |
+
+ + + + viewdetails + + + + | +||||||||
+viewdetails(res: any)
+ |
+ ||||||||
+ + | +||||||||
+ view details about resource by slug if DSU and by id if NGO +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + Public + authService + + + | +
+ Type : AuthenticationService
+
+ |
+
+ + | +
+ + + + Public + breakpointObserver + + + | +
+ Type : BreakpointObserver
+
+ |
+
+ + | +
+ + + + categoryFilterValues + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ values to select from when filtering + |
+
+ + + + displayBlock + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for HTML to know how to display data + |
+
+ + + + locationFilterValues + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ + + + navigationExtras + + + | +
+ Type : any
+
+ |
+
+ + | +
+ navigation extras will be sent to add resource if user is ngo. + |
+
+ + + + pager + + + | +
+ Type : any
+
+ |
+
+ Default value : {}
+ |
+
+ + | +
+ pager for the resources table + |
+
+ + + + propertyMap + + + | +
+ Type : object
+
+ |
+
+ Default value : {
+ '_id': 'id',
+ 'parent_id': 'parent_id'
+ }
+ |
+
+ + | +
+ match the id with _id and display parent_id in order to indent the apropriate subcategories + |
+
+ + + + resourcesData + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ store the resources list + |
+
+ + + + selected + + + | +
+ Default value : new Array(2)
+ |
+
+ + | +
+ selected filters array + |
+
import { Component, OnInit } from '@angular/core';
+import { ResourcesService } from '../../../resources.service';
+
+import { FiltersService, CitiesCountiesService } from '../../../../../core/service';
+import { BreakpointObserver } from '@angular/cdk/layout';
+import { AuthenticationService } from '@app/core';
+import { Router } from '@angular/router';
+
+@Component({
+ selector: 'app-resources-dashboard',
+ templateUrl: './resources-dashboard.component.html',
+ styleUrls: ['./resources-dashboard.component.scss']
+})
+export class ResourcesdashboardComponent implements OnInit {
+ /**
+ * store the resources list
+ */
+ resourcesData: any[] = [];
+ /**
+ * pager for the resources table
+ */
+ pager: any = {};
+ /**
+ * flag for HTML to know how to display data
+ */
+ displayBlock = false;
+ /**
+ *values to select from when filtering
+ */
+ categoryFilterValues: any[] = [];
+ locationFilterValues: any[] = [];
+ /**
+ * selected filters array
+ */
+ selected = new Array(2);
+ /**
+ * match the id with _id and display parent_id in order to indent the apropriate subcategories
+ */
+ propertyMap = {
+ '_id': 'id',
+ 'parent_id': 'parent_id'
+ };
+ /**
+ * navigation extras will be sent to add resource if user is ngo.
+ */
+ navigationExtras: any;
+ constructor(private resourceService: ResourcesService,
+ private filterService: FiltersService,
+ private citiesandCounties: CitiesCountiesService,
+ public breakpointObserver: BreakpointObserver,
+ public authService: AuthenticationService,
+ private router: Router) { }
+
+ ngOnInit() {
+ this.resourceService.setPager();
+ this.pager = this.resourceService.getPager();
+
+ this.getData();
+ /**
+ * get filterable values
+ */
+ this.filterService.getCategoryFilters().subscribe((data) => {
+ this.categoryFilterValues = data.map((x: any) => {
+ const parent = data.find((y: any) => y._id === x.parent_id);
+ return {
+ id: x._id,
+ name: x.name,
+ parent_id: x.parent_id,
+ pp: x.parent_id === '0' ? x.name : ( parent ? parent.name : null),
+ level: x.parent_id === '0' ? 0 : 1
+ };
+ });
+ });
+
+ this.citiesandCounties.getCounties('').subscribe((response: any) => {
+ const aux = response;
+ aux.map((elem: { id: any; _id: any; }) => elem.id = elem._id);
+ this.locationFilterValues = aux;
+ });
+ /**
+ *observe screen chage and and switch to grid view if screen is too smal
+ */
+ this.breakpointObserver
+ .observe(['(max-width: 768px)'])
+ .subscribe(result => {
+ if (result.matches) {
+ this.switchtoblock();
+ }
+ });
+ }
+ /**
+ * get data from server and store localy
+
+ */
+ getData() {
+ this.resourceService.getResources(this.pager).subscribe((data) => {
+ this.resourcesData = data.data;
+ this.pager.total = data.pager.total;
+ });
+ }
+ /**
+ * send user to add resource. if is NGO the ngo id is static.
+ */
+ addresource() {
+ if (this.authService.is('NGO')) {
+ const navigationExtras = {
+ state: {
+ ngo: {
+ // TO-DO: extragere informatiilor din contu utilizatorului
+ name: this.authService.user.organisation.name,
+ ngoid: this.authService.user.organisation._id
+ }
+ }
+ };
+ this.router.navigateByUrl('/resources/add', navigationExtras);
+ } else {
+ this.router.navigate(['resources/add']);
+ }
+ }
+ /**
+ * sort callback. Filters added to pager and then a request is made
+ * @param {any} pager the pager with the search filer added
+ */
+ sortChanged(pager: any) {
+ this.pager = pager;
+ this.getData();
+ }
+ /**
+ * search callback. Filters added to pager and then a request is made
+ * @param {any} pager the pager with the search filer added
+ */
+ searchChanged(pager: any) {
+ this.pager = pager;
+ this.getData();
+ }
+ /**
+ * filter callback. Filters added to pager and then a request is made
+ * @param {number} id the index in the pager filters and filters Selected array
+ */
+ filterChanged(id?: number) {
+ console.log(this.selected);
+ this.pager.filters[id] = this.selected[id].map((elem: any) => elem.id).join(',');
+ this.getData();
+ }
+ /**
+ * view details about resource by slug if DSU and by id if NGO
+ * @param {any} res the resource to be viewed
+ */
+ viewdetails(res: any) {
+ if (this.authService.is('DSU')) {
+ this.router.navigateByUrl(`/resources/name/${res.slug}`);
+ } else {
+ this.router.navigateByUrl(`/resources/id/${res.resources[0]._id}`);
+ }
+ }
+ /**
+ * set flag for HTML to list view
+ */
+ switchtolist() {
+ this.displayBlock = false;
+ }
+
+ /**
+ * set flag for HTML to grid view
+ */
+ switchtoblock() {
+ this.displayBlock = true;
+ }
+}
+
+ <div class="container">
+ <nav class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <h4 class="navtitle mb-2"> Resurse </h4>
+ <div class="form-inline ml-auto">
+ <app-table-search [pager]="pager" id=2 (searchChanged)="searchChanged($event)"></app-table-search>
+ <div class="btn-group btn-group-toggle m-2 hidden-radio" ngbRadioGroup name="radioBasic"
+ [(ngModel)]="displayBlock">
+ <label ngbButtonLabel (click)="switchtoblock()" class="btn btn-primary">
+ <input ngbButton type="radio" [value]="true"><span class="fa fa-th-large"></span>
+ </label>
+ <label ngbButtonLabel (click)="switchtolist()" class="btn btn-primary">
+ <input ngbButton type="radio" [value]="false"><span class="fa fa-bars"></span>
+ </label>
+ </div>
+ <button (click)="addresource()" class="add-btn btn-adjusting btn btn-info btn-rounded m-2">
+ <span class="fa fa-plus-circle"></span> Adaugă Resursă
+ </button>
+ </div>
+ </nav>
+ <div class="filters my-3 mx-30px row">
+ <span class="padding-rem">Total: {{pager.total}}</span>
+ <span class="padding-rem"> </span>
+ <span class="padding-rem">Filtrează după: </span>
+ <ngx-multiselect class="category"
+ [(ngModel)]="selected[0]"
+ [options]="categoryFilterValues"
+ (showMaxLables) = "1"
+ (onItemClick)="filterChanged(0)"
+ (onSelectNone)="filterChanged(0)"
+ (onSelectAll)="filterChanged(0)"
+ [propertyMap]="propertyMap" >
+ <ng-template let-option="option">
+ <span [ngClass]="{'pl-3': option.parent_id!=='0'}">
+ {{option.name}}
+ </span>
+ </ng-template>
+ </ngx-multiselect>
+ <span class="vertical-bar"> </span>
+ <ngx-multiselect
+ class="location"
+ [(ngModel)]="selected[1]"
+ showMaxLables = "1"
+ [options]="locationFilterValues"
+ (onItemClick)="filterChanged(1)"
+ (onSelectNone)="filterChanged(1)"
+ (onSelectAll)="filterChanged(1)">
+ </ngx-multiselect>
+ </div>
+ <div *ngIf="displayBlock; else displayList" class="cardlist row mt-5">
+ <div *ngFor="let elem of resourcesData" class="d-flex col-md-6 col-sm-12 col-lg-4">
+ <div class="card mb-2" style="flex-grow: 1" (click)="viewdetails(elem)">
+ <div class="card-body">
+ <a class="btn btn-fix text-left">
+ <h4 class="card-title">{{elem.name}}</h4>
+ <div class="card-text row">
+ <div *ngIf="elem.categories" class="col-md-12 d-flex">
+ <i class="fa fa-list"></i>
+ <span *ngFor="let cat of elem.categories;let isLast=last">
+ {{cat?.name}}{{isLast ? '' : ', '}}
+ </span>
+ </div>
+
+ <span class="col-md-12">
+ <i class="fa fa-hashtag"></i>
+ {{elem.quantity}}
+ </span>
+ <span class="col-md-12">
+ <i class="fa fa-building"></i>
+ {{elem.organisations_total}}
+ <!-- TODO add actual organisations number -->
+ </span>
+ </div>
+ </a>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-12 mx-30px">
+ <ngb-pagination
+ [maxSize]="5"
+ [(page)]="pager.page"
+ [pageSize]="pager.size"
+ (pageChange)="getData()"
+ [collectionSize]="pager.total">
+ </ngb-pagination>
+ </div>
+ </div>
+ <ng-template #displayList>
+ <div class="table-responsive">
+ <table class="table customTable table-sm">
+ <thead>
+ <tr>
+ <!-- sortable="name" (sort)="onSort($event)" -->
+ <th scope="col" appTableSort [pager]="pager" [value]="1" (sortChanged)="sortChanged($event)">
+ Nume Resursă</th>
+ <th scope="col" appTableSort [pager]="pager" [value]="2" (sortChanged)="sortChanged($event)">
+ Categorie</th>
+ <th scope="col" appTableSort [pager]="pager" [value]="3" (sortChanged)="sortChanged($event)">
+ Cantitate</th>
+ <th scope="col" appTableSort [pager]="pager" [value]="4" (sortChanged)="sortChanged($event)">
+ Nr. Organizații</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let res of resourcesData">
+ <!-- <tr> -->
+ <td>{{res.name}}</td>
+ <td>
+ <div *ngIf="res.categories">
+ <div *ngFor="let cat of res.categories;let isLast=last">
+ {{cat?.name}}{{isLast ? '' : ', '}}
+ </div>
+ </div>
+ </td>
+ <td>{{res.quantity}}</td>
+ <td>{{res.organisations_total}}</td>
+ <!-- <td>{{res.updated_at}}</td> -->
+ <td *ngIf="authService.is('DSU')" class="text-right"><button class="btn btn-info"
+ [routerLink]="['name',res.slug]">Vezi detalii</button></td>
+ <td *ngIf="authService.is('NGO')" class="text-right"><button class="btn btn-info"
+ [routerLink]="['id',res.resources[0]._id]">Vezi detalii</button></td>
+ <!-- <td *ngIf="authService.is('NGO')" class="text-right"><button class="btn btn-danger"
+ (click)="deleteres(res)">Șterge</button></td> -->
+ </tr>
+
+ </tbody>
+ </table>
+ </div>
+ <ngb-pagination
+ [maxSize]="5" [(page)]="pager.page" [pageSize]="pager.size" (pageChange)="getData()"
+ [collectionSize]="pager.total">
+ </ngb-pagination>
+ </ng-template>
+</div>
+
+ ./resources-dashboard.component.scss
+
+.card {
+ background-color: transparent;
+ margin: 10px;
+}
+
+.card-body {
+ padding: 0.25rem;
+ background-color:white;
+}
+
+@media (max-width: 425px) {
+ .container {
+ width: 100%;
+ max-width: none;
+ }
+}
+
+@media (max-width: 1199px) {
+ .btn-adjusting {
+ width: min-content;
+ }
+}
+::ng-deep .category{
+ .none-selected:before {
+ content: 'Categorie' !important;
+ }
+}
+.card-text {
+
+ span {
+ line-height: 1;
+ height: auto;
+ display: block;
+ overflow: initial;
+ overflow-wrap: initial;
+ word-break:initial;
+ }
+}
+ +
+ src/app/pages/authentication/signup/signup.component.ts
+
+
+ OnInit
+
selector | +app-signup |
+
styleUrls | +./signup.component.scss |
+
templateUrl | +./signup.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(router: Router, formBuilder: FormBuilder, authenticationService: AuthenticationService)
+ |
+ ||||||||||||
+ + | +||||||||||||
+ Component to show signup page +
+ Parameters :
+
+
|
+
+ + + + Private + createForm + + + + | +
+
+ createForm()
+ |
+
+ + | +
+ Sign up form definition for reactive forms +
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+ Angular ng on init +
+ Returns :
+ void
+
+ |
+
+ + + + signup + + + + | +
+signup()
+ |
+
+ + | +
+ Signup method called on form submit +
+ Returns :
+ void
+
+ |
+
+ + + + isLoading + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + Public + router + + + | +
+ Type : Router
+
+ |
+
+ + | +
+ + + + signupForm + + + | +
+ Type : FormGroup
+
+ |
+
+ + | +
+ Signup internal variables + |
+
import { Component, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { Router } from '@angular/router';
+import { AuthenticationService } from '@app/core';
+import { finalize } from 'rxjs/operators';
+import { EmailValidation } from '@app/core/validators/email-validation';
+import { PhoneValidation } from '@app/core/validators/phone-validation';
+
+@Component({
+ selector: 'app-signup',
+ templateUrl: './signup.component.html',
+ styleUrls: ['./signup.component.scss']
+})
+export class SignupComponent implements OnInit {
+ /**
+ * Signup internal variables
+ */
+ signupForm: FormGroup;
+ isLoading = false;
+ /**
+ * Component to show signup page
+ */
+ constructor(
+ public router: Router,
+ private formBuilder: FormBuilder,
+ private authenticationService: AuthenticationService
+ ) {
+ this.createForm();
+ }
+
+ /**
+ * Angular ng on init
+ */
+ ngOnInit() {}
+
+
+ /**
+ * Signup method called on form submit
+ */
+ signup() {
+ this.isLoading = true;
+ this.authenticationService
+ .signup(this.signupForm.value)
+ .pipe(
+ finalize(() => {
+ this.signupForm.markAsPristine();
+ this.isLoading = false;
+ })
+ )
+ .subscribe(
+ (user: Authentication.User) => {
+ console.log(user);
+ this.router.navigate(['/login']);
+ },
+ (error: any) => {
+ console.log('Signup error: ', error);
+ }
+ );
+ }
+
+ /**
+ * Sign up form definition for reactive forms
+ */
+ private createForm() {
+ this.signupForm = this.formBuilder.group({
+ firstName: ['', [Validators.required]],
+ lastName: ['', [Validators.required]],
+ email: ['', [Validators.required, EmailValidation.emailValidation]],
+ phone: ['', [Validators.required, PhoneValidation.phoneValidation]],
+ password: ['', Validators.required],
+ cPassword: ['', Validators.required]
+ });
+ }
+}
+
+
+
+ ./signup.component.scss
+
+.login-register {
+ .login-box.card {
+ .card-body {
+ height: 100%;
+ overflow-y: scroll;
+ flex: 1 1 auto;
+ padding: 1.25rem;
+ margin-left: auto;
+ margin-right: auto;
+ /* display: block; */
+ max-width: 550px;
+ /* margin: auto; */
+ }
+ }
+}
+
+ +
+ src/app/shared/spinner.component.ts
+
+
+ OnDestroy
+
encapsulation | +ViewEncapsulation.None |
+
selector | +app-spinner |
+
template | +
|
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+ Inputs+ |
+
+
|
+
+constructor(router: Router, document: Document)
+ |
+ |||||||||
+ Defined in src/app/shared/spinner.component.ts:37
+ |
+ |||||||||
+ loading screen spinner trigger +
+ Parameters :
+
+
|
+
+ + backgroundColor + | +|
+ Default value : 'rgba(0, 115, 170, 0.69)'
+ |
+ |
+ Defined in src/app/shared/spinner.component.ts:37
+ |
+ |
+ Background input variable + |
+
+ + + + ngOnDestroy + + + + | +
+ngOnDestroy()
+ |
+
+ Defined in src/app/shared/spinner.component.ts:66
+ |
+
+ Spinner component destroy method called by Angular internally +
+ Returns :
+ void
+
+ |
+
+ + + + Public + isSpinnerVisible + + + | +
+ Default value : true
+ |
+
+ Defined in src/app/shared/spinner.component.ts:31
+ |
+
+ Variable for spinner visibility status + |
+
import {
+ Component,
+ Input,
+ OnDestroy,
+ Inject,
+ ViewEncapsulation
+} from '@angular/core';
+import {
+ Router,
+ NavigationStart,
+ NavigationEnd,
+ NavigationCancel,
+ NavigationError
+} from '@angular/router';
+import { DOCUMENT } from '@angular/common';
+
+@Component({
+ selector: 'app-spinner',
+ template: `<div class="preloader" *ngIf="isSpinnerVisible">
+ <div class="spinner">
+ <div class="double-bounce1"></div>
+ <div class="double-bounce2"></div>
+ </div>
+ </div>`,
+ encapsulation: ViewEncapsulation.None
+})
+export class SpinnerComponent implements OnDestroy {
+ /**
+ * Variable for spinner visibility status
+ */
+ public isSpinnerVisible = true;
+
+ /**
+ * Background input variable
+ */
+ @Input()
+ public backgroundColor = 'rgba(0, 115, 170, 0.69)';
+ /**
+ * loading screen spinner trigger
+ */
+ constructor(
+ private router: Router,
+ @Inject(DOCUMENT) private document: Document
+ ) {
+ this.router.events.subscribe(
+ event => {
+ if (event instanceof NavigationStart) {
+ this.isSpinnerVisible = true;
+ } else if (
+ event instanceof NavigationEnd ||
+ event instanceof NavigationCancel ||
+ event instanceof NavigationError
+ ) {
+ this.isSpinnerVisible = false;
+ }
+ },
+ () => {
+ this.isSpinnerVisible = false;
+ }
+ );
+ }
+
+ /**
+ * Spinner component destroy method called by Angular internally
+ */
+ ngOnDestroy(): void {
+ this.isSpinnerVisible = false;
+ }
+}
+
+ +
+ src/app/shared/table-search/table-search.component.ts
+
+
+ OnInit
+
selector | +app-table-search |
+
templateUrl | +./table-search.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+ + | +
+ Inputs+ |
+
+ + | +
+ Outputs+ |
+
+
|
+
+ Accessors+ |
+
+ + | +
+constructor()
+ |
+
+ + | +
+ + id + | +|
+ Type : any
+
+ |
+ |
+ + | +|
+ input val to set id for filter + |
+
+ + pager + | +|
+ Type : any
+
+ |
+ |
+ + | +|
+ input val to set pager for filter + |
+
+ + searchChanged + | +|
+ Type : EventEmitter
+
+ |
+ |
+ + | +|
+ Observable sortChanged for triggering modifications in the main component + |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + search + + + + | +||||||||
+search(term: string)
+ |
+ ||||||||
+ + | +||||||||
+ emit search event with the input term +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + _id + + + | +
+ Type : number
+
+ |
+
+ + | +
+ internal id for filter + |
+
+ + + + _pager + + + | +
+ Type : any
+
+ |
+
+ Default value : {}
+ |
+
+ + | +
+ internal pager value + |
+
+ + + + searchInput + + + | +
+ Type : ElementRef
+
+ |
+
+ Decorators :
+ +
+ @ViewChild('searchInput', {static: true})
+ |
+
+ + | +
+ + id + | +||||||
+ getid()
+ |
+ ||||||
+ + | +||||||
+ return id + |
+ ||||||
+ setid(data: any)
+ |
+ ||||||
+ + | +||||||
+ input val to set id for filter +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+ |
+
+ + pager + | +||||||
+ getpager()
+ |
+ ||||||
+ + | +||||||
+ return pager +
+ Returns :
+ any
+
+ |
+ ||||||
+ setpager(data: any)
+ |
+ ||||||
+ + | +||||||
+ input val to set pager for filter +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+ |
+
import { Component, HostBinding, Input, Output, EventEmitter, HostListener, OnInit, ViewChild, ElementRef } from '@angular/core';
+import { Subject, fromEvent } from 'rxjs';
+import { debounceTime, distinctUntilChanged, filter, map } from 'rxjs/operators';
+
+@Component({
+ selector: 'app-table-search',
+ templateUrl: './table-search.component.html'// ,
+ // styleUrls: ['./table-search.directive.scss']
+})
+/**
+ * Reusable table search component
+ */
+export class TableSearchComponent implements OnInit {
+ /**
+ * internal pager value
+ */
+ _pager: any = {};
+ /**
+ * internal id for filter
+ */
+ _id: number;
+ /**
+ * input val to set id for filter
+ */
+ @Input()
+ set id(data: any) {
+ this._id = data;
+ }
+ /**
+ * return id
+ */
+ get id() {
+ return this._id;
+ }
+ @ViewChild('searchInput', {static: true}) searchInput: ElementRef;
+/**
+ * return pager
+ */
+ get pager(): any {
+ return this._pager;
+ }
+/**
+ * input val to set pager for filter
+ */
+ @Input()
+ set pager(data: any) {
+ this._pager = data;
+ }
+/**
+ * Observable sortChanged for triggering modifications in the main component
+ */
+ @Output() searchChanged = new EventEmitter();
+
+
+ constructor() {
+ }
+
+
+ ngOnInit() {
+ /**
+ * register to key events in the referenced input
+ */
+ fromEvent(this.searchInput.nativeElement, 'keyup').pipe(
+ debounceTime(500),
+ map((event: any) => {
+ return event.target.value;
+ }),
+ filter(res => res.length > 2 || res.length === 0),
+ distinctUntilChanged(),
+ ).subscribe(term => {
+ this.search(term);
+ });
+ }
+ /**
+ * emit search event with the input term
+ * @param {string} term the current value of the input
+ */
+ search(term: string) {
+ this.pager.filters[this.id] = term;
+ this.searchChanged.emit({...this.pager});
+ }
+
+}
+
+ <div class="p-2">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <span class="input-group-text" id="basic-addon1"><span class="fa fa-search"></span></span>
+ </div>
+ <input type="text" class="form-control" placeholder="Caută" aria-label="Caută"
+ aria-describedby="basic-addon1" #searchInput>
+ </div>
+</div>
+ +
+ src/app/top-bar/top-bar.component.ts
+
+
+ OnInit
+
selector | +app-top-bar |
+
styleUrls | +./top-bar.component.scss |
+
templateUrl | +./top-bar.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(authService: AuthenticationService, router: Router)
+ |
+ |||||||||
+ Defined in src/app/top-bar/top-bar.component.ts:11
+ |
+ |||||||||
+ top bar to be shown over all the other components +
+ Parameters :
+
+
|
+
+ + + + goToDashboard + + + + | +
+goToDashboard()
+ |
+
+ Defined in src/app/top-bar/top-bar.component.ts:36
+ |
+
+ go to home path on icon click +
+ Returns :
+ void
+
+ |
+
+ + + + logout + + + + | +
+logout()
+ |
+
+ Defined in src/app/top-bar/top-bar.component.ts:22
+ |
+
+ logout from account +
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Defined in src/app/top-bar/top-bar.component.ts:18
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + Public + authService + + + | +
+ Type : AuthenticationService
+
+ |
+
+ Defined in src/app/top-bar/top-bar.component.ts:15
+ |
+
import { Component, OnInit } from '@angular/core';
+import { AuthenticationService } from '@app/core';
+import { Router } from '@angular/router';
+
+@Component({
+ selector: 'app-top-bar',
+ templateUrl: './top-bar.component.html',
+ styleUrls: ['./top-bar.component.scss']
+})
+
+export class TopBarComponent implements OnInit {
+ /**
+ * top bar to be shown over all the other components
+ */
+ constructor(public authService: AuthenticationService,
+ private router: Router) {}
+
+ ngOnInit() { }
+/**
+ * logout from account
+ */
+ logout() {
+ this.authService.logout().subscribe(
+ (didlogout: Boolean) => {
+ if (didlogout) {
+ this.router.navigate(['/login']);
+ }
+ },
+ (error: any) => {
+ console.log('logout error: ', error);
+ });
+ }
+/**
+ * go to home path on icon click
+ */
+ goToDashboard() {
+ this.router.navigate(['/' + this.authService.homePath()], {
+ replaceUrl: true
+ });
+ }
+}
+
+ <header *ngIf="authService.isAuthenticated()">
+ <nav class=" yellow container-full-height">
+ <div class="flex-container container flex-row">
+ <div class="px-3 bg-white">
+ <a (click)="goToDashboard()" class=" navbar-brand logo" href="javascript:void(0);">
+ <img src="../../assets/images/DSU_logo.PNG">
+ </a>
+ </div>
+ <div class="row" style="width: 100%; margin: 0">
+ <div class="flex-column pr-3 d-none d-xl-block col-xl-10 pl-0">
+ <ul class="nav">
+ <li>
+ <a routerLink="/volunteers" class="nav__link" routerLinkActive="active-link">Voluntari</a>
+ </li>
+ <li *ngIf="authService.is('DSU', 'NGO')">
+ <a routerLink='/resources' class="nav__link" routerLinkActive="active-link">Resurse</a>
+ </li>
+ <li *ngIf="authService.is('DSU')">
+ <a routerLink='/organisations' class="nav__link" routerLinkActive="active-link">Organizații</a>
+ </li>
+ <li *ngIf="authService.is('DSU', 'INS')">
+ <a routerLink='/users' class="nav__link" routerLinkActive="active-link">Utilizatori</a>
+ </li>
+ <li *ngIf="authService.is('DSU')">
+ <a routerLink='/map' class="nav__link" routerLinkActive="active-link">Hartă</a>
+ </li>
+ <li>
+ <a routerLink="/info" class="nav__link" routerLinkActive="active-link">Info</a>
+ </li>
+ </ul>
+ </div>
+ <div class="navbar-btn flex-row d-flex justify-end align-center col-xs-12 col-sm-12 col-md-12 col-lg-12 col-xl-2 pr-4 pl-0">
+ <div ngbDropdown class="d-inline-block">
+ <div class="userarea" id="dropdown1" ngbDropdownToggle>
+ <app-current-profile></app-current-profile>
+ </div>
+ <div ngbDropdownMenu aria-labelledby="dropdown1">
+ <button ngbDropdownItem [routerLink]="['users', 'id', authService.user._id]">Profil</button>
+ <button ngbDropdownItem routerLink='/volunteers' class="hidden-button">Voluntari</button>
+ <button *ngIf="authService.is('DSU', 'NGO')" ngbDropdownItem routerLink='/resources' class="hidden-button">Resurse</button>
+ <button *ngIf="authService.is('DSU')" ngbDropdownItem routerLink='/organisations' class="hidden-button">Organizații</button>
+ <button *ngIf="authService.is('DSU', 'INS')" ngbDropdownItem routerLink='/users' class="hidden-button">Utilizatori</button>
+ <button *ngIf="authService.is('DSU')" ngbDropdownItem routerLink='/map' class="hidden-button">Hartă</button>
+ <button ngbDropdownItem routerLink='/info' class="hidden-button">Info</button>
+ <div class="dropdown-divider"></div>
+ <button ngbDropdownItem (click)="logout()">Delogare</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </nav>
+</header>
+
+
+ ./top-bar.component.scss
+
header {
+ height: 60px;
+}
+
+.logo {
+ display: inline-block;
+ height: 60px;
+ width:60px;
+ margin: auto;
+ img{
+ margin: auto;
+ display: block;
+ max-width: 100%;
+ max-height: 100%;
+ }
+}
+
+.nav {
+ display: flex;
+ height: 60px;
+ flex-direction: row;
+ line-height: 60px;
+ white-space: nowrap;
+}
+
+.nav .nav__link {
+ color: #000;
+ display: inline-block;
+ font-weight: bold;
+ font-size: 14px;
+ padding: 0 25px;
+ text-transform: uppercase;
+}
+
+.nav .nav__link:hover {
+ background-color: #264998;
+ color: #fff;
+}
+
+.yellow {
+ background-color:rgb(243, 217, 115 );
+}
+/* Layout */
+.flex-container {
+ display: flex;
+ width: 100%;
+}
+
+.flex-row {
+ flex-direction: row;
+}
+
+.align-center {
+ align-items: center;
+}
+
+.justify-end {
+ justify-content: flex-end;
+}
+
+.container-full-height {
+ height: 100%;
+}
+
+.hidden-button {
+ display: none;
+}
+
+@media (max-width: 1199px) {
+ .hidden-button {
+ display: block;
+ }
+}
+
+.dropdown-item:active {
+ background-color: gray;
+}
+
+.dropdown-item:focus {
+ outline:none;
+ border:none;
+}
+
+.active-link {
+ background-color: #264998;
+ color: #fff!important;
+}
+ +
+ src/app/pages/users/users/components/user-dashboard/user-dashboard.component.ts
+
+
+ OnInit
+
selector | +app-user-dashboard |
+
styleUrls | +./user-dashboard.component.scss |
+
templateUrl | +./user-dashboard.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(usersService: UsersService, breakpointObserver: BreakpointObserver, modalService: NgbModal, filterService: FiltersService, router: Router, authService: AuthenticationService, fb: FormBuilder)
+ |
+ ||||||||||||||||||||||||
+ + | +||||||||||||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + addUser + + + + | +||||||
+addUser(content: any)
+ |
+ ||||||
+ + | +||||||
+ go to add page for rescue officer or open the modal to select type of user +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + continue + + + + | +
+continue()
+ |
+
+ + | +
+ go to add page and dismiss the modal +
+ Returns :
+ void
+
+ |
+
+ + + + filterChanged + + + + | +||||||||
+filterChanged(id?: number)
+ |
+ ||||||||
+ + | +||||||||
+ filter callback. Filters added to pager and then a request is made +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + getData + + + + | +
+getData()
+ |
+
+ + | +
+ get data from server and store localy +
+ Returns :
+ void
+
+ |
+
+ + + + getRole + + + + | +||||||||
+getRole(id: string)
+ |
+ ||||||||
+ + | +||||||||
+ get role names from local list to display in select +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + goToOrganisation + + + + | +||||||||||||
+goToOrganisation(id: string, e: any)
+ |
+ ||||||||||||
+ + | +||||||||||||
+ navigate to organisation by id +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + searchChanged + + + + | +||||||||
+searchChanged(pager: any)
+ |
+ ||||||||
+ + | +||||||||
+ search callback. Filters added to pager and then a request is made +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + sortChanged + + + + | +||||||||
+sortChanged(pager: any)
+ |
+ ||||||||
+ + | +||||||||
+ sort callback. Filters added to pager and then a request is made +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + switchtoblock + + + + | +
+switchtoblock()
+ |
+
+ + | +
+ set class of display element with grid view +
+ Returns :
+ void
+
+ |
+
+ + + + switchtolist + + + + | +
+switchtolist()
+ |
+
+ + | +
+ set class of display element with list view +
+ Returns :
+ void
+
+ |
+
+ + + + Public + authService + + + | +
+ Type : AuthenticationService
+
+ |
+
+ + | +
+ + + + Public + breakpointObserver + + + | +
+ Type : BreakpointObserver
+
+ |
+
+ + | +
+ + + + data + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ var to hold the users + |
+
+ + + + displayBlock + + + | +
+ Default value : true
+ |
+
+ + | +
+ flag for HTML to know how to display data + |
+
+ + + + form + + + | +
+ Type : FormGroup
+
+ |
+
+ + | +
+ form in which to store the role from the modal + |
+
+ + + + institutionfiltervalues + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ insitution to select from + |
+
+ + + + pager + + + | +
+ Type : any
+
+ |
+
+ Default value : {}
+ |
+
+ + | +
+ pager for the resources table + |
+
+ + + + roles + + + | +
+ Type : []
+
+ |
+
+ Default value : [
+ {
+ id: 0,
+ name: 'Ofițer de intervenție'
+ },
+ {
+ id: 1,
+ name: 'Administrator instituțional'
+ },
+ {
+ id: 2,
+ name: 'Administrator ONG'
+ },
+ {
+ id: 3,
+ name: 'Administrator General'
+ },
+ ]
+ |
+
+ + | +
+ + + + selected + + + | +
+ Default value : Array(1)
+ |
+
+ + | +
+ selected values for the filter + |
+
import { Component, OnInit } from '@angular/core';
+import { BreakpointObserver } from '@angular/cdk/layout';
+import { UsersService } from '@app/core/service/users.service';
+import {
+ FormGroup,
+ Validators,
+ FormBuilder
+} from '@angular/forms';
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
+import { Router } from '@angular/router';
+import { AuthenticationService, FiltersService } from '@app/core';
+
+@Component({
+ selector: 'app-user-dashboard',
+ templateUrl: './user-dashboard.component.html',
+ styleUrls: ['./user-dashboard.component.scss']
+})
+
+export class UserDashboardComponent implements OnInit {
+ /**
+ * var to hold the users
+ */
+ data: any[] = [];
+ /**
+ * pager for the resources table
+ */
+ pager: any = {};
+ /**
+ * flag for HTML to know how to display data
+ */
+ displayBlock = true;
+ /**
+ * form in which to store the role from the modal
+ */
+ form: FormGroup;
+ roles = [
+ {
+ id: 0,
+ name: 'Ofițer de intervenție'
+ },
+ {
+ id: 1,
+ name: 'Administrator instituțional'
+ },
+ {
+ id: 2,
+ name: 'Administrator ONG'
+ },
+ {
+ id: 3,
+ name: 'Administrator General'
+ },
+ ];
+ /**
+ * selected values for the filter
+ */
+ selected = Array(1);
+ /**
+ * insitution to select from
+ */
+ institutionfiltervalues: any[] = [];
+
+ constructor(private usersService: UsersService,
+ public breakpointObserver: BreakpointObserver,
+ private modalService: NgbModal,
+ private filterService: FiltersService,
+ private router: Router, public authService: AuthenticationService,
+ private fb: FormBuilder) { }
+
+ ngOnInit() {
+ this.pager = this.usersService.getPager();
+ this.getData();
+ this.filterService.getInstitutionFilters().subscribe((data: any) => {
+ this.institutionfiltervalues = data.map((elem: any) => {
+ return {id: elem._id, name: elem.name};
+ });
+ });
+ this.breakpointObserver.observe([
+ '(max-width: 768px)'
+ ]).subscribe(result => {
+ if (result.matches) {
+ this.switchtoblock();
+ }
+ });
+
+ this.form = this.fb.group({
+ role: ['', Validators.required]
+ });
+ }
+/**
+ * get role names from local list to display in select
+ * @param {string} id of the role to be found in the roles array
+ */
+ getRole(id: string) {
+ for (const elem of this.roles) {
+ if (elem.id === parseInt(id, 10)) { return elem.name; }
+ }
+ }
+/**
+ * go to add page for rescue officer or open the modal to select type of user
+ */
+ addUser(content: any) {
+ if (this.authService.is('DSU')) {
+ this.modalService.open(content, { centered: true });
+ } else {
+ this.router.navigateByUrl('/users/add/0');
+ }
+ }
+/**
+ * get data from server and store localy
+
+ */
+ getData() {
+ this.usersService.getUsers(this.pager).subscribe(element => {
+ if (element.data) {
+ this.data = element.data;
+ this.pager.total = element.pager.total;
+ }
+ });
+ }
+ /**
+ * go to add page and dismiss the modal
+ */
+ continue() {
+ this.router.navigateByUrl('/users/add/' + this.form.value.role);
+ this.modalService.dismissAll();
+ }
+ /**
+ * sort callback. Filters added to pager and then a request is made
+ * @param {any} pager the pager with the search filer added
+ */
+ sortChanged(pager: any) {
+ this.pager = pager;
+ this.getData();
+ }
+ /**
+ * search callback. Filters added to pager and then a request is made
+ * @param {any} pager the pager with the search filer added
+ */
+ searchChanged(pager: any) {
+ this.pager = pager;
+ this.getData();
+ }
+/**
+ * filter callback. Filters added to pager and then a request is made
+ * @param {number} id the index in the pager filters and filters Selected array
+ */
+ filterChanged(id?: number) {
+ this.pager.filters[id] = this.selected[id].map((elem: any) => elem.id).join(',');
+ this.getData();
+ }
+
+ /**
+ * set class of display element with list view
+ */
+ switchtolist() {
+ this.displayBlock = false;
+ }
+ /**
+ * set class of display element with grid view
+ */
+ switchtoblock() {
+ this.displayBlock = true;
+ }
+ /**
+ * navigate to organisation by id
+ * @param {string} id of the NGO to display
+ */
+ goToOrganisation(id: string, e: any) {
+ e.preventDefault();
+ this.router.navigate(['../organisations/id/' + id]);
+ }
+}
+
+ <div class="container">
+ <nav class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <h4 class="navtitle mb-2"> Utilizatori </h4>
+ <div class="form-inline ml-auto">
+ <app-table-search [pager]="pager" id=1 (searchChanged)="searchChanged($event)"></app-table-search>
+ <div class="btn-group btn-group-toggle m-2 hidden-radio" ngbRadioGroup name="radioBasic" [(ngModel)]="displayBlock">
+ <label ngbButtonLabel (click)="switchtoblock()" class="btn btn-primary">
+ <input ngbButton type="radio" [value]="true"><span class="fa fa-th-large"></span>
+ </label>
+ <label ngbButtonLabel (click)="switchtolist()" class="btn btn-primary">
+ <input ngbButton type="radio" [value]="false"><span class="fa fa-bars"></span>
+ </label>
+ </div>
+ <button (click)="addUser(content)" class=" add-btn btn-adjusting btn btn-info btn-rounded m-2"><span class="fa fa-plus-circle"></span> Adaugă Utilizator</button>
+ </div>
+ </nav>
+ <div class="filters my-3 mx-30px row">
+ <span class="padding-rem">Total: {{pager.total}}</span>
+ <span class="padding-rem"> </span>
+ <span class="padding-rem">Filtrează după: </span>
+ <ngx-multiselect
+ class="institutie"
+ [(ngModel)]="selected[0]"
+ showMaxLables = "1"
+ [options]="institutionfiltervalues"
+ (onItemClick)="filterChanged(0)"
+ (onSelectNone)="filterChanged(0)"
+ (onSelectAll)="filterChanged(0)">
+ </ngx-multiselect>
+ </div>
+ <div *ngIf="displayBlock; else displayList" class="cardlist row mt-5">
+ <div *ngFor="let elem of data" class="d-flex col-md-6 col-sm-12 col-lg-4">
+ <div class="card mb-2" style="flex-grow: 1" [routerLink]="['id',elem._id]" >
+ <div class="card-body">
+ <a class="btn btn-fix text-left">
+ <h4 class="card-title">{{ elem.name }}</h4>
+ <div class="card-text row">
+ <span class="col-md-12">
+ <i class="fa fa-envelope"></i>
+ {{ elem.email }}
+ </span>
+ <span class="col-md-12">
+ <i class="fa fa-phone"></i>
+ {{ elem.phone }}
+ </span>
+ </div>
+ </a>
+
+ <span *ngIf="!!elem.organisation" class="ml-3">
+ <i class="fa fa-building"></i>
+ <a class="col-md-12 text-left pl-0" (click)="goToOrganisation(elem.organisation?._id, $event)" href="javascript:void(0);">
+ {{ elem.organisation.name }}
+ </a>
+ </span>
+
+ <span *ngIf="!!elem.institution" class="ml-3">
+ <i class="fa fa-building"></i>
+ {{ elem.institution.name }}
+ </span>
+
+ <span *ngIf="elem.role === '3'" class="ml-3">
+ <i class="fa fa-building"></i>
+ Administrator General
+ </span>
+ </div>
+
+ </div>
+ </div>
+ <div class="col-md-12 mx-30px">
+ <ngb-pagination
+ [maxSize]="5"
+ [(page)]="pager.page"
+ [pageSize]="pager.size"
+ (pageChange)="getData()"
+ [collectionSize]="pager.total">
+ </ngb-pagination>
+ </div>
+ </div>
+
+ <ng-template #displayList>
+ <div class="table-responsive">
+ <table class="table customTable table-sm">
+ <thead>
+ <tr>
+ <!-- sortable="name" (sort)="onSort($event)" -->
+ <th scope="col" appTableSort [pager]="pager" [value]="1" (sortChanged)="sortChanged($event)">Nume</th>
+ <th scope="col" appTableSort [pager]="pager" [value]="2" (sortChanged)="sortChanged($event)">Tip</th>
+ <th scope="col" appTableSort [pager]="pager" [value]="3" (sortChanged)="sortChanged($event)">Instituție</th>
+ <!-- <th scope="col" appTableSort [pager]="pager" [value]="4" (sortChanged)="sortChanged($event)"></!-->
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let res of data">
+ <!-- <tr> -->
+ <td>{{res.name}}</td>
+ <td>{{getRole(res.role)}}</td>
+ <td>{{res.institution?.name}}</td>
+ <td class="text-right"><button class="btn btn-info" [routerLink]="['id',res._id]">Vezi detalii</button></td>
+ </tr>
+
+ </tbody>
+ </table>
+ </div>
+ <ngb-pagination
+ [maxSize]="5"
+ [(page)]="pager.page"
+ [pageSize]="pager.size"
+ (pageChange)="getData()"
+ [collectionSize]="pager.total">
+ </ngb-pagination>
+ </ng-template>
+</div>
+
+<ng-template #content let-modal>
+ <div class="modal-header">
+ <h4 class="modal-title">Utilizator nou</h4>
+ <button type="button" class="close" (click)="modal.dismiss('Cross click')">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <form [formGroup]="form">
+ <div class="modal-body">
+ <div class="row">
+ <div class="mt-4 col-md-12">
+ <div class="form-group">
+ <label>Tipul utilizatorului</label>
+ <select formControlName="role" class="form-control">
+ <option [value]="role.id" *ngFor="let role of roles"> {{role.name}} </option>
+ </select>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button type="button" ngbAutofocus class="btn btn-info rounded" (click)="continue()" [disabled]="form.invalid">Continuă</button>
+ </div>
+ </form>
+</ng-template>
+
+
+ ./user-dashboard.component.scss
+
::ng-deep .institutie{
+ .none-selected::before{
+ content: 'Instituție' !important;
+ }
+}
+ +
+ src/app/pages/users/users/components/user-details/user-details.component.ts
+
+
+ OnInit
+
selector | +app-user-details |
+
styleUrls | +./user-details.component.scss |
+
templateUrl | +./user-details.component.html |
+
+ Properties+ |
+
+ + | +
+ Methods+ |
+
+ + | +
+constructor(route: ActivatedRoute, authService: AuthenticationService, router: Router, usersService: UsersService, location: Location)
+ |
+ ||||||||||||||||||
+ + | +||||||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + delete + + + + | +
+delete()
+ |
+
+ + | +
+ send to delete page +
+ Returns :
+ void
+
+ |
+
+ + + + edit + + + + | +
+edit()
+ |
+
+ + | +
+ send to edit page +
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + data + + + | +
+ Type : any
+
+ |
+
+ + | +
+ store the user details + |
+
+ + + + loading + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for HTML to display loading animation + |
+
+ + + + userTypes + + + | +
+ Type : []
+
+ |
+
+ Default value : [ 'Ofițer de intervenție', 'Administratorul instituțional', 'Administrator ONG', 'Administrator General']
+ |
+
+ + | +
+ user types to display instead of role which is number + |
+
import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+import { UsersService } from '@app/core/service/users.service';
+import { Location } from '@angular/common';
+import { AuthenticationService } from '@app/core';
+
+@Component({
+ selector: 'app-user-details',
+ templateUrl: './user-details.component.html',
+ styleUrls: ['./user-details.component.scss']
+})
+export class UserDetailsComponent implements OnInit {
+ /**
+ * store the user details
+ */
+ data: any;
+ /**
+ * user types to display instead of role which is number
+ */
+ userTypes = [ 'Ofițer de intervenție', 'Administratorul instituțional', 'Administrator ONG', 'Administrator General'];
+ /**
+ * flag for HTML to display loading animation
+ */
+ loading = false;
+
+ constructor(private route: ActivatedRoute,
+ private authService: AuthenticationService,
+ private router: Router,
+ private usersService: UsersService,
+ private location: Location) { }
+
+ ngOnInit() {
+ this.usersService.getUser(this.route.snapshot.paramMap.get('id')).subscribe(response => {
+ this.data = response;
+ });
+ }
+/**
+ * send to edit page
+ */
+ edit() {
+ this.router.navigate(['/users/edit/' + this.data._id]);
+ }
+/**
+ * send to delete page
+ */
+ delete() {
+ /**
+ * check if is the current user deleting his own account
+ */
+ if (this.authService.user._id === this.data._id) {
+ if (confirm('Sunteți sigur că doriți să vă ștergeți contul?')) {
+ this.loading = true;
+ this.usersService.deleteUser(this.data._id).subscribe(response => {
+ this.loading = false;
+ this.authService.setCredentials();
+ this.router.navigateByUrl('/login');
+ }, () => {
+ this.location.back();
+ });
+ }
+ } else {
+ if (confirm('Sunteți sigur că doriți să ștergeți această intrare? Odată ștearsă nu va mai putea fi recuperată.')) {
+ this.loading = true;
+ this.usersService.deleteUser(this.data._id).subscribe(response => {
+ this.loading = false;
+ this.location.back();
+ }, () => {
+ this.location.back();
+ });
+ }
+ }
+ }
+}
+
+ <div class="container">
+ <nav class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <app-back-button></app-back-button>
+ </nav>
+ <div class="ngo-details row">
+ <h3 class="col-md-12">Profil Utilizator: {{data?.name}}</h3>
+
+ <span class="col-md-12 mt-1"><span class="fa fa-envelope"></span> {{data?.email}}</span>
+ <span class="col-md-12 mt-1"><span class="fa fa-phone"></span> {{data?.phone}}</span>
+
+ <div class="col-md-12 mt-1">
+ <span><b>Tipul utilizatorului: </b> {{ userTypes[data?.role] }}</span>
+ </div>
+ <div class="col-md-12 mt-1">
+ <span><b>Data adăugării: </b> {{ data?.created_at | date: 'dd.MM.yyyy' }}</span>
+ </div>
+ </div>
+
+ <button class="btn btn-info rounded mt-3" (click)="edit()" [disabled]="!data">
+ <span class="fa fa-edit"></span>
+ Editează utilizatorul
+ </button>
+ <button class="btn btn-danger rounded mt-3 ml-3" (click)="delete()" [disabled]="!data" *ngIf="!loading">
+ <span class="fa fa-trash-o"></span>
+ Șterge utilizatorul
+ </button>
+ <div class="spinner-border text-danger" role="status" *ngIf="loading">
+ <span class="sr-only">Loading...</span>
+ </div>
+</div>
+
+
+ ./user-details.component.scss
+
+ +
+ src/app/pages/users/users/users.component.ts
+
+
+ OnInit
+
selector | +app-users |
+
styleUrls | +./users.component.scss |
+
templateUrl | +./users.component.html |
+
+ Methods+ |
+
+
|
+
+constructor()
+ |
+
+ + | +
+ wrapper for the users pages. acts as frame + |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
import { Component, OnInit } from '@angular/core';
+@Component({
+ selector: 'app-users',
+ templateUrl: './users.component.html',
+ styleUrls: ['./users.component.scss']
+})
+
+export class UsersComponent implements OnInit {
+ /**
+ * wrapper for the users pages. acts as frame
+ */
+ constructor() { }
+
+ ngOnInit() {
+ }
+
+}
+
+ <router-outlet></router-outlet>
+
+ ./users.component.scss
+
+ +
+ src/app/pages/volunteers/volunteers/components/volunteer-dashboard/volunteer-dashboard.component.ts
+
+
+ OnInit
+
selector | +app-volunteer-dashboard |
+
styleUrls | +./volunteer-dashboard.component.scss |
+
templateUrl | +./volunteer-dashboard.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(volunteerService: VolunteerService, breakpointObserver: BreakpointObserver, citiesandcounties: CitiesCountiesService, filterService: FiltersService, authService: AuthenticationService, router: Router)
+ |
+ |||||||||||||||||||||
+ + | +|||||||||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + addvolunteer + + + + | +
+addvolunteer()
+ |
+
+ + | +
+ send user to add volunteers. if is NGO the ngo id is static. +
+ Returns :
+ void
+
+ |
+
+ + + + filterChanged + + + + | +||||||||
+filterChanged(id?: number)
+ |
+ ||||||||
+ + | +||||||||
+ filter callback. Filters added to pager and then a request is made +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + getData + + + + | +
+getData()
+ |
+
+ + | +
+ get data from server and store localy +
+ Returns :
+ void
+
+ |
+
+ + + + goToOrganisation + + + + | +||||||||||||
+goToOrganisation(id: string, e: any)
+ |
+ ||||||||||||
+ + | +||||||||||||
+ navigate to organisation by id +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + searchChanged + + + + | +||||||||
+searchChanged(pager: any)
+ |
+ ||||||||
+ + | +||||||||
+ search callback. Filters added to pager and then a request is made +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + sortChanged + + + + | +||||||||
+sortChanged(pager: any)
+ |
+ ||||||||
+ + | +||||||||
+ sort callback. Filters added to pager and then a request is made +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + switchtoblock + + + + | +
+switchtoblock()
+ |
+
+ + | +
+ set flag for HTML to grid view +
+ Returns :
+ void
+
+ |
+
+ + + + switchtolist + + + + | +
+switchtolist()
+ |
+
+ + | +
+ set flag for HTML to list view +
+ Returns :
+ void
+
+ |
+
+ + + + Public + authService + + + | +
+ Type : AuthenticationService
+
+ |
+
+ + | +
+ + + + Public + breakpointObserver + + + | +
+ Type : BreakpointObserver
+
+ |
+
+ + | +
+ + + + displayBlock + + + | +
+ Default value : true
+ |
+
+ + | +
+ flag for HTML to know how to display data + |
+
+ + + + locationFilterValues + + + | +
+ Type : any[]
+
+ |
+
+ + | +
+ + + + NGOFilterValues + + + | +
+ Type : any[]
+
+ |
+
+ + | +
+ values to select from when filtering + |
+
+ + + + pager + + + | +
+ Type : any
+
+ |
+
+ Default value : {}
+ |
+
+ + | +
+ pager for the resources table + |
+
+ + + + selected + + + | +
+ Default value : Array(3)
+ |
+
+ + | +
+ selected filters array + |
+
+ + + + specializationFilterValues + + + | +
+ Type : any[]
+
+ |
+
+ + | +
+ + + + volunteersData + + + | +
+ Type : any
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ store the volunteers list + |
+
import { Component, OnInit } from '@angular/core';
+import { BreakpointObserver } from '@angular/cdk/layout';
+import { VolunteerService } from '../../../volunteers.service';
+import { CitiesCountiesService, FiltersService, AuthenticationService } from '@app/core';
+import { Router } from '@angular/router';
+
+@Component({
+ selector: 'app-volunteer-dashboard',
+ templateUrl: './volunteer-dashboard.component.html',
+ styleUrls: ['./volunteer-dashboard.component.scss']
+})
+export class VolunteerDashboardComponent implements OnInit {
+ /**
+ * store the volunteers list
+ */
+ volunteersData: any = [];
+ /**
+ * pager for the resources table
+ */
+ pager: any = {};
+ /**
+ * flag for HTML to know how to display data
+ */
+ displayBlock = true;
+ /**
+ * selected filters array
+ */
+ selected = Array(3);
+ /**
+ *values to select from when filtering
+ */
+ NGOFilterValues: any[];
+ locationFilterValues: any[];
+ specializationFilterValues: any[];
+
+ constructor(private volunteerService: VolunteerService, public breakpointObserver: BreakpointObserver,
+ private citiesandcounties: CitiesCountiesService, private filterService: FiltersService,
+ public authService: AuthenticationService, private router: Router) { }
+
+ ngOnInit() {
+ this.volunteerService.setPager();
+ this.pager = this.volunteerService.getPager();
+
+ this.getData();
+ /**
+ * get filterable values
+ */
+ this.citiesandcounties.getCounties('').subscribe((response: any) => {
+ const aux = response;
+ aux.map((elem: { id: any; _id: any; }) => elem.id = elem._id);
+ this.locationFilterValues = aux;
+ });
+
+ this.filterService.getorganisationbyName('').subscribe((data) => {
+ this.NGOFilterValues = data.map((elem: any) => {
+ return {id: elem._id, name: elem.name};
+ });
+ // this.ngofilterResult = data.map((elem:any) => elem.name);
+ });
+ this.filterService.getSpecializationFilters().subscribe((data) => {
+ this.specializationFilterValues = data.map((elem: any) => {
+ return {id: elem._id, name: elem.name};
+ });
+ });
+
+ /**
+ *observe screen chage and and switch to grid view if screen is too smal
+ */
+ this.breakpointObserver.observe([
+ '(max-width: 768px)'
+ ]).subscribe(result => {
+ if (result.matches) {
+ this.switchtoblock();
+ }
+ });
+ }
+/**
+ * get data from server and store localy
+
+ */
+ getData() {
+ this.volunteerService.getVolunteers(this.pager).subscribe((element: any) => {
+ if (element) {
+ this.volunteersData = element.data;
+ this.pager.total = element.pager.total;
+ }
+ });
+ }
+/**
+ * send user to add volunteers. if is NGO the ngo id is static.
+ */
+ addvolunteer() {
+ if (this.authService.is('NGO')) {
+ const navigationExtras = {
+ state: {
+ ngo: {
+ // TODO: extragere informatiilor din contu utilizatorului
+ name: this.authService.user.organisation.name,
+ ngoid: this.authService.user.organisation._id
+ }
+ }
+ };
+ this.router.navigateByUrl('/volunteers/add', navigationExtras);
+ } else {
+ this.router.navigate(['volunteers/add']);
+ }
+ }
+/**
+ * sort callback. Filters added to pager and then a request is made
+ * @param {any} pager the pager with the search filer added
+ */
+ sortChanged(pager: any) {
+ this.pager = pager;
+ this.getData();
+ }
+/**
+ * search callback. Filters added to pager and then a request is made
+ * @param {any} pager the pager with the search filer added
+ */
+ searchChanged(pager: any) {
+ this.pager = pager;
+ this.getData();
+ }
+/**
+ * filter callback. Filters added to pager and then a request is made
+ * @param {number} id the index in the pager filters and filters Selected array
+ */
+ filterChanged(id?: number) {
+ console.log(this.selected[id]);
+ this.pager.filters[id] = this.selected[id].map((elem: any) => elem.id).join(',');
+ this.getData();
+ }
+
+ /**
+ * set flag for HTML to list view
+ */
+ switchtolist() {
+ this.displayBlock = false;
+ }
+ /**
+ * set flag for HTML to grid view
+ */
+ switchtoblock() {
+ this.displayBlock = true;
+ }
+ /**
+ * navigate to organisation by id
+ * @param {string} id of the NGO to display
+ */
+ goToOrganisation(id: string, e: any) {
+ e.preventDefault();
+ this.router.navigate(['../organisations/id/' + id]);
+ }
+
+}
+
+ <div class="container">
+ <nav class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <h4 class="navtitle mb-2"> Voluntari </h4>
+ <div class="form-inline ml-auto">
+ <app-table-search id=3 [pager]="pager" (searchChanged)="searchChanged($event)"></app-table-search>
+ <div class="btn-group btn-group-toggle m-2 hidden-button" ngbRadioGroup name="radioBasic"
+ [(ngModel)]="displayBlock">
+ <label ngbButtonLabel (click)="switchtoblock()" class="btn btn-primary">
+ <input ngbButton type="radio" [value]="true"><span class="fa fa-th-large"></span>
+ </label>
+ <label ngbButtonLabel (click)="switchtolist()" class="btn btn-primary">
+ <input ngbButton type="radio" [value]="false"><span class="fa fa-bars"></span>
+ </label>
+ </div>
+ <button *ngIf ="authService.is('DSU', 'NGO')" (click)="addvolunteer()" class="add-btn btn-adjusting btn btn-info btn-rounded m-2">
+ <span class="fa fa-plus-circle"></span> Adaugă Voluntar
+ </button>
+ </div>
+ </nav>
+ <div class="filters my-3 mx-30px row">
+ <span class='padding-rem'>Total: {{pager.total}}</span>
+ <span class='padding-rem'> </span>
+ <span class='padding-rem'>Filtrează după: </span>
+ <ngx-multiselect
+ class="location"
+ [(ngModel)]="selected[0]"
+ showMaxLables = "1"
+ [options]="locationFilterValues"
+ (onItemClick)="filterChanged(0)"
+ (onSelectNone)="filterChanged(0)"
+ (onSelectAll)="filterChanged(0)">
+ </ngx-multiselect>
+ <span class="vertical-bar"> </span>
+ <ngx-multiselect
+ class="specialization"
+ [(ngModel)]="selected[1]"
+ showMaxLables = "1"
+ [options]="specializationFilterValues"
+ (onItemClick)="filterChanged(1)"
+ (onSelectNone)="filterChanged(1)"
+ (onSelectAll)="filterChanged(1)">
+ </ngx-multiselect>
+ <span *ngIf="authService.is('DSU')" class="vertical-bar"> </span>
+ <ngx-multiselect
+ *ngIf="authService.is('DSU')"
+ class="ngo"
+ [(ngModel)]="selected[2]"
+ showMaxLables = "1"
+ [options]="NGOFilterValues"
+ (onItemClick)="filterChanged(2)"
+ (onSelectNone)="filterChanged(2)"
+ (onSelectAll)="filterChanged(2)">
+ </ngx-multiselect>
+ </div>
+
+ <div *ngIf="displayBlock; else displayList" class="mt-5 cardlist row">
+ <div *ngFor="let elem of volunteersData" class="d-flex col-md-6 col-sm-12 col-lg-4">
+ <div class="card mb-2" style="flex-grow: 1">
+ <div class="card-body" [routerLink]="['id',elem._id]">
+ <a class="btn btn-fix text-left d-block">
+ <h4 class="card-title">{{elem.name}}</h4>
+ <div class="card-text row">
+ <span class="col-md-12">
+ <i class="fa fa-envelope"></i>
+ {{ elem.email }}
+ </span>
+ <span class="col-md-12">
+ <i class="fa fa-phone"></i>
+ {{ elem.phone }}
+ </span>
+ <span *ngIf="!!elem.organisation && !authService.is('INS')" class="col-md-12">
+ <i class="fa fa-building"></i>
+ <a (click)="goToOrganisation(elem.organisation._id, $event)" href="javascript:void(0);" class="ml-1">{{ elem.organisation.name }}</a>
+ </span>
+ <span *ngIf="!elem.organisation && !authService.is('INS')" class="col-md-12">
+ <i class="fa fa-building"></i>
+ Neafiliat
+ </span>
+ </div>
+ </a>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-12 mx-30px">
+ <ngb-pagination
+ [maxSize]="5"
+ [(page)]="pager.page"
+ [pageSize]="pager.size"
+ (pageChange)="getData()"
+ [collectionSize]="pager.total">
+ </ngb-pagination>
+ </div>
+ </div>
+
+ <ng-template #displayList>
+ <div class="table-responsive">
+ <table class="table customTable table-sm">
+ <thead>
+ <tr>
+ <!-- sortable="name" (sort)="onSort($event)" -->
+ <th scope="col" appTableSort [pager]="pager" [value]="1" (sortChanged)="sortChanged($event)">
+ Nume</th>
+ <th scope="col" appTableSort [pager]="pager" [value]="2" (sortChanged)="sortChanged($event)">
+ Locație</th>
+ <th *ngIf="!authService.is('INS')" scope="col" appTableSort [pager]="pager" [value]="3" (sortChanged)="sortChanged($event)">
+ Organizație</th>
+ <th scope="col" class="non-clickable">Specializări</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let res of volunteersData">
+ <!-- <tr> -->
+ <td>{{res.name}}</td>
+ <td>{{res.city?.name}}, {{res.county?.name}}</td>
+ <td *ngIf="!authService.is('INS')">{{res.organisation?.name || 'Neafiliat'}}</td>
+ <td><div *ngFor="let elem of res?.courses">
+ {{elem.course_name.name}}
+ </div></td>
+ <!-- <td>{{res.updated_at}}</td> -->
+ <td class="text-right"><button class="btn btn-info" [routerLink]="['id',res._id]">Vezi
+ detalii</button></td>
+ </tr>
+
+ </tbody>
+ </table>
+ </div>
+ <ngb-pagination
+ [maxSize]="5" [(page)]="pager.page" [pageSize]="pager.size" (pageChange)="getData()"
+ [collectionSize]="pager.total">
+ </ngb-pagination>
+ </ng-template>
+</div>
+
+ ./volunteer-dashboard.component.scss
+
::ng-deep .specialization{
+ .none-selected:before{
+ content:'Specializări' !important;
+ }
+}
+::ng-deep .ngo{
+ .none-selected:before{
+ content:'Organizații' !important;
+ }
+}
+.non-clickable{
+ cursor: default !important;
+}
+ +
+ src/app/pages/volunteers/volunteers/components/volunteer-details/volunteer-details.component.ts
+
+
+ OnInit
+
selector | +app-volunteer-details |
+
styleUrls | +./volunteer-details.component.scss |
+
templateUrl | +./volunteer-details.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(volunteerService: VolunteerService, route: ActivatedRoute, authService: AuthenticationService, router: Router, location: Location)
+ |
+ ||||||||||||||||||
+ + | +||||||||||||||||||
+ Vounteer details component +
+ Parameters :
+
+
|
+
+ + + + deleteSelf + + + + | +
+deleteSelf()
+ |
+
+ + | +
+ delete this volunteer +
+ Returns :
+ void
+
+ |
+
+ + + + edit + + + + | +
+edit()
+ |
+
+ + | +
+ edit this volunteer +
+ Returns :
+ void
+
+ |
+
+ + + + getAllocations + + + + | +
+getAllocations()
+ |
+
+ + | +
+ get alocations data from server +
+ Returns :
+ void
+
+ |
+
+ + + + getData + + + + | +
+getData()
+ |
+
+ + | +
+ get volunteer data from server +
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + allocations + + + | +
+ Type : any[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ store alocations data + |
+
+ + + + Public + authService + + + | +
+ Type : AuthenticationService
+
+ |
+
+ + | +
+ + + + canEdit + + + | +
+ Default value : true
+ |
+
+ + | +
+ flag for HTML to display edit button + |
+
+ + + + Public + data + + + | +
+ Type : any
+
+ |
+
+ + | +
+ store volunteer data + |
+
+ + + + hasAccreditation + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + hasAlocation + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for ngtemplate in HTML + |
+
+ + + + loading + + + | +
+ Default value : false
+ |
+
+ + | +
+ flag for HTML to display loading animation + |
+
import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+import { VolunteerService } from '../../../volunteers.service';
+import { AuthenticationService } from '@app/core';
+import { Location } from '@angular/common';
+
+@Component({
+ selector: 'app-volunteer-details',
+ templateUrl: './volunteer-details.component.html',
+ styleUrls: ['./volunteer-details.component.scss']
+})
+export class VolunteerDetailsComponent implements OnInit {
+ /**
+ * store volunteer data
+ */
+ public data: any;
+ /**
+ * flag for ngtemplate in HTML
+ */
+ hasAlocation = false;
+ hasAccreditation = false;
+ /**
+ * flag for HTML to display edit button
+ */
+ canEdit = true;
+ /**
+ * flag for HTML to display loading animation
+ */
+ loading = false;
+ /**
+ * store alocations data
+ */
+ allocations: any[] = [];
+
+ /**
+ * Vounteer details component
+ */
+
+ constructor(private volunteerService: VolunteerService,
+ private route: ActivatedRoute,
+ public authService: AuthenticationService,
+ private router: Router,
+ private location: Location) { }
+
+ ngOnInit() {
+ this.getData();
+ this.getAllocations();
+ }
+/**
+ * edit this volunteer
+ */
+ edit() {
+ this.router.navigateByUrl(`/volunteers/edit/${this.data._id}`);
+ }
+/**
+ * delete this volunteer
+ */
+ deleteSelf() {
+ if (confirm('Sunteți sigur că doriți să ștergeți această intrare? Odată ștearsă nu va mai putea fi recuperată.')) {
+ this.loading = true;
+ this.volunteerService.deleteVolunteer(this.data._id).subscribe(() => {
+ this.loading = false;
+ this.location.back();
+ }, () => {
+ this.loading = false;
+ });
+ }
+ }
+/**
+ * get alocations data from server
+ */
+ getAllocations() {
+ this.volunteerService.getAllocations(this.route.snapshot.paramMap.get('id')).subscribe((data: any[]) => {
+ this.allocations = data;
+
+ if (this.allocations.length > 0) {
+ this.hasAlocation = true;
+ }
+ });
+ }
+/**
+ * get volunteer data from server
+ */
+ getData() {
+ this.volunteerService.getVolunteer(this.route.snapshot.paramMap.get('id')).subscribe((data) => {
+ this.data = data;
+
+ if (data.courses && data.courses.length > 0) {
+ this.hasAccreditation = true;
+ }
+
+ this.canEdit = this.authService.is('DSU') ||
+ (this.authService.is('NGO') && (this.data.organisation._id === this.authService.user.organisation._id));
+ });
+ }
+
+}
+
+ <div class="container">
+ <nav class="navbar navbar-expand-lg justify-content-between navbar-light my-5">
+ <app-back-button></app-back-button>
+ <div class="mx-2 my-sm-0">
+ <button *ngIf="canEdit && !loading" (click)="deleteSelf()" class="btn btn-danger mx-2 my-sm-0"><span
+ class="fa fa-trash-o"></span> Șterge Voluntarul</button>
+ <div class="spinner-border text-danger" role="status" *ngIf="canEdit && loading">
+ <span class="sr-only">Loading...</span>
+ </div>
+ <button (click)="edit()" class="mx-2 btn btn-info" *ngIf="canEdit"><span class="fa fa-edit"></span> Modifică</button>
+ <!-- <button></button> -->
+ </div>
+ </nav>
+ <div class="ngo-details row">
+ <h3 class="col-md-12">Profil Voluntar: {{data?.name}}</h3>
+
+ <span class="col-md-12"><span class="fa fa-envelope"></span> {{data?.email}}</span>
+ <span class="col-md-12"><span class="fa fa-phone"></span> {{data?.phone}}</span>
+ <span class="col-md-12"><b>Organizație: </b> {{data?.organisation?.name || 'Neafiliat'}}</span>
+ <span class="col-md-12"><b>Localizare: </b>{{data?.city.name}}, {{data?.county.name}}</span>
+ <span class="col-md-12"><b>CNP: </b> {{data?.ssn }}</span>
+ <span class="col-md-12"><b>Profesie: </b> {{data?.job || 'Nici o profesie' }}</span>
+ <div class="col-md-12">
+ <span><b>Data ultimului update: </b> {{ (data?.updated_at | date: 'dd.MM.yyyy') || 'Nu există' }}</span>
+ </div>
+ </div>
+ <div class="resource-list">
+ <ngb-tabset type="pills">
+ <ngb-tab>
+ <ng-template ngbTabTitle>Listă Acreditări</ng-template>
+ <ng-template ngbTabContent>
+ <div *ngIf="hasAccreditation; else noAccreditation">
+ <div class="table-responsive-md">
+ <table class="table customTable table-sm">
+ <thead>
+ <tr>
+ <th>Nume Curs</th>
+ <th>Data Acreditării</th>
+ <th>Acreditat de</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let res of data?.courses">
+ <td>{{res.course_name.name}}</td>
+ <td>{{res.obtained | date: 'dd.MM.yyyy' }}</td>
+ <td>{{res.accredited?.name}}</td>
+ </tr>
+ </tbody>
+ </table>
+ <!-- TO-DO CAND AVEM BACKEND-->
+ <!-- <ngb-pagination
+ [maxSize]="5"
+ [(page)]="pager.page"
+ [pageSize]="pager.size"
+ (pageChange)="getData()"
+ [collectionSize]="pager.total">
+ </ngb-pagination> -->
+ </div>
+ </div>
+ <ng-template #noAccreditation>
+ <span>Aceast voluntar nu are nici o acreditare adăugată în sistem.</span>
+ </ng-template>
+ </ng-template>
+ </ngb-tab>
+ <ngb-tab title="|" [disabled]="true"></ngb-tab>
+ <ngb-tab>
+ <ng-template ngbTabTitle>Listă Alocări</ng-template>
+ <ng-template ngbTabContent>
+ <div *ngIf="hasAlocation; else noAlocation">
+ <div class="table-responsive-md">
+ <table class="table customTable table-sm">
+ <thead>
+ <tr>
+ <th>Nume Ofițer</th>
+ <th>Data Alocării</th>
+ <th>Localizare</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let res of allocations">
+ <td>{{res.rescue_officer.name}}</td>
+ <td>{{res.created_at | date: 'dd.MM.yyyy' }}</td>
+ <td>{{res.city.name}}, {{res.county.name}}</td>
+ </tr>
+ </tbody>
+ </table>
+ <!-- <ngb-pagination
+ [maxSize]="5"
+ [(page)]="pager.page"
+ [pageSize]="pager.size"
+ (pageChange)="getData()"
+ [collectionSize]="pager.total">
+ </ngb-pagination> -->
+ </div>
+ </div>
+ <ng-template #noAlocation>
+ <span>Acest voluntar nu a fost alocat niciodată.</span>
+ </ng-template>
+ </ng-template>
+ </ngb-tab>
+ </ngb-tabset>
+ </div>
+</div>
+
+ ./volunteer-details.component.scss
+
+ +
+ src/app/pages/volunteers/volunteers/volunteers.component.ts
+
+
+ OnInit
+
selector | +app-volunteers |
+
styleUrls | +./volunteers.component.scss |
+
templateUrl | +./volunteers.component.html |
+
+ Methods+ |
+
+
|
+
+constructor()
+ |
+
+ + | +
+ wrapper for the volunteers pages. acts as frame + |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'app-volunteers',
+ templateUrl: './volunteers.component.html',
+ styleUrls: ['./volunteers.component.scss']
+})
+
+export class VolunteersComponent implements OnInit {
+
+ /**
+ * wrapper for the volunteers pages. acts as frame
+ */
+ constructor() { }
+
+ ngOnInit() {
+ }
+
+}
+
+ <router-outlet></router-outlet>
+
+ ./volunteers.component.scss
+
+ File | +Type | +Identifier | +Statements | +
---|---|---|---|
+ + src/app/app.component.ts + | +component | +AppComponent | ++ 33 % + (1/3) + | +
+ + src/app/core/authentication/anonymous.guard.ts + | +guard | +AnonymousGuard | ++ 33 % + (1/3) + | +
+ + src/app/core/authentication/authentication.guard.ts + | +guard | +AuthenticationGuard | ++ 33 % + (1/3) + | +
+ + src/app/core/authentication/authentication.service.ts + | +injectable | +AuthenticationService | ++ 64 % + (9/14) + | +
+ + src/app/core/authentication/authentication.service.ts + | +variable | +credentialsKey | ++ 100 % + (1/1) + | +
+ + src/app/core/authentication/redirect.guard.ts + | +guard | +RedirectGuard | ++ 100 % + (4/4) + | +
+ + src/app/core/authentication/role.guard.ts + | +guard | +RoleGuard | ++ 33 % + (1/3) + | +
+ + src/app/core/http/api-prefix.interceptor.ts + | +interceptor | +ApiPrefixInterceptor | ++ 33 % + (1/3) + | +
+ + src/app/core/http/error-handler.interceptor.ts + | +interceptor | +ErrorHandlerInterceptor | ++ 50 % + (2/4) + | +
+ + src/app/core/local-storage.service.ts + | +injectable | +LocalStorageService | ++ 87 % + (7/8) + | +
+ + src/app/core/local-storage.service.ts + | +function | +clearData | ++ 100 % + (1/1) + | +
+ + src/app/core/local-storage.service.ts + | +function | +createCookie | ++ 100 % + (1/1) + | +
+ + src/app/core/local-storage.service.ts + | +function | +getData | ++ 100 % + (1/1) + | +
+ + src/app/core/local-storage.service.ts + | +function | +getSessionName | ++ 100 % + (1/1) + | +
+ + src/app/core/local-storage.service.ts + | +function | +readCookie | ++ 100 % + (1/1) + | +
+ + src/app/core/local-storage.service.ts + | +function | +setData | ++ 100 % + (1/1) + | +
+ + src/app/core/service/cities-counties.service.ts + | +injectable | +CitiesCountiesService | ++ 50 % + (2/4) + | +
+ + src/app/core/service/error-message.service.ts + | +injectable | +ErrorMessageService | ++ 33 % + (2/6) + | +
+ + src/app/core/service/filters.service.ts + | +injectable | +FiltersService | ++ 75 % + (6/8) + | +
+ + src/app/core/service/users.service.ts + | +injectable | +UsersService | ++ 88 % + (8/9) + | +
+ + src/app/core/service/util.service.ts + | +injectable | +UtilService | ++ 60 % + (3/5) + | +
+ + src/app/core/validators/email-validation.ts + | +class | +EmailValidation | ++ 50 % + (1/2) + | +
+ + src/app/core/validators/location-validation.ts + | +class | +LocationValidation | ++ 50 % + (1/2) + | +
+ + src/app/core/validators/password-validation.ts + | +class | +PasswordValidation | ++ 66 % + (2/3) + | +
+ + src/app/core/validators/phone-validation.ts + | +class | +PhoneValidation | ++ 50 % + (1/2) + | +
+ + src/app/core/validators/ssn-validation.ts + | +class | +SsnValidation | ++ 50 % + (1/2) + | +
+ + src/app/core/validators/website-validation.ts + | +class | +WebsiteValidation | ++ 50 % + (1/2) + | +
+ + src/app/pages/404/not-found.component.ts + | +component | +NotFoundComponent | ++ 50 % + (1/2) + | +
+ + src/app/pages/authentication/login/login.component.ts + | +component | +LoginComponent | ++ 50 % + (5/10) + | +
+ + src/app/pages/authentication/recover-password/recover-password.component.ts + | +component | +RecoverPasswordComponent | ++ 50 % + (4/8) + | +
+ + src/app/pages/authentication/reset-password/reset-password.component.ts + | +component | +ResetPasswordComponent | ++ 55 % + (5/9) + | +
+ + src/app/pages/authentication/signup/signup.component.ts + | +component | +SignupComponent | ++ 62 % + (5/8) + | +
+ + src/app/pages/info/info/info.component.ts + | +component | +InfoComponent | ++ 33 % + (1/3) + | +
+ + src/app/pages/map/map.service.ts + | +injectable | +MapService | ++ 33 % + (1/3) + | +
+ + src/app/pages/map/map/map.component.ts + | +component | +MapComponent | ++ 35 % + (5/14) + | +
+ + src/app/pages/organisations/organisations.service.ts + | +injectable | +OrganisationService | ++ 84 % + (16/19) + | +
+ + src/app/pages/organisations/organisations/components/organisation-add/organisation-add.component.ts + | +component | +OrganisationaddComponent | ++ 52 % + (11/21) + | +
+ + src/app/pages/organisations/organisations/components/organisation-details/organisation-details.component.ts + | +component | +NgodetailsComponent | ++ 58 % + (27/46) + | +
+ + src/app/pages/organisations/organisations/components/organisation-details/organisation-details.component.ts + | +interface | +Alert | ++ 33 % + (1/3) + | +
+ + src/app/pages/organisations/organisations/components/organisation-edit/organisation-edit.component.ts + | +component | +OrganisationEditComponent | ++ 59 % + (13/22) + | +
+ + src/app/pages/organisations/organisations/components/organisations-dashboard/organisations-dashboard.component.ts + | +component | +OrganisationsDashboardComponent | ++ 57 % + (12/21) + | +
+ + src/app/pages/organisations/organisations/organisations.component.ts + | +component | +OrganisationsComponent | ++ 33 % + (1/3) + | +
+ + src/app/pages/resources/resources.service.ts + | +injectable | +ResourcesService | ++ 83 % + (10/12) + | +
+ + src/app/pages/resources/resources/components/add-resource/add-resource.component.ts + | +component | +AddResourceComponent | ++ 41 % + (16/39) + | +
+ + src/app/pages/resources/resources/components/edit-resource/edit-resource.component.ts + | +component | +EditResourceComponent | ++ 41 % + (16/39) + | +
+ + src/app/pages/resources/resources/components/import-resources/import-resources.component.ts + | +component | +ImportResourcesComponent | ++ 69 % + (9/13) + | +
+ + src/app/pages/resources/resources/components/resource-details/resource-details.component.ts + | +component | +ResourcedetailsComponent | ++ 60 % + (6/10) + | +
+ + src/app/pages/resources/resources/components/resource-list/resource-list.component.ts + | +component | +ResourceListComponent | ++ 66 % + (8/12) + | +
+ + src/app/pages/resources/resources/components/resources-dashboard/resources-dashboard.component.ts + | +component | +ResourcesdashboardComponent | ++ 71 % + (15/21) + | +
+ + src/app/pages/resources/resources/resources.component.ts + | +component | +ResourcesComponent | ++ 33 % + (1/3) + | +
+ + src/app/pages/users/users/components/add-user/add-user.component.ts + | +component | +AddUserComponent | ++ 59 % + (13/22) + | +
+ + src/app/pages/users/users/components/edit-user/edit-user.component.ts + | +component | +EditUserComponent | ++ 62 % + (15/24) + | +
+ + src/app/pages/users/users/components/user-dashboard/user-dashboard.component.ts + | +component | +UserDashboardComponent | ++ 72 % + (16/22) + | +
+ + src/app/pages/users/users/components/user-details/user-details.component.ts + | +component | +UserDetailsComponent | ++ 62 % + (5/8) + | +
+ + src/app/pages/users/users/users.component.ts + | +component | +UsersComponent | ++ 33 % + (1/3) + | +
+ + src/app/pages/volunteers/volunteers.service.ts + | +injectable | +VolunteerService | ++ 83 % + (10/12) + | +
+ + src/app/pages/volunteers/volunteers/components/add-volunteer/add-volunteer.component.ts + | +component | +AddVolunteerComponent | ++ 44 % + (22/49) + | +
+ + src/app/pages/volunteers/volunteers/components/edit-volunteer/edit-volunteer.component.ts + | +component | +EditVolunteerComponent | ++ 50 % + (25/50) + | +
+ + src/app/pages/volunteers/volunteers/components/import-volunteers/import-volunteers.component.ts + | +component | +ImportVolunteersComponent | ++ 69 % + (9/13) + | +
+ + src/app/pages/volunteers/volunteers/components/volunteer-dashboard/volunteer-dashboard.component.ts + | +component | +VolunteerDashboardComponent | ++ 65 % + (13/20) + | +
+ + src/app/pages/volunteers/volunteers/components/volunteer-details/volunteer-details.component.ts + | +component | +VolunteerDetailsComponent | ++ 71 % + (10/14) + | +
+ + src/app/pages/volunteers/volunteers/volunteers.component.ts + | +component | +VolunteersComponent | ++ 33 % + (1/3) + | +
+ + src/app/shared/back-button/back-button.component.ts + | +component | +BackButtonComponent | ++ 75 % + (3/4) + | +
+ + src/app/shared/date-parser-formatter.ts + | +injectable | +DateParserFormatter | ++ 50 % + (5/10) + | +
+ + src/app/shared/date-parser-formatter.ts + | +function | +isNumber | ++ 100 % + (1/1) + | +
+ + src/app/shared/date-parser-formatter.ts + | +function | +toInteger | ++ 100 % + (1/1) + | +
+ + src/app/shared/spinner.component.ts + | +component | +SpinnerComponent | ++ 80 % + (4/5) + | +
+ + src/app/shared/table-search/table-search.component.ts + | +component | +TableSearchComponent | ++ 60 % + (6/10) + | +
+ + src/app/shared/table-sort/table-sort.directive.ts + | +directive | +TableSortDirective | ++ 63 % + (7/11) + | +
+ + src/app/top-bar/components/current-profile/current-profile.component.ts + | +component | +CurrentProfileComponent | ++ 50 % + (3/6) + | +
+ + src/app/top-bar/top-bar.component.ts + | +component | +TopBarComponent | ++ 50 % + (3/6) + | +
+
+ src/app/shared/table-sort/table-sort.directive.ts
+
Selector | +[appTableSort] |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+ Inputs+ |
+
+ + | +
+ Outputs+ |
+
+
|
+
+ HostBindings+ |
+
+
|
+
+ HostListeners+ |
+
+
|
+
+ Accessors+ |
+
+
|
+
+constructor()
+ |
+
+ + | +
+ + pager + | +|
+ Type : any
+
+ |
+ |
+ + | +|
+ set pager from input value and adjendat values + |
+
+ + value + | +|
+ Type : any
+
+ |
+ |
+ + | +|
+ value + |
+
+ + sortChanged + | +|
+ Type : EventEmitter
+
+ |
+ |
+ + | +|
+ Observable sortChanged for triggering modifications in the main component + |
+
+ + + + class.asc + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + class.desc + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + class.sort + + + | +
+ Default value : false
+ |
+
+ + | +
+ angular bindings for class display + |
+
+ + + + click + + + + | +
+click()
+ |
+
+ + | +
+ register to click events in the referenced input + |
+
+ + + + sort + + + + | +
+sort()
+ |
+
+ + | +
+ emit sort event on click +
+ Returns :
+ void
+
+ |
+
+ + + + _pager + + + | +
+ Type : any
+
+ |
+
+ Default value : {}
+ |
+
+ + | +
+ internal pager value + |
+
+ + pager + | +||||||
+ getpager()
+ |
+ ||||||
+ + | +||||||
+ return pager +
+ Returns :
+ any
+
+ |
+ ||||||
+ setpager(data: any)
+ |
+ ||||||
+ + | +||||||
+ set pager from input value and adjendat values +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+ |
+
import { Directive, HostBinding, Input, Output, EventEmitter, HostListener } from '@angular/core';
+
+@Directive({
+ selector: '[appTableSort]'
+})
+export class TableSortDirective {
+ /**
+ * internal pager value
+ */
+ _pager: any = {};
+ /**
+ * angular bindings for class display
+ */
+ @HostBinding('class.sort') active = false;
+ @HostBinding('class.asc') asc = false;
+ @HostBinding('class.desc') desc = false;
+/**
+ * value
+ */
+ @Input() value: any;
+/**
+ * return pager
+ */
+ get pager(): any {
+ return this._pager;
+ }
+/**
+ * set pager from input value and adjendat values
+ */
+ @Input()
+ set pager(data: any) {
+ this._pager = data;
+ this.active = this._pager.sort === this.value;
+ this.asc = this._pager.sort === this.value && this._pager.method === 'ASC';
+ this.desc = this._pager.sort === this.value && this._pager.method === 'DESC';
+ }
+ /**
+ * Observable sortChanged for triggering modifications in the main component
+ */
+ @Output() sortChanged = new EventEmitter();
+ /**
+ * register to click events in the referenced input
+ */
+ @HostListener('click') onClick() {
+ this.sort();
+ }
+
+ constructor() {
+ }
+/**
+ * emit sort event on click
+ */
+ sort() {
+ if (this.pager.sort === this.value) {
+ this.pager.method = this.pager.method === 'ASC' ? 'DESC' : 'ASC';
+ } else {
+ this.pager.method = 'ASC';
+ }
+ this.pager.sort = this.value;
+ this.sortChanged.emit({...this.pager});
+ }
+
+}
+
+ +
+ src/app/core/authentication/anonymous.guard.ts
+
+ Methods+ |
+
+
|
+
+constructor(router: Router, authenticationService: AuthenticationService)
+ |
+ |||||||||
+ + | +|||||||||
+ Angular anonymous user guard for routes +
+ Parameters :
+
+
|
+
+ + + + canActivate + + + + | +
+canActivate()
+ |
+
+ + | +
+
+
+
+
+ Returns :
+ boolean
+
+
+
+ Boolean based on authentification Service isAuthenticated Answer +else redirect to home + + |
+
import { Injectable } from '@angular/core';
+import { CanActivate, Router } from '@angular/router';
+
+import { AuthenticationService } from '@app/core/authentication/authentication.service';
+
+@Injectable()
+export class AnonymousGuard implements CanActivate {
+ /**
+ * Angular anonymous user guard for routes
+ */
+ constructor(
+ private router: Router,
+ private authenticationService: AuthenticationService
+ ) {}
+ /**
+ * @returns Boolean based on authentification Service isAuthenticated Answer
+ *
+ * else redirect to home
+ */
+ canActivate(): boolean {
+ if (!this.authenticationService.isAuthenticated()) {
+ return true;
+ }
+
+ this.router.navigate(['/'], {
+ replaceUrl: true
+ });
+ return false;
+ }
+}
+
+ +
+ src/app/core/authentication/authentication.guard.ts
+
+ Methods+ |
+
+
|
+
+constructor(router: Router, authenticationService: AuthenticationService)
+ |
+ |||||||||
+ + | +|||||||||
+ Angular auth guard for routes +
+ Parameters :
+
+
|
+
+ + + + canActivate + + + + | +||||||
+canActivate(route: ActivatedRouteSnapshot)
+ |
+ ||||||
+ + | +||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ boolean
+
+
+
+ Boolean based on authentification Service isAuthenificated Answer +else redirect to login + + |
+
import { Injectable } from '@angular/core';
+import { CanActivate, Router, ActivatedRouteSnapshot } from '@angular/router';
+
+import { AuthenticationService } from '@app/core/authentication/authentication.service';
+
+@Injectable()
+export class AuthenticationGuard implements CanActivate {
+ /**
+ * Angular auth guard for routes
+ */
+ constructor(
+ private router: Router,
+ private authenticationService: AuthenticationService
+ ) {}
+ /**
+ * @returns Boolean based on authentification Service isAuthenificated Answer
+ *
+ * else redirect to login
+ */
+ canActivate(route: ActivatedRouteSnapshot): boolean {
+ const isDashboard = !!route.data['dashboard'];
+ if (this.authenticationService.isAuthenticated() && this.authenticationService.accessLevel !== '0') {
+ if (isDashboard && (!route.children || !route.children.length)) {
+ this.router.navigate(['/' + this.authenticationService.homePath()], {
+ replaceUrl: true
+ });
+ }
+ return true;
+ }
+
+ this.router.navigate(['/login'], {
+ replaceUrl: true
+ });
+ return false;
+ }
+}
+
+ +
+ src/app/core/authentication/redirect.guard.ts
+
+
+
Redirect if not logged in to home angular guard
+ + + + + + +
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(authService: AuthenticationService, router: Router)
+ |
+ |||||||||
+ + | +|||||||||
+ Redirect angular guard constructor +
+ Parameters :
+
+
|
+
+ + + + canActivate + + + + | +|||||||||
+canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot)
+ |
+ |||||||||
+ + | +|||||||||
+ Redirect guard will redirect the user to its specific home page. +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable | Promise | boolean
+
+
+
+
+ |
+
import { Injectable } from '@angular/core';
+
+import { AuthenticationService } from './authentication.service';
+import {
+ ActivatedRouteSnapshot,
+ CanActivate,
+ RouterStateSnapshot,
+ Router
+} from '@angular/router';
+import { Observable } from 'rxjs';
+
+/**
+* Redirect if not logged in to home angular guard
+*/
+@Injectable()
+export class RedirectGuard implements CanActivate {
+ /**
+ * Current user data
+ */
+ private currentUser: any;
+
+ /**
+ * Redirect angular guard constructor
+ */
+ constructor(private authService: AuthenticationService,
+ private router: Router) {
+ this.currentUser = this.authService.user;
+ }
+ /**
+ * Redirect guard will redirect the user to its specific home page.
+ */
+ canActivate(
+ route: ActivatedRouteSnapshot,
+ state: RouterStateSnapshot
+ ): Observable<boolean> | Promise<boolean> | boolean {
+ if (this.authService.isAuthenticated) {
+ this.router.navigate([this.authService.homePath()]);
+ }
+
+ return false;
+ }
+}
+
+ +
+ src/app/core/authentication/role.guard.ts
+
+ Methods+ |
+
+
|
+
+constructor(authService: AuthenticationService, router: Router)
+ |
+ |||||||||
+ Defined in src/app/core/authentication/role.guard.ts:12
+ |
+ |||||||||
+
+ Parameters :
+
+
|
+
+ + + + canActivate + + + + | +|||||||||
+canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot)
+ |
+ |||||||||
+ Defined in src/app/core/authentication/role.guard.ts:20
+ |
+ |||||||||
+ Roleguyard blocks users of type rescue officer, redirects to login +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable | Promise | boolean
+
+
+
+
+ |
+
import {
+ ActivatedRouteSnapshot,
+ CanActivate,
+ RouterStateSnapshot,
+ Router
+} from '@angular/router';
+import { Observable } from 'rxjs';
+import { AuthenticationService } from '@app/core/authentication/authentication.service';
+import { Injectable } from '@angular/core';
+
+@Injectable()
+export class RoleGuard implements CanActivate {
+ constructor(
+ private authService: AuthenticationService,
+ private router: Router
+ ) {}
+ /**
+ * Roleguyard blocks users of type rescue officer, redirects to login
+ */
+ canActivate(
+ route: ActivatedRouteSnapshot,
+ state: RouterStateSnapshot
+ ): Observable<boolean> | Promise<boolean> | boolean {
+ const roles = route.data['roles'].map((elem: string) => elem);
+ const type = this.authService.role;
+
+ if (roles.indexOf(type) < 0) {
+ this.router.navigate(['/'], {
+ replaceUrl: true
+ });
+ return false;
+ }
+
+ return true;
+ }
+}
+
+ Webapp client of the resource and volunteers management app of DSU (Departamentul pentru Situatii de Urgenta)
+See the project live - a clickable prototype
+DSU (Departamentul pentru Situatii de Urgenta) needs a digital tool to manage the resources it has at its disposal, their location, as well as the volunteers and NGOs that are registered to offer help during a crisis situation. The aim of this project is to offer a better management solution so that DSU is better prepared for an emergency situation.
+Contributing | Built with | Repos and projects | Deployment | Feedback | License | About Code4Ro
+This project is built by amazing volunteers and you can be one of them! Here's a list of ways in which you can contribute to this project.
+You can also list any pending features and planned improvements for the project here.
+JavaScript
+Angular
+NPM
+API: https://github.com/code4romania/rvm-api
+TBD
+This project is licensed under the MPL 2.0 License - see the LICENSE file for details
+Started in 2016, Code for Romania is a civic tech NGO, official member of the Code for All network. We have a community of over 500 volunteers (developers, ux/ui, communications, data scientists, graphic designers, devops, it security and more) who work pro-bono for developing digital solutions to solve social problems. #techforsocialgood. If you want to learn more details about our projects visit our site or if you want to talk to one of our staff members, please e-mail us at contact@code4.ro.
+Last, but not least, we rely on donations to ensure the infrastructure, logistics and management of our community that is widely spread across 11 timezones, coding for social change to make Romania and the world a better place. If you want to support us, you can do it here.
+ + + + + + + + + + + + + + + + + + + ++
+ src/app/core/authentication/authentication.service.ts
+
+
+
Provides a base for authentication workflow. +The Credentials interface as well as login/logout methods should be replaced with proper implementation.
+ + + + + +
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+ Accessors+ |
+
+
|
+
+constructor(httpClient: HttpClient, localStorageService: LocalStorageService)
+ |
+ |||||||||
+ + | +|||||||||
+ Authentication service constructor. +
+ Parameters :
+
+
|
+
+ + + + Public + homePath + + + + | +
+
+ homePath()
+ |
+
+ + | +
+ Get current user home path +
+ Returns :
+ any
+
+ |
+
+ + + + Public + is + + + + | +||||||
+
+ is(...roles: string[])
+ |
+ ||||||
+ + | +||||||
+ Check if current user has a specific role +
+ Parameters :
+
+
+
+
+ Returns :
+ boolean
+
+
+
+
+ |
+
+ + + + isAuthenticated + + + + | +
+isAuthenticated()
+ |
+
+ + | +
+ Checks is the user is authenticated. +
+
+
+ Returns :
+ boolean
+
+
+
+ True if the user is authenticated. + + |
+
+ + + + login + + + + | +||||||||
+login(payload: Authentication.LoginPayload)
+ |
+ ||||||||
+ + | +||||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ The post result mapped to Credentials type, not useful since its just a token + + |
+
+ + + + logout + + + + | +
+logout()
+ |
+
+ + | +
+ Logs out the user and clear credentials. +
+
+
+ Returns :
+ Observable<boolean>
+
+
+
+ True if the user was logged out successfully. + + |
+
+ + + + Public + recoverPassword + + + + | +||||||
+
+ recoverPassword(email: string)
+ |
+ ||||||
+ + | +||||||
+ Recover user password service endpoint +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + Public + resetPassword + + + + | +|||||||||
+
+ resetPassword(password: string, token: string)
+ |
+ |||||||||
+ + | +|||||||||
+ Reset user password service endpoint +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + Public + setCredentials + + + + | +||||||
+
+ setCredentials(credentials?: Authentication.Credentials)
+ |
+ ||||||
+ + | +||||||
+ Sets the user credentials. +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + signup + + + + | +||||||||
+signup(payload: Authentication.SignupPayload)
+ |
+ ||||||||
+ + | +||||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<Authentication.User>
+
+
+
+ The post result mapped to User type, not very useful + + |
+
+ + + + Private + _credentials + + + | +
+ Type : Authentication.Credentials | null
+
+ |
+
+ + | +
+ + + + Private + homes + + + | +
+ Type : []
+
+ |
+
+ Default value : ['', 'users', 'organisations/id/:id', 'organisations']
+ |
+
+ + | +
+ + + + Private + roles + + + | +
+ Type : []
+
+ |
+
+ Default value : ['OFF', 'INS', 'NGO', 'DSU']
+ |
+
+ + | +
+ + credentials + | +
+ getcredentials()
+ |
+
+ + | +
+ Gets the user credentials. +
+
+
+ Returns :
+ Authentication.Credentials | null
+
+ |
+
+ + accessToken + | +
+ getaccessToken()
+ |
+
+ + | +
+ Get the auth token. +
+
+
+ Returns :
+ string | null
+
+ |
+
+ + accessLevel + | +
+ getaccessLevel()
+ |
+
+ + | +
+ Get current user access level based on role +
+ Returns :
+ any
+
+ |
+
+ + role + | +
+ getrole()
+ |
+
+ + | +
+ Get current user role + |
+
+ + user + | +
+ getuser()
+ |
+
+ + | +
+ Get current user +
+ Returns :
+ any | null
+
+ |
+
import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable} from 'rxjs';
+import { map } from 'rxjs/operators';
+
+import { LocalStorageService } from '@app/core/local-storage.service';
+
+/**
+* Local storage variable name
+*/
+const credentialsKey = 'credentials';
+
+/**
+ * Provides a base for authentication workflow.
+ * The Credentials interface as well as login/logout methods should be replaced with proper implementation.
+ */
+@Injectable()
+export class AuthenticationService {
+ private _credentials: Authentication.Credentials | null;
+
+ private roles = ['OFF', 'INS', 'NGO', 'DSU'];
+ private homes = ['', 'users', 'organisations/id/:id', 'organisations'];
+
+ /**
+ * Authentication service constructor.
+ */
+ constructor(
+ private httpClient: HttpClient,
+ private localStorageService: LocalStorageService
+ ) {
+ const savedCredentials = this.localStorageService.getItem(
+ credentialsKey
+ );
+ if (savedCredentials) {
+ this._credentials = JSON.parse(savedCredentials);
+ }
+ }
+ /**
+ *
+ * @param {Authentication.LoginPayload} payload Login with object of specific type
+ *
+ * Function auto stores credentials for future use.
+ *
+ * @returns The post result mapped to Credentials type, not useful since its just a token
+ *
+ */
+ login(
+ payload: Authentication.LoginPayload
+ ): Observable<any> {
+
+ return this.httpClient.post('/login', payload).pipe(map((credentials: any) => {
+ if (credentials.user.role !== '0') {
+ this.setCredentials(credentials);
+ }
+ return credentials;
+ }));
+ }
+ /**
+ *
+ * @param {Authentication.SignupPayload} payload Signup with object of specific type
+ *
+ * @returns The post result mapped to User type, not very useful
+ *
+ */
+ signup(
+ payload: Authentication.SignupPayload
+ ): Observable<Authentication.User> {
+ return this.httpClient.post('/register', payload).pipe(
+ map((body: Authentication.User) => {
+ return body;
+ })
+ );
+ }
+
+ /**
+ * Logs out the user and clear credentials.
+ * @return {Observable<boolean>} True if the user was logged out successfully.
+ */
+ logout(): Observable<boolean> {
+ return this.httpClient.get('/logout').pipe(
+ map(() => {
+ this.setCredentials();
+ return true;
+ })
+ );
+ }
+
+ /**
+ * Checks is the user is authenticated.
+ *
+ * @return {boolean} True if the user is authenticated.
+ */
+ isAuthenticated(): boolean {
+ return !!this.credentials && !!this.credentials.user;
+ }
+
+ /**
+ * Gets the user credentials.
+ *
+ * @return {Credentials} The user credentials or null if the user is not authenticated.
+ */
+ get credentials(): Authentication.Credentials | null {
+ return this._credentials;
+ }
+
+ /**
+ * Get the auth token.
+ *
+ * @return {string} The auth token is null if user is not authenticated.
+ */
+ get accessToken(): string | null {
+ if (!this._credentials || !this._credentials.token) {
+ return;
+ }
+ return this._credentials.token;
+ }
+
+ /**
+ * Get current user access level based on role
+ */
+ get accessLevel(): any {
+ if (!this.isAuthenticated()) {
+ return;
+ }
+ return this._credentials.user.role;
+ }
+
+ /**
+ * Get current user role
+ */
+ get role() {
+ if (!this.isAuthenticated()) {
+ return;
+ }
+ return this.roles[this.accessLevel] ? this.roles[this.accessLevel] : 'OFF';
+ }
+
+ /**
+ * Get current user
+ */
+ get user(): any | null {
+ if (!this.isAuthenticated()) {
+ return;
+ }
+
+ return this._credentials.user;
+ }
+
+ /**
+ * Check if current user has a specific role
+ */
+ public is(...roles: string[]) {
+ if (!this.isAuthenticated()) {
+ return false;
+ }
+
+ return roles.indexOf(this.role) > -1;
+ }
+
+ /**
+ * Get current user home path
+ */
+ public homePath() {
+ if (!this.isAuthenticated()) {
+ return '';
+ }
+
+ if (this._credentials.user.organisation) {
+ return this.homes[this.accessLevel].replace(':id', this._credentials.user.organisation._id);
+ } else {
+ return this.homes[this.accessLevel];
+ }
+ }
+
+ /**
+ * Sets the user credentials.
+ *
+ * @param {Credentials=} Authentication.Credentials The user credentials.
+ */
+ public setCredentials(credentials?: Authentication.Credentials) {
+ this._credentials = credentials || null;
+ if (credentials) {
+ this.localStorageService.setItem(
+ credentialsKey,
+ JSON.stringify(credentials)
+ );
+ } else {
+ this.localStorageService.clearItem(credentialsKey);
+ }
+ }
+
+ /**
+ * Recover user password service endpoint
+ */
+ public recoverPassword(email: string) {
+ return this.httpClient.post('/recoverpassword', {email: email});
+ }
+
+ /**
+ * Reset user password service endpoint
+ */
+ public resetPassword(password: string, token: string) {
+ return this.httpClient.post('/resetpassword', {password: password, password_confirmation: password, token: token});
+ }
+}
+
+ +
+ src/app/core/service/cities-counties.service.ts
+
+ Methods+ |
+
+
|
+
+constructor(http: HttpClient)
+ |
+ ||||||
+ + | +||||||
+
+ Parameters :
+
+
|
+
+ + + + getCitiesbyCounty + + + + | +||||||||||||
+getCitiesbyCounty(county_id: string, searchterm: string)
+ |
+ ||||||||||||
+ + | +||||||||||||
+ get cities in specific county (filters[1]) and optionaly filter by name (filters[2]) +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ an observable containing the cities object list + + |
+
+ + + + getCounties + + + + | +||||||||
+getCounties(searchterm?: string)
+ |
+ ||||||||
+ + | +||||||||
+ get counties in Romania (filters[1]) and optionaly filter by name (filters[2]) +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ an observable containing the counties object list + + |
+
import { Injectable } from '@angular/core';
+import { Observable, pipe } from 'rxjs';
+import { HttpClient } from '@angular/common/http';
+import { map, filter } from 'rxjs/operators';
+
+@Injectable()
+export class CitiesCountiesService {
+
+ constructor(private http: HttpClient) {}
+ /**
+ * get counties in Romania (filters[1]) and optionaly filter by name (filters[2])
+ * @param {string} searchterm The term to filter by
+ * @returns an observable containing the counties object list
+ */
+ getCounties(searchterm?: string): Observable<any> {
+ return this.http.get(`/counties?filters[1]=country_romania_1&filters[2]=${searchterm ? searchterm : ''}`);
+ }
+ /**
+ * get cities in specific county (filters[1]) and optionaly filter by name (filters[2])
+ * @param {string} searchterm id of the county from which to search the cities
+ * @param {string} searchterm The term to filter by
+ * @returns an observable containing the cities object list
+ */
+ getCitiesbyCounty(county_id: string, searchterm: string): Observable<any> {
+ return this.http.get(`/cities?filters[1]=${county_id}&filters[2]=${searchterm ? searchterm : ''}`);
+ }
+}
+
+ +
+ src/app/shared/date-parser-formatter.ts
+
+
+
Date parser class
+ + + ++
+ Properties+ |
+
+ + | +
+ Methods+ |
+
+ + | +
+ Accessors+ |
+
+
|
+
+constructor()
+ |
+
+ Defined in src/app/shared/date-parser-formatter.ts:48
+ |
+
+ Date parser constructor + |
+
+ + + + format + + + + | +||||||
+format(date: NgbDateStruct)
+ |
+ ||||||
+ Defined in src/app/shared/date-parser-formatter.ts:106
+ |
+ ||||||
+ Date parser format date +
+ Parameters :
+
+
+
+
+ Returns :
+ string
+
+
+
+
+ |
+
+ + + + parse + + + + | +||||||
+parse(value: string)
+ |
+ ||||||
+ Defined in src/app/shared/date-parser-formatter.ts:69
+ |
+ ||||||
+ Date parser function +
+ Parameters :
+
+
+
+
+ Returns :
+ NgbDateStruct
+
+
+
+
+ |
+
+ + + + anioSumar + + + | +
+ Type : number
+
+ |
+
+ Default value : 0
+ |
+
+ Defined in src/app/shared/date-parser-formatter.ts:39
+ |
+
+ + + + ddIndex + + + | +
+ Type : number
+
+ |
+
+ Defined in src/app/shared/date-parser-formatter.ts:36
+ |
+
+ + + + mask + + + | +
+ Type : string
+
+ |
+
+ Default value : 'dd.MM.yyyy'
+ |
+
+ Defined in src/app/shared/date-parser-formatter.ts:40
+ |
+
+ + + + mmIndex + + + | +
+ Type : number
+
+ |
+
+ Defined in src/app/shared/date-parser-formatter.ts:37
+ |
+
+ + + + separator + + + | +
+ Type : string
+
+ |
+
+ Defined in src/app/shared/date-parser-formatter.ts:35
+ |
+
+ Date parser internal variables + |
+
+ + + + yyIndex + + + | +
+ Type : number
+
+ |
+
+ Defined in src/app/shared/date-parser-formatter.ts:38
+ |
+
+ + Mask + | +||||||
+ setMask(value: string)
+ |
+ ||||||
+ Defined in src/app/shared/date-parser-formatter.ts:46
+ |
+ ||||||
+ Date parser set mask value +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+ |
+
import { Injectable } from '@angular/core';
+import { NgbDateParserFormatter, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';
+/**
+ * pad number with zeros
+ */
+function padNumber(value: number) {
+ if (isNumber(value)) {
+ return `0${value}`.slice(-2);
+ } else {
+ return '';
+ }
+}
+/**
+ * check if is number
+ */
+function isNumber(value: any): boolean {
+ return !isNaN(toInteger(value));
+}
+/**
+ * conver to integer
+ */
+function toInteger(value: any): number {
+ return parseInt(`${value}`, 10);
+}
+
+/**
+ * Date parser class
+ */
+@Injectable()
+export class DateParserFormatter extends NgbDateParserFormatter {
+
+ /**
+ * Date parser internal variables
+ */
+ separator: string;
+ ddIndex: number;
+ mmIndex: number;
+ yyIndex: number;
+ anioSumar = 0;
+ mask = 'dd.MM.yyyy';
+
+ /**
+ * Date parser set mask value
+ */
+
+ set Mask(value: string) {
+ this.mask = value;
+ }
+
+ /**
+ * Date parser constructor
+ */
+ constructor() {
+ super();
+ this.separator = this.mask.indexOf('-') >= 0 ? '-' : this.mask.indexOf('.') >= 0 ? '.' : '/';
+ const part = this.mask.split(this.separator);
+ this.ddIndex = part.indexOf('dd');
+ this.mmIndex = part.indexOf('MM');
+ this.yyIndex = part.indexOf('yyyy');
+ if (this.yyIndex < 0) {
+ this.yyIndex = part.indexOf('yy');
+ this.anioSumar = 2000;
+ }
+ }
+
+ /**
+ * Date parser function
+ */
+ parse(value: string): NgbDateStruct {
+ if (value) {
+ value = value.replace(/\.|\/|-/g, this.separator);
+ const dateParts = value.trim().split(this.separator);
+ if (dateParts.length !== 3) {
+ return { year: 0, month: 0, day: 0 };
+ }
+ let anio = 0;
+ let mes = 0;
+ let dia = 0;
+ const today = new Date();
+ if (isNumber(dateParts[0]) && isNumber(dateParts[1]) && isNumber(dateParts[2])) {
+ // tslint:disable-next-line: max-line-length
+ dia = this.ddIndex === 0 ? toInteger(dateParts[0]) : this.ddIndex === 1 ? toInteger(dateParts[1]) : this.ddIndex === 2 ? toInteger(dateParts[2]) : 0;
+ // tslint:disable-next-line: max-line-length
+ mes = this.mmIndex === 0 ? toInteger(dateParts[0]) : this.mmIndex === 1 ? toInteger(dateParts[1]) : this.mmIndex === 2 ? toInteger(dateParts[2]) : 0;
+ anio = this.yyIndex === 0 ? (dateParts[0].length < 2) ? 0 : toInteger(dateParts[0]) + this.anioSumar :
+ this.yyIndex === 1 ? (dateParts[1].length < 2) ? 0 : toInteger(dateParts[1]) + this.anioSumar :
+ this.yyIndex === 2 ? (dateParts[2].length < 2) ? 0 : toInteger(dateParts[2]) + this.anioSumar : 0;
+ }
+ if (dia === 0 || mes === 0 || anio === 0) {
+ return { year: 0, month: 0, day: 0 };
+ }
+
+ if (anio < 100) {
+ anio = 2000 + anio;
+ }
+
+ return { year: anio, month: mes, day: dia };
+ }
+ return { year: 0, month: 0, day: 0 };
+ }
+
+
+ /**
+ * Date parser format date
+ */
+ format(date: NgbDateStruct): string {
+ let stringDate = '';
+ if (date) {
+ const stringDay = isNumber(date.day) ? padNumber(date.day) : '';
+ const stringMonth = isNumber(date.month) ? padNumber(date.month) : '';
+ const stringYear = isNumber(date.year) ? (date.year - this.anioSumar).toString() : '';
+ // tslint:disable-next-line: max-line-length
+ stringDate = (stringDay) ? this.mask.replace('dd', stringDay) : this.ddIndex === 0 ? this.mask.replace('dd' + this.separator, '') : this.mask.replace(this.separator + 'dd', '');
+ // tslint:disable-next-line: max-line-length
+ stringDate = (stringMonth) ? stringDate.replace('MM', stringMonth) : this.mmIndex === 0 ? stringDate.replace('MM' + this.separator, '') : stringDate.replace(this.separator + 'MM', '');
+ if (this.anioSumar) {
+ // tslint:disable-next-line: max-line-length
+ stringDate = (stringDay) ? stringDate.replace('yy', stringYear) : this.yyIndex === 0 ? stringDate.replace('yy' + this.separator, '') : stringDate.replace(this.separator + 'yy', '');
+ } else {
+ // tslint:disable-next-line: max-line-length
+ stringDate = (stringDay) ? stringDate.replace('yyyy', stringYear) : this.yyIndex === 0 ? stringDate.replace('yyyy' + this.separator, '') : stringDate.replace(this.separator + 'yyyy', '');
+ }
+ }
+ return stringDate;
+ }
+}
+
+ +
+ src/app/core/service/error-message.service.ts
+
+ Properties+ |
+
+ + | +
+ Methods+ |
+
+ + | +
+ Accessors+ |
+
+
|
+
+constructor()
+ |
+
+ + | +
+ + + + Public + clear + + + + | +
+
+ clear()
+ |
+
+ + | +
+ Clear all errors from error list +
+ Returns :
+ void
+
+ |
+
+ + + + Public + set + + + + | +||||||||||||
+
+ set(error: string, type: string, serviceUrl: string)
+ |
+ ||||||||||||
+ + | +||||||||||||
+ Add error to errors list +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + Private + _errors + + + | +
+ Type : ErrorModel.ErrorMessageObject[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ + + + Public + errors$ + + + | +
+ Default value : new EventEmitter<ErrorModel.ErrorMessageObject[]>()
+ |
+
+ + | +
+ + errors + | +
+ geterrors()
+ |
+
+ + | +
import { EventEmitter, Injectable } from '@angular/core';
+@Injectable()
+/**
+ * Error service to show http error message in console
+*/
+export class ErrorMessageService {
+ private _errors: ErrorModel.ErrorMessageObject[] = [];
+ public errors$ = new EventEmitter<ErrorModel.ErrorMessageObject[]>();
+
+ constructor() {}
+ /*
+ * return errors
+*/
+ get errors(): ErrorModel.ErrorMessageObject[] {
+ return this._errors;
+ }
+ /**
+ * Add error to errors list
+*/
+ public set(error: string, type: string, serviceUrl: string) {
+ this._errors.push({
+ id: Date.now(),
+ error: error,
+ type: type,
+ serviceUrl: serviceUrl
+ });
+ console.log(this._errors);
+ this.errors$.emit(this._errors);
+ }
+/**
+ * Clear all errors from error list
+*/
+ public clear() {
+ this._errors = [];
+ this.errors$.emit(this._errors);
+ }
+}
+
+ +
+ src/app/core/service/filters.service.ts
+
+ Methods+ |
+
+
|
+
+constructor(http: HttpClient)
+ |
+ ||||||
+ Defined in src/app/core/service/filters.service.ts:6
+ |
+ ||||||
+
+ Parameters :
+
+
|
+
+ + + + getAcreditedFilters + + + + | +||||||
+getAcreditedFilters(name?: String)
+ |
+ ||||||
+ Defined in src/app/core/service/filters.service.ts:59
+ |
+ ||||||
+ get all acreditors in system, but only name and id. Optional filter by name +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ an observable containing the acreditors + + |
+
+ + + + getCategoryFilters + + + + | +
+getCategoryFilters()
+ |
+
+ Defined in src/app/core/service/filters.service.ts:12
+ |
+
+ get resource categories +
+
+
+ Returns :
+ Observable<any>
+
+
+
+ an observable containing the categories object list + + |
+
+ + + + getInstitutionFilters + + + + | +||||||
+getInstitutionFilters(name?: String)
+ |
+ ||||||
+ Defined in src/app/core/service/filters.service.ts:49
+ |
+ ||||||
+ get all institutions in system, but only name and id. Optional filter by name +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ an observable containing the institutions + + |
+
+ + + + getorganisationbyName + + + + | +||||||
+getorganisationbyName(name?: String)
+ |
+ ||||||
+ Defined in src/app/core/service/filters.service.ts:29
+ |
+ ||||||
+ get all organisations in system, but only name and id. Optional filter by name +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ an observable containing the organisations + + |
+
+ + + + getSpecializationFilters + + + + | +||||||
+getSpecializationFilters(name?: String)
+ |
+ ||||||
+ Defined in src/app/core/service/filters.service.ts:39
+ |
+ ||||||
+ get all courses in system, but only name and id. Optional filter by name +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ an observable containing the courses + + |
+
+ + + + getSubCategories + + + + | +||||||||||||
+getSubCategories(id: string, term: string)
+ |
+ ||||||||||||
+ Defined in src/app/core/service/filters.service.ts:21
+ |
+ ||||||||||||
+ get subcategories of a category (filters[2]) and optionaly filter by name (filters[1]) +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ an observable containing the categories with parent id = id of category + + |
+
import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+
+@Injectable()
+export class FiltersService {
+ constructor(private http: HttpClient) {}
+ /**
+ * get resource categories
+ * @returns an observable containing the categories object list
+ */
+ getCategoryFilters(): Observable<any> {
+ return this.http.get('/resources/categories' );
+ }
+ /**
+ * get subcategories of a category (filters[2]) and optionaly filter by name (filters[1])
+ * @param {string} searchterm The term to filter by
+ * @param {string} id of the category
+ * @returns an observable containing the categories with parent id = id of category
+ */
+ getSubCategories(id: string, term: string): Observable<any> {
+ return this.http.get(`/resources/categories?filters[2]=${id}&filters[1]=${term}`);
+ }
+ /**
+ * get all organisations in system, but only name and id. Optional filter by name
+ * @param {string} searchterm The term to filter by
+ * @returns an observable containing the organisations
+ */
+ getorganisationbyName(name?: String): Observable<any> {
+ let params = {};
+ if (name) { params = {...params, ...{name: name}}; }
+ return this.http.get('/filter/organisations', {params: params} );
+ }
+ /**
+ * get all courses in system, but only name and id. Optional filter by name
+ * @param {string} searchterm The term to filter by
+ * @returns an observable containing the courses
+ */
+ getSpecializationFilters(name?: String): Observable<any> {
+ let params = {};
+ if (name) { params = {...params, ...{name: name}}; }
+ return this.http.get('/filter/volunteers/courses', {params: params} );
+ }
+ /**
+ * get all institutions in system, but only name and id. Optional filter by name
+ * @param {string} searchterm The term to filter by
+ * @returns an observable containing the institutions
+ */
+ getInstitutionFilters(name?: String): Observable<any> {
+ let params = {};
+ if (name) { params = {...params, ...{name: name}}; }
+ return this.http.get('/filter/users/institutions', {params: params});
+ }
+ /**
+ * get all acreditors in system, but only name and id. Optional filter by name
+ * @param {string} searchterm The term to filter by
+ * @returns an observable containing the acreditors
+ */
+ getAcreditedFilters(name?: String): Observable<any> {
+ let params = {};
+ if (name) { params = {...params, ...{name: name}}; }
+ return this.http.get('/filter/accreditedby', {params: params} );
+ }
+}
+
+ +
+ src/app/core/local-storage.service.ts
+
+
+
LocalStorage injectable service
+ + + + + +
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+ + | +
+constructor()
+ |
+
+ Defined in src/app/core/local-storage.service.ts:99
+ |
+
+ Local storage wrapper + |
+
+ + + + Public + clearAll + + + + | +
+
+ clearAll()
+ |
+
+ Defined in src/app/core/local-storage.service.ts:192
+ |
+
+ Local storage remove all variables +
+ Returns :
+ any
+
+ |
+
+ + + + Public + clearItem + + + + | +||||||
+
+ clearItem(key: string)
+ |
+ ||||||
+ Defined in src/app/core/local-storage.service.ts:185
+ |
+ ||||||
+ Local storage remove variable +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + Public + getItem + + + + | +||||||
+
+ getItem(key: string)
+ |
+ ||||||
+ Defined in src/app/core/local-storage.service.ts:178
+ |
+ ||||||
+ Local storage get variable +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + Public + setItem + + + + | +|||||||||
+
+ setItem(key: string, value: any)
+ |
+ |||||||||
+ Defined in src/app/core/local-storage.service.ts:171
+ |
+ |||||||||
+ Local storage set variable +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + localStorage + + + | +
+ Type : any
+
+ |
+
+ Defined in src/app/core/local-storage.service.ts:98
+ |
+
+ Local storage internal variables + |
+
+ + + + sessionStorage + + + | +
+ Type : any
+
+ |
+
+ Defined in src/app/core/local-storage.service.ts:99
+ |
+
import { Injectable } from '@angular/core';
+
+/**
+* Create cookie for domain
+*/
+function createCookie(name: any, value: any, days: any) {
+ let date: any, expires;
+
+ if (days) {
+ date = new Date();
+ date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
+ expires = '; expires=' + date.toGMTString();
+ } else {
+ expires = '';
+ }
+ document.cookie = name + '=' + value + expires + '; path=/';
+}
+
+/**
+* Read cookie for domain
+*/
+function readCookie(name: any) {
+ const nameEQ = name + '=',
+ ca = document.cookie.split(';');
+ let i, c;
+
+ for (i = 0; i < ca.length; i++) {
+ c = ca[i];
+ while (c.charAt(0) === ' ') {
+ c = c.substring(1, c.length);
+ }
+
+ if (c.indexOf(nameEQ) === 0) {
+ return c.substring(nameEQ.length, c.length);
+ }
+ }
+ return null;
+}
+
+/**
+* Set cookie data
+*/
+function setData(type: any, data: any) {
+ // Convert data into JSON and encode to accommodate for special characters.
+ data = encodeURIComponent(JSON.stringify(data));
+ // Create cookie.
+ if (type === 'session') {
+ createCookie(getSessionName(), data, 365);
+ } else {
+ createCookie('localStorage', data, 365);
+ }
+}
+
+/**
+* Clear all cookies
+*/
+function clearData(type: any) {
+ if (type === 'session') {
+ createCookie(getSessionName(), '', 365);
+ } else {
+ createCookie('localStorage', '', 365);
+ }
+}
+
+/**
+* Get cookie data
+*/
+function getData(type: any) {
+ // Get cookie data.
+ const data =
+ type === 'session'
+ ? readCookie(getSessionName())
+ : readCookie('localStorage');
+ // If we have some data decode, parse and return it.
+ return data ? JSON.parse(decodeURIComponent(data)) : {};
+}
+
+/**
+* Get session name
+*/
+function getSessionName() {
+ // If there is no name for this window, set one.
+ // To ensure it's unquie use the current timestamp.
+ if (!window.name) {
+ window.name = new Date().getTime().toString();
+ }
+ return 'sessionStorage' + window.name;
+}
+
+/**
+ * LocalStorage injectable service
+ */
+@Injectable()
+export class LocalStorageService {
+ /**
+ * Local storage internal variables
+ */
+ localStorage: any;
+ sessionStorage: any;
+ /**
+ * Local storage wrapper
+ */
+ constructor() {
+ try {
+ // Test webstorage existence.
+ if (!window.localStorage || !window.sessionStorage) {
+ throw new Error('exception');
+ }
+ // Test webstorage accessibility - Needed for Safari private browsing.
+ window.localStorage.setItem('storage_test', '1');
+ window.localStorage.removeItem('storage_test');
+ this.localStorage = window.localStorage;
+ } catch (e) {
+ class CustomStorage {
+ type: any;
+ data: any;
+ length = 0;
+ constructor(type: any) {
+ this.type = type;
+ // Init data, if already present
+ this.data = getData(type);
+ }
+ public clear() {
+ this.data = {};
+ this.length = 0;
+ clearData(this.type);
+ }
+ public getItem(key: any) {
+ return this.data[key] === undefined ? null : this.data[key];
+ }
+ public key(i: any) {
+ // not perfect, but works
+ let ctr = 0;
+ for (const k in this.data) {
+ if (ctr === i) {
+ return k;
+ } else {
+ ctr++;
+ }
+ }
+ return null;
+ }
+ public removeItem(key: any) {
+ delete this.data[key];
+ this.length--;
+ setData(this.type, this.data);
+ }
+ public setItem(key: any, value: any) {
+ this.data[key] = value + ''; // forces the value to a string
+ this.length++;
+ setData(this.type, this.data);
+ }
+ }
+
+ // Replace window.localStorage and window.sessionStorage with out custom
+ // implementation.
+
+ const localStorage = new CustomStorage('local');
+ const sessionStorage = new CustomStorage('session');
+ Object.defineProperty(window, 'localStorage', {
+ get: () => localStorage
+ });
+ this.localStorage = localStorage;
+ this.sessionStorage = sessionStorage;
+ }
+ }
+
+ /**
+ * Local storage set variable
+ */
+ public setItem(key: string, value: any) {
+ return this.localStorage.setItem(key, value);
+ }
+
+ /**
+ * Local storage get variable
+ */
+ public getItem(key: string) {
+ return this.localStorage.getItem(key);
+ }
+
+ /**
+ * Local storage remove variable
+ */
+ public clearItem(key: string) {
+ return this.localStorage.removeItem(key);
+ }
+
+ /**
+ * Local storage remove all variables
+ */
+ public clearAll() {
+ return this.localStorage.clear();
+ }
+}
+
+ +
+ src/app/pages/map/map.service.ts
+
+
+
Map services
+ + + + + +
+ Methods+ |
+
+
|
+
+constructor(http: HttpClient)
+ |
+ ||||||
+ Defined in src/app/pages/map/map.service.ts:10
+ |
+ ||||||
+
+ Parameters :
+
+
|
+
+ + + + getMapFilters + + + + | +
+getMapFilters()
+ |
+
+ Defined in src/app/pages/map/map.service.ts:13
+ |
+
+
+
+ Returns :
+ Observable<any>
+
+ |
+
import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+/**
+ * Map services
+ */
+@Injectable({
+ providedIn: 'root'
+ })
+export class MapService {
+ constructor(private http: HttpClient) {}
+
+ getMapFilters(): Observable<any> {
+ return this.http.get('/filter/map');
+ }
+}
+
+ +
+ src/app/pages/organisations/organisations.service.ts
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(httpClient: HttpClient)
+ |
+ ||||||
+ + | +||||||
+ fields for adding a new resource +
+ Parameters :
+
+
|
+
+ + + + addorganisation + + + + | +||||||||
+addorganisation(payload: any)
+ |
+ ||||||||
+ + | +||||||||
+ post a new organisation to website, auto add Header +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+ observable with response + + |
+
+ + + + deleteorganisation + + + + | +||||||||
+deleteorganisation(id: String)
+ |
+ ||||||||
+ + | +||||||||
+ delete organisation by id +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ observable with response + + |
+
+ + + + editOrganisation + + + + | +||||||||||||
+editOrganisation(id: string, payload: any)
+ |
+ ||||||||||||
+ + | +||||||||||||
+ edit a new organisation +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+ observable with response + + |
+
+ + + + getorganisation + + + + | +||||||||
+getorganisation(id: String)
+ |
+ ||||||||
+ + | +||||||||
+ get organisation by id +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ observable with response organisation + + |
+
+ + + + getorganisationbyName + + + + | +||||||||
+getorganisationbyName(name: String)
+ |
+ ||||||||
+ + | +||||||||
+ get organisation by name +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ observable with response + + |
+
+ + + + getorganisations + + + + | +||||||
+getorganisations(paginationObj?: any)
+ |
+ ||||||
+ + | +||||||
+ get all organisations +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ observable with organisations list + + |
+
+ + + + getPager + + + + | +
+getPager()
+ |
+
+ + | +
+ get the standard organisation pager +
+
+
+ Returns :
+ any
+
+
+
+ pager + + |
+
+ + + + getResourcePager + + + + | +
+getResourcePager()
+ |
+
+ + | +
+ resource pager for ngo details resource table +
+ Returns :
+ any
+
+ |
+
+ + + + getResourcesbyorganisation + + + + | +||||||||||||
+getResourcesbyorganisation(id: String, paginationObj?: any)
+ |
+ ||||||||||||
+ + | +||||||||||||
+ get resource table with ngo id +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ observable with response + + |
+
+ + + + getVolunteerPager + + + + | +
+getVolunteerPager()
+ |
+
+ + | +
+ volunteer pager for ngo details volunteer table +
+ Returns :
+ any
+
+ |
+
+ + + + getVolunteersbyorganisation + + + + | +||||||||||||
+getVolunteersbyorganisation(id: String, paginationObj?: any)
+ |
+ ||||||||||||
+ + | +||||||||||||
+ get volunteers table with ngo id +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ observable with response + + |
+
+ + + + sendUpdateDataEmail + + + + | +||||||||
+sendUpdateDataEmail(id: String)
+ |
+ ||||||||
+ + | +||||||||
+ send email to the org to update data +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ observable with response + + |
+
+ + + + setPager + + + + | +
+setPager()
+ |
+
+ + | +
+ init pager with default values +
+ Returns :
+ void
+
+ |
+
+ + + + updated + + + + | +||||||||
+updated(id: String)
+ |
+ ||||||||
+ + | +||||||||
+ get organisation by id +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ observable with response + + |
+
+ + + + pager + + + | +
+ Type : any
+
+ |
+
+ Default value : {
+ sort: 1,
+ method: 'ASC',
+ page: 1,
+ size: 15,
+ total: 0,
+ filters: {}
+ }
+ |
+
+ + | +
+ pagers for the tables in the component + |
+
+ + + + resourcePager + + + | +
+ Type : any
+
+ |
+
+ Default value : {
+ sort: 1,
+ method: 'ASC',
+ page: 1,
+ size: 20,
+ total: 0,
+ filters: {}
+ }
+ |
+
+ + | +
+ + + + volunteerPager + + + | +
+ Type : any
+
+ |
+
+ Default value : {
+ sort: 1,
+ method: 'ASC',
+ page: 1,
+ size: 20,
+ total: 0,
+ filters: {}
+ }
+ |
+
+ + | +
import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs/internal/Observable';
+import { HttpHeaders, HttpClient } from '@angular/common/http';
+
+@Injectable({
+ providedIn: 'root'
+ })
+export class OrganisationService {
+ /**
+ * pagers for the tables in the component
+ */
+ pager: any = {
+ sort: 1,
+ method: 'ASC',
+ page: 1,
+ size: 15,
+ total: 0,
+ filters: {}
+ };
+ volunteerPager: any = {
+ sort: 1,
+ method: 'ASC',
+ page: 1,
+ size: 20,
+ total: 0,
+ filters: {}
+ };
+ resourcePager: any = {
+ sort: 1,
+ method: 'ASC',
+ page: 1,
+ size: 20,
+ total: 0,
+ filters: {}
+ };
+/**
+ * get the standard organisation pager
+ * @returns pager
+ */
+ getPager() {
+ return {...this.pager};
+ }
+ /**
+ * init pager with default values
+ */
+ setPager() {
+ this.pager = {
+ sort: 1,
+ method: 'ASC',
+ page: 1,
+ size: 15,
+ total: 0,
+ filters: {}
+ };
+ }
+/**
+ * volunteer pager for ngo details volunteer table
+ */
+ getVolunteerPager() {
+ return {...this.volunteerPager};
+ }
+/**
+ * resource pager for ngo details resource table
+ */
+ getResourcePager() {
+ return {...this.resourcePager};
+ }
+ /**
+ * fields for adding a new resource
+ */
+ constructor(private httpClient: HttpClient) {}
+
+ /**
+ * post a new organisation to website, auto add Header
+ * @param {any} payload the org data to be added
+ * @returns observable with response
+ */
+ addorganisation(payload: any) {
+ return this.httpClient.post('/organisations', payload );
+ }
+/**
+ * edit a new organisation
+ * @param {any} payload the org data to be modified
+ * @param {string} id of the organistation to be modified
+ * @returns observable with response
+ */
+ editOrganisation(id: string, payload: any) {
+
+ return this.httpClient.put(`/organisations/${id}`, payload );
+ }
+
+ /**
+ * get all organisations
+ * @param {any} pager with sorting, filters, page etc.
+ * @returns observable with organisations list
+ */
+ getorganisations(paginationObj?: any): Observable<any> {
+ let params: any = {};
+
+ params = {...params, ...paginationObj};
+ if (params.filters) {
+ Object.keys(params.filters).forEach((key) => {
+ if (params.filters[key]) {
+ params['filters[' + key + ']'] = params.filters[key];
+ }
+ });
+ delete params.filters;
+ }
+ return this.httpClient.get('/organisations', { params: params });
+ }
+ /**
+ * get organisation by id
+ * @param {string} id of the organistation to be fetched
+ * @returns observable with response organisation
+ */
+ getorganisation(id: String): Observable<any> {
+ return this.httpClient.get(`/organisations/${id}`);
+ }
+ /**
+ * send email to the org to update data
+ * @param {string} id of the organistation to be notified
+ * @returns observable with response
+ */
+ sendUpdateDataEmail(id: String): Observable<any> {
+ return this.httpClient.get(`/organisations/${id}/email`);
+ }
+ /**
+ * get organisation by id
+ * @param {string} id of the organistation that has been updated
+ * @returns observable with response
+ */
+ updated(id: String): Observable<any> {
+ return this.httpClient.get(`/organisations/${id}/validate`);
+ }
+ /**
+ * delete organisation by id
+ * @param {string} id of the organistation to be deleted
+ * @returns observable with response
+ */
+ deleteorganisation(id: String): Observable<any> {
+ return this.httpClient.delete(`/organisations/${id}`);
+ }
+ /**
+ * get organisation by name
+ * @param {string} name of the organistation to be found
+ * @returns observable with response
+ */
+ getorganisationbyName(name: String): Observable<any> {
+ let params = {};
+ params = {...params, ...{name: name}};
+ return this.httpClient.get('/organisations', {params: params} );
+ }
+/**
+ * get resource table with ngo id
+ * @param {string} id of the organistation to be queried
+ * @param {any} paginationObj of the resource table
+ * @returns observable with response
+ */
+ getResourcesbyorganisation(id: String, paginationObj?: any): Observable<any> {
+ let params: any = {};
+
+ params = {...params, ...paginationObj};
+ if (params.filters) {
+ Object.keys(params.filters).forEach((key) => {
+ if (params.filters[key]) {
+ params['filters[' + key + ']'] = params.filters[key];
+ }
+ });
+ delete params.filters;
+ }
+ return this.httpClient.get(`/organisations/${id}/resources`, {params: params});
+ }
+/**
+ * get volunteers table with ngo id
+ * @param {string} id of the organistation to be queried
+ * @param {any} paginationObj of the volunteers table
+ * @returns observable with response
+ */
+ getVolunteersbyorganisation(id: String, paginationObj?: any): Observable<any> {
+ let params: any = {};
+
+ params = {...params, ...paginationObj};
+ if (params.filters) {
+ Object.keys(params.filters).forEach((key) => {
+ if (params.filters[key]) {
+ params['filters[' + key + ']'] = params.filters[key];
+ }
+ });
+ delete params.filters;
+ }
+ return this.httpClient.get(`/organisations/${id}/volunteers`, {params: params});
+ }
+}
+
+ +
+ src/app/pages/resources/resources.service.ts
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(httpClient: HttpClient)
+ |
+ ||||||
+ + | +||||||
+
+ Parameters :
+
+
|
+
+ + + + addResource + + + + | +||||||||
+addResource(payload: any)
+ |
+ ||||||||
+ + | +||||||||
+ post a new resource to website, auto add Header +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+ observable with response + + |
+
+ + + + deleteResource + + + + | +||||||||
+deleteResource(id: any)
+ |
+ ||||||||
+ + | +||||||||
+ delete resource by id +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+ observable with response + + |
+
+ + + + editResource + + + + | +||||||||||||
+editResource(id: string, payload: any)
+ |
+ ||||||||||||
+ + | +||||||||||||
+ edit a resource +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+ observable with response + + |
+
+ + + + getPager + + + + | +
+getPager()
+ |
+
+ + | +
+ get the resource pager +
+
+
+ Returns :
+ any
+
+
+
+ pager + + |
+
+ + + + getResource + + + + | +||||||||||||
+getResource(id: String, paginationObj?: any)
+ |
+ ||||||||||||
+ + | +||||||||||||
+ get resource by id +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ observable with response resource + + |
+
+ + + + getResourceBySlug + + + + | +||||||||||||
+getResourceBySlug(slug: string, pager: any)
+ |
+ ||||||||||||
+ + | +||||||||||||
+ get resource by slug +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+ observable with response resource + + |
+
+ + + + getResources + + + + | +||||||
+getResources(paginationObj?: any)
+ |
+ ||||||
+ + | +||||||
+ get all Resources +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ observable with resources list + + |
+
+ + + + importCsv + + + + | +||||||||||||
+importCsv(file: any, id: any)
+ |
+ ||||||||||||
+ + | +||||||||||||
+ send CSV and organisation id to server +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+ observable with response resource + + |
+
+ + + + setPager + + + + | +
+setPager()
+ |
+
+ + | +
+ init pager with default values +
+ Returns :
+ void
+
+ |
+
+ + + + pager + + + | +
+ Type : any
+
+ |
+
+ Default value : {
+ sort: 1,
+ method: 'ASC',
+ page: 1,
+ size: 15,
+ total: 0,
+ filters: {}
+ }
+ |
+
+ + | +
+ pager for resources table + |
+
import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs/internal/Observable';
+import { HttpHeaders, HttpClient } from '@angular/common/http';
+import { map } from 'rxjs/internal/operators/map';
+@Injectable({
+ providedIn: 'root'
+ })
+export class ResourcesService {
+ constructor(private httpClient: HttpClient) {}
+/**
+ * pager for resources table
+ */
+ pager: any = {
+ sort: 1,
+ method: 'ASC',
+ page: 1,
+ size: 15,
+ total: 0,
+ filters: {}
+ };
+/**
+ * get the resource pager
+ * @returns pager
+ */
+ getPager() {
+ return {...this.pager};
+ }
+ /**
+ * init pager with default values
+ */
+ setPager() {
+ this.pager = {
+ sort: 1,
+ method: 'ASC',
+ page: 1,
+ size: 15,
+ total: 0,
+ filters: {}
+ };
+ }
+
+ /**
+ * get all Resources
+ * @param {any} pager with sorting, filters, page etc
+ * @returns observable with resources list
+ */
+ getResources(paginationObj?: any): Observable<any> {
+ let params: any = {};
+
+ params = {...params, ...paginationObj};
+ if (params.filters) {
+ Object.keys(params.filters).forEach((key) => {
+ if (params.filters[key]) {
+ params['filters[' + key + ']'] = params.filters[key];
+ }
+ });
+ delete params.filters;
+ }
+
+ return this.httpClient.get('/resources', { params: params });
+ }
+ /**
+ * get resource by id
+ * @param {string} id of the resource to be fetched
+ * @param {any} pager with sorting, filters, page etc
+ * @returns observable with response resource
+ */
+ getResource(id: String, paginationObj?: any): Observable<any> {
+ let params: any = {};
+
+ params = {...params, ...paginationObj};
+ if (params.filters) {
+ Object.keys(params.filters).forEach((key) => {
+ if (params.filters[key]) {
+ params['filters[' + key + ']'] = params.filters[key];
+ }
+ });
+ delete params.filters;
+ }
+
+ return this.httpClient.get(`/resources/${id}`, { params: params } );
+ }
+ /**
+ * post a new resource to website, auto add Header
+ * @param {any} payload the org data to be added
+ * @returns observable with response
+ */
+ addResource(payload: any) {
+ return this.httpClient.post('/resources', payload );
+ }
+ /**
+ * delete resource by id
+ * @param {string} id of the resource to be deleted
+ * @returns observable with response
+ */
+ deleteResource(id: any) {
+ return this.httpClient.delete(`/resources/${id}`);
+ }
+ /**
+ * edit a resource
+ * @param {any} payload the resource data to be modified
+ * @param {string} id of the resource to be modified
+ * @returns observable with response
+ */
+ editResource(id: string, payload: any) {
+ return this.httpClient.put(`/resources/${id}`, payload );
+ }
+ /**
+ * send CSV and organisation id to server
+ * @param {string} id of the organisation to be modfified
+ * @param {any} file with CSV data
+ * @returns observable with response resource
+ */
+ importCsv(file: any, id: any) {
+ const formdata: FormData = new FormData();
+ formdata.append('file', file);
+ formdata.append('organisation_id', id);
+ return this.httpClient.post('/resources/import', formdata);
+ }
+ /**
+ * get resource by slug
+ * @param {string} slug name without diacritics the resource to be fetched
+ * @param {any} pager with sorting, filters, page etc
+ * @returns observable with response resource
+ */
+ getResourceBySlug(slug: string, pager: any) {
+ let params: any = {};
+
+ params = {...params, ...pager};
+ if (params.filters) {
+ Object.keys(params.filters).forEach((key) => {
+ if (params.filters[key]) {
+ params['filters[' + key + ']'] = params.filters[key];
+ }
+ });
+ delete params.filters;
+ }
+ return this.httpClient.get(`/resources/by_slug/${slug}`, {params: params});
+ }
+}
+
+ +
+ src/app/core/service/users.service.ts
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(httpClient: HttpClient)
+ |
+ ||||||
+ Defined in src/app/core/service/users.service.ts:10
+ |
+ ||||||
+ class constructor +
+ Parameters :
+
+
|
+
+ + + + addUser + + + + | +||||||
+addUser(payload: any)
+ |
+ ||||||
+ Defined in src/app/core/service/users.service.ts:35
+ |
+ ||||||
+ post a new User to website, auto add Header +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + deleteUser + + + + | +||||||
+deleteUser(id: string)
+ |
+ ||||||
+ Defined in src/app/core/service/users.service.ts:77
+ |
+ ||||||
+ Delete user by id +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+
+ |
+
+ + + + getPager + + + + | +
+getPager()
+ |
+
+ Defined in src/app/core/service/users.service.ts:29
+ |
+
+ return pager +
+ Returns :
+ any
+
+ |
+
+ + + + getUser + + + + | +|||||||||
+getUser(id: string, paginationObj?: any)
+ |
+ |||||||||
+ Defined in src/app/core/service/users.service.ts:59
+ |
+ |||||||||
+ get User by id +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+
+ |
+
+ + + + getUsers + + + + | +||||||
+getUsers(paginationObj?: any)
+ |
+ ||||||
+ Defined in src/app/core/service/users.service.ts:42
+ |
+ ||||||
+ get all Users +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+
+ |
+
+ + + + updateUser + + + + | +||||||
+updateUser(payload: any)
+ |
+ ||||||
+ Defined in src/app/core/service/users.service.ts:69
+ |
+ ||||||
+ Edit user +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+
+ |
+
+ + + + pager + + + | +
+ Type : any
+
+ |
+
+ Default value : {
+ sort: 1,
+ method: 'ASC',
+ page: 1,
+ size: 15,
+ total: 0,
+ filters: {}
+ }
+ |
+
+ Defined in src/app/core/service/users.service.ts:18
+ |
+
+ init pager + |
+
import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs/internal/Observable';
+import { HttpClient } from '@angular/common/http';
+@Injectable({
+ providedIn: 'root'
+})
+/**
+ * User service class
+ */
+export class UsersService {
+/**
+ * class constructor
+ */
+ constructor(private httpClient: HttpClient) {}
+ /**
+ *init pager
+ */
+ pager: any = {
+ sort: 1,
+ method: 'ASC',
+ page: 1,
+ size: 15,
+ total: 0,
+ filters: {}
+ };
+/**
+ * return pager
+ */
+ getPager() {
+ return {...this.pager};
+ }
+ /**
+ * post a new User to website, auto add Header
+ */
+ addUser(payload: any) {
+ return this.httpClient.post('/users', payload );
+ }
+
+ /**
+ * get all Users
+ */
+ getUsers(paginationObj?: any): Observable<any> {
+ let params: any = {};
+ params = {...params, ...paginationObj};
+ if (params.filters) {
+ Object.keys(params.filters).forEach((key) => {
+ if (params.filters[key]) {
+ params['filters[' + key + ']'] = params.filters[key];
+ }
+ });
+ delete params.filters;
+ }
+ return this.httpClient.get('/users', { params: params });
+ }
+
+ /**
+ * get User by id
+ */
+ getUser(id: string, paginationObj?: any): Observable<any> {
+ let params: any = {};
+ params = {...params, ...paginationObj};
+ return this.httpClient.get(`/users/${id}`, {params: params});
+ }
+
+ /**
+ * Edit user
+ * @param payload
+ */
+ updateUser(payload: any): Observable<any> {
+ return this.httpClient.put(`/users/${payload._id}`, payload );
+ }
+
+ /**
+ * Delete user by id
+ * @param id
+ */
+ deleteUser(id: string): Observable<any> {
+ return this.httpClient.delete(`/users/${id}`);
+ }
+
+}
+
+ +
+ src/app/core/service/util.service.ts
+
+ Methods+ |
+
+
|
+
+constructor()
+ |
+
+ Defined in src/app/core/service/util.service.ts:7
+ |
+
+ + + + copyToClipboard + + + + | +||||||
+copyToClipboard(value: string)
+ |
+ ||||||
+ Defined in src/app/core/service/util.service.ts:45
+ |
+ ||||||
+ copy value to clipboard. Create new textarea and apply copy command +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + removeDiacritics + + + + | +||||||||
+removeDiacritics(term: string)
+ |
+ ||||||||
+ Defined in src/app/core/service/util.service.ts:32
+ |
+ ||||||||
+ remove all diacrirics from sting +
+ Parameters :
+
+
+
+
+ Returns :
+ string
+
+
+
+ the string without diacritics + + |
+
+ + + + removeEmpty + + + + | +||||||||
+removeEmpty(obj: any)
+ |
+ ||||||||
+ Defined in src/app/core/service/util.service.ts:14
+ |
+ ||||||||
+ remove all empty prop in obj sent as param +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+ the object without empty properties + + |
+
import { Injectable } from '@angular/core';
+
+@Injectable()
+/**
+ * class with general utility methods
+ */
+export class UtilService {
+ constructor() {}
+ /**
+ * remove all empty prop in obj sent as param
+ * @param {any} obj the object from which to remove
+ * @returns the object without empty properties
+ */
+ removeEmpty(obj: any): any {
+ for (const propName in obj) {
+ if (
+ obj[propName] === null ||
+ obj[propName] === undefined ||
+ obj[propName] === '' ||
+ (Array.isArray(obj[propName]) && !obj[propName].length)
+ ) {
+ delete obj[propName];
+ }
+ }
+ return obj;
+ }
+ /**
+ * remove all diacrirics from sting
+ * @param {string} term the string with diacritics
+ * @returns the string without diacritics
+ */
+ removeDiacritics(term: string): string {
+ const diac = ['ă', 'Ă', 'â', 'Â', 'î', 'Î', 'ș', 'Ș', 'ț', 'Ț'];
+ const repl = ['a', 'A', 'a', 'A', 'i', 'I', 's', 'S', 't', 'T'];
+ for (let i = 0; i < diac.length; i++) {
+ const reg = new RegExp(diac[i], 'g');
+ term = term.replace(reg, repl[i]);
+ }
+ return term;
+ }
+ /**
+ * copy value to clipboard. Create new textarea and apply copy command
+ * @param {string} vale the string to be copied
+ */
+ copyToClipboard(value: string) {
+ const selBox = document.createElement('textarea');
+ selBox.style.position = 'fixed';
+ selBox.style.left = '0';
+ selBox.style.top = '0';
+ selBox.style.opacity = '0';
+ selBox.value = value;
+ document.body.appendChild(selBox);
+ selBox.focus();
+ selBox.select();
+ document.execCommand('copy');
+ document.body.removeChild(selBox);
+ }
+}
+
+ +
+ src/app/pages/volunteers/volunteers.service.ts
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(httpClient: HttpClient)
+ |
+ ||||||
+ + | +||||||
+
+ Parameters :
+
+
|
+
+ + + + addVolunteer + + + + | +||||||||
+addVolunteer(payload: any)
+ |
+ ||||||||
+ + | +||||||||
+ post a new volunteer to website, auto add Header +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+ observable with response + + |
+
+ + + + deleteVolunteer + + + + | +||||||||
+deleteVolunteer(id: String)
+ |
+ ||||||||
+ + | +||||||||
+ delete volunteer by id +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ observable with response + + |
+
+ + + + editVolunteer + + + + | +||||||||||||
+editVolunteer(id: String, payload: any)
+ |
+ ||||||||||||
+ + | +||||||||||||
+ edit a volunteer +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ observable with response + + |
+
+ + + + getAllocations + + + + | +||||||||
+getAllocations(volunteerId: string)
+ |
+ ||||||||
+ + | +||||||||
+ get alocations for a volunteer +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+ observable with response alocations + + |
+
+ + + + getPager + + + + | +
+getPager()
+ |
+
+ + | +
+ get the volunteer pager +
+
+
+ Returns :
+ any
+
+
+
+ pager + + |
+
+ + + + getVolunteer + + + + | +||||||||
+getVolunteer(id: String)
+ |
+ ||||||||
+ + | +||||||||
+ get volunteer by id +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ observable with response volunteer + + |
+
+ + + + getVolunteers + + + + | +||||||
+getVolunteers(paginationObj?: any)
+ |
+ ||||||
+ + | +||||||
+ get all volunteers +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<any>
+
+
+
+ observable with volunteers list + + |
+
+ + + + importCsv + + + + | +||||||||||||
+importCsv(file: any, id: any)
+ |
+ ||||||||||||
+ + | +||||||||||||
+ send CSV and organisation id to server +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+ observable with response + + |
+
+ + + + setPager + + + + | +
+setPager()
+ |
+
+ + | +
+ init pager with default values +
+ Returns :
+ void
+
+ |
+
+ + + + pager + + + | +
+ Type : any
+
+ |
+
+ Default value : {
+ sort: 1,
+ method: 'ASC',
+ page: 1,
+ size: 15,
+ total: 0,
+ filters: {}
+ }
+ |
+
+ + | +
+ pager for the volunteers table + |
+
import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs/internal/Observable';
+import { HttpHeaders, HttpClient, HttpParams } from '@angular/common/http';
+import { map } from 'rxjs/internal/operators/map';
+@Injectable({
+ providedIn: 'root'
+ })
+export class VolunteerService {
+ /**
+ * pager for the volunteers table
+ */
+ pager: any = {
+ sort: 1,
+ method: 'ASC',
+ page: 1,
+ size: 15,
+ total: 0,
+ filters: {}
+ };
+/**
+ * get the volunteer pager
+ * @returns pager
+ */
+ getPager() {
+ return {...this.pager};
+ }
+ /**
+ * init pager with default values
+ */
+ setPager() {
+ this.pager = {
+ sort: 1,
+ method: 'ASC',
+ page: 1,
+ size: 15,
+ total: 0,
+ filters: {}
+ };
+ }
+ constructor(private httpClient: HttpClient) {}
+/**
+ * post a new volunteer to website, auto add Header
+ * @param {any} payload the org data to be added
+ * @returns observable with response
+ */
+ addVolunteer(payload: any) {
+ return this.httpClient.post('/volunteers', payload );
+ }
+ /**
+ * get all volunteers
+ * @param {any} pager with sorting, filters, page etc
+ * @returns observable with volunteers list
+ */
+ getVolunteers(paginationObj?: any): Observable<any> {
+ let params: any = {};
+
+ params = {...params, ...paginationObj};
+ if (params.filters) {
+ Object.keys(params.filters).forEach((key) => {
+ if (params.filters[key]) {
+ params['filters[' + key + ']'] = params.filters[key];
+ }
+ });
+ delete params.filters;
+ }
+ return this.httpClient.get('/volunteers', { params: params });
+ }
+ /**
+ * get volunteer by id
+ * @param {string} id of the volunteer to be fetched
+ * @param {any} pager with sorting, filters, page etc
+ * @returns observable with response volunteer
+ */
+ getVolunteer(id: String): Observable<any> {
+ return this.httpClient.get(`/volunteers/${id}`);
+ }
+ /**
+ * edit a volunteer
+ * @param {any} payload the volunteer data to be modified
+ * @param {string} id of the volunteer to be modified
+ * @returns observable with response
+ */
+ editVolunteer(id: String, payload: any ): Observable<any> {
+ return this.httpClient.put(`/volunteers/${id}`, payload);
+ }
+ /**
+ * delete volunteer by id
+ * @param {string} id of the volunteer to be deleted
+ * @returns observable with response
+ */
+ deleteVolunteer(id: String): Observable<any> {
+ return this.httpClient.delete(`/volunteers/${id}`);
+ }
+/**
+ * send CSV and organisation id to server
+ * @param {string} id of the organisation to be modfified
+ * @param {any} file with CSV data
+ * @returns observable with response
+ */
+ importCsv(file: any, id: any) {
+ const formdata: FormData = new FormData();
+ console.log(id);
+ formdata.append('file', file);
+ formdata.append('organisation_id', id);
+ return this.httpClient.post('/volunteers/import', formdata);
+ }
+/**
+ * get alocations for a volunteer
+ * @param {string} volunteerId of the volunteer for whom to get alocations
+ * @returns observable with response alocations
+ */
+ getAllocations(volunteerId: string) {
+ return this.httpClient.get(`/volunteers/${volunteerId}/allocations`);
+ }
+}
+
+ +
+ src/app/core/http/api-prefix.interceptor.ts
+
+
+
Prefixes all requests with environment.serverUrl
.
+ Methods+ |
+
+
|
+
+constructor(authService: AuthenticationService)
+ |
+ ||||||
+ + | +||||||
+
+ Parameters :
+
+
|
+
+ + + + intercept + + + + | +|||||||||
+intercept(request: HttpRequest
+ |
+ |||||||||
+ + | +|||||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<HttpEvent<any>>
+
+
+
+
+ |
+
import {
+ HttpEvent,
+ HttpHandler,
+ HttpInterceptor,
+ HttpRequest,
+ HttpHeaders
+} from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { environment } from '@env/environment';
+import { Observable } from 'rxjs';
+import { AuthenticationService } from '../authentication/authentication.service';
+/**
+ * Prefixes all requests with `environment.serverUrl`.
+ */
+@Injectable()
+export class ApiPrefixInterceptor implements HttpInterceptor {
+ constructor(private authService: AuthenticationService) {}
+ intercept(
+ request: HttpRequest<any>,
+ next: HttpHandler
+ ): Observable<HttpEvent<any>> {
+ if (request.url.indexOf('assets') === -1) {
+ const token = this.authService.accessToken || null;
+ request = request.clone({
+ setHeaders: {
+ 'Authorization': (token ? `Bearer ${token}` : '')
+ },
+ url: environment.serverUrl + request.url
+ });
+ }
+ return next.handle(request);
+ }
+}
+
+ +
+ src/app/core/http/error-handler.interceptor.ts
+
+
+
Adds a default error handler to all requests.
+ + + + +
+ Methods+ |
+
+
|
+
+constructor(router: Router, errorMessageService: ErrorMessageService, authService: AuthenticationService)
+ |
+ ||||||||||||
+ + | +||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + Private + errorHandler + + + + | +||||||
+
+ errorHandler(response: HttpResponse
+ |
+ ||||||
+ + | +||||||
+ Customize the default error handler here if needed +
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<HttpEvent<any>>
+
+
+
+
+ |
+
+ + + + intercept + + + + | +|||||||||
+intercept(request: HttpRequest
+ |
+ |||||||||
+ + | +|||||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ Observable<HttpEvent<any>>
+
+
+
+
+ |
+
import {
+ HttpEvent,
+ HttpHandler,
+ HttpInterceptor,
+ HttpRequest,
+ HttpResponse
+} from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Router } from '@angular/router';
+import { ErrorMessageService } from '@app/core/service';
+import { environment } from '@env/environment';
+import { Observable } from 'rxjs';
+import { catchError } from 'rxjs/operators';
+import { AuthenticationService } from '../authentication/authentication.service';
+
+/**
+ * Adds a default error handler to all requests.
+ */
+@Injectable()
+export class ErrorHandlerInterceptor implements HttpInterceptor {
+ constructor(
+ private router: Router,
+ private errorMessageService: ErrorMessageService,
+ private authService: AuthenticationService
+ ) {}
+
+ intercept(
+ request: HttpRequest<any>,
+ next: HttpHandler
+ ): Observable<HttpEvent<any>> {
+ return next
+ .handle(request)
+ .pipe(catchError(error => this.errorHandler(error)));
+ }
+
+ /**
+ * Customize the default error handler here if needed
+ */
+ private errorHandler(
+ response: HttpResponse<any>
+ ): Observable<HttpEvent<any>> {
+ if (response.status === 401) {
+ this.authService.setCredentials(null);
+ this.router.navigate(['/login'], {
+ replaceUrl: true
+ });
+ } else if (response.status === 400) {
+ const errorResponse: any = response;
+ if (errorResponse.error) {
+ if (errorResponse.error.validation) {
+ errorResponse.error.validation.keys.forEach((key: string) => {
+ this.errorMessageService.set(
+ errorResponse.error.validation.errors[key],
+ key,
+ response.url
+ );
+ });
+ } else {
+ this.errorMessageService.set(
+ errorResponse.error.error,
+ '_GLOBAL_',
+ response.url
+ );
+ }
+ }
+ }
+ if (!environment.production) {
+ // Do something with the error
+ console.error('Request error', response);
+ }
+ throw response;
+ }
+}
+
+ +
+ src/app/pages/organisations/organisations/components/organisation-details/organisation-details.component.ts
+
+
+
Alert message interface
+ + + + +
+ Properties+ |
+
+ + | +
+ + message + | +
+ message:
+ |
+
+ Type : string
+
+ |
+
+ + type + | +
+ type:
+ |
+
+ Type : string
+
+ |
+
import {
+ Component,
+ OnInit,
+ ViewChild,
+ AfterContentChecked,
+} from '@angular/core';
+import { ActivatedRoute, Router, NavigationExtras, ParamMap } from '@angular/router';
+import { OrganisationService } from '../../../organisations.service';
+import { NgbModal, NgbTabset } from '@ng-bootstrap/ng-bootstrap';
+import {
+ FormGroup,
+ Validators,
+ FormBuilder
+} from '@angular/forms';
+import { AuthenticationService } from '../../../../../core/authentication/authentication.service';
+
+import { CitiesCountiesService } from '../../../../../core/service/cities-counties.service';
+import { ResourcesService } from '@app/pages/resources/resources.service';
+import { Location } from '@angular/common';
+import { FiltersService, UsersService } from '@app/core';
+
+/**
+ * Alert message interface
+ */
+interface Alert {
+ type: string;
+ message: string;
+}
+
+@Component({
+ selector: 'app-organisation-details',
+ templateUrl: './organisation-details.component.html',
+ styleUrls: ['./organisation-details.component.scss']
+})
+export class NgodetailsComponent implements OnInit, AfterContentChecked {
+
+ /**
+ * var that holds data about NGO, resources and volunteers
+ */
+ needupdate = false;
+ data: any;
+ resourceData: any[] = [];
+ volunteersData: any[] = [];
+ /**
+ * var that holds pager and filters for resources and volunteers
+ */
+ resourcePager: any = {};
+ resourceFiltersSelected = Array(2);
+ volunteerPager: any = {};
+ volunteerFiltersSelected = Array(2);
+ /**
+ * flag for ngtemplate in HTML
+ */
+ hasVolunteers = false;
+ hasResources = false;
+
+ nrvol = 0;
+ /**
+ * flag used to get ID from link and pass it to get method
+ */
+ ngoid: string;
+ /**
+ * var for data to send when adding new resource. Only for DSU
+ */
+ navigationExtras: NavigationExtras;
+ /**
+ * Fliterable values
+ */
+ volunteerTypeFilterValues: any[] = [];
+ categoryFilterValues: any[] = [];
+ specializationFilterValues: any[] = [];
+ locationFilterValues: any[] = [];
+ /**
+ * Tabs reference for vefifing which is open
+ */
+ @ViewChild('tabRef', { static: true}) tabRef: NgbTabset;
+ tabsInitialized = false;
+ selectedTab = 'volunteers';
+
+ /**
+ * flag for toast message
+ */
+ messageSent = false;
+ updateSent = false;
+ /**
+ * flag for HTML to display loading animation
+ */
+ loading = false;
+ /**
+ * mapping of object keys to filter recognizable keys
+ */
+ propertyMap = {
+ '_id': 'id',
+ 'parent_id': 'parent_id'
+ };
+ /**
+ * store the current voluneer that has courses open
+ */
+ currentVolunteerId = '';
+
+ constructor(
+ private route: ActivatedRoute,
+ private router: Router,
+ private resourceService: ResourcesService,
+ public authService: AuthenticationService,
+ private organisationService: OrganisationService,
+ private filterService: FiltersService,
+ private location: Location,
+ private userService: UsersService,
+ private citiesandcounties: CitiesCountiesService,
+ ) {
+ if (this.router.url.indexOf('validate') > -1) {
+ this.needupdate = true;
+ }
+ /**
+ * set a specific open tab if necessary
+ */
+ const navigation = this.router.getCurrentNavigation();
+
+ if (navigation && navigation.extras && navigation.extras.state) {
+ this.selectedTab = navigation.extras.state.tabName;
+ }
+ }
+
+ ngOnInit() {
+ /**
+ * get values that can be queried for the filters
+ */
+ this.citiesandcounties.getCounties().subscribe((response: any) => {
+ const aux = response;
+ aux.map((elem: { id: any; _id: any; }) => elem.id = elem._id);
+ this.locationFilterValues = aux;
+ });
+ this.filterService.getCategoryFilters().subscribe((data) => {
+ this.categoryFilterValues = data.map((x: any) => {
+ const parent = data.find((y: any) => y._id === x.parent_id);
+ return {
+ id: x._id,
+ name: x.name,
+ parent_id: x.parent_id,
+ pp: x.parent_id === '0' ? x.name : ( parent ? parent.name : null),
+ level: x.parent_id === '0' ? 0 : 1
+ };
+ });
+ });
+ this.filterService.getSpecializationFilters().subscribe((data) => {
+ this.specializationFilterValues = data.map((elem: any) => {
+ return {id: elem._id, name: elem.name};
+ });
+ });
+ /**
+ * get current id, init pager, and get all data with the id
+ */
+ this.ngoid = this.route.snapshot.paramMap.get('id');
+ this.volunteerPager = this.organisationService.getVolunteerPager();
+ this.resourcePager = this.organisationService.getResourcePager();
+ this.getData();
+ this.getResources();
+ this.getVolunteers();
+ }
+ /**
+ * switch tab of necessary
+ */
+ ngAfterContentChecked() {
+ if (this.tabRef.tabs) {
+ this.tabRef.select(this.selectedTab);
+ }
+ }
+ /**
+ * get org data
+ */
+ getData() {
+ this.organisationService.getorganisation(this.ngoid).subscribe(data => {
+ this.data = data;
+ this.navigationExtras = {
+ state: {
+ ngo: {
+ name: data.name,
+ _id: this.ngoid
+ }
+ }
+ };
+ });
+ }
+ /**
+ * get volunteers data
+ */
+ getVolunteers() {
+ this.organisationService.getVolunteersbyorganisation(this.ngoid, this.volunteerPager).subscribe(data => {
+ this.volunteerPager.total = data.pager.total;
+ this.volunteersData = data.data;
+ if (!!data.data.courses) {
+ data.data.courses = data.data.courses.reverse();
+ }
+ if (Object.entries(this.volunteerPager.filters).length === 0 && this.volunteerPager.filters.constructor === Object) {
+ if (this.volunteersData.length === 0) {
+ this.hasVolunteers = false;
+ } else {
+ this.hasVolunteers = true;
+ this.nrvol = data.pager.total;
+ }
+ } else {
+ this.hasVolunteers = true;
+ }
+ });
+ }
+ /**
+ * get resourcesData
+ */
+ getResources() {
+ this.organisationService.getResourcesbyorganisation(this.ngoid, this.resourcePager).subscribe(data => {
+ this.resourcePager.total = data.pager.total;
+ this.resourceData = data.data;
+ if (Object.entries(this.volunteerPager.filters).length === 0 &&
+ this.volunteerPager.filters.constructor === Object &&
+ this.resourceData.length === 0) {
+
+ this.hasResources = false;
+ } else {
+ this.hasResources = true;
+ }
+ });
+ }
+ /**
+ * resource filter callback. Filters added to pager and then a request is made
+ * @param {number} id the index in the pager filters and filters Selected array
+ */
+ resourcefilterChanged(id: number) {
+ this.resourcePager.filters[id] = this.resourceFiltersSelected[id].map((elem: any) => elem.id).join(',');
+ this.getResources();
+ }
+/**
+ * volunteer filter callback. Filters added to pager and then a request is made
+ * @param {number} id the index in the pager filters and filters Selected array
+ */
+ volunteerfilterChanged(id: number) {
+ this.volunteerPager.filters[id] = this.volunteerFiltersSelected[id].map((elem: any) => elem.id).join(',');
+ this.getVolunteers();
+ }
+ // deleteRes(id: string) {
+ // this.resourceService.deleteResource(id).subscribe(resp => {
+ // this.getResources();
+ // });
+ // }
+ /**
+ * delete NGO
+ */
+ deleteSelf() {
+ if (this.authService.user._id === this.data._id) {
+ if (confirm('Sunteți sigur că doriți să vă ștergeți contul?')) {
+ this.loading = true;
+ this.organisationService.deleteorganisation(this.ngoid).subscribe(data => {
+ this.loading = false;
+ this.authService.setCredentials();
+ this.router.navigateByUrl('/login');
+ }, () => {
+ this.location.back();
+ });
+ }
+ } else {
+ if (confirm('Sunteți sigur că doriți să ștergeți această intrare? Odată ștearsă nu va mai putea fi recuperată.')) {
+ this.loading = true;
+ this.organisationService.deleteorganisation(this.ngoid).subscribe(data => {
+ this.loading = false;
+ this.router.navigateByUrl('/organisations');
+ }, () => {
+ this.loading = false;
+ });
+ }
+ }
+ }
+ /**
+ * navigate to add resource with ngo data
+ */
+ addresource() {
+ this.router.navigateByUrl('/resources/add', this.navigationExtras);
+ }
+ /**
+ * navigate to add volunteer with ngo data
+ */
+ addvolunteer() {
+ this.router.navigateByUrl('/volunteers/add', this.navigationExtras);
+ }
+ /**
+ * sort callback for volunteers table
+ */
+ volunteerSortChanged(pager: any) {
+ this.volunteerPager = pager;
+ this.getVolunteers();
+ }
+ /**
+ * sort callback for resource table
+ */
+ resourceSortChanged(pager: any) {
+ this.resourcePager = pager;
+ this.getResources();
+ }
+/**
+ * search callback for both tabels
+ */
+ searchChanged(pager: any) {
+ if (pager.search !== '') {
+ if (this.selectedTab === 'volunteers') {
+ this.volunteerPager = pager;
+ this.getVolunteers();
+ } else {
+ this.resourcePager = pager;
+ this.getResources();
+ }
+ }
+ }
+ /**
+ * send manual notification and trigger popup
+ */
+ sendNotification() {
+ this.organisationService.sendUpdateDataEmail(this.ngoid).subscribe(() => {
+ this.messageSent = true;
+ setTimeout(() => this.close(), 5000);
+ });
+ }
+ /**
+ * send info updated and trigger popup
+ */
+ validateinfo() {
+ this.organisationService.updated(this.ngoid).subscribe(() => {
+ this.updateSent = true;
+ this.needupdate = false;
+ setTimeout(() => this.close(), 5000);
+ });
+ }
+ /**
+ * manual close for message send popup
+ */
+ close() {
+ this.messageSent = false;
+ this.updateSent = false;
+ }
+ /**
+ * expand volunteer specialization row for a specific volunteer
+ * @param {string} volunteerId of the current voluneer that is referenced
+ * @param {boolean} status is open or is cloed
+ */
+ openMenu(volunteerId: string, status: boolean) {
+ if (status) {
+ this.currentVolunteerId = volunteerId;
+ } else {
+ this.currentVolunteerId = null;
+ }
+ }
+
+ canEdit() {
+ if (this.data) {
+ return this.authService.is('DSU') || (this.authService.is('NGO') && this.data._id === this.authService.user.organisation._id);
+ } else {
+ return false;
+ }
+ }
+}
+
+ +
+ src/app/core/model/authentication.model.ts
+
+
+
Credential model to be stored in local storage
+ + + + +
+ Properties+ |
+
+ + | +
+ + token + | +
+ token:
+ |
+
+ Type : string
+
+ |
+
+ + user + | +
+ user:
+ |
+
+ Type : User
+
+ |
+
declare namespace Authentication {
+ /**
+ * Login payload model to be sent to server
+ */
+ export interface LoginPayload {
+ username: string;
+ password: string;
+ }
+/**
+ * Signup payload model to be sent to server
+ */
+ export interface SignupPayload {
+ firstName: string;
+ lastName: string;
+ email: string;
+ phone: string;
+ password: string;
+ cPassword: string;
+ }
+ /**
+ * User model to be stored in credentials
+ */
+ export interface User {
+ _id: string;
+ role: string;
+ firstName: string;
+ lastName: string;
+ emailVerified: boolean;
+ emailHash: string;
+ passwordLastUpdated?: any;
+ lastLogin: Date;
+ phone?: any;
+ email: string;
+ createdAt: Date;
+ updatedAt: Date;
+ organisation?: any;
+ institution?: any;
+ }
+ /**
+ * Credential model to be stored in local storage
+ */
+ export interface Credentials {
+ token: string;
+ user: User;
+ }
+}
+
+ +
+ src/app/core/model/error.model.ts
+
+
+
Error message object model for consistency
+ + + + +
+ Properties+ |
+
+
|
+
+ + error + | +
+ error:
+ |
+
+ Type : string
+
+ |
+
+ + id + | +
+ id:
+ |
+
+ Type : number
+
+ |
+
+ + serviceUrl + | +
+ serviceUrl:
+ |
+
+ Type : string
+
+ |
+
+ + type + | +
+ type:
+ |
+
+ Type : string
+
+ |
+
declare namespace ErrorModel {
+ /**
+ * Error object model for consistency
+ */
+ export interface RootObject {
+ statusCode: number;
+ error: string;
+ message: string;
+ attributes?: any;
+ }
+ /**
+ * Error message object model for consistency
+ */
+ export interface ErrorMessageObject {
+ id: number;
+ error: string;
+ type: string;
+ serviceUrl: string;
+ }
+}
+
+ +
+ src/app/core/model/authentication.model.ts
+
+
+
Login payload model to be sent to server
+ + + + +
+ Properties+ |
+
+ + | +
+ + password + | +
+ password:
+ |
+
+ Type : string
+
+ |
+
+ + username + | +
+ username:
+ |
+
+ Type : string
+
+ |
+
declare namespace Authentication {
+ /**
+ * Login payload model to be sent to server
+ */
+ export interface LoginPayload {
+ username: string;
+ password: string;
+ }
+/**
+ * Signup payload model to be sent to server
+ */
+ export interface SignupPayload {
+ firstName: string;
+ lastName: string;
+ email: string;
+ phone: string;
+ password: string;
+ cPassword: string;
+ }
+ /**
+ * User model to be stored in credentials
+ */
+ export interface User {
+ _id: string;
+ role: string;
+ firstName: string;
+ lastName: string;
+ emailVerified: boolean;
+ emailHash: string;
+ passwordLastUpdated?: any;
+ lastLogin: Date;
+ phone?: any;
+ email: string;
+ createdAt: Date;
+ updatedAt: Date;
+ organisation?: any;
+ institution?: any;
+ }
+ /**
+ * Credential model to be stored in local storage
+ */
+ export interface Credentials {
+ token: string;
+ user: User;
+ }
+}
+
+ +
+ src/app/core/model/error.model.ts
+
+
+
Error object model for consistency
+ + + + +
+ Properties+ |
+
+
|
+
+ + attributes + | +
+ attributes:
+ |
+
+ Type : any
+
+ |
+
+ Optional + | +
+ + error + | +
+ error:
+ |
+
+ Type : string
+
+ |
+
+ + message + | +
+ message:
+ |
+
+ Type : string
+
+ |
+
+ + statusCode + | +
+ statusCode:
+ |
+
+ Type : number
+
+ |
+
declare namespace ErrorModel {
+ /**
+ * Error object model for consistency
+ */
+ export interface RootObject {
+ statusCode: number;
+ error: string;
+ message: string;
+ attributes?: any;
+ }
+ /**
+ * Error message object model for consistency
+ */
+ export interface ErrorMessageObject {
+ id: number;
+ error: string;
+ type: string;
+ serviceUrl: string;
+ }
+}
+
+ +
+ src/app/core/model/authentication.model.ts
+
+
+
Signup payload model to be sent to server
+ + + + +
+ Properties+ |
+
+ + | +
+ + cPassword + | +
+ cPassword:
+ |
+
+ Type : string
+
+ |
+
+ + email + | +
+ email:
+ |
+
+ Type : string
+
+ |
+
+ + firstName + | +
+ firstName:
+ |
+
+ Type : string
+
+ |
+
+ + lastName + | +
+ lastName:
+ |
+
+ Type : string
+
+ |
+
+ + password + | +
+ password:
+ |
+
+ Type : string
+
+ |
+
+ + phone + | +
+ phone:
+ |
+
+ Type : string
+
+ |
+
declare namespace Authentication {
+ /**
+ * Login payload model to be sent to server
+ */
+ export interface LoginPayload {
+ username: string;
+ password: string;
+ }
+/**
+ * Signup payload model to be sent to server
+ */
+ export interface SignupPayload {
+ firstName: string;
+ lastName: string;
+ email: string;
+ phone: string;
+ password: string;
+ cPassword: string;
+ }
+ /**
+ * User model to be stored in credentials
+ */
+ export interface User {
+ _id: string;
+ role: string;
+ firstName: string;
+ lastName: string;
+ emailVerified: boolean;
+ emailHash: string;
+ passwordLastUpdated?: any;
+ lastLogin: Date;
+ phone?: any;
+ email: string;
+ createdAt: Date;
+ updatedAt: Date;
+ organisation?: any;
+ institution?: any;
+ }
+ /**
+ * Credential model to be stored in local storage
+ */
+ export interface Credentials {
+ token: string;
+ user: User;
+ }
+}
+
+ +
+ src/app/core/model/authentication.model.ts
+
+
+
User model to be stored in credentials
+ + + + +
+ Properties+ |
+
+
|
+
+ + _id + | +
+ _id:
+ |
+
+ Type : string
+
+ |
+
+ + createdAt + | +
+ createdAt:
+ |
+
+ Type : Date
+
+ |
+
+ + email + | +
+ email:
+ |
+
+ Type : string
+
+ |
+
+ + emailHash + | +
+ emailHash:
+ |
+
+ Type : string
+
+ |
+
+ + emailVerified + | +
+ emailVerified:
+ |
+
+ Type : boolean
+
+ |
+
+ + firstName + | +
+ firstName:
+ |
+
+ Type : string
+
+ |
+
+ + institution + | +
+ institution:
+ |
+
+ Type : any
+
+ |
+
+ Optional + | +
+ + lastLogin + | +
+ lastLogin:
+ |
+
+ Type : Date
+
+ |
+
+ + lastName + | +
+ lastName:
+ |
+
+ Type : string
+
+ |
+
+ + organisation + | +
+ organisation:
+ |
+
+ Type : any
+
+ |
+
+ Optional + | +
+ + passwordLastUpdated + | +
+ passwordLastUpdated:
+ |
+
+ Type : any
+
+ |
+
+ Optional + | +
+ + phone + | +
+ phone:
+ |
+
+ Type : any
+
+ |
+
+ Optional + | +
+ + role + | +
+ role:
+ |
+
+ Type : string
+
+ |
+
+ + updatedAt + | +
+ updatedAt:
+ |
+
+ Type : Date
+
+ |
+
declare namespace Authentication {
+ /**
+ * Login payload model to be sent to server
+ */
+ export interface LoginPayload {
+ username: string;
+ password: string;
+ }
+/**
+ * Signup payload model to be sent to server
+ */
+ export interface SignupPayload {
+ firstName: string;
+ lastName: string;
+ email: string;
+ phone: string;
+ password: string;
+ cPassword: string;
+ }
+ /**
+ * User model to be stored in credentials
+ */
+ export interface User {
+ _id: string;
+ role: string;
+ firstName: string;
+ lastName: string;
+ emailVerified: boolean;
+ emailHash: string;
+ passwordLastUpdated?: any;
+ lastLogin: Date;
+ phone?: any;
+ email: string;
+ createdAt: Date;
+ updatedAt: Date;
+ organisation?: any;
+ institution?: any;
+ }
+ /**
+ * Credential model to be stored in local storage
+ */
+ export interface Credentials {
+ token: string;
+ user: User;
+ }
+}
+
+