Combinez vos apprentissages des outils Angular pour ajouter les fonctionnalités ci-dessous au projet de démonstration Housing.
Il est fréquent pour les applications de type SPA de communiquer avec un API HTTP pour accéder aux fonctionnalités serveurs et services de traitement des données. Vous devez utilisez les ressources Martha ci-dessous pour intégrer la gestion CRUDL des bâtiments(Locations).
Votre travail se situe au niveau du Front-end, vous n'avez pas à modifier l'implémentation SQL sur Martha.
import { Component } from '@angular/core';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatIconModule} from '@angular/material/icon';
import {MatInputModule} from '@angular/material/input';
import {MatButtonModule} from '@angular/material/button';
import {MatCheckboxModule} from '@angular/material/checkbox';
@Component({
selector: 'app-location-form',
imports: [
MatFormFieldModule, MatInputModule, MatIconModule, MatButtonModule, MatCheckboxModule
],
template: `
<h1>Location</h1>
<form>
<div id="layout">
<div id="inputs">
<mat-form-field appearance="outline">
<mat-label>Name</mat-label>
<input matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>City</mat-label>
<input matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>State</mat-label>
<input matInput>
</mat-form-field>
<div class="small-inputs">
<mat-form-field appearance="outline">
<mat-label>Units</mat-label>
<input matInput type="number">
</mat-form-field>
<mat-checkbox>Wifi</mat-checkbox>
<mat-checkbox>Laundry</mat-checkbox>
</div>
<div class="buttons-all">
<button mat-button color="danger">Delete</button>
<div class="buttons-right">
<button mat-button>Cancel</button>
<button mat-flat-button>Save</button>
</div>
</div>
</div>
<div id="photo">
<img src="https://angular.dev/assets/images/tutorials/common/i-do-nothing-but-love-lAyXdl1-Wmc-unsplash.jpg">
<mat-form-field appearance="outline">
<mat-label>Image URL</mat-label>
<input matInput type="text">
</mat-form-field>
</div>
</div>
</form>
`,
styles: `
#layout {
display: flex;
gap: 32px;
justify-content: space-between;
}
#inputs {
flex-grow: 1;
display: flex;
flex-direction: column;
}
#photo {
display: flex;
flex-direction: column;
gap: 16px;
height: 600px;
width: 50%;
}
#photo img {
object-fit: cover;
border-radius: 4px;
width: 100%;
height: 100%;
}
.small-inputs {
display: flex;
align-items: baseline;
justify-content: space-between;
}
.mdc-label {
color: black;
}
.buttons-all {
display: flex;
justify-content: space-between;
}
.buttons-right {
display: flex;
justify-content: center;
gap: 32px;
}
@media (max-width: 890px) {
#layout {
flex-direction: column-reverse;
gap: 0px;
}
#photo {
width: 100%;
height: 300px;
}
}
`
})
export class LocationFormPage {
}
import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';
import {MatTableModule} from '@angular/material/table';
import {MatIconModule} from '@angular/material/icon';
import {MatButtonModule} from '@angular/material/button';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatInputModule} from '@angular/material/input';
@Component({
selector: 'app-locations',
imports: [
RouterModule,
MatIconModule, MatButtonModule, MatFormFieldModule, MatInputModule, MatTableModule,
],
template: `
<div id="header">
<h1>Locations</h1>
<a mat-flat-button routerLink="/locations/new">
<mat-icon>add</mat-icon>
</a>
</div>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let element"> {{element.name}} </td>
</ng-container>
<ng-container matColumnDef="city">
<th mat-header-cell *matHeaderCellDef> City </th>
<td mat-cell *matCellDef="let element"> {{element.city}} </td>
</ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef> State </th>
<td mat-cell *matCellDef="let element"> {{element.state}} </td>
</ng-container>
<ng-container matColumnDef="availableUnits">
<th mat-header-cell *matHeaderCellDef> Units </th>
<td mat-cell *matCellDef="let element"> {{element.availableUnits}} </td>
</ng-container>
<ng-container matColumnDef="wifi">
<th mat-header-cell *matHeaderCellDef>Wifi</th>
<td mat-cell *matCellDef="let element">
<mat-icon class="availability" [attr.available]="element.wifi">{{ availabilityIcon(element.wifi) }}</mat-icon>
</td>
</ng-container>
<ng-container matColumnDef="laundry">
<th mat-header-cell *matHeaderCellDef> Laundry </th>
<td mat-cell *matCellDef="let element">
<mat-icon class="availability" [attr.available]="element.laundry">{{ availabilityIcon(element.laundry) }}</mat-icon>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let element">
<button class="edit" mat-icon-button>
<mat-icon>edit</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
`,
styles: `
#header {
display: flex;
flex-direction: row;
align-items: baseline;
gap: 16px;
}
#header mat-icon {
margin: 0;
}
.mat-mdc-row {
background-color: white;
color: var(--mat-on-sys-secondary);
}
.mat-mdc-header-row {
color: var(--mat-on-sys-secondary);
background-color: var(--mat-sys-secondary);
}
.mat-mdc-row:hover .mat-mdc-cell {
background-color: color-mix(in lab, var(--mat-sys-secondary) 30%, transparent 100%);
}
.edit {
--color: rgb(255, 165, 0);
color: var(--color);
background-color: color-mix(in lab, var(--mat-sys-secondary) 30%, transparent 100%);
/* https://www.fusonic.net/en/blog/angular-material-customization */
--mat-icon-button-ripple-color: color-mix(in lab, var(--color) 10%, transparent 100%);
--mat-icon-button-hover-state-layer-opacity: .8;
--mat-icon-button-state-layer-color: color-mix(in lab, var(--mat-sys-secondary) 100%, transparent 100%);
}
.mat-column-actions, .mat-column-laundry, .mat-column-wifi, mat-column-availableUnits, mat-column-state {
width: 0px;
}
.mat-column-availableUnits, .mat-column-laundry, .mat-column-wifi, .mat-column-state {
text-align: center
}
.availability[available=true] {
color: green;
}
.availability[available=false] {
color: red;
}
`
})
export class LocationsPage {
displayedColumns: string[] = ['name', 'city', 'state', 'availableUnits', 'wifi', 'laundry', 'actions'];
readonly baseUrl = 'https://angular.dev/assets/images/tutorials/common';
dataSource = [
{
id: 0,
name: 'Acme Fresh Start Housing',
city: 'Chicago',
state: 'IL',
photo: `${this.baseUrl}/bernard-hermant-CLKGGwIBTaY-unsplash.jpg`,
availableUnits: 4,
wifi: true,
laundry: true,
},
{
id: 1,
name: 'A113 Transitional Housing',
city: 'Santa Monica',
state: 'CA',
photo: `${this.baseUrl}/brandon-griggs-wR11KBaB86U-unsplash.jpg`,
availableUnits: 0,
wifi: false,
laundry: true,
},
{
id: 2,
name: 'Warm Beds Housing Support',
city: 'Juneau',
state: 'AK',
photo: `${this.baseUrl}/i-do-nothing-but-love-lAyXdl1-Wmc-unsplash.jpg`,
availableUnits: 1,
wifi: false,
laundry: false,
},
]
availabilityIcon(available: boolean | undefined): string | undefined {
if( available != undefined ) {
return available ? 'check' : 'close'
}
return undefined
}
}
create or replace table locations (
id int unsigned auto_increment key,
name varchar(250) not null,
city varchar(250) not null,
state char(2) not null,
photo varchar(500) not null,
available_units int unsigned,
wifi boolean,
laundry boolean,
user_email varchar(50) not null,
foreign key (user_email) references users(email)
);
insert into locations (name, city, state, photo, available_units, wifi, laundry, user_email)
values
(
'Acme Fresh Start Housing',
'Chicago',
'IL',
'https://angular.dev/assets/images/tutorials/common/bernard-hermant-CLKGGwIBTaY-unsplash.jpg',
4,
true,
true,
'a@a'
),
(
'A113 Transitional Housing',
'Santa Monica',
'CA',
'https://angular.dev/assets/images/tutorials/common/brandon-griggs-wR11KBaB86U-unsplash.jpg',
0,
false,
true,
'a@a'
),
(
'Warm Beds Housing Support',
'Juneau',
'AK',
'https://angular.dev/assets/images/tutorials/common/i-do-nothing-but-love-lAyXdl1-Wmc-unsplash.jpg',
1,
false,
false,
'a@a'
),
(
'Homesteady Housing',
'Chicago',
'IL',
'https://angular.dev/assets/images/tutorials/common/ian-macdonald-W8z6aiwfi1E-unsplash.jpg',
1,
true,
false,
'a@a'
),
(
'Happy Homes Group',
'Gary',
'IN',
'https://angular.dev/assets/images/tutorials/common/krzysztof-hepner-978RAXoXnH4-unsplash.jpg',
1,
true,
false,
'a@a'
),
(
'Hopeful Apartment Group',
'Oakland',
'CA',
'https://angular.dev/assets/images/tutorials/common/r-architecture-JvQ0Q5IkeMM-unsplash.jpg',
2,
true,
true,
'b@b'
),
(
'Seriously Safe Towns',
'Oakland',
'CA',
'https://angular.dev/assets/images/tutorials/common/phil-hearing-IYfp2Ixe9nM-unsplash.jpg',
5,
true,
true,
'b@b'
),
(
'Hopeful Housing Solutions',
'Oakland',
'CA',
'https://angular.dev/assets/images/tutorials/common/r-architecture-GGupkreKwxA-unsplash.jpg',
2,
true,
true,
'b@b'
),
(
'Seriously Safe Towns',
'Oakland',
'CA',
'https://angular.dev/assets/images/tutorials/common/saru-robert-9rP3mxf8qWI-unsplash.jpg',
10,
false,
false,
'c@c'
),
(
'Capital Safe Towns',
'Portland',
'OR',
'https://angular.dev/assets/images/tutorials/common/webaliser-_TPTXZd9mOo-unsplash.jpg',
6,
true,
true,
'c@c'
);
[
{
"name": "users-login",
"string": "select email from users where email = '?email' && passwd = sha2('?password', 256);",
"type": 0
},
{
"name": "users-signup",
"string": "insert into users(email, passwd) values ('?email', sha2('?password', 256));",
"type": 1
},
{
"name": "select-locations",
"string": "select * from locations where coalesce(?email, user_email) = user_email;",
"type": 0
},
{
"name": "select-location",
"string": "select * from locations where id = ?id && coalesce(?email, user_email) = user_email;",
"type": 0
},
{
"name": "delete-location",
"string": "delete from locations \r\nwhere id = ?id && user_email = \"?email\"\r\nreturning id;",
"type": 0
},
{
"name": "DO_NOT_USE_TEST_RESET",
"string": "create or replace table locations (\r\n id int unsigned auto_increment key,\r\n name varchar(250) not null,\r\n city varchar(250) not null,\r\n state char(2) not null,\r\n photo varchar(500) not null,\r\n available_units int unsigned,\r\n wifi boolean,\r\n laundry boolean,\r\n\r\n user_email varchar(50) not null,\r\n\r\n foreign key (user_email) references users(email)\r\n);\r\n\r\ninsert into locations (name, city, state, photo, available_units, wifi, laundry, user_email)\r\nvalues\r\n (\r\n 'Acme Fresh Start Housing',\r\n 'Chicago',\r\n 'IL',\r\n 'https://angular.dev/assets/images/tutorials/common/bernard-hermant-CLKGGwIBTaY-unsplash.jpg',\r\n 4,\r\n true,\r\n true,\r\n 'a@a'\r\n ),\r\n (\r\n 'A113 Transitional Housing',\r\n 'Santa Monica',\r\n 'CA',\r\n 'https://angular.dev/assets/images/tutorials/common/brandon-griggs-wR11KBaB86U-unsplash.jpg',\r\n 0,\r\n false,\r\n true,\r\n 'a@a'\r\n ),\r\n (\r\n 'Warm Beds Housing Support',\r\n 'Juneau',\r\n 'AK',\r\n 'https://angular.dev/assets/images/tutorials/common/i-do-nothing-but-love-lAyXdl1-Wmc-unsplash.jpg',\r\n 1,\r\n false,\r\n false,\r\n 'a@a'\r\n ),\r\n (\r\n 'Homesteady Housing',\r\n 'Chicago',\r\n 'IL',\r\n 'https://angular.dev/assets/images/tutorials/common/ian-macdonald-W8z6aiwfi1E-unsplash.jpg',\r\n 1,\r\n true,\r\n false,\r\n 'a@a'\r\n ),\r\n (\r\n 'Happy Homes Group',\r\n 'Gary',\r\n 'IN',\r\n 'https://angular.dev/assets/images/tutorials/common/krzysztof-hepner-978RAXoXnH4-unsplash.jpg',\r\n 1,\r\n true,\r\n false,\r\n 'a@a'\r\n ),\r\n (\r\n 'Hopeful Apartment Group',\r\n 'Oakland',\r\n 'CA',\r\n 'https://angular.dev/assets/images/tutorials/common/r-architecture-JvQ0Q5IkeMM-unsplash.jpg',\r\n 2,\r\n true,\r\n true,\r\n 'b@b'\r\n ),\r\n (\r\n 'Seriously Safe Towns',\r\n 'Oakland',\r\n 'CA',\r\n 'https://angular.dev/assets/images/tutorials/common/phil-hearing-IYfp2Ixe9nM-unsplash.jpg',\r\n 5,\r\n true,\r\n true,\r\n 'b@b'\r\n ),\r\n (\r\n 'Hopeful Housing Solutions',\r\n 'Oakland',\r\n 'CA',\r\n 'https://angular.dev/assets/images/tutorials/common/r-architecture-GGupkreKwxA-unsplash.jpg',\r\n 2,\r\n true,\r\n true,\r\n 'b@b'\r\n ),\r\n (\r\n 'Seriously Safe Towns',\r\n 'Oakland',\r\n 'CA',\r\n 'https://angular.dev/assets/images/tutorials/common/saru-robert-9rP3mxf8qWI-unsplash.jpg',\r\n 10,\r\n false,\r\n false,\r\n 'c@c'\r\n ),\r\n (\r\n 'Capital Safe Towns',\r\n 'Portland',\r\n 'OR',\r\n 'https://angular.dev/assets/images/tutorials/common/webaliser-_TPTXZd9mOo-unsplash.jpg',\r\n 6,\r\n true,\r\n true,\r\n 'c@c'\r\n );",
"type": 1
},
{
"name": "insert-location",
"string": "insert into locations (name, city, state, photo, available_units, wifi, laundry, user_email) \r\nvalues (\"?name\", \"?city\", \"?state\", \"?photo\", ?units, ?wifi, ?laundry, \"?email\")\r\nreturning id;",
"type": 0
},
{
"name": "update-location",
"string": "update locations set \r\n\tname = \"?name\",\r\n\tcity = \"?city\",\r\n\tstate = \"?state\",\r\n\tphoto = \"?photo\",\r\n\tavailable_units = ?units,\r\n\twifi = ?wifi,\r\n\tlaundry = ?laundry\r\nwhere id = ?id && user_email = \"?email\";",
"type": 0
}
]
# USAGE
# MARTHA_USERNAME='...' MARTHA_PASSWORD='...' ruby locations_martha_tests.rb
# Vim remap
# :nnoremap ;; :w <bar> :terminal ruby %<cr>
# set fallback variables at line 23
ENV["APP_ENV"] = "test"
require "bundler/inline"
require "minitest/autorun"
require "rack/test"
require "base64"
gemfile do
source "https://rubygems.org"
gem "faraday"
end
require "faraday"
USERNAME = ENV["MARTHA_USERNAME"] || "..."
PASSWORD = ENV["MARTHA_PASSWORD"] || "..."
class Test < Minitest::Test
def setup
options = { headers: { "auth" => Base64.encode64("#{USERNAME}:#{PASSWORD}") } }
@martha = Faraday.new("http://martha.jh.shawinigan.info", options) do |config|
config.request :json
config.response :json, **{ parser_options: { symbolize_names: true } }
end
q "DO_NOT_USE_TEST_RESET"
end
def q(query, params = nil)
@martha.post("/queries/#{query}/execute", params).body
end
##
### LIST
##
def test_select_all_public
r = q "select-locations", { email: nil }
assert r[:data].length == 10
end
def test_select_all_owner_a
r = q "select-locations", { email: "'a@a'" } # !!! Nested string, ' inside "
assert r[:data].length == 5
end
def test_select_all_owner_b
r = q "select-locations", { email: "'b@b'" }
assert r[:data].length == 3
end
def test_select_all_owner_invalid
r = q "select-locations", { email: "'z@z'" }
assert r[:data].length == 0
end
##
### READ
##
def test_select_one_public
r = q "select-location", { id: 1, email: nil }
assert r[:data].length == 1
end
def test_select_one_a
r = q "select-location", { id: 2, email: "'a@a'" }
assert r[:data].length == 1
end
def test_select_one_not_a
r = q "select-location", { id: 10, email: "'a@a'" }
assert r[:data].length == 0
end
def test_select_one_public_invalid_id
r = q "select-location", { id: 999, email: nil }
assert r[:data].length == 0
end
##
### CREATE
##
def test_insert_a
r = q "insert-location", { name: "new a", city: "city a", state: "SA", photo: "https://cdn-icons-png.flaticon.com/128/619/619153.png", units: 2, wifi: 1, laundry: 0, email: "a@a" }
assert r[:data].length == 1
r = q "select-locations", { email: "'a@a'" }
assert r[:data].length == 6
end
##
### UPDATE
##
def test_update_a
r = q "update-location", { id: 2, name: "new a2", city: "city a2", state: "A2", photo: "https://cdn-icons-png.flaticon.com/128/619/619153.png", units: 4, wifi: 0, laundry: 0, email: "a@a" }
assert r[:success]
r = q "select-location", { id: 2, email: nil }
assert r[:data].first[:name] == "new a2"
end
##
### DELETE
##
def test_delete_1_a
r = q "delete-location", { id: 1, email: "a@a" }
assert r[:data].length == 1
end
def test_delete_10_not_a
r = q "delete-location", { id: 10, email: "a@a" }
assert r[:data].length == 0
end
def test_delete_1_invalid_email
r = q "delete-location", { id: 1, email: "z@z" }
assert r[:data].length == 0
end
end
Solidifiez les mécanismes de navigation en ajoutant
Exploitez la librairie ngx-translate pour implémenter la traduction en 2 langues(FR et EN) du formulaire Location.
Mettre en ligne la version production de votre application sur votre micro-serveur.
Vous devez présenter une démonstration fonctionnelle de votre implémentation avant vendredi 31 janvier @ 15h
Nom:
Appréciation générale | |
UI | 0 -0.5 -1 -2 |
UX | 0 -0.5 -1 -2 |
Code | 0 -0.5 -1 -2 |
Séparations judicieuses des responsabilités | 0 -0.5 -1 -2 |
HTTP | |
Intégration UI/UX, Navigation, Connecté vs Public, Liste, Formulaire | 2 1.5 1 0 |
C (R)U D L Locations | 3 2.5 2 1 0 |
Validations, restrictions | 2 1.5 1 0 |
Routes | |
Wildcard | 1 0.5 0 |
Guards | 2 1.5 1 0 |
Traduction | |
Intégration ngx-translate exhaustive, Formulaire | 2 1.5 1 0 |
Persistente | 1 0.5 0 |
Déploiement | |
Production en ligne | 1 0.5 0 |