Merge branch 'master' into _77_address_household

merge master
This commit is contained in:
nobohan 2021-06-04 09:09:59 +02:00
commit d806a074a7
24 changed files with 489 additions and 301 deletions

View File

@ -73,7 +73,7 @@
"symfony/web-profiler-bundle": "^5.0", "symfony/web-profiler-bundle": "^5.0",
"symfony/var-dumper": "4.*", "symfony/var-dumper": "4.*",
"symfony/debug-bundle": "^5.1", "symfony/debug-bundle": "^5.1",
"symfony/phpunit-bridge": "^5.2", "symfony/phpunit-bridge": "^5.2",
"nelmio/alice": "^3.8" "nelmio/alice": "^3.8"
}, },
"scripts": { "scripts": {

View File

@ -1,63 +0,0 @@
<?php
namespace Chill\MainBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\Routing\Annotation\Route;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\AddressReference;
/**
* Class AddressController
*
* @package Chill\MainBundle\Controller
*/
class AddressController extends AbstractController
{
/**
* Get API Data for showing endpoint
*
* @Route(
* "/{_locale}/main/api/1.0/address/{address_id}/show.{_format}",
* name="chill_main_address_api_show"
* )
* @ParamConverter("address", options={"id": "address_id"})
*/
public function showAddress(Address $address, $_format): Response
{
// TODO check ACL ?
switch ($_format) {
case 'json':
return $this->json($address);
default:
throw new BadRequestException('Unsupported format');
}
}
/**
* Get API Data for showing endpoint
*
* @Route(
* "/{_locale}/main/api/1.0/address-reference/{address_reference_id}/show.{_format}",
* name="chill_main_address_reference_api_show"
* )
* @ParamConverter("addressReference", options={"id": "address_reference_id"})
*/
public function showAddressReference(AddressReference $addressReference, $_format): Response
{
// TODO check ACL ?
switch ($_format) {
case 'json':
return $this->json($addressReference);
default:
throw new BadRequestException('Unsupported format');
}
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Symfony\Component\HttpFoundation\Request;
/**
* Class AddressReferenceAPIController
*
* @package Chill\MainBundle\Controller
* @author Champs Libres
*/
class AddressReferenceAPIController extends ApiController
{
protected function customizeQuery(string $action, Request $request, $qb): void
{
if ($request->query->has('postal_code')) {
$qb->where('e.postcode = :postal_code')
->setParameter('postal_code', $request->query->get('postal_code'));
}
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Symfony\Component\HttpFoundation\Request;
/**
* Class PostalCodeAPIController
*
* @package Chill\MainBundle\Controller
* @author Champs Libres
*/
class PostalCodeAPIController extends ApiController
{
protected function customizeQuery(string $action, Request $request, $qb): void
{
if ($request->query->has('country')) {
$qb->where('e.country = :country')
->setParameter('country', $request->query->get('country'));
}
}
}

View File

@ -284,6 +284,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
] ]
], ],
[ [
'controller' => \Chill\MainBundle\Controller\AddressReferenceAPIController::class,
'class' => \Chill\MainBundle\Entity\AddressReference::class, 'class' => \Chill\MainBundle\Entity\AddressReference::class,
'name' => 'address_reference', 'name' => 'address_reference',
'base_path' => '/api/1.0/main/address-reference', 'base_path' => '/api/1.0/main/address-reference',
@ -304,6 +305,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
] ]
], ],
[ [
'controller' => \Chill\MainBundle\Controller\PostalCodeAPIController::class,
'class' => \Chill\MainBundle\Entity\PostalCode::class, 'class' => \Chill\MainBundle\Entity\PostalCode::class,
'name' => 'postal_code', 'name' => 'postal_code',
'base_path' => '/api/1.0/main/postal-code', 'base_path' => '/api/1.0/main/postal-code',

View File

@ -9,11 +9,11 @@ use \JsonSerializable;
* *
*/ */
class Point implements JsonSerializable { class Point implements JsonSerializable {
private float $lat; private ?float $lat = null;
private float $lon; private ?float $lon = null;
public static string $SRID = '4326'; public static string $SRID = '4326';
private function __construct(float $lon, float $lat) private function __construct(?float $lon, ?float $lat)
{ {
$this->lat = $lat; $this->lat = $lat;
$this->lon = $lon; $this->lon = $lon;

View File

@ -4,6 +4,7 @@ namespace Chill\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Serializer\Annotation\Groups;
use Chill\MainBundle\Doctrine\Model\Point; use Chill\MainBundle\Doctrine\Model\Point;
use Chill\ThirdPartyBundle\Entity\ThirdParty; use Chill\ThirdPartyBundle\Entity\ThirdParty;
@ -22,6 +23,7 @@ class Address
* @ORM\Id * @ORM\Id
* @ORM\Column(name="id", type="integer") * @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO") * @ORM\GeneratedValue(strategy="AUTO")
* @groups({"write"})
*/ */
private $id; private $id;
@ -29,6 +31,7 @@ class Address
* @var string * @var string
* *
* @ORM\Column(type="string", length=255) * @ORM\Column(type="string", length=255)
* @groups({"write"})
*/ */
private $street = ''; private $street = '';
@ -36,6 +39,7 @@ class Address
* @var string * @var string
* *
* @ORM\Column(type="string", length=255) * @ORM\Column(type="string", length=255)
* @groups({"write"})
*/ */
private $streetNumber = ''; private $streetNumber = '';
@ -43,6 +47,7 @@ class Address
* @var PostalCode * @var PostalCode
* *
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode") * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode")
* @groups({"write"})
*/ */
private $postcode; private $postcode;
@ -50,6 +55,7 @@ class Address
* @var string|null * @var string|null
* *
* @ORM\Column(type="string", length=16, nullable=true) * @ORM\Column(type="string", length=16, nullable=true)
* @groups({"write"})
*/ */
private $floor; private $floor;
@ -57,6 +63,7 @@ class Address
* @var string|null * @var string|null
* *
* @ORM\Column(type="string", length=16, nullable=true) * @ORM\Column(type="string", length=16, nullable=true)
* @groups({"write"})
*/ */
private $corridor; private $corridor;
@ -64,6 +71,7 @@ class Address
* @var string|null * @var string|null
* *
* @ORM\Column(type="string", length=16, nullable=true) * @ORM\Column(type="string", length=16, nullable=true)
* @groups({"write"})
*/ */
private $steps; private $steps;
@ -71,6 +79,7 @@ class Address
* @var string|null * @var string|null
* *
* @ORM\Column(type="string", length=255, nullable=true) * @ORM\Column(type="string", length=255, nullable=true)
* @groups({"write"})
*/ */
private $buildingName; private $buildingName;
@ -78,6 +87,7 @@ class Address
* @var string|null * @var string|null
* *
* @ORM\Column(type="string", length=16, nullable=true) * @ORM\Column(type="string", length=16, nullable=true)
* @groups({"write"})
*/ */
private $flat; private $flat;
@ -85,6 +95,7 @@ class Address
* @var string|null * @var string|null
* *
* @ORM\Column(type="string", length=255, nullable=true) * @ORM\Column(type="string", length=255, nullable=true)
* @groups({"write"})
*/ */
private $distribution; private $distribution;
@ -92,6 +103,7 @@ class Address
* @var string|null * @var string|null
* *
* @ORM\Column(type="string", length=255, nullable=true) * @ORM\Column(type="string", length=255, nullable=true)
* @groups({"write"})
*/ */
private $extra; private $extra;
@ -102,6 +114,7 @@ class Address
* @var \DateTime * @var \DateTime
* *
* @ORM\Column(type="date") * @ORM\Column(type="date")
* @groups({"write"})
*/ */
private $validFrom; private $validFrom;
@ -112,11 +125,13 @@ class Address
* @var \DateTime|null * @var \DateTime|null
* *
* @ORM\Column(type="date", nullable=true) * @ORM\Column(type="date", nullable=true)
* @groups({"write"})
*/ */
private $validTo; private $validTo;
/** /**
* True if the address is a "no address", aka homeless person, ... * True if the address is a "no address", aka homeless person, ...
* @groups({"write"})
* *
* @var bool * @var bool
*/ */
@ -128,6 +143,7 @@ class Address
* @var Point|null * @var Point|null
* *
* @ORM\Column(type="point", nullable=true) * @ORM\Column(type="point", nullable=true)
* @groups({"write"})
*/ */
private $point; private $point;
@ -137,6 +153,7 @@ class Address
* @var ThirdParty|null * @var ThirdParty|null
* *
* @ORM\ManyToOne(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty") * @ORM\ManyToOne(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty")
* @groups({"write"})
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL") * @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/ */
private $linkedToThirdParty; private $linkedToThirdParty;

View File

@ -4,6 +4,7 @@ namespace Chill\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Chill\MainBundle\Doctrine\Model\Point; use Chill\MainBundle\Doctrine\Model\Point;
use Symfony\Component\Serializer\Annotation\Groups;
/** /**
* @ORM\Entity() * @ORM\Entity()
@ -16,21 +17,25 @@ class AddressReference
* @ORM\Id * @ORM\Id
* @ORM\GeneratedValue * @ORM\GeneratedValue
* @ORM\Column(type="integer") * @ORM\Column(type="integer")
* @groups({"read"})
*/ */
private $id; private $id;
/** /**
* @ORM\Column(type="string", length=255) * @ORM\Column(type="string", length=255)
* @groups({"read"})
*/ */
private $refId; private $refId;
/** /**
* @ORM\Column(type="string", length=255, nullable=true) * @ORM\Column(type="string", length=255, nullable=true)
* @groups({"read"})
*/ */
private $street; private $street;
/** /**
* @ORM\Column(type="string", length=255, nullable=true) * @ORM\Column(type="string", length=255, nullable=true)
* @groups({"read"})
*/ */
private $streetNumber; private $streetNumber;
@ -38,16 +43,19 @@ class AddressReference
* @var PostalCode * @var PostalCode
* *
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode") * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode")
* @groups({"read"})
*/ */
private $postcode; private $postcode;
/** /**
* @ORM\Column(type="string", length=255, nullable=true) * @ORM\Column(type="string", length=255, nullable=true)
* @groups({"read"})
*/ */
private $municipalityCode; private $municipalityCode;
/** /**
* @ORM\Column(type="string", length=255, nullable=true) * @ORM\Column(type="string", length=255, nullable=true)
* @groups({"read"})
*/ */
private $source; private $source;
@ -57,6 +65,7 @@ class AddressReference
* @var Point * @var Point
* *
* @ORM\Column(type="point") * @ORM\Column(type="point")
* @groups({"read"})
*/ */
private $point; private $point;

View File

@ -3,6 +3,7 @@
namespace Chill\MainBundle\Entity; namespace Chill\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
/** /**
* Country * Country
@ -20,6 +21,7 @@ class Country
* @ORM\Id * @ORM\Id
* @ORM\Column(name="id", type="integer") * @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO") * @ORM\GeneratedValue(strategy="AUTO")
* @groups({"read"})
*/ */
private $id; private $id;
@ -27,13 +29,16 @@ class Country
* @var string * @var string
* *
* @ORM\Column(type="json_array") * @ORM\Column(type="json_array")
* @groups({"read"})
*
*/ */
private $name; private $name;
/** /**
* @var string * @var string
* *
* @ORM\Column(type="string", length=3) * @ORM\Column(type="string", length=3)
* @groups({"read"})
*/ */
private $countryCode; private $countryCode;
@ -41,7 +46,7 @@ class Country
/** /**
* Get id * Get id
* *
* @return integer * @return integer
*/ */
public function getId() public function getId()
{ {
@ -57,20 +62,20 @@ class Country
public function setName($name) public function setName($name)
{ {
$this->name = $name; $this->name = $name;
return $this; return $this;
} }
/** /**
* Get name * Get name
* *
* @return string * @return string
*/ */
public function getName() public function getName()
{ {
return $this->name; return $this->name;
} }
/** /**
* @return string * @return string
*/ */
@ -90,12 +95,12 @@ class Country
/** /**
* *
* @param string $countryCode * @param string $countryCode
*/ */
public function setCountryCode($countryCode) public function setCountryCode($countryCode)
{ {
$this->countryCode = $countryCode; $this->countryCode = $countryCode;
return $this; return $this;
} }
} }

View File

@ -3,6 +3,7 @@
namespace Chill\MainBundle\Entity; namespace Chill\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
/** /**
* PostalCode * PostalCode
@ -25,6 +26,7 @@ class PostalCode
* @ORM\Id * @ORM\Id
* @ORM\Column(name="id", type="integer") * @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO") * @ORM\GeneratedValue(strategy="AUTO")
* @groups({"read"})
*/ */
private $id; private $id;
@ -32,6 +34,7 @@ class PostalCode
* @var string * @var string
* *
* @ORM\Column(type="string", length=255, name="label") * @ORM\Column(type="string", length=255, name="label")
* @groups({"read"})
*/ */
private $name; private $name;
@ -39,6 +42,7 @@ class PostalCode
* @var string * @var string
* *
* @ORM\Column(type="string", length=100) * @ORM\Column(type="string", length=100)
* @groups({"read"})
*/ */
private $code; private $code;
@ -46,6 +50,7 @@ class PostalCode
* @var Country * @var Country
* *
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Country") * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Country")
* @groups({"read"})
*/ */
private $country; private $country;

View File

@ -50,7 +50,7 @@ div#header-accompanying_course-name {
background: none repeat scroll 0 0 #718596; background: none repeat scroll 0 0 #718596;
color: #FFF; color: #FFF;
h1 { h1 {
margin: 0.4em 0; margin: 0.4em 0;
} }
span { span {
a { a {
@ -71,7 +71,7 @@ div#header-accompanying_course-details {
/* /*
* FLEX RESPONSIVE TABLE/BLOCK PRESENTATION * FLEX RESPONSIVE TABLE/BLOCK PRESENTATION
*/ */
div.flex-bloc, div.flex-bloc,
div.flex-table { div.flex-table {
h2, h3, h4, dl, p { h2, h3, h4, dl, p {
margin: 0; margin: 0;
@ -85,38 +85,38 @@ div.flex-table {
* Bloc appearance * Bloc appearance
*/ */
div.flex-bloc { div.flex-bloc {
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
align-items: stretch; align-items: stretch;
align-content: stretch; align-content: stretch;
div.item-bloc { div.item-bloc {
flex-grow: 0; flex-shrink: 1; flex-basis: 50%; flex-grow: 0; flex-shrink: 1; flex-basis: 50%;
margin: 0; margin: 0;
border: 1px solid #000; border: 1px solid #000;
padding: 1em; padding: 1em;
border-top: 0; border-top: 0;
&:nth-child(1), &:nth-child(2) { &:nth-child(1), &:nth-child(2) {
border-top: 1px solid #000; border-top: 1px solid #000;
} }
border-left: 0; border-left: 0;
&:nth-child(odd) { &:nth-child(odd) {
border-left: 1px solid #000; border-left: 1px solid #000;
} }
//background-color: #e6e6e6; //background-color: #e6e6e6;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
div.item-row { div.item-row {
flex-grow: 1; flex-shrink: 1; flex-basis: auto; flex-grow: 1; flex-shrink: 1; flex-basis: auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
div.item-col { div.item-col {
&:first-child { &:first-child {
flex-grow: 0; flex-shrink: 0; flex-basis: auto; flex-grow: 0; flex-shrink: 0; flex-basis: auto;
@ -124,7 +124,7 @@ div.flex-bloc {
&:last-child { &:last-child {
flex-grow: 1; flex-shrink: 1; flex-basis: auto; flex-grow: 1; flex-shrink: 1; flex-basis: auto;
display: flex; display: flex;
.list-content { // ul, dl, or div .list-content { // ul, dl, or div
} }
ul.record_actions { ul.record_actions {
@ -134,7 +134,7 @@ div.flex-bloc {
li { li {
margin-right: 5px; margin-right: 5px;
} }
} }
} }
} }
} }
@ -147,7 +147,7 @@ div.flex-bloc {
margin: auto 0; margin: auto 0;
div.item-bloc { div.item-bloc {
border-left: 1px solid #000; border-left: 1px solid #000;
&:nth-child(2) { &:nth-child(2) {
border-top: 0; border-top: 0;
} }
} }
@ -156,13 +156,13 @@ div.flex-bloc {
/* /*
* Table appearance * Table appearance
*/ */
div.flex-table { div.flex-table {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: stretch;
align-content: stretch; align-content: stretch;
div.item-bloc { div.item-bloc {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -173,7 +173,7 @@ div.flex-table {
border-top: 1px solid #000; border-top: 1px solid #000;
} }
&:nth-child(even) { &:nth-child(even) {
background-color: #e6e6e6; background-color: #e6e6e6;
} }
div.item-row { div.item-row {
@ -185,7 +185,7 @@ div.flex-table {
padding-top: 0.5em; padding-top: 0.5em;
flex-direction: column; flex-direction: column;
} }
div.item-col { div.item-col {
&:first-child { &:first-child {
flex-grow: 0; flex-shrink: 0; flex-basis: 33%; flex-grow: 0; flex-shrink: 0; flex-basis: 33%;
@ -194,7 +194,7 @@ div.flex-table {
flex-grow: 1; flex-shrink: 1; flex-basis: auto; flex-grow: 1; flex-shrink: 1; flex-basis: auto;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.list-content { // ul, dl, or div .list-content { // ul, dl, or div
} }
ul.record_actions { ul.record_actions {
@ -204,7 +204,7 @@ div.flex-table {
li { li {
margin-right: 5px; margin-right: 5px;
} }
} }
} }
} }
@media only screen and (max-width: 900px) { @media only screen and (max-width: 900px) {
@ -217,9 +217,47 @@ div.flex-table {
} }
} }
} }
// neutralize // neutralize
div.chill_address div.chill_address_address p { text-indent: 0; } div.chill_address div.chill_address_address p { text-indent: 0; }
} }
} }
} }
/*
* Address form
*/
div.address_form {
display: flex;
flex-direction: column;
div.address_form__header {
}
div.address_form__select {
display: flex;
flex-direction: row;
justify-content: space-between;
div.address_form__select__body {
display: flex;
flex-direction: column;
flex-grow: 1;
}
div.address_form__select__map {
margin: 0px 20px;
div#address_map {
height:400px;
width:400px;
}
}
}
div.address_form__more {
}
}

View File

@ -1,5 +1,5 @@
<template> <template>
<div v-if="address.address"> <div v-if="address.address">
{{ address.address.street }}, {{ address.address.streetNumber }} {{ address.address.street }}, {{ address.address.streetNumber }}
</div> </div>
@ -9,7 +9,7 @@
<div v-if="address.country"> <div v-if="address.country">
{{ address.country.name }} {{ address.country.name }}
</div> </div>
<add-address <add-address
@addNewAddress="addNewAddress"> @addNewAddress="addNewAddress">
</add-address> </add-address>
@ -33,7 +33,26 @@ export default {
methods: { methods: {
addNewAddress({ address, modal }) { addNewAddress({ address, modal }) {
console.log('@@@ CLICK button addNewAdress', address); console.log('@@@ CLICK button addNewAdress', address);
this.$store.dispatch('addAddress', address.selected);
let newAddress = {
'isNoAddress': address.isNoAddress,
'street': address.selected.address.street,
'streetNumber': address.selected.address.streetNumber,
'postcode': {'id': address.selected.city.id },
'floor': address.floor,
'corridor': address.corridor,
'steps': address.steps,
'flat': address.flat,
'buildingName': address.buildingName,
'distribution': address.distribution,
'extra': address.extra
};
if (address.selected.address.point !== undefined){
newAddress = Object.assign(newAddress, {'point': address.selected.address.point.coordinates});
}
this.$store.dispatch('addAddress', newAddress);
modal.showModal = false; modal.showModal = false;
} }
} }

View File

@ -1,12 +1,13 @@
const addressMessages = { const addressMessages = {
fr: { fr: {
add_an_address: 'Ajouter une adresse', add_an_address_title: 'Ajouter une adresse',
select_an_address: 'Sélectionner une adresse', select_an_address_title: 'Sélectionner une adresse',
fill_an_address: 'Compléter l\'adresse', fill_an_address: 'Compléter l\'adresse',
select_country: 'Choisir le pays', select_country: 'Choisir le pays',
select_city: 'Choisir une localité', select_city: 'Choisir une localité',
select_address: 'Choisir une adresse', select_address: 'Choisir une adresse',
isNoAddress: 'L\'adresse n\'est pas celle d\'un domicile fixe ?', create_address: 'Appuyer sur "Entrée" pour créer une nouvelle adresse',
isNoAddress: 'Pas d\'adresse complète',
floor: 'Étage', floor: 'Étage',
corridor: 'Couloir', corridor: 'Couloir',
steps: 'Escalier', steps: 'Escalier',

View File

@ -1,8 +1,7 @@
import 'es6-promise/auto'; import 'es6-promise/auto';
import { createStore } from 'vuex'; import { createStore } from 'vuex';
// le fetch POST serait rangé dans la logique du composant qui appelle AddAddress import { postAddress } from '../../_api/AddAddress'
//import { postAddress } from '... api'
const debug = process.env.NODE_ENV !== 'production'; const debug = process.env.NODE_ENV !== 'production';
@ -10,11 +9,14 @@ const store = createStore({
strict: debug, strict: debug,
state: { state: {
address: {}, address: {},
errorMsg: {} errorMsg: []
}, },
getters: { getters: {
}, },
mutations: { mutations: {
catchError(state, error) {
state.errorMsg.push(error);
},
addAddress(state, address) { addAddress(state, address) {
console.log('@M addAddress address', address); console.log('@M addAddress address', address);
state.address = address; state.address = address;
@ -23,19 +25,19 @@ const store = createStore({
actions: { actions: {
addAddress({ commit }, payload) { addAddress({ commit }, payload) {
console.log('@A addAddress payload', payload); console.log('@A addAddress payload', payload);
commit('addAddress', payload); // à remplacer par //commit('addAddress', payload); // à remplacer par la suite
// fetch POST qui envoie l'adresse, et récupère la confirmation que c'est ok. //fetch POST qui envoie l'adresse, et récupère la confirmation que c'est ok.
// La confirmation est l'adresse elle-même. //La confirmation est l'adresse elle-même.
//
// postAddress(payload) postAddress(payload)
// .fetch(address => new Promise((resolve, reject) => { .then(address => new Promise((resolve, reject) => {
// commit('addAddress', address); commit('addAddress', address);
// resolve(); resolve();
// })) }))
// .catch((error) => { .catch((error) => {
// state.errorMsg.push(error.message); commit('catchError', error);
// }); });
} }
} }
}); });

View File

@ -1,37 +1,43 @@
/* /*
* Endpoint countries GET * Endpoint chill_api_single_country__index
* TODO * method GET, get Country Object
* @returns {Promise} a promise containing all Country object
*/ */
const fetchCountries = () => { const fetchCountries = () => {
console.log('<<< fetching countries'); console.log('<<< fetching countries');
return [
{id: 1, name: 'France', countryCode: 'FR'}, const url = `/api/1.0/main/country.json?item_per_page=1000`;
{id: 2, name: 'Belgium', countryCode: 'BE'} return fetch(url)
]; .then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
}; };
/* /*
* Endpoint cities GET * Endpoint chill_api_single_postal_code__index
* TODO * method GET, get Country Object
* @returns {Promise} a promise containing all Postal Code objects filtered with country
*/ */
const fetchCities = (country) => { const fetchCities = (country) => {
console.log('<<< fetching cities for', country); console.log('<<< fetching cities for', country);
return [ const url = `/api/1.0/main/postal-code.json?item_per_page=1000&country=${country.id}`;
{id: 1, name: 'Bruxelles', code: '1000', country: 'BE'}, return fetch(url)
{id: 2, name: 'Aisne', code: '85045', country: 'FR'}, .then(response => {
{id: 3, name: 'Saint-Gervais', code: '85230', country: 'FR'} if (response.ok) { return response.json(); }
]; throw Error('Error with request resource response');
});
}; };
/* /*
* Endpoint chill_main_address_reference_api_show * Endpoint chill_api_single_address_reference__index
* method GET, get AddressReference Object * method GET, get AddressReference Object
* @returns {Promise} a promise containing all AddressReference object * @returns {Promise} a promise containing all AddressReference objects filtered with postal code
*/ */
const fetchReferenceAddresses = (city) => { const fetchReferenceAddresses = (postalCode) => {
console.log('<<< fetching references addresses for', city); // city n'est pas utilisé pour le moment console.log('<<< fetching references addresses for', postalCode);
//TODO deal with huge number of addresses... we should do suggestion...
const url = `/api/1.0/main/address-reference.json`; const url = `/api/1.0/main/address-reference.json?item_per_page=1000&postal_code=${postalCode.id}`;
return fetch(url) return fetch(url)
.then(response => { .then(response => {
if (response.ok) { return response.json(); } if (response.ok) { return response.json(); }
@ -39,8 +45,31 @@ const fetchReferenceAddresses = (city) => {
}); });
}; };
/*
* Endpoint chill_api_single_address__entity__create
* method POST, post Address Object
* @returns {Promise}
*/
const postAddress = (address) => {
console.log(address);
const url = `/api/1.0/main/address.json?`;
const body = address;
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify(body)
}).then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};
export { export {
fetchCountries, fetchCountries,
fetchCities, fetchCities,
fetchReferenceAddresses fetchReferenceAddresses,
postAddress
}; };

View File

@ -1,6 +1,6 @@
<template> <template>
<button class="sc-button bt-create centered mt-4" @click="openModal"> <button class="sc-button bt-create centered mt-4" @click="openModal">
{{ $t('add_an_address') }} {{ $t('add_an_address_title') }}
</button> </button>
<teleport to="body"> <teleport to="body">
@ -9,76 +9,64 @@
@close="modal.showModal = false"> @close="modal.showModal = false">
<template v-slot:header> <template v-slot:header>
<h3 class="modal-title">{{ $t('add_an_address') }}</h3> <h3 class="modal-title">{{ $t('add_an_address_title') }}</h3>
</template> </template>
<template v-slot:body> <template v-slot:body>
<div class="address_form">
<h4>{{ $t('select_an_address') }}</h4>
<label for="isNoAddress">
<input type="checkbox"
name="isNoAddress"
v-bind:placeholder="$t('isNoAddress')"
v-model="isNoAddress"
v-bind:value="value"/>
{{ $t('isNoAddress') }}
</label>
<country-selection <div class="address_form__header">
v-bind:address="address" <h4>{{ $t('select_an_address_title') }}</h4>
v-bind:getCities="getCities"> </div>
</country-selection>
<city-selection
v-bind:address="address"
v-bind:getReferenceAddresses="getReferenceAddresses">
</city-selection>
<address-selection
v-bind:address="address"
v-bind:updateMapCenter="updateMapCenter">
</address-selection>
<address-map <div class="address_form__select">
v-bind:address="address"
ref="addressMap"> <div class="address_form__select__body">
</address-map> <label for="isNoAddress">
<input type="checkbox"
<address-more name="isNoAddress"
v-if="!isNoAddress" v-bind:placeholder="$t('isNoAddress')"
v-bind:address="address"> v-model="isNoAddress"
</address-more> v-bind:value="value"/>
{{ $t('isNoAddress') }}
<!-- </label>
<div class="address_form__fields__isNoAddress"></div>
<div class="address_form__select"> <country-selection
<div class="address_form__select__header"></div> v-bind:address="address"
<div class="address_form__select__left"></div> v-bind:getCities="getCities">
<div class="address_form__map"></div> </country-selection>
<city-selection
v-bind:address="address"
v-bind:getReferenceAddresses="getReferenceAddresses">
</city-selection>
<address-selection
v-bind:address="address"
v-bind:updateMapCenter="updateMapCenter">
</address-selection>
</div>
<div class="address_form__select__map">
<address-map
v-bind:address="address"
ref="addressMap">
</address-map>
</div>
</div>
<div class="address_form__more">
<address-more
v-if="!isNoAddress"
v-bind:address="address">
</address-more>
</div>
</div> </div>
<div class="address_form__fields">
<div class="address_form__fields__header"></div>
<div class="address_form__fields__left"></div>
<div class="address_form__fields__right"></div>
</div>
à discuter,
mais je pense qu'il est préférable de profiter de l'imbriquation des classes css
div.address_form {
div.select {
div.header {}
div.left {}
div.map {}
}
}
-->
</template> </template>
<template v-slot:footer> <template v-slot:footer>
<button class="sc-button green" <button class="sc-button green"
@click.prevent="$emit('addNewAddress', { address, modal })"> @click.prevent="$emit('addNewAddress', { address, modal })">
<i class="fa fa-plus fa-fw"></i>{{ $t('action.add')}} <i class="fa fa-plus fa-fw"></i>{{ $t('action.add')}}
</button> </button>
@ -155,49 +143,47 @@ export default {
} }
} }
}, },
mounted() {
this.getCountries();
},
methods: { methods: {
openModal() { openModal() {
this.modal.showModal = true; this.modal.showModal = true;
this.resetAll(); this.resetAll();
this.getCountries();
//this.$nextTick(function() { //this.$nextTick(function() {
// this.$refs.search.focus(); // positionner le curseur à l'ouverture de la modale // this.$refs.search.focus(); // positionner le curseur à l'ouverture de la modale
//}) //})
}, },
getCountries() { getCountries() {
console.log('getCountries'); console.log('getCountries');
this.address.loaded.countries = fetchCountries(); // à remplacer par fetchCountries().then(countries => new Promise((resolve, reject) => {
// fetchCountries().then(countries => new Promise((resolve, reject) => { this.address.loaded.countries = countries.results;
// this.address.loaded.countries = countries; resolve()
// resolve() }))
// })) .catch((error) => {
// .catch((error) => { this.errorMsg.push(error.message);
// this.errorMsg.push(error.message); });
// });
}, },
getCities(country) { getCities(country) {
console.log('getCities for', country.name); console.log('getCities for', country.name);
this.address.loaded.cities = fetchCities(); // à remplacer par fetchCities(country).then(cities => new Promise((resolve, reject) => {
// fetchCities(country).then(cities => new Promise((resolve, reject) => { this.address.loaded.cities = cities.results;
// this.address.loaded.cities = cities; resolve()
// resolve() }))
// })) .catch((error) => {
// .catch((error) => { this.errorMsg.push(error.message);
// this.errorMsg.push(error.message); });
// });
}, },
getReferenceAddresses(city) { getReferenceAddresses(city) {
console.log('getReferenceAddresses for', city.name); console.log('getReferenceAddresses for', city.name);
fetchReferenceAddresses(city) // il me semble que le paramètre city va limiter le poids des adresses de références reçues fetchReferenceAddresses(city).then(addresses => new Promise((resolve, reject) => {
.then(addresses => new Promise((resolve, reject) => {
console.log('addresses', addresses); console.log('addresses', addresses);
this.address.loaded.addresses = addresses.results; this.address.loaded.addresses = addresses.results;
resolve(); resolve();
})) }))
.catch((error) => { .catch((error) => {
this.errorMsg.push(error.message); this.errorMsg.push(error.message);
}); });
}, },
updateMapCenter(point) { updateMapCenter(point) {
console.log('point', point); console.log('point', point);
@ -208,7 +194,7 @@ export default {
resetAll() { resetAll() {
console.log('reset all selected'); console.log('reset all selected');
this.address.loaded.addresses = []; this.address.loaded.addresses = [];
this.address.selected.address = {}; this.address.selected.address = {};
this.address.loaded.cities = []; this.address.loaded.cities = [];
this.address.selected.city = {}; this.address.selected.city = {};
this.address.selected.country = {}; this.address.selected.country = {};

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="container"> <div class="container">
<div id='address_map' style='height:400px; width:400px;'></div> <div id='address_map'></div>
</div> </div>
</template> </template>
@ -11,6 +11,7 @@ import markerIconPng from 'leaflet/dist/images/marker-icon.png'
import 'leaflet/dist/leaflet.css'; import 'leaflet/dist/leaflet.css';
let map; let map;
let marker;
export default { export default {
name: 'AddressMap', name: 'AddressMap',
@ -30,13 +31,15 @@ export default {
const markerIcon = L.icon({ const markerIcon = L.icon({
iconUrl: markerIconPng, iconUrl: markerIconPng,
iconAnchor: [12, 41],
}); });
L.marker([48.8589, 2.3469], {icon: markerIcon}).addTo(map); marker = L.marker([48.8589, 2.3469], {icon: markerIcon}).addTo(map);
}, },
update() { update() {
console.log('update map with : ', this.address.addressMap.center) console.log('update map with : ', this.address.addressMap.center)
marker.setLatLng(this.address.addressMap.center);
map.setView(this.address.addressMap.center, 12); map.setView(this.address.addressMap.center, 12);
} }
}, },

View File

@ -1,38 +1,56 @@
<template> <template>
<div class="container"> <div class="container">
<select <VueMultiselect
v-model="selected"> v-model="value"
<option :value="{}" disabled selected>{{ $t('select_address') }}</option> @select="selectAddress"
<option name="field"
v-for="item in this.addresses" track-by="id"
v-bind:item="item" label="value"
v-bind:key="item.id" :custom-label="transName"
v-bind:value="item"> :taggable="true"
{{ item.street }}, {{ item.streetNumber }} :multiple="false"
</option> @tag="addAddress"
</select> :placeholder="$t('select_address')"
:tagPlaceholder="$t('create_address')"
:options="addresses">
</VueMultiselect>
</div> </div>
</template> </template>
<script> <script>
import VueMultiselect from 'vue-multiselect';
export default { export default {
name: 'AddressSelection', name: 'AddressSelection',
components: { VueMultiselect },
props: ['address', 'updateMapCenter'], props: ['address', 'updateMapCenter'],
data() {
return {
value: null
}
},
computed: { computed: {
addresses() { addresses() {
return this.address.loaded.addresses; return this.address.loaded.addresses;
}
},
methods: {
transName(value) {
return value.streetNumber === undefined ? value.street : `${value.street}, ${value.streetNumber}`
}, },
selected: { selectAddress(value) {
set(value) { this.address.selected.address = value;
console.log('selected value', value); this.updateMapCenter(value.point);
this.address.selected.address = value;
this.updateMapCenter(value.point);
},
get() {
return this.address.selected.address;
}
}, },
addAddress (newAddress) {
const address = {
street: newAddress
};
this.value = address;
this.address.selected.address = address;
}
} }
}; };
</script> </script>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>

View File

@ -1,38 +1,45 @@
<template> <template>
<div class="container"> <div class="container">
<select <VueMultiselect
v-model="selected"> v-model="value"
<option :value="{}" disabled selected>{{ $t('select_city') }}</option> @select="selectCity"
<option name="field"
v-for="item in this.cities" track-by="id"
v-bind:item="item" label="value"
v-bind:key="item.id" :custom-label="transName"
v-bind:value="item"> :placeholder="$t('select_city')"
{{ item.code }}-{{ item.name }} :options="cities">
</option> </VueMultiselect>
</select>
</div> </div>
</template> </template>
<script> <script>
import VueMultiselect from 'vue-multiselect';
export default { export default {
name: 'CitySelection', name: 'CitySelection',
components: { VueMultiselect },
props: ['address', 'getReferenceAddresses'], props: ['address', 'getReferenceAddresses'],
data() {
return {
value: null
}
},
methods: {
transName(value) {
return `${value.code}-${value.name}`
},
selectCity(value) {
this.address.selected.city = value;
this.getReferenceAddresses(value);
},
},
computed: { computed: {
cities() { cities() {
return this.address.loaded.cities; return this.address.loaded.cities;
}, }
selected: {
set(value) {
console.log('selected value', value.name);
this.address.selected.city = value;
this.getReferenceAddresses(value);
},
get() {
return this.address.selected.city;
}
},
} }
}; };
</script> </script>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>

View File

@ -1,38 +1,59 @@
<template> <template>
<div class="container"> <div class="container">
<select <VueMultiselect
v-model="selected"> v-model="value"
<option :value="{}" disabled selected>{{ $t('select_country') }}</option> name="field"
<option track-by="id"
v-for="item in this.countries" label="name"
v-bind:item="item" :custom-label="transName"
v-bind:key="item.id" :placeholder="$t('select_country')"
v-bind:value="item"> :options="countries"
{{ item.name }} @select="selectCountry">
</option> </VueMultiselect>
</select>
</div> </div>
</template> </template>
<script> <script>
import VueMultiselect from 'vue-multiselect';
export default { export default {
name: 'CountrySelection', name: 'CountrySelection',
components: { VueMultiselect },
props: ['address', 'getCities'], props: ['address', 'getCities'],
computed: { data() {
countries() { return {
return this.address.loaded.countries; value: this.address.loaded.countries.filter(c => c.countryCode === 'FR')[0]
}, }
selected: { },
set(value) { methods: {
console.log('selected value', value.name); init() {
this.address.selected.country = value; if (this.value !== undefined) {
this.getCities(value); this.getCities(this.value);
},
get() {
return this.address.selected.country;
} }
},
transName ({ name }) {
return name.fr //TODO multilang
},
selectCountry(value) {
console.log(value);
this.address.selected.country = value;
this.getCities(value);
},
},
mounted(){
this.init()
},
computed: {
countries() {
const countries = this.address.loaded.countries;
let orderedCountries = [];
orderedCountries.push(...countries.filter(c => c.countryCode === 'FR'))
orderedCountries.push(...countries.filter(c => c.countryCode === 'BE'))
orderedCountries.push(...countries.filter(c => c.countryCode !== 'FR').filter(c => c.countryCode !== 'BE'))
return orderedCountries;
} }
} }
}; };
</script> </script>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>

View File

@ -0,0 +1,34 @@
<?php
namespace Chill\MainBundle\Serializer\Normalizer;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Chill\MainBundle\Doctrine\Model\Point;
class PointNormalizer implements DenormalizerInterface
{
public function supportsDenormalization($data, string $type, string $format = null) : bool
{
return $type === Point::class;
}
public function denormalize($data, string $type, string $format = null, array $context = [])
{
if (!is_array($data)) {
throw new InvalidArgumentException('point data is not an array. It should be an array of 2 coordinates.');
} else {
if (count($data) !== 2) {
throw new InvalidArgumentException('point data is not an array of 2 elements. It should be an array of 2 coordinates.');
} else {
return Point::fromLonLat($data[0], $data[1]);
}
}
}
}

View File

@ -31,14 +31,14 @@ services:
- [ setContainer, ["@service_container"]] - [ setContainer, ["@service_container"]]
tags: tags:
- { name: twig.extension } - { name: twig.extension }
chill.main.twig.widget: chill.main.twig.widget:
class: Chill\MainBundle\Templating\Widget\WidgetRenderingTwig class: Chill\MainBundle\Templating\Widget\WidgetRenderingTwig
arguments: arguments:
- "@event_dispatcher" - "@event_dispatcher"
tags: tags:
- { name: twig.extension } - { name: twig.extension }
chill.main.twig.csv_cell: chill.main.twig.csv_cell:
class: Chill\MainBundle\Templating\CSVCellTwig class: Chill\MainBundle\Templating\CSVCellTwig
tags: tags:
@ -65,3 +65,4 @@ services:
- "@security.authorization_checker" - "@security.authorization_checker"
- "@chill.main.security.authorization.helper" - "@chill.main.security.authorization.helper"
- "@security.token_storage" - "@security.token_storage"

View File

@ -7,7 +7,7 @@ services:
$validator: '@Symfony\Component\Validator\Validator\ValidatorInterface' $validator: '@Symfony\Component\Validator\Validator\ValidatorInterface'
tags: tags:
- { name: console.command } - { name: console.command }
Chill\MainBundle\Command\ChillUserSendRenewPasswordCodeCommand: Chill\MainBundle\Command\ChillUserSendRenewPasswordCodeCommand:
arguments: arguments:
$logger: '@Psr\Log\LoggerInterface' $logger: '@Psr\Log\LoggerInterface'

View File

@ -1,7 +1,7 @@
--- ---
services: services:
# note: the autowiring for serializers and normalizers is declared # note: the autowiring for serializers and normalizers is declared
# into ../services.yaml # into ../services.yaml
Chill\MainBundle\Serializer\Normalizer\DoctrineExistingEntityNormalizer: Chill\MainBundle\Serializer\Normalizer\DoctrineExistingEntityNormalizer: