Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Map on search results #238

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions src/components/vmd-appointment-map.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { css, unsafeCSS, customElement, html, LitElement, property, internalProperty } from 'lit-element';
import { Icon, map, marker, tileLayer, LatLngTuple } from 'leaflet';
import leafletCss from 'leaflet/dist/leaflet.css';
import leafletMarkerCss from 'leaflet.markercluster/dist/MarkerCluster.Default.css';
import * as L from 'leaflet';
import 'leaflet.markercluster';
import { Router } from '../routing/Router';
import { LieuAffichableAvecDistance, Coordinates, SearchRequest, TYPES_LIEUX } from '../state/State';
import { CSS_Global } from '../styles/ConstructibleStyleSheets';
import { format as formatDate, parseISO } from 'date-fns';
import { fr } from 'date-fns/locale';
import { Strings } from '../utils/Strings';

@customElement('vmd-appointment-map')
export class VmdAppointmentMapComponent extends LitElement {
//language=css
static styles = [
CSS_Global,
css`
${unsafeCSS(leafletCss)}
`,
css`
${unsafeCSS(leafletMarkerCss)}
`,
css`
:host {
display: block;
}
#appointment-map {
min-height: 500px;
}
`,
];
@property({ type: Object, attribute: false }) lieux!: LieuAffichableAvecDistance[];
@internalProperty() protected currentSearch: SearchRequest | void = undefined;

constructor() {
super();
}

render() {
return html` <div id="appointment-map"></div> `;
}

private loadMap() {
const coordinates =
this.currentSearch instanceof SearchRequest.ByCommune &&
this.toCoordinates((this.currentSearch as SearchRequest.ByCommune).commune);
const mymap = map(this.shadowRoot!.querySelector('#appointment-map') as HTMLElement).setView(
coordinates || [46.505, 3],
13
);

tileLayer(
'https://{s}.tile.jawg.io/jawg-sunny/{z}/{x}/{y}.png?access-token=sOXVrxPultoFMoo0oQigvvfXgPxaX0OFlFJF7y1rw0ZQy1c1yFTSnXSVOBqw0W6Y',
{
maxZoom: 19,
attribution:
'<a href="http://jawg.io" title="Tiles Courtesy of Jawg Maps" target="_blank" class="jawg-attrib">&copy; <b>Jawg</b>Maps</a> | <a href="https://www.openstreetmap.org/copyright" title="OpenStreetMap is open data licensed under ODbL" target="_blank" class="osm-attrib">&copy; OSM contributors</a>',
}
).addTo(mymap);
const { markers, bounds } = this.creer_pins(this.lieux, coordinates || [0, 0]);
markers.addTo(mymap);
mymap.fitBounds([
[bounds.minLat, bounds.minLon],
[bounds.maxLat, bounds.maxLon],
]);
}

private toCoordinates(o: Coordinates): LatLngTuple | void {
if (o && typeof o.latitude === 'number' && typeof o.longitude === 'number') {
return [o.latitude, o.longitude];
}
}

connectedCallback() {
super.connectedCallback();

this.requestUpdate().then(() => this.loadMap());
}

private creer_pins(lieux: LieuAffichableAvecDistance[], defaultCoordinates: LatLngTuple) {
const bounds = {
minLat: defaultCoordinates[0] || 180,
maxLat: defaultCoordinates[0] || -180,
minLon: defaultCoordinates[1] || 180,
maxLon: defaultCoordinates[1] || -180,
};
const markers = lieux
.filter((lieu) => lieu.disponible)
.reduce((markers: L.MarkerClusterGroup, lieu: LieuAffichableAvecDistance) => {
const coordinates = this.toCoordinates(lieu.location);
if (coordinates) {
var string_popup = `
<span style='font-size: 1.5em;' class="fw-bold text-dark">${lieu.nom}</span>
<vmd-appointment-metadata class="mb-2" widthType="full-width" icon="vmdicon-geo-alt-fill">
<em slot="content">${lieu.metadata.address}</em>
</vmd-appointment-metadata>
${
lieu.metadata.phone_number
? `
<vmd-appointment-metadata class="mb-2" widthType="fit-to-content" icon="vmdicon-telephone-fill">
<span slot="content">
<a href="tel:${lieu.metadata.phone_number}"
@click="${(e: Event) => {
e.stopImmediatePropagation();
}}">
${Strings.toNormalizedPhoneNumber(lieu.metadata.phone_number)}
</a>
</span>
</vmd-appointment-metadata>
`
: ''
}
<vmd-appointment-metadata class="mb-2" widthType="fit-to-content" icon="vmdicon-commerical-building">
<span slot="content">${TYPES_LIEUX[lieu.type]}</span>
</vmd-appointment-metadata>
<vmd-appointment-metadata class="mb-2" widthType="fit-to-content" icon="vmdicon-syringe" .displayed="${!!lieu.vaccine_type}">
<span slot="content">${lieu.vaccine_type}</span>
</vmd-appointment-metadata>
<vmd-appointment-metadata class="mb-2" widthType="fit-to-content" icon="vmdicon-calendar2-check-fill">
<span slot="content">${this.prochainRDV(lieu)}</span>
</vmd-appointment-metadata>
<a href="${
lieu.url
}" target="_blank" style="color: #fff; margin-top: 0.5em;" class="btn btn-sm btn-primary">
Prendre rendez-vous
</a>
`;
if (lieu.distance === undefined || lieu.distance < 50) {
bounds.minLat = Math.min(coordinates[0], bounds.minLat);
bounds.maxLat = Math.max(coordinates[0], bounds.maxLat);
bounds.minLon = Math.min(coordinates[1], bounds.minLon);
bounds.maxLon = Math.max(coordinates[1], bounds.maxLon);
}
var newMarker = marker(coordinates, {
icon: new Icon.Default({ imagePath: `${Router.basePath}assets/images/png/` }),
}).bindPopup(string_popup);
newMarker.on('click', function () {
// @ts-ignore
this.openPopup();
});
markers.addLayer(newMarker);
}

return markers;
}, new L.MarkerClusterGroup({ disableClusteringAtZoom: 9 }));

return { markers, bounds };
}
private prochainRDV(lieu: LieuAffichableAvecDistance): string {
if (lieu && lieu.prochain_rdv) {
return this.toTitleCase(formatDate(parseISO(lieu.prochain_rdv), "EEEE d MMMM 'à' HH:mm", { locale: fr }));
} else {
return 'Aucun rendez-vous';
}
}

private toTitleCase(date: string): string {
return date.replace(/(^|\s)([a-z])(\w)/g, (_, leader, letter, loser) =>
[leader, letter.toUpperCase(), loser].join('')
);
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'tippy.js/dist/tippy.css';
import './vmd-app.component'
import './components/vmd-search.component'
import './components/vmd-appointment-card.component'
import './components/vmd-appointment-map.component'
import './components/vmd-appointment-metadata.component'
import './components/vmd-commune-or-departement-selector.component'
import './components/vmd-button-switch.component'
4 changes: 2 additions & 2 deletions src/views/vmd-lieux.view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ export class VmdLieuxView extends LitElement {
console.log("error1")
});

tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
tileLayer('https://{s}.tile.jawg.io/jawg-sunny/{z}/{x}/{y}.png?access-token=sOXVrxPultoFMoo0oQigvvfXgPxaX0OFlFJF7y1rw0ZQy1c1yFTSnXSVOBqw0W6Y', {
maxZoom: 19,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
attribution: '<a href="http://jawg.io" title="Tiles Courtesy of Jawg Maps" target="_blank" class="jawg-attrib">&copy; <b>Jawg</b>Maps</a> | <a href="https://www.openstreetmap.org/copyright" title="OpenStreetMap is open data licensed under ODbL" target="_blank" class="osm-attrib">&copy; OSM contributors</a> '
}).addTo(mymap);
}

Expand Down
36 changes: 25 additions & 11 deletions src/views/vmd-rdv.view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export abstract class AbstractVmdRdvView extends LitElement {
@property({type: Array, attribute: false}) cartesAffichees: LieuAffichableAvecDistance[] = [];

@internalProperty() protected currentSearch: SearchRequest | void = undefined
@property() private showMap: boolean = false

@query("#chronodose-label") $chronodoseLabel!: HTMLSpanElement;
protected derniereCommuneSelectionnee: Commune|undefined = undefined;
Expand Down Expand Up @@ -170,7 +171,6 @@ export abstract class AbstractVmdRdvView extends LitElement {
</h3>

<div class="spacer mt-5 mb-5"></div>
<div class="resultats px-2 py-5 text-dark bg-light rounded-3">
${lieuxDisponibles.length ? html`
<h2 class="row align-items-center justify-content-center mb-5 h5 px-3">
<i class="bi vmdicon-calendar2-check-fill text-success me-2 fs-3 col-auto"></i>
Expand Down Expand Up @@ -200,21 +200,35 @@ export abstract class AbstractVmdRdvView extends LitElement {
</p>
</div>
`}

<div class="criteria-container text-dark rounded-3 pb-3 bg-light">
<ul class="p-0 d-flex flex-row mb-5 bg-white fs-5">
<li class="col bg-light text-std tab ${classMap({selected: !this.showMap})}" @click="${() => {this.showMap = false}}">
Liste des lieux
</li>
<li class="col bg-light text-std tab ${classMap({selected: this.showMap})}" @click="${() => {this.showMap = true}}">
Carte des lieux
</li>
</ul>
<div style="display: ${this.showMap ? 'none' : 'block'};">
<div id="scroller">
${repeat(this.cartesAffichees || [],
(c => `${c.departement}||${c.nom}||${c.plateforme}}`),
(lieu, index) => {
return html`<vmd-appointment-card
style="--list-index: ${index}"
.lieu="${lieu}"
theme="${(!!this.currentSearch)?this.currentSearch.type:''}"
.highlightable="${SearchRequest.isChronodoseType(this.currentSearch)}"
@prise-rdv-cliquee="${(event: LieuCliqueCustomEvent) => this.prendreRdv(event.detail.lieu)}"
@verification-rdv-cliquee="${(event: LieuCliqueCustomEvent) => this.verifierRdv(event.detail.lieu)}"
/>`;
(c => `${c.departement}||${c.nom}||${c.plateforme}}`),
(lieu, index) => {
return html`<vmd-appointment-card
style="--list-index: ${index}"
.lieu="${lieu}"
theme="${(!!this.currentSearch)?this.currentSearch.type:''}"
.highlightable="${SearchRequest.isChronodoseType(this.currentSearch)}"
@prise-rdv-cliquee="${(event: LieuCliqueCustomEvent) => this.prendreRdv(event.detail.lieu)}"
@verification-rdv-cliquee="${(event: LieuCliqueCustomEvent) => this.verifierRdv(event.detail.lieu)}"
/>`;
})}
<div id="sentinel"></div>
</div>
</div>
${this.showMap ? html`<vmd-appointment-map .currentSearch="${this.currentSearch}" .lieux="${this.lieuxParDepartementAffiches?this.lieuxParDepartementAffiches.lieuxAffichables:[]}"/>` : html``}
</div>
${SearchRequest.isStandardType(this.currentSearch)?html`
<div class="eligibility-criteria fade-in-then-fade-out">
<p>Les critères d'éligibilité sont vérifiés lors de la prise de rendez-vous</p>
Expand Down