diff --git a/src/app/app.component.html b/src/app/app.component.html index f0cfafdb3121a5d13ae3b55a8bc09f073ffa9c69..f752e2e749bef88d778a4a25316c122aa1536965 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,7 +1,9 @@ <div class="grid"> <header class="main-header"> - <div class="js-navigation hamburger" aria-haspopup="true" aria-owns="main-nav" aria-expanded="false"> + <div class="hamburger" + (click)="sidebarOpened = !sidebarOpened" + [ngClass]="{'expanded': sidebarOpened}"> </div> <h4>Plateforme Data Admin</h4> @@ -10,10 +12,10 @@ </div> </header> - <nav class="main-nav js-nav" id="main-nav" aria-hidden="true"> - <app-menu></app-menu> - </nav> - <div class="main-content wide"> + <nav class="main-nav" [ngClass]="{'is-active': sidebarOpened}"> + <app-menu [expanded]="sidebarOpened"></app-menu> + </nav> + <div class="main-content" [ngClass]="{'wide': !sidebarOpened}"> <router-outlet></router-outlet> </div> diff --git a/src/app/app.component.scss b/src/app/app.component.scss index 763ab3982c0c318543c1fc2e41dd534eaf225f2e..e88db96820f572b128d6900e0b8290a631430028 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -1,7 +1,7 @@ .grid{ display: grid; - grid-template-columns:50px auto; + grid-template-columns: 50px auto; grid-template-rows: 60px minmax(calc(100vh - 60px), auto); overflow: hidden; } @@ -16,6 +16,11 @@ background-color: white;; border-bottom: 1px solid lightgray; + + h4 { + margin: 0 20px; + } + .logo { margin-left: auto; padding-right: 20px; @@ -36,23 +41,17 @@ } -.main-header h4{ - margin: 0 20px; -} - - .main-nav{ grid-row: 2; grid-column: 1 / span 1; width: 50px; z-index: 200; - transition: all .2s linear; - will-change: transform; + transition: all .2s linear; background-color: #333745; -} -.main-nav.is-active{ - width: 200px; + &.is-active { + width: 200px; + } } .main-content{ @@ -60,17 +59,10 @@ grid-column: 2 / -1; margin-left: 150px; background-color: #f2f2f2; -} - -.main-content.wide { - margin-left: 0; -} - -.main-content { transition: all 200ms cubic-bezier(0.7, 0, 0.3, 1); + + &.wide { + margin-left: 0; + } } - - - /* Stuff not needed for the demo */ - *{box-sizing: border-box;} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 36739e6d2b3092441407d0963e75d0e580fb477c..787f0501c925a40ed96a104d29ac382c017426d8 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,6 +1,4 @@ import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; -import { Title } from '@angular/platform-browser'; import 'rxjs/add/operator/filter'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/mergeMap'; @@ -16,59 +14,13 @@ export class AppComponent implements OnInit { sidebarOpened: boolean; constructor( - private activatedRoute: ActivatedRoute, - private router: Router, - private titleService: Title, ) { } ngOnInit(): void { - const navTrigger = document.querySelector('.js-navigation'); - const navCont = document.querySelector('.js-nav'); - const mainContent = document.querySelector('.main-content'); - const menu = document.querySelector('.menu'); this.sidebarOpened = false; - - navTrigger.addEventListener('click', function () { - navCont.classList.toggle('is-active'); - - if (navCont.classList.contains('is-active')) { - navCont.setAttribute('aria-hidden', 'false'); - menu.classList.add('expanded'); - navTrigger.setAttribute('aria-expanded', 'true'); - navTrigger.classList.add('expanded'); - mainContent.classList.remove('wide'); - this.sidebarOpened = true; - } else { - navCont.setAttribute('aria-hidden', 'true'); - navTrigger.setAttribute('aria-expanded', 'false'); - menu.classList.remove('expanded'); - mainContent.classList.add('wide'); - navTrigger.classList.remove('expanded'); - this.sidebarOpened = false; - - } - }); - - if (window.matchMedia('(min-width: 1600px)').matches) { - navCont.setAttribute('aria-hidden', 'false'); - } - // this.initResponsive(); - // this.router.events - // .filter(event => event instanceof NavigationEnd) - // .map(() => this.activatedRoute) - // .map((route) => { - // let r = route; - // while (r.firstChild) { r = r.firstChild; } - // return r; - // }) - // .filter(route => route.outlet === 'primary') - // .mergeMap(route => route.data) - // .subscribe((event) => { - // this.title = event['title'][window.navigator.language]; - // this.titleService.setTitle(this.title); - // }); } + } diff --git a/src/app/app.routing.module.ts b/src/app/app.routing.module.ts index 108f2320a642822357e9c1d20a86bab9c7dfa2fc..597ca7389b040a6bca7f9252938390411486b4c8 100644 --- a/src/app/app.routing.module.ts +++ b/src/app/app.routing.module.ts @@ -11,35 +11,35 @@ const appRoutes: Routes = [ path: '', component: WelcomeComponent, data: { - title: { en: 'Welcome', fr: 'Bienvenue' }, + title: 'Bienvenue', }, }, { path: 'organizations', component: OrganizationsComponent, data: { - title: { en: 'Organizations', fr: 'Organisations' }, + title: 'Organisations', + }, + }, + { + path: 'organizations/new', + component: OrganizationFormComponent, + data: { + title: 'Nouvelle organisation', }, }, - // { - // path: 'organizations/new', - // component: OrganizationFormComponent, - // data: { - // title: { en: 'New order', fr: 'Nouvelle organisation' }, - // }, - // }, { path: 'organizations/:id/edit', component: OrganizationFormComponent, data: { - title: { en: 'Edit order', fr: 'Modifier l\'organisation' }, + title: 'Modifier l\'organisation', }, }, { path: 'organizations/:id', component: OrganizationDetailComponent, data: { - title: { en: 'Organizations', fr: 'Organisations' }, + title: 'Organisations', }, }, ]; diff --git a/src/app/components/menu/menu.component.html b/src/app/components/menu/menu.component.html index 113a6e1665f61f9689d6dd13178a20757af32535..890e492f1af8beed9b015486cf7f397d8edc48ff 100644 --- a/src/app/components/menu/menu.component.html +++ b/src/app/components/menu/menu.component.html @@ -1,5 +1,5 @@ -<aside class="menu"> +<aside class="menu" [ngClass]="{'expanded': expanded}"> <ul class="menu-list"> <li><a [routerLink]="['/']" routerLinkActive="active-link" [routerLinkActiveOptions]="{ exact: true }"> <span class="icon"> diff --git a/src/app/components/menu/menu.component.ts b/src/app/components/menu/menu.component.ts index 8fc0d08848edc9dc022e7f3c1063f00dc3cb71d8..66e6a52ad33786ea8f4d382374ee20d4e036ea5d 100644 --- a/src/app/components/menu/menu.component.ts +++ b/src/app/components/menu/menu.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, Input } from '@angular/core'; @Component({ selector: 'app-menu', @@ -9,6 +9,8 @@ import { Component } from '@angular/core'; }) export class MenuComponent { + @Input() expanded: boolean; + constructor( ) { } diff --git a/src/app/components/organizations/edit/organization-form.component.html b/src/app/components/organizations/edit/organization-form.component.html index bfa9e96acf3ccc8b9aa638c2af967a5108dc85f8..995e419619140c3a55ee28da71899255abb96c8c 100644 --- a/src/app/components/organizations/edit/organization-form.component.html +++ b/src/app/components/organizations/edit/organization-form.component.html @@ -9,17 +9,21 @@ </a> </div> - <h1>Modifier l'organisation</h1> + <h1>{{ title }}</h1> <form [formGroup]="form" (ngSubmit)="onSubmit()" class="columns is-centered is-marginless"> <div class="column is-7"> <input type="hidden" formControlName="id" value="{{organization.id}}"> - <div class="field"> - <label class="label" for="name">Nom</label> + <label class="label required" for="name">Nom</label> <div class="control"> - <input class="input" type="text" [value]="organization.name" formControlName="name" id="name"> + <input class="input" type="text" [value]="organization.name" formControlName="name" id="name" required> + </div> + <div *ngIf="name.invalid && (name.dirty || name.touched)" class="alert alert-danger"> + <p *ngIf="name.errors['required']" class="help is-danger"> + Le nom de l'organisation est obligatoire. + </p> </div> </div> @@ -31,11 +35,16 @@ </div> <div class="field"> - <label class="label" for="description">Description</label> + <label class="label required" for="description">Description</label> <div class="control"> - <textarea class="textarea" formControlName="description" id="description"> + <textarea class="textarea" formControlName="description" id="description" required> {{ organization.description }} - </textarea> + </textarea> + </div> + <div *ngIf="description.invalid && (description.dirty || description.touched)" class="alert alert-danger"> + <p *ngIf="description.errors['required']" class="help is-danger"> + La description de l'organisation est obligatoire. + </p> </div> </div> @@ -52,10 +61,20 @@ </div> <div formArrayName="links"> - <div *ngFor="let link of formLinks.controls; let i = index;" [formGroupName]="i" class="columns"> + <div *ngFor="let link of formLinks.controls; let i = index;" [formGroupName]="i" + class="columns is-multiline field"> <div class="column is-11"> - <input class="input" type="text" formControlName="url"> + <div class="control"> + <input class="input" type="text" formControlName="url" required> + </div> + <div *ngIf="link['controls'].url.invalid && (link['controls'].url.dirty || link['controls'].url.touched)" + class="alert alert-danger"> + <p *ngIf="link.hasError('required', 'url')" class="help is-danger"> + L'URL du lien est obligatoire. + </p> + </div> </div> + <div class="column is-1"> <span class="icon" (click)="removeLink(i)" title="Supprimer le lien"> <i class="fas fa-trash"></i> @@ -67,7 +86,7 @@ <div class="field"> <div class="control"> - <button class="button validate">Valider</button> + <button class="button validate" [disabled]="formInvalid == true">Valider</button> </div> </div> diff --git a/src/app/components/organizations/edit/organization-form.component.scss b/src/app/components/organizations/edit/organization-form.component.scss index 8423b64dfb2dc01a0e37d4e29efb0105c4fb291a..8522a9ab88fb48e0ae39d622940fa851978291f8 100644 --- a/src/app/components/organizations/edit/organization-form.component.scss +++ b/src/app/components/organizations/edit/organization-form.component.scss @@ -34,4 +34,4 @@ h1 { color: #d5232a; } } -} \ No newline at end of file +} diff --git a/src/app/components/organizations/edit/organization-form.component.ts b/src/app/components/organizations/edit/organization-form.component.ts index efb91e6503ff82833fedb9a35ab39cee106e24b2..5ccb1db9212bf79e46970d4300c2a6843167f26a 100644 --- a/src/app/components/organizations/edit/organization-form.component.ts +++ b/src/app/components/organizations/edit/organization-form.component.ts @@ -3,6 +3,7 @@ import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { Organization } from 'src/app/models/organization.model'; import { OrganizationService } from 'src/app/services/organization.service'; import { FormBuilder, FormGroup, Validators, FormArray } from '@angular/forms'; +import { tap } from 'rxjs/operators'; @Component({ selector: 'app-organization-form', @@ -13,6 +14,7 @@ export class OrganizationFormComponent implements OnInit { organization: Organization; form: FormGroup; + title: string; constructor( private organizationService: OrganizationService, @@ -23,25 +25,27 @@ export class OrganizationFormComponent implements OnInit { } ngOnInit() { + this.title = this.route.snapshot.data.title; + this.initForm(); this.route.paramMap .filter((paramMap: ParamMap) => (paramMap.get('id') !== null)) .switchMap((paramMap: ParamMap) => this.organizationService.findById(paramMap.get('id'))) .subscribe((organization: Organization) => { + this.organization = organization; const arr = new FormArray([]); this.organization.links.forEach(y => { arr.push(this._fb.group({ url: y.url, - id: y.id, organizationId: y.organizationId, })); }); this.form = this._fb.group( { - id: [organization.id], + id: [this.organization.id], name: [organization.name, Validators.required], description: [organization.description, Validators.required], logo: [organization.logo], @@ -52,20 +56,34 @@ export class OrganizationFormComponent implements OnInit { } - iniItemLinkForm() { + initForm() { + this.organization = new Organization(); + const arr = new FormArray([]); + this.form = this._fb.group( + { + id: [this.organization.id], + name: [this.organization.name, Validators.required], + description: [this.organization.description, Validators.required], + logo: [this.organization.logo], + links: arr, + }); + } + + initItemLinkForm() { return this._fb.group({ - // list all your form controls here, which belongs to your form array name: '', - url: '', - organizationId: this.organization.id - }); + url: ['', Validators.required], + }); } addLink() { if (!this.form.controls.links) { this.organization.links = []; } - (this.form.controls.links as FormArray).push(this._fb.group({ id: null, name: '', url: '', organizationId: this.organization.id })); + (this.form.controls.links as FormArray).push(this._fb.group( + { id: null, name: '', + url: ['', Validators.required], + organizationId: this.organization.id })); } removeLink(index) { @@ -77,19 +95,32 @@ export class OrganizationFormComponent implements OnInit { } onSubmit() { - console.log(this.form.value); - this.organization = new Organization(this.form.value); - this.organizationService.replaceOrCreate(this.organization) - .subscribe( - (organizationCreated) => { - this.router.navigate(['/organizations', organizationCreated.id]) - .then(() => { - }); - }, - (err) => { - console.error(err); - alert(err.message); - }, - ); + if (! this.formInvalid) { + this.organization = new Organization(this.form.value); + this.organizationService.replaceOrCreate(this.organization) + .subscribe( + (organizationCreated) => { + this.router.navigate(['/organizations', organizationCreated.id]) + .then(() => { + }); + }, + (err) => { + alert(err.message); + }, + ); + } + } + + // Getters for each property + get name() { + return this.form.controls['name']; + } + + get description() { + return this.form.controls['description']; + } + + get formInvalid() { + return this.form.invalid; } } diff --git a/src/app/components/organizations/list/organizations.component.html b/src/app/components/organizations/list/organizations.component.html index 76bae61f68dc309cbc3efe850ef3f15b3f8f5c02..375795393a6d32c00a60f306939eab7f515ab420 100644 --- a/src/app/components/organizations/list/organizations.component.html +++ b/src/app/components/organizations/list/organizations.component.html @@ -6,6 +6,11 @@ <h2>{{ totalElement }} organisations trouvées</h2> </div> </div> + <div class="add-organization"> + <a class="button is-link" [routerLink]="['new']"> + Ajouter + </a> + </div> <div class="table"> <div class="header columns is-marginless"> <div class="column is-1 has-text-centered"> diff --git a/src/app/components/organizations/list/organizations.component.scss b/src/app/components/organizations/list/organizations.component.scss index a10df3d7a4eb1c31c34195c7c42ffdbcbc926852..e445bc77f63ae5d209c7a1729cc41a3cb19582ea 100644 --- a/src/app/components/organizations/list/organizations.component.scss +++ b/src/app/components/organizations/list/organizations.component.scss @@ -82,17 +82,6 @@ i { } -@media screen and (max-width: 640px) { - section { - padding: 10px 0; - } - - mat-cell { - font-size: 10px; - } - - a { - font-size: 10px; - padding-left: 0; - } +.add-organization { + margin-bottom: 20px; } diff --git a/src/app/models/organization.model.ts b/src/app/models/organization.model.ts index adedaea657ba7406902777f612500db0dbba0606..ae08a19529d9e4d9eea4250ebc6db54d0ab505f4 100644 --- a/src/app/models/organization.model.ts +++ b/src/app/models/organization.model.ts @@ -12,6 +12,10 @@ export class Organization { this.description = organization.description; this.links = organization.links; this.logo = organization.logo; + } else { + this.name = ''; + this.description = ''; + this.logo = ''; } } diff --git a/src/app/services/organization.service.ts b/src/app/services/organization.service.ts index dda8880382ca556383564c8ab154c4b10eb992fd..954ed26edfeec27a01fc19c5b8d8e1483ebbe1c2 100644 --- a/src/app/services/organization.service.ts +++ b/src/app/services/organization.service.ts @@ -55,7 +55,6 @@ export class OrganizationService { } replaceOrCreate(data): Observable<Organization> { - console.log('replaceOrCreate ', data); if (data.id) { return this._httpClient.put<IOrganization>(environment.organizations.url + data.id, data).pipe( map((response) => { diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 0e14e2096b86d81679c87958bc5acf7c0dacce49..0614367b00ba8caee00694ffa8dd2e02af97fe58 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -6,7 +6,7 @@ export const environment = { production: false, organizations: { - url: 'http://localhost:3000/organizations/', + url: 'http://localhost:3001/organizations/', }, }; diff --git a/src/index.html b/src/index.html index d5527fbbe8ac4d9581c12b21676de36be8eb4226..fffe33505be8307060536ab482fc2758dc8a3794 100644 --- a/src/index.html +++ b/src/index.html @@ -6,7 +6,6 @@ <title>GrandLyon</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> - <link rel="manifest" href="manifest.json"> <link rel="icon" type="image/x-icon" href="assets/img/favicon.ico"> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.2/css/all.css" integrity="sha384-/rXc/GQVaYpyDdyxK+ecHPVYJSN9bmVFBvjA/9eOB+pb3F2w2N6fc5qB9Ew5yIns" crossorigin="anonymous"> @@ -16,9 +15,6 @@ <app-root> <section class="AppLoader"> <img src="assets/img/logo.svg" alt="Grand Lyon, la métropole"> - <!-- <mat-progress-spinner - [color]="primary" - [mode]="indeterminate"> --> </mat-progress-spinner> </section> </app-root> diff --git a/src/styles.scss b/src/styles.scss index 86aff986814178e8c4b4e1fad29899f81f756fb1..b70b8572e5fdc6dd15e4da8edda5c78e7cdb7010 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -54,3 +54,19 @@ section { padding: 20px; } +.form { + .help { + font-size: 0.9rem; + margin-bottom: 0.5rem; + } +} + +.required::after { + content: " *"; + color: $red; +} + +input.ng-invalid:not(form).ng-dirty, input.ng-invalid:not(form).ng-touched, +textarea.ng-invalid:not(form).ng-dirty, textarea.ng-invalid:not(form).ng-touched,{ + border-left: 3px solid $red; /* red */ +} \ No newline at end of file