mirror of
https://forge.chapril.org/tykayn/osm-commerces
synced 2025-06-20 01:44:42 +02:00
Compare commits
6 commits
e71177dee1
...
ca00f8c0be
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ca00f8c0be | ||
![]() |
43139d50d9 | ||
![]() |
123bd8d4d1 | ||
![]() |
d9219db84f | ||
![]() |
06ced163e6 | ||
![]() |
dbe2f62c45 |
17 changed files with 911 additions and 56 deletions
|
@ -20,7 +20,7 @@ body {
|
|||
}
|
||||
|
||||
.filled {
|
||||
background-color: #b0dfa0 !important;
|
||||
background-color: rgba(0, 255, 0, 0.2) !important;
|
||||
}
|
||||
|
||||
.filled:hover {
|
||||
|
|
35
migrations/Version20250619074501.php
Normal file
35
migrations/Version20250619074501.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250619074501 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE place ADD osm_data_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE place DROP osm_data_date
|
||||
SQL);
|
||||
}
|
||||
}
|
53
migrations/Version20250619074657.php
Normal file
53
migrations/Version20250619074657.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250619074657 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE place ADD osm_data_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE stats ADD osm_data_date_min TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE stats ADD osm_data_date_avg TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE stats ADD osm_data_date_max TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE place DROP osm_data_date
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE stats DROP osm_data_date_min
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE stats DROP osm_data_date_avg
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE stats DROP osm_data_date_max
|
||||
SQL);
|
||||
}
|
||||
}
|
|
@ -82,8 +82,11 @@ final class AdminController extends AbstractController
|
|||
->setSiret($this->motocultrice->find_siret($placeData['tags']) ?? '')
|
||||
->setAskedHumainsSupport(false)
|
||||
->setLastContactAttemptDate(null)
|
||||
->setNote('')
|
||||
->setPlaceCount(0);
|
||||
->setNote($this->motocultrice->find_tag($placeData['tags'], 'note') ? true : false)
|
||||
->setNoteContent($this->motocultrice->find_tag($placeData['tags'], 'note') ?? '')
|
||||
->setPlaceCount(0)
|
||||
// ->setOsmData($placeData['modified'] ?? null)
|
||||
;
|
||||
|
||||
// Mettre à jour les données depuis Overpass
|
||||
$place->update_place_from_overpass_data($placeData);
|
||||
|
@ -139,8 +142,74 @@ final class AdminController extends AbstractController
|
|||
}
|
||||
|
||||
$stats->computeCompletionPercent();
|
||||
$this->entityManager->persist($stats);
|
||||
|
||||
// Calculer les statistiques de fraîcheur des données OSM
|
||||
$timestamps = [];
|
||||
foreach ($stats->getPlaces() as $place) {
|
||||
if ($place->getOsmDataDate()) {
|
||||
$timestamps[] = $place->getOsmDataDate()->getTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($timestamps)) {
|
||||
// Date la plus ancienne (min)
|
||||
$minTimestamp = min($timestamps);
|
||||
$stats->setOsmDataDateMin(new \DateTime('@' . $minTimestamp));
|
||||
|
||||
// Date la plus récente (max)
|
||||
$maxTimestamp = max($timestamps);
|
||||
$stats->setOsmDataDateMax(new \DateTime('@' . $maxTimestamp));
|
||||
|
||||
// Date moyenne
|
||||
$avgTimestamp = array_sum($timestamps) / count($timestamps);
|
||||
$stats->setOsmDataDateAvg(new \DateTime('@' . (int)$avgTimestamp));
|
||||
}
|
||||
|
||||
if($stats->getDateCreated() == null) {
|
||||
$stats->setDateCreated(new \DateTime());
|
||||
}
|
||||
|
||||
$stats->setDateModified(new \DateTime());
|
||||
|
||||
// Créer un historique des statistiques
|
||||
$statsHistory = new StatsHistory();
|
||||
$statsHistory->setDate(new \DateTime())
|
||||
->setStats($stats);
|
||||
|
||||
// Compter les Places avec email et SIRET
|
||||
$placesWithEmail = 0;
|
||||
$placesWithSiret = 0;
|
||||
foreach ($stats->getPlaces() as $place) {
|
||||
if ($place->getEmail() && $place->getEmail() !== '') {
|
||||
$placesWithEmail++;
|
||||
}
|
||||
if ($place->getSiret() && $place->getSiret() !== '') {
|
||||
$placesWithSiret++;
|
||||
}
|
||||
}
|
||||
|
||||
$statsHistory->setPlacesCount($stats->getPlaces()->count())
|
||||
->setOpeningHoursCount($stats->getAvecHoraires())
|
||||
->setAddressCount($stats->getAvecAdresse())
|
||||
->setWebsiteCount($stats->getAvecSite())
|
||||
->setSiretCount($placesWithSiret)
|
||||
->setEmailsCount($placesWithEmail)
|
||||
// ->setAccessibiliteCount($stats->getAvecAccessibilite())
|
||||
// ->setNoteCount($stats->getAvecNote())
|
||||
->setCompletionPercent($stats->getCompletionPercent())
|
||||
->setStats($stats);
|
||||
|
||||
$this->entityManager->persist($statsHistory);
|
||||
|
||||
|
||||
$this->entityManager->persist($stats);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$message = 'Labourage terminé avec succès. ' . $processedCount . ' nouveaux lieux traités.';
|
||||
if ($updateExisting) {
|
||||
$message .= ' ' . $updatedCount . ' lieux existants mis à jour pour la zone '.$stats->getName().' ('.$stats->getZone().').';
|
||||
}
|
||||
$this->addFlash('success', $message);
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
@ -175,15 +244,6 @@ final class AdminController extends AbstractController
|
|||
|
||||
$urls = $stats->getAllCTCUrlsMap();
|
||||
|
||||
$statsHistory = $this->entityManager->getRepository(StatsHistory::class)
|
||||
->createQueryBuilder('sh')
|
||||
->where('sh.stats = :stats')
|
||||
->setParameter('stats', $stats)
|
||||
->orderBy('sh.id', 'DESC')
|
||||
->setMaxResults(365)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
// Calculer les statistiques
|
||||
$calculatedStats = $this->motocultrice->calculateStats($commerces);
|
||||
|
||||
|
@ -206,9 +266,77 @@ final class AdminController extends AbstractController
|
|||
$this->entityManager->flush();
|
||||
|
||||
$stats->computeCompletionPercent();
|
||||
|
||||
// Calculer les statistiques de fraîcheur des données OSM
|
||||
$timestamps = [];
|
||||
foreach ($stats->getPlaces() as $place) {
|
||||
if ($place->getOsmDataDate()) {
|
||||
$timestamps[] = $place->getOsmDataDate()->getTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($timestamps)) {
|
||||
// Date la plus ancienne (min)
|
||||
$minTimestamp = min($timestamps);
|
||||
$stats->setOsmDataDateMin(new \DateTime('@' . $minTimestamp));
|
||||
|
||||
// Date la plus récente (max)
|
||||
$maxTimestamp = max($timestamps);
|
||||
$stats->setOsmDataDateMax(new \DateTime('@' . $maxTimestamp));
|
||||
|
||||
// Date moyenne
|
||||
$avgTimestamp = array_sum($timestamps) / count($timestamps);
|
||||
$stats->setOsmDataDateAvg(new \DateTime('@' . (int)$avgTimestamp));
|
||||
}
|
||||
|
||||
if($stats->getDateCreated() == null) {
|
||||
$stats->setDateCreated(new \DateTime());
|
||||
}
|
||||
|
||||
$stats->setDateModified(new \DateTime());
|
||||
|
||||
// Créer un historique des statistiques
|
||||
$statsHistory = new StatsHistory();
|
||||
$statsHistory->setDate(new \DateTime())
|
||||
->setStats($stats);
|
||||
|
||||
// Compter les Places avec email et SIRET
|
||||
$placesWithEmail = 0;
|
||||
$placesWithSiret = 0;
|
||||
foreach ($stats->getPlaces() as $place) {
|
||||
if ($place->getEmail() && $place->getEmail() !== '') {
|
||||
$placesWithEmail++;
|
||||
}
|
||||
if ($place->getSiret() && $place->getSiret() !== '') {
|
||||
$placesWithSiret++;
|
||||
}
|
||||
}
|
||||
|
||||
$statsHistory->setPlacesCount($stats->getPlaces()->count())
|
||||
->setOpeningHoursCount($stats->getAvecHoraires())
|
||||
->setAddressCount($stats->getAvecAdresse())
|
||||
->setWebsiteCount($stats->getAvecSite())
|
||||
->setSiretCount($placesWithSiret)
|
||||
->setEmailsCount($placesWithEmail)
|
||||
// ->setAccessibiliteCount($stats->getAvecAccessibilite())
|
||||
// ->setNoteCount($stats->getAvecNote())
|
||||
->setCompletionPercent($stats->getCompletionPercent())
|
||||
->setStats($stats);
|
||||
|
||||
$this->entityManager->persist($statsHistory);
|
||||
|
||||
|
||||
$this->entityManager->persist($stats);
|
||||
$this->entityManager->flush();
|
||||
|
||||
|
||||
$statsHistory = $this->entityManager->getRepository(StatsHistory::class)
|
||||
->createQueryBuilder('sh')
|
||||
->where('sh.stats = :stats')
|
||||
->setParameter('stats', $stats)
|
||||
->orderBy('sh.id', 'DESC')
|
||||
->setMaxResults(365)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
return $this->render('admin/stats.html.twig', [
|
||||
'stats' => $stats,
|
||||
|
@ -269,6 +397,12 @@ final class AdminController extends AbstractController
|
|||
#[Route('/admin/labourer/{insee_code}', name: 'app_admin_labourer')]
|
||||
public function labourer(string $insee_code, bool $updateExisting = true): Response
|
||||
{
|
||||
|
||||
// Vérifier si le code INSEE est valide (composé uniquement de chiffres)
|
||||
if (!ctype_digit($insee_code) || $insee_code == 'undefined' || $insee_code == '') {
|
||||
$this->addFlash('error', 'Code INSEE invalide : il doit être composé uniquement de chiffres.');
|
||||
return $this->redirectToRoute('app_public_index');
|
||||
}
|
||||
try {
|
||||
// Récupérer ou créer les stats pour cette zone
|
||||
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
|
||||
|
@ -366,6 +500,28 @@ final class AdminController extends AbstractController
|
|||
// Mettre à jour les statistiques finales
|
||||
$stats->computeCompletionPercent();
|
||||
|
||||
// Calculer les statistiques de fraîcheur des données OSM
|
||||
$timestamps = [];
|
||||
foreach ($stats->getPlaces() as $place) {
|
||||
if ($place->getOsmDataDate()) {
|
||||
$timestamps[] = $place->getOsmDataDate()->getTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($timestamps)) {
|
||||
// Date la plus ancienne (min)
|
||||
$minTimestamp = min($timestamps);
|
||||
$stats->setOsmDataDateMin(new \DateTime('@' . $minTimestamp));
|
||||
|
||||
// Date la plus récente (max)
|
||||
$maxTimestamp = max($timestamps);
|
||||
$stats->setOsmDataDateMax(new \DateTime('@' . $maxTimestamp));
|
||||
|
||||
// Date moyenne
|
||||
$avgTimestamp = array_sum($timestamps) / count($timestamps);
|
||||
$stats->setOsmDataDateAvg(new \DateTime('@' . (int)$avgTimestamp));
|
||||
}
|
||||
|
||||
if($stats->getDateCreated() == null) {
|
||||
$stats->setDateCreated(new \DateTime());
|
||||
}
|
||||
|
@ -377,11 +533,24 @@ final class AdminController extends AbstractController
|
|||
$statsHistory->setDate(new \DateTime())
|
||||
->setStats($stats);
|
||||
|
||||
// Compter les Places avec email et SIRET
|
||||
$placesWithEmail = 0;
|
||||
$placesWithSiret = 0;
|
||||
foreach ($stats->getPlaces() as $place) {
|
||||
if ($place->getEmail() && $place->getEmail() !== '') {
|
||||
$placesWithEmail++;
|
||||
}
|
||||
if ($place->getSiret() && $place->getSiret() !== '') {
|
||||
$placesWithSiret++;
|
||||
}
|
||||
}
|
||||
|
||||
$statsHistory->setPlacesCount($stats->getPlaces()->count())
|
||||
->setOpeningHoursCount($stats->getAvecHoraires())
|
||||
->setAddressCount($stats->getAvecAdresse())
|
||||
->setWebsiteCount($stats->getAvecSite())
|
||||
->setSiretCount($stats->getAvecSiret())
|
||||
->setSiretCount($placesWithSiret)
|
||||
->setEmailsCount($placesWithEmail)
|
||||
// ->setAccessibiliteCount($stats->getAvecAccessibilite())
|
||||
// ->setNoteCount($stats->getAvecNote())
|
||||
->setCompletionPercent($stats->getCompletionPercent())
|
||||
|
@ -514,4 +683,62 @@ final class AdminController extends AbstractController
|
|||
|
||||
return $response;
|
||||
}
|
||||
|
||||
#[Route('/admin/make_email_for_place/{id}', name: 'app_admin_make_email_for_place')]
|
||||
public function make_email_for_place(Place $place): Response
|
||||
{
|
||||
|
||||
return $this->render('admin/view_email_for_place.html.twig', ['place' => $place]);
|
||||
}
|
||||
|
||||
#[Route('/admin/no_more_sollicitation_for_place/{id}', name: 'app_admin_no_more_sollicitation_for_place')]
|
||||
public function no_more_sollicitation_for_place(Place $place): Response
|
||||
{
|
||||
$place->setOptedOut(true);
|
||||
$this->entityManager->persist($place);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'Votre lieu '.$place->getName().' ne sera plus sollicité pour mettre à jour ses informations.');
|
||||
|
||||
return $this->redirectToRoute('app_public_index');
|
||||
}
|
||||
|
||||
#[Route('/admin/send_email_to_place/{id}', name: 'app_admin_send_email_to_place')]
|
||||
public function send_email_to_place(Place $place, \Symfony\Component\Mailer\MailerInterface $mailer): Response
|
||||
{
|
||||
|
||||
// Vérifier si le lieu est opted out
|
||||
if ($place->isOptedOut()) {
|
||||
$this->addFlash('error', 'Ce lieu a demandé à ne plus être sollicité pour mettre à jour ses informations.');
|
||||
return $this->redirectToRoute('app_public_index');
|
||||
}
|
||||
// Vérifier si le lieu a déjà été contacté
|
||||
if ($place->getLastContactAttemptDate() !== null) {
|
||||
$this->addFlash('error', 'Ce lieu a déjà été contacté le ' . $place->getLastContactAttemptDate()->format('d/m/Y H:i:s'));
|
||||
return $this->redirectToRoute('app_public_index');
|
||||
}
|
||||
|
||||
// Générer le contenu de l'email avec le template
|
||||
$emailContent = $this->renderView('admin/email_content.html.twig', [
|
||||
'place' => $place
|
||||
]);
|
||||
|
||||
// Envoyer l'email
|
||||
$email = (new \Symfony\Component\Mime\Email())
|
||||
->from('contact@openstreetmap.fr')
|
||||
->to('contact+send_email@cipherbliss.com')
|
||||
->subject('Mise à jour des informations de votre établissement dans OpenStreetMap')
|
||||
->html($emailContent);
|
||||
|
||||
$mailer->send($email);
|
||||
|
||||
|
||||
// Mettre à jour la date de dernier contact
|
||||
$place->setLastContactAttemptDate(new \DateTime());
|
||||
$this->entityManager->persist($place);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'Email envoyé avec succès à ' . $place->getName() . ' le ' . $place->getLastContactAttemptDate()->format('d/m/Y H:i:s'));
|
||||
return $this->redirectToRoute('app_public_index');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,6 +103,54 @@ class Place
|
|||
#[ORM\Column(nullable: true)]
|
||||
private ?int $habitants = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?\DateTime $osm_data_date = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?int $osm_version = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $osm_user = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?int $osm_uid = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?int $osm_changeset = null;
|
||||
|
||||
public function getPlaceTypeName(): ?string
|
||||
{
|
||||
if ($this->main_tag == 'amenity=restaurant') {
|
||||
return 'restaurant';
|
||||
}
|
||||
if ($this->main_tag == 'amenity=bar') {
|
||||
return 'bar';
|
||||
}
|
||||
if ($this->main_tag == 'amenity=cafe') {
|
||||
return 'café';
|
||||
}
|
||||
if ($this->main_tag == 'amenity=hotel') {
|
||||
return 'hôtel';
|
||||
}
|
||||
if ($this->main_tag == 'amenity=supermarket') {
|
||||
return 'supermarché';
|
||||
}
|
||||
if ($this->main_tag == 'amenity=pharmacy') {
|
||||
return 'pharmacie';
|
||||
}
|
||||
if ($this->main_tag == 'amenity=bank') {
|
||||
return 'banque';
|
||||
}
|
||||
if ($this->main_tag == 'amenity=post_office') {
|
||||
return 'poste';
|
||||
}
|
||||
if ($this->main_tag == 'amenity=school') {
|
||||
return 'école';
|
||||
}
|
||||
|
||||
return 'établissement';
|
||||
}
|
||||
|
||||
public function getMainTag(): ?string
|
||||
{
|
||||
return $this->main_tag;
|
||||
|
@ -161,7 +209,7 @@ class Place
|
|||
/**
|
||||
* mettre à jour le lieu selon les tags osm
|
||||
*/
|
||||
public function update_place_from_overpass_data(array $overpass_data) {
|
||||
public function update_place_from_overpass_data(array $overpass_data) {
|
||||
|
||||
if ( ! isset($overpass_data['tags']) || $overpass_data['tags'] == null) {
|
||||
return;
|
||||
|
@ -179,7 +227,8 @@ class Place
|
|||
'addr:street' => '',
|
||||
'website' => '',
|
||||
'wheelchair' => '',
|
||||
'note' => ''
|
||||
'note' => '',
|
||||
'fixme' => '',
|
||||
], $overpass_data['tags'] );
|
||||
|
||||
|
||||
|
@ -190,19 +239,41 @@ class Place
|
|||
$this
|
||||
->setOsmId( $orignal_overpass_data['id'])
|
||||
->setOsmKind($orignal_overpass_data['type'] )
|
||||
->setLat($orignal_overpass_data['lat'])
|
||||
->setLon($orignal_overpass_data['lon'])
|
||||
->setLat((float) $orignal_overpass_data['lat'])
|
||||
->setLon((float) $orignal_overpass_data['lon'])
|
||||
->setName(isset($overpass_data['name']) && $overpass_data['name'] != '' ? $overpass_data['name'] : null);
|
||||
|
||||
// Traiter le timestamp OSM si disponible
|
||||
if (isset($orignal_overpass_data['timestamp']) && $orignal_overpass_data['timestamp']) {
|
||||
try {
|
||||
$osmDate = new \DateTime($orignal_overpass_data['timestamp']);
|
||||
$this->setOsmDataDate($osmDate);
|
||||
} catch (\Exception $e) {
|
||||
// En cas d'erreur de parsing de la date, on ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Traiter les autres métadonnées OSM
|
||||
if (isset($orignal_overpass_data['version'])) {
|
||||
$this->setOsmVersion($orignal_overpass_data['version']);
|
||||
}
|
||||
if (isset($orignal_overpass_data['user'])) {
|
||||
$this->setOsmUser($orignal_overpass_data['user']);
|
||||
}
|
||||
if (isset($orignal_overpass_data['uid'])) {
|
||||
$this->setOsmUid($orignal_overpass_data['uid']);
|
||||
}
|
||||
if (isset($orignal_overpass_data['changeset'])) {
|
||||
$this->setOsmChangeset($orignal_overpass_data['changeset']);
|
||||
}
|
||||
|
||||
$mapping = [
|
||||
['key' => 'postcode', 'setter' => 'setZipCode', 'source' => $overpass_data],
|
||||
['key' => 'email', 'setter' => 'setEmail', 'source' => $overpass_data],
|
||||
['key' => 'opening_hours', 'setter' => 'setHasOpeningHours', 'source' => $overpass_data['tags'] ?? []],
|
||||
['key' => 'note', 'setter' => 'setNote', 'source' => $overpass_data['tags'] ?? []],
|
||||
['key' => 'addr:housenumber', 'setter' => 'setHasAddress', 'source' => $overpass_data['tags'] ?? []],
|
||||
['key' => 'website', 'setter' => 'setHasWebsite', 'source' => $overpass_data['tags'] ?? []],
|
||||
['key' => 'wheelchair', 'setter' => 'setHasWheelchair', 'source' => $overpass_data['tags'] ?? []],
|
||||
['key' => 'note', 'setter' => 'setHasNote', 'source' => $overpass_data['tags'] ?? []],
|
||||
['key' => 'siret', 'setter' => 'setSiret', 'source' => $overpass_data['tags'] ?? []],
|
||||
['key' => 'addr:street', 'setter' => 'setStreet', 'source' => $overpass_data['tags'] ?? []],
|
||||
['key' => 'addr:housenumber', 'setter' => 'setHousenumber', 'source' => $overpass_data['tags'] ?? []],
|
||||
|
@ -214,6 +285,26 @@ class Place
|
|||
}
|
||||
}
|
||||
|
||||
// Traiter les notes et fixme
|
||||
$noteContent = '';
|
||||
$hasNote = false;
|
||||
|
||||
if (isset($orignal_overpass_data['tags']['note']) && $orignal_overpass_data['tags']['note'] !== '') {
|
||||
$noteContent .= $orignal_overpass_data['tags']['note'];
|
||||
$hasNote = true;
|
||||
}
|
||||
|
||||
if (isset($orignal_overpass_data['tags']['fixme']) && $orignal_overpass_data['tags']['fixme'] !== '') {
|
||||
if ($noteContent !== '') {
|
||||
$noteContent .= "\n\n";
|
||||
}
|
||||
$noteContent .= "FIXME: " . $orignal_overpass_data['tags']['fixme'];
|
||||
$hasNote = true;
|
||||
}
|
||||
|
||||
$this->setNoteContent($noteContent);
|
||||
$this->setHasNote($hasNote);
|
||||
|
||||
$this
|
||||
// ->setOsmId($overpass_data['id'])
|
||||
// ->setOsmKind($overpass_data['type'])
|
||||
|
@ -226,8 +317,7 @@ class Place
|
|||
->setHasOpeningHours($overpass_data['opening_hours'] ? true : false)
|
||||
->setHasAddress($overpass_data['addr:housenumber'] && $overpass_data['addr:street'] ? true : false)
|
||||
->setHasWebsite($overpass_data['website'] ? true : false)
|
||||
->setHasWheelchair($overpass_data['wheelchair'] and $overpass_data['wheelchair'] != '' ? true : false)
|
||||
->setHasNote($overpass_data['note'] and $overpass_data['note'] != '' ? true : false);
|
||||
->setHasWheelchair($overpass_data['wheelchair'] and $overpass_data['wheelchair'] != '' ? true : false);
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
|
@ -523,24 +613,24 @@ class Place
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getLat(): ?int
|
||||
public function getLat(): ?float
|
||||
{
|
||||
return $this->lat;
|
||||
}
|
||||
|
||||
public function setLat(?int $lat): static
|
||||
public function setLat(?float $lat): static
|
||||
{
|
||||
$this->lat = $lat;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLon(): ?int
|
||||
public function getLon(): ?float
|
||||
{
|
||||
return $this->lon;
|
||||
}
|
||||
|
||||
public function setLon(?int $lon): static
|
||||
public function setLon(?float $lon): static
|
||||
{
|
||||
$this->lon = $lon;
|
||||
|
||||
|
@ -594,4 +684,64 @@ class Place
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOsmDataDate(): ?\DateTime
|
||||
{
|
||||
return $this->osm_data_date;
|
||||
}
|
||||
|
||||
public function setOsmDataDate(?\DateTime $osm_data_date): static
|
||||
{
|
||||
$this->osm_data_date = $osm_data_date;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOsmVersion(): ?int
|
||||
{
|
||||
return $this->osm_version;
|
||||
}
|
||||
|
||||
public function setOsmVersion(?int $osm_version): static
|
||||
{
|
||||
$this->osm_version = $osm_version;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOsmUser(): ?string
|
||||
{
|
||||
return $this->osm_user;
|
||||
}
|
||||
|
||||
public function setOsmUser(?string $osm_user): static
|
||||
{
|
||||
$this->osm_user = $osm_user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOsmUid(): ?int
|
||||
{
|
||||
return $this->osm_uid;
|
||||
}
|
||||
|
||||
public function setOsmUid(?int $osm_uid): static
|
||||
{
|
||||
$this->osm_uid = $osm_uid;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOsmChangeset(): ?int
|
||||
{
|
||||
return $this->osm_changeset;
|
||||
}
|
||||
|
||||
public function setOsmChangeset(?int $osm_changeset): static
|
||||
{
|
||||
$this->osm_changeset = $osm_changeset;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,6 +86,15 @@ class Stats
|
|||
#[ORM\Column(nullable: true)]
|
||||
private ?int $avec_name = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?\DateTime $osm_data_date_min = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?\DateTime $osm_data_date_avg = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?\DateTime $osm_data_date_max = null;
|
||||
|
||||
public function getCTCurlBase(): ?string
|
||||
{
|
||||
$base = 'https://complete-tes-commerces.fr/';
|
||||
|
@ -493,6 +502,42 @@ class Stats
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOsmDataDateMin(): ?\DateTime
|
||||
{
|
||||
return $this->osm_data_date_min;
|
||||
}
|
||||
|
||||
public function setOsmDataDateMin(?\DateTime $osm_data_date_min): static
|
||||
{
|
||||
$this->osm_data_date_min = $osm_data_date_min;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOsmDataDateAvg(): ?\DateTime
|
||||
{
|
||||
return $this->osm_data_date_avg;
|
||||
}
|
||||
|
||||
public function setOsmDataDateAvg(?\DateTime $osm_data_date_avg): static
|
||||
{
|
||||
$this->osm_data_date_avg = $osm_data_date_avg;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOsmDataDateMax(): ?\DateTime
|
||||
{
|
||||
return $this->osm_data_date_max;
|
||||
}
|
||||
|
||||
public function setOsmDataDateMax(?\DateTime $osm_data_date_max): static
|
||||
{
|
||||
$this->osm_data_date_max = $osm_data_date_max;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -101,10 +101,10 @@ public function find_siret($tags) {
|
|||
|
||||
public function get_export_query($zone) {
|
||||
return <<<QUERY
|
||||
[out:csv(::id,::type,::lat,::lon,name,amenity,shop,office,healthcare,"contact:email",email,"contact:phone",phone,"contact:website",website,image,url,wikidata, opening_hours,"contact:housenumber","addr:housenumber","contact:street","addr:street",note,fixme,harassment_prevention,cuisine,brand,tourism,source,zip_code,"ref:FR:SIRET")];
|
||||
[out:csv(::id,::type,::lat,::lon,::timestamp,::version,::user,::uid,::changeset,name,amenity,shop,office,healthcare,"contact:email",email,"contact:phone",phone,"contact:website",website,image,url,wikidata, opening_hours,"contact:housenumber","addr:housenumber","contact:street","addr:street",note,fixme,harassment_prevention,cuisine,brand,tourism,source,zip_code,"ref:FR:SIRET")];
|
||||
{{geocodeArea:"{$zone}, France"}}->.searchArea;
|
||||
{$this->overpass_base_places}
|
||||
out skel qt;
|
||||
out meta;
|
||||
QUERY;
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,7 @@ QUERY;
|
|||
return '[out:json][timeout:25];
|
||||
area["ref:INSEE"="'.$zone.'"]->.searchArea;
|
||||
'.$this->overpass_base_places.'
|
||||
out center tags;';
|
||||
out meta;';
|
||||
}
|
||||
|
||||
private $more_tags = ['image', 'ref:FR:SIRET'];
|
||||
|
@ -180,7 +180,14 @@ out center tags;';
|
|||
'name' => $element['tags']['name'] ?? '',
|
||||
'lat' => $element['lat'] ?? null,
|
||||
'lon' => $element['lon'] ?? null,
|
||||
'tags' => $element['tags']
|
||||
'tags' => $element['tags'],
|
||||
// Métadonnées OSM
|
||||
'timestamp' => $element['timestamp'] ?? null,
|
||||
'version' => $element['version'] ?? null,
|
||||
'user' => $element['user'] ?? null,
|
||||
'uid' => $element['uid'] ?? null,
|
||||
'changeset' => $element['changeset'] ?? null,
|
||||
'modified' => $element['timestamp'] ?? null
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -216,6 +223,13 @@ out center tags;';
|
|||
return null;
|
||||
}
|
||||
|
||||
public function find_tag($tags, $tag) {
|
||||
if(isset($tags[$tag]) && $tags[$tag] != '') {
|
||||
return $tags[$tag];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get_city_osm_from_zip_code($zip_code) {
|
||||
// Requête Overpass pour obtenir la zone administrative de niveau 8 avec un nom
|
||||
$query = "[out:json][timeout:25];
|
||||
|
|
32
templates/admin/email_content.html.twig
Normal file
32
templates/admin/email_content.html.twig
Normal file
|
@ -0,0 +1,32 @@
|
|||
<div class="content">
|
||||
<i class="bi bi-shop-window"></i>
|
||||
<p>Bonjour, votre {{place.getPlaceTypeName()}} "{{place.name }}" est présent dans la base de données mondiale OpenStreetMap (OSM) avec 650 000 autres en France.
|
||||
<br>
|
||||
Ces informations sont utilisées dans des milliers de sites web et annuaires, par Île de France mobilités, TomTom, Geovelo, Cartes IGN, Facebook, Instagram, Apple Plans et bien d'autres.
|
||||
|
||||
<br>
|
||||
Plus les informations seront à jour et plus vous aurez de chances d'avoir des clients satisfaits.</p>
|
||||
|
||||
<p> Vous pouvez le modifier en cliquant sur le bouton ci-dessous, c'est gratuit et sans engagement.</p>
|
||||
|
||||
<a href="{{ path('app_public_edit', {'zipcode': place.zipCode, 'name': place.name != '' ? place.name : '?', 'uuid': place.uuidForUrl}) }}" class="btn btn-primary">
|
||||
<i class="bi bi-pencil-square"></i>
|
||||
Compléter les informations de mon commerce
|
||||
</a>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
Les bénévoles de l'association OpenStreetMap France ont mis en place cet outil pour faciliter la mise à jour des informations de vos commerces et améliorer la souveraineté numérique. Si vous avez besoin d'aide, n'hésitez pas à nous contacter à l'adresse <a href="mailto:contact@openstreetmap.fr">contact@openstreetmap.fr</a>.
|
||||
<br>
|
||||
<br>
|
||||
Pour des besoins de prestation de services concernant l'intégration ou l'exportation de données depuis OSM, vous pouvez contacter la fédération des pros d'OpenStreetMap France sur <a href="https://fposm.fr">https://fposm.fr</a>.
|
||||
<br>
|
||||
<br>
|
||||
En vous souhaitant une bonne journée.
|
||||
<br>
|
||||
- Les bénévoles de l'association OpenStreetMap France.
|
||||
<br>
|
||||
<hr>
|
||||
|
||||
<a href="{{ path('app_admin_commerce', {'id': place.id}) }}">Ne plus être sollicité pour mettre à jour mon commerce</a>
|
||||
</div>
|
|
@ -19,6 +19,28 @@
|
|||
.completion-info {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.osm-modification-info {
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.osm-modification-info .text-muted {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.osm-modification-info a {
|
||||
text-decoration: none;
|
||||
color: #0d6efd;
|
||||
}
|
||||
.osm-modification-info a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.osm-freshness-info {
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.osm-freshness-info .alert {
|
||||
border-left: 4px solid #0dcaf0;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -59,6 +81,74 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Affichage de la fraîcheur des données OSM #}
|
||||
{% if stats.osmDataDateMin and stats.osmDataDateMax and stats.osmDataDateAvg %}
|
||||
{% set now = "now"|date("U") %}
|
||||
{% set minDate = stats.osmDataDateMin|date("U") %}
|
||||
{% set maxDate = stats.osmDataDateMax|date("U") %}
|
||||
{% set avgDate = stats.osmDataDateAvg|date("U") %}
|
||||
|
||||
{% set minDiff = now - minDate %}
|
||||
{% set maxDiff = now - maxDate %}
|
||||
{% set avgDiff = now - avgDate %}
|
||||
|
||||
{% set minYears = (minDiff / 31536000)|round(0, 'floor') %}
|
||||
{% set minMonths = ((minDiff % 31536000) / 2592000)|round(0, 'floor') %}
|
||||
{% set maxYears = (maxDiff / 31536000)|round(0, 'floor') %}
|
||||
{% set maxMonths = ((maxDiff % 31536000) / 2592000)|round(0, 'floor') %}
|
||||
{% set avgYears = (avgDiff / 31536000)|round(0, 'floor') %}
|
||||
{% set avgMonths = ((avgDiff % 31536000) / 2592000)|round(0, 'floor') %}
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info osm-freshness-info">
|
||||
<i class="bi bi-clock-history"></i>
|
||||
<strong>Fraîcheur des données OSM :</strong>
|
||||
{% if minYears == maxYears and minMonths == maxMonths %}
|
||||
Toutes les modifications ont été faites il y a
|
||||
{% if minYears > 0 %}
|
||||
{{ minYears }} an{{ minYears > 1 ? 's' : '' }}
|
||||
{% if minMonths > 0 %}, {{ minMonths }} mois{% endif %}
|
||||
{% elseif minMonths > 0 %}
|
||||
{{ minMonths }} mois
|
||||
{% else %}
|
||||
moins d'un mois
|
||||
{% endif %}
|
||||
{% else %}
|
||||
Les modifications ont été faites entre il y a
|
||||
{% if maxYears > 0 %}
|
||||
{{ maxYears }} an{{ maxYears > 1 ? 's' : '' }}
|
||||
{% if maxMonths > 0 %}, {{ maxMonths }} mois{% endif %}
|
||||
{% elseif maxMonths > 0 %}
|
||||
{{ maxMonths }} mois
|
||||
{% else %}
|
||||
moins d'un mois
|
||||
{% endif %}
|
||||
et il y a
|
||||
{% if minYears > 0 %}
|
||||
{{ minYears }} an{{ minYears > 1 ? 's' : '' }}
|
||||
{% if minMonths > 0 %}, {{ minMonths }} mois{% endif %}
|
||||
{% elseif minMonths > 0 %}
|
||||
{{ minMonths }} mois
|
||||
{% else %}
|
||||
moins d'un mois
|
||||
{% endif %},
|
||||
en moyenne il y a
|
||||
{% if avgYears > 0 %}
|
||||
{{ avgYears }} an{{ avgYears > 1 ? 's' : '' }}
|
||||
{% if avgMonths > 0 %}, {{ avgMonths }} mois{% endif %}
|
||||
{% elseif avgMonths > 0 %}
|
||||
{{ avgMonths }} mois
|
||||
{% else %}
|
||||
moins d'un mois
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-12">
|
||||
<span class="badge {% if stats.getCompletionPercent() > 85 %}bg-success{% else %}bg-warning{% endif %}">
|
||||
|
@ -954,8 +1044,8 @@ window.updateMarkers = updateMarkers;
|
|||
{# markClosedSiretsOnTable(); #}
|
||||
|
||||
function makeDonutGraphOfTags() {
|
||||
// Récupérer tous les tags de la colonne 2
|
||||
const tags = Array.from(document.querySelectorAll('table tbody tr td:nth-child(3)'))
|
||||
// Récupérer tous les tags de la colonne 4 (Type)
|
||||
const tags = Array.from(document.querySelectorAll('table tbody tr td:nth-child(4)'))
|
||||
.map(cell => cell.textContent.trim())
|
||||
.filter(tag => tag.includes('=')) // Filtrer les cellules qui ne contiennent pas de =
|
||||
.filter(tag => tag); // Filtrer les cellules vides
|
||||
|
|
|
@ -10,6 +10,12 @@
|
|||
{% endif %}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ path('app_admin_make_email_for_place', {'id': commerce.id}) }}">
|
||||
voir email
|
||||
<i class="bi bi-envelope-fill"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-right completion-cell"
|
||||
style="background : rgba(0,255,0,{{ commerce.getCompletionPercentage() / 100 }})"
|
||||
data-bs-toggle="popover"
|
||||
|
@ -84,11 +90,67 @@
|
|||
<td class="{{ commerce.hasWheelchair() ? 'filled' : '' }}">{{ commerce.wheelchair }}</td>
|
||||
<td class="{{ commerce.hasNote() ? 'filled' : '' }}">{{ commerce.note }}</td>
|
||||
<td class="{{ commerce.noteContent ? 'filled' : '' }}">{{ commerce.noteContent }}</td>
|
||||
<td class="{{ commerce.siret ? 'filled' : '' }}"> <a href="https://annuaire-entreprises.data.gouv.fr/etablissement/{{ commerce.siret }}" > {{ commerce.siret }}</a></td>
|
||||
<td class="{{ commerce.siret ? 'filled' : '' }}">
|
||||
|
||||
{% if commerce.siret %}
|
||||
{% set sirets = commerce.siret|split(';')|map(siret => siret|trim)|filter(siret => siret) %}
|
||||
{% for siret in sirets %}
|
||||
{% if not loop.first %}, {% endif %}
|
||||
<a href="https://annuaire-entreprises.data.gouv.fr/etablissement/{{ siret }}" >{{ siret }}</a>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<a href="https://annuaire-entreprises.data.gouv.fr/etablissement/{{ commerce.siret }}" >
|
||||
{% endif %}
|
||||
|
||||
|
||||
<td>
|
||||
{# (si siret clos) #}
|
||||
</td>
|
||||
<td>
|
||||
{% if commerce.osmDataDate %}
|
||||
{% set now = "now"|date("U") %}
|
||||
{% set osmDate = commerce.osmDataDate|date("U") %}
|
||||
{% set diff = now - osmDate %}
|
||||
{% set years = (diff / 31536000)|round(0, 'floor') %}
|
||||
{% set months = ((diff % 31536000) / 2592000)|round(0, 'floor') %}
|
||||
{% set days = ((diff % 2592000) / 86400)|round(0, 'floor') %}
|
||||
|
||||
<div class="small osm-modification-info">
|
||||
<div>
|
||||
<i class="bi bi-calendar"></i>
|
||||
{{ commerce.osmDataDate|date('d/m/Y H:i') }}
|
||||
</div>
|
||||
{% if commerce.osmUser %}
|
||||
<div>
|
||||
<i class="bi bi-person"></i>
|
||||
<a href="https://www.openstreetmap.org/user/{{ commerce.osmUser }}" target="_blank" title="Voir le profil OSM">
|
||||
{{ commerce.osmUser }}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="text-muted">
|
||||
<small>
|
||||
{% if diff < 86400 %}
|
||||
Aujourd'hui
|
||||
{% elseif years > 0 %}
|
||||
{{ years }} an{{ years > 1 ? 's' : '' }}
|
||||
{% if months > 0 %}, {{ months }} mois{% endif %}
|
||||
{% elseif months > 0 %}
|
||||
{{ months }} mois
|
||||
{% if days > 0 %}, {{ days }} jour{{ days > 1 ? 's' : '' }}{% endif %}
|
||||
{% elseif days > 0 %}
|
||||
{{ days }} jour{{ days > 1 ? 's' : '' }}
|
||||
{% else %}
|
||||
Aujourd'hui
|
||||
{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="text-muted">Non disponible</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://www.openstreetmap.org/{{ commerce.osmKind }}/{{ commerce.osmId }}" title="{{ commerce.osmKind }} - {{ commerce.osmId }} " >
|
||||
<i class="bi bi-globe"></i>
|
||||
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
<tr>
|
||||
<th>Nom ({{ stats.places|length }})</th>
|
||||
<th>
|
||||
<i class="bi bi-envelope-fill"></i>
|
||||
Email
|
||||
</th>
|
||||
<th>
|
||||
<i class="bi bi-circle-fill"></i>
|
||||
Completion %
|
||||
</th>
|
||||
|
@ -46,6 +50,10 @@
|
|||
Siret clos
|
||||
</th>
|
||||
<th>
|
||||
<i class="bi bi-clock-history"></i>
|
||||
Dernière modif. OSM
|
||||
</th>
|
||||
<th>
|
||||
|
||||
Osm id</th>
|
||||
<th>
|
||||
|
|
|
@ -11,33 +11,143 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const ctx = document.getElementById('completionHistoryChart').getContext('2d');
|
||||
|
||||
// Préparer les données pour chaque aspect
|
||||
const labels = [
|
||||
{% for stat in statsHistory|reverse %}
|
||||
'{{ stat.date|date('d/m/Y') }}'{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
|
||||
const completionData = [
|
||||
{% for stat in statsHistory|reverse %}
|
||||
{{ stat.completionPercent }}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
|
||||
const openingHoursData = [
|
||||
{% for stat in statsHistory|reverse %}
|
||||
{% if stat.placesCount > 0 %}
|
||||
{{ ((stat.openingHoursCount / stat.placesCount) * 100)|round(1) }}{% if not loop.last %},{% endif %}
|
||||
{% else %}
|
||||
0{% if not loop.last %},{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
|
||||
const addressData = [
|
||||
{% for stat in statsHistory|reverse %}
|
||||
{% if stat.placesCount > 0 %}
|
||||
{{ ((stat.addressCount / stat.placesCount) * 100)|round(1) }}{% if not loop.last %},{% endif %}
|
||||
{% else %}
|
||||
0{% if not loop.last %},{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
|
||||
const websiteData = [
|
||||
{% for stat in statsHistory|reverse %}
|
||||
{% if stat.placesCount > 0 %}
|
||||
{{ ((stat.websiteCount / stat.placesCount) * 100)|round(1) }}{% if not loop.last %},{% endif %}
|
||||
{% else %}
|
||||
0{% if not loop.last %},{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
|
||||
const siretData = [
|
||||
{% for stat in statsHistory|reverse %}
|
||||
{% if stat.placesCount > 0 %}
|
||||
{{ ((stat.siretCount / stat.placesCount) * 100)|round(1) }}{% if not loop.last %},{% endif %}
|
||||
{% else %}
|
||||
0{% if not loop.last %},{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
|
||||
const emailData = [
|
||||
{% for stat in statsHistory|reverse %}
|
||||
{% if stat.placesCount > 0 %}
|
||||
{{ ((stat.emailsCount / stat.placesCount) * 100)|round(1) }}{% if not loop.last %},{% endif %}
|
||||
{% else %}
|
||||
0{% if not loop.last %},{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [
|
||||
{% for stat in statsHistory %}
|
||||
'{{ stat.date|date('d/m/Y') }}'{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
],
|
||||
datasets: [{
|
||||
label: 'Taux de complétion (%)',
|
||||
data: [
|
||||
{% for stat in statsHistory %}
|
||||
{{ stat.completionPercent }}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
],
|
||||
borderColor: 'rgb(75, 192, 192)',
|
||||
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||
tension: 0.3,
|
||||
fill: true
|
||||
}]
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Taux de complétion global (%)',
|
||||
data: completionData,
|
||||
borderColor: 'rgb(75, 192, 192)',
|
||||
backgroundColor: 'rgba(75, 192, 192, 0.1)',
|
||||
tension: 0.3,
|
||||
fill: false,
|
||||
borderWidth: 3
|
||||
},
|
||||
{
|
||||
label: 'Horaires d\'ouverture (%)',
|
||||
data: openingHoursData,
|
||||
borderColor: 'rgb(255, 99, 132)',
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.1)',
|
||||
tension: 0.3,
|
||||
fill: false,
|
||||
borderWidth: 2
|
||||
},
|
||||
{
|
||||
label: 'Adresses (%)',
|
||||
data: addressData,
|
||||
borderColor: 'rgb(54, 162, 235)',
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.1)',
|
||||
tension: 0.3,
|
||||
fill: false,
|
||||
borderWidth: 2
|
||||
},
|
||||
{
|
||||
label: 'Sites web (%)',
|
||||
data: websiteData,
|
||||
borderColor: 'rgb(255, 205, 86)',
|
||||
backgroundColor: 'rgba(255, 205, 86, 0.1)',
|
||||
tension: 0.3,
|
||||
fill: false,
|
||||
borderWidth: 2
|
||||
},
|
||||
{
|
||||
label: 'SIRET (%)',
|
||||
data: siretData,
|
||||
borderColor: 'rgb(153, 102, 255)',
|
||||
backgroundColor: 'rgba(153, 102, 255, 0.1)',
|
||||
tension: 0.3,
|
||||
fill: false,
|
||||
borderWidth: 2
|
||||
},
|
||||
{
|
||||
label: 'Emails (%)',
|
||||
data: emailData,
|
||||
borderColor: 'rgb(199, 199, 199)',
|
||||
backgroundColor: 'rgba(199, 199, 199, 0.1)',
|
||||
tension: 0.3,
|
||||
fill: false,
|
||||
borderWidth: 2
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Évolution du taux de complétion au fil du temps'
|
||||
text: 'Évolution des taux de complétion par aspect au fil du temps'
|
||||
},
|
||||
legend: {
|
||||
position: 'top',
|
||||
labels: {
|
||||
usePointStyle: true,
|
||||
padding: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
|
@ -46,7 +156,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
max: 100,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Complétion (%)'
|
||||
text: 'Pourcentage (%)'
|
||||
}
|
||||
},
|
||||
x: {
|
||||
|
@ -55,6 +165,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
text: 'Date'
|
||||
}
|
||||
}
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
22
templates/admin/view_email_for_place.html.twig
Normal file
22
templates/admin/view_email_for_place.html.twig
Normal file
|
@ -0,0 +1,22 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Email pour {{ place.name }}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
<h1>Email pour {{ place.name }}</h1>
|
||||
<div class="content">
|
||||
{% include 'admin/email_content.html.twig' with {'place': place} %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<a href="{{ path('app_admin_send_email_to_place', {'id': place.id}) }}" class="btn btn-primary">
|
||||
<i class="bi bi-envelope-fill"></i>
|
||||
Envoyer l'email
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -36,7 +36,7 @@
|
|||
</div>
|
||||
{% for label, messages in app.flashes %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ label }} alert-dismissible fade show mt-3" role="alert">
|
||||
<div class="alert alert-{{ label }} is-{{ label }} alert-dismissible fade show mt-3" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
>
|
||||
<i class="bi bi-recycle"></i>
|
||||
</a>
|
||||
|
||||
<a href="{{ path('app_admin_delete_by_zone', {'insee_code': stat.zone}) }}"
|
||||
class="btn btn-sm btn-danger"
|
||||
onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette zone ?')"
|
||||
|
|
|
@ -128,7 +128,7 @@
|
|||
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
<script src='{{ asset('js/utils.js') }}'></script>
|
||||
{# <script src='{{ asset('js/utils.js') }}'></script> #}
|
||||
<script type="module">
|
||||
import { adjustListGroupFontSize } from '{{ asset('js/utils.js') }}';
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
|
|
@ -9,8 +9,9 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th>Code postal</th>
|
||||
<th>Code insee</th>
|
||||
<th>Note</th>
|
||||
<th>contenu de note</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -25,6 +26,7 @@
|
|||
<td>
|
||||
{{place.zipcode}}
|
||||
</td>
|
||||
<td>{{ place.note }}</td>
|
||||
<td>{{ place.noteContent }}</td>
|
||||
<td><a class="btn btn-primary" href="{{ path('app_admin_commerce', {'id': place.id}) }}">
|
||||
<i class="bi bi-pencil"></i>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue