Add today/by date or taxon_id stats and record access
This commit is contained in:
parent
e4488e1918
commit
128e3f33bb
@ -24,7 +24,7 @@ header img#logo {
|
||||
}
|
||||
|
||||
main {
|
||||
min-height: 80vh;
|
||||
min-height: 100vh;
|
||||
padding: 5em;
|
||||
}
|
||||
|
||||
@ -51,14 +51,14 @@ nav ul {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
background-color: lightgreen;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #333;
|
||||
margin: auto;
|
||||
min-width: 100vw;
|
||||
}
|
||||
|
||||
nav ul li a,
|
||||
.dropdown-button {
|
||||
color: green;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
padding: 1em 0.5em;
|
||||
@ -66,7 +66,13 @@ nav ul li a,
|
||||
|
||||
nav ul li a.active,
|
||||
nav ul li a:hover {
|
||||
background-color: aquamarine;
|
||||
background-color: #999;
|
||||
color: #101010
|
||||
}
|
||||
|
||||
.dropdown-button:hover {
|
||||
background-color: #900;
|
||||
color: white
|
||||
}
|
||||
|
||||
.dropdown:hover .dropdown-content {
|
||||
@ -76,7 +82,6 @@ nav ul li a:hover {
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #f9f9f9;
|
||||
min-width: 160px;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1;
|
||||
@ -87,3 +92,7 @@ canvas {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scientific-name {
|
||||
font-style: italic;
|
||||
}
|
@ -7,17 +7,22 @@
|
||||
"php": ">=8.1",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"doctrine/doctrine-bundle": "^2.7",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.2",
|
||||
"doctrine/orm": "^2.13",
|
||||
"sensio/framework-extra-bundle": "^6.2",
|
||||
"symfony/console": "6.1.*",
|
||||
"symfony/dotenv": "6.1.*",
|
||||
"symfony/flex": "^2",
|
||||
"symfony/framework-bundle": "6.1.*",
|
||||
"symfony/proxy-manager-bridge": "6.1.*",
|
||||
"symfony/runtime": "6.1.*",
|
||||
"symfony/translation": "6.1.*",
|
||||
"symfony/twig-bundle": "6.1.*",
|
||||
"symfony/webpack-encore-bundle": "^1.15",
|
||||
"symfony/yaml": "6.1.*",
|
||||
"twig/extra-bundle": "^2.12|^3.0",
|
||||
"twig/intl-extra": "^3.4",
|
||||
"twig/twig": "^2.12|^3.0"
|
||||
},
|
||||
"config": {
|
||||
@ -71,5 +76,8 @@
|
||||
"allow-contrib": false,
|
||||
"require": "6.1.*"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/maker-bundle": "^1.45"
|
||||
}
|
||||
}
|
||||
|
1912
www/composer.lock
generated
1912
www/composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -6,4 +6,7 @@ return [
|
||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
||||
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
|
||||
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||
];
|
||||
|
42
www/config/packages/doctrine.yaml
Normal file
42
www/config/packages/doctrine.yaml
Normal file
@ -0,0 +1,42 @@
|
||||
doctrine:
|
||||
dbal:
|
||||
url: '%env(resolve:DATABASE_URL)%'
|
||||
|
||||
# IMPORTANT: You MUST configure your server version,
|
||||
# either here or in the DATABASE_URL env var (see .env file)
|
||||
#server_version: '13'
|
||||
orm:
|
||||
auto_generate_proxy_classes: true
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||
auto_mapping: true
|
||||
mappings:
|
||||
App:
|
||||
is_bundle: false
|
||||
dir: '%kernel.project_dir%/src/Entity'
|
||||
prefix: 'App\Entity'
|
||||
alias: App
|
||||
|
||||
when@test:
|
||||
doctrine:
|
||||
dbal:
|
||||
# "TEST_TOKEN" is typically set by ParaTest
|
||||
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
|
||||
|
||||
when@prod:
|
||||
doctrine:
|
||||
orm:
|
||||
auto_generate_proxy_classes: false
|
||||
query_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.system_cache_pool
|
||||
result_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.result_cache_pool
|
||||
|
||||
framework:
|
||||
cache:
|
||||
pools:
|
||||
doctrine.result_cache_pool:
|
||||
adapter: cache.app
|
||||
doctrine.system_cache_pool:
|
||||
adapter: cache.system
|
6
www/config/packages/doctrine_migrations.yaml
Normal file
6
www/config/packages/doctrine_migrations.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
doctrine_migrations:
|
||||
migrations_paths:
|
||||
# namespace is arbitrary but should be different from App\Migrations
|
||||
# as migrations classes should NOT be autoloaded
|
||||
'DoctrineMigrations': '%kernel.project_dir%/migrations'
|
||||
enable_profiler: '%kernel.debug%'
|
8
www/docker-compose.override.yml
Normal file
8
www/docker-compose.override.yml
Normal file
@ -0,0 +1,8 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database:
|
||||
ports:
|
||||
- "5432"
|
||||
###< doctrine/doctrine-bundle ###
|
21
www/docker-compose.yml
Normal file
21
www/docker-compose.yml
Normal file
@ -0,0 +1,21 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database:
|
||||
image: postgres:${POSTGRES_VERSION:-14}-alpine
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB:-app}
|
||||
# You should definitely change the password in production
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!}
|
||||
POSTGRES_USER: ${POSTGRES_USER:-app}
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data:rw
|
||||
# You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data!
|
||||
# - ./docker/db/data:/var/lib/postgresql/data:rw
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
volumes:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
db-data:
|
||||
###< doctrine/doctrine-bundle ###
|
0
www/migrations/.gitignore
vendored
Normal file
0
www/migrations/.gitignore
vendored
Normal file
146
www/src/Controller/TodayController.php
Normal file
146
www/src/Controller/TodayController.php
Normal file
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
// src/Controller/TodayController.php
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Doctrine\DBAL\Connection;
|
||||
|
||||
class TodayController extends AbstractController
|
||||
{
|
||||
|
||||
private Connection $connection;
|
||||
|
||||
/**
|
||||
* @Route("/today", name="today")
|
||||
*/
|
||||
public function today(Connection $connection)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$date = date('Y-m-d');
|
||||
return $this->render('today/index.html.twig', [
|
||||
"species" => $this->recorded_species_by_date($date),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/today/species", name="today_species")
|
||||
*/
|
||||
public function today_species_page(Connection $connection)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$date = date('Y-m-d');
|
||||
return $this->render('today/index.html.twig', [
|
||||
"species" => $this->recorded_species_by_date($date)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/today/species/{id}", name="today_species_id")
|
||||
*/
|
||||
public function today_species_by_id(Connection $connection, $id)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$date = date('Y-m-d');
|
||||
return $this->render('today/species.html.twig', [
|
||||
"results" => $this->recorded_species_by_id_and_date($id, $date)
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @Route("/today/{date}", name="today_date")
|
||||
*/
|
||||
public function today_date(Connection $connection, $date)
|
||||
{
|
||||
return $this->redirectToRoute('today_species_date', array('date' => $date));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/today/{date}/species", name="today_species_date")
|
||||
*/
|
||||
public function today_species_by_date(Connection $connection, $date)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
return $this->render('today/index.html.twig', [
|
||||
"date" => $date,
|
||||
"results" => $this->recorded_species_by_date($date)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/today/{date}/species/{id}", name="today_species_id_and_date")
|
||||
*/
|
||||
public function today_species_by_id_and_date(Connection $connection, $date, $id)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
return $this->render('today/species.html.twig', [
|
||||
"date" => $date,
|
||||
"results" => $this->recorded_species_by_id_and_date($id, $date)
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
private function recorded_species_by_date($date)
|
||||
{
|
||||
$sql = "SELECT `taxon`.`taxon_id`, `scientific_name`, `common_name`, COUNT(*) AS `contact_count`, MAX(`confidence`) AS max_confidence
|
||||
FROM observation
|
||||
INNER JOIN taxon
|
||||
ON observation.taxon_id = taxon.taxon_id
|
||||
WHERE strftime('%Y-%m-%d', `observation`.`date`) =:date
|
||||
GROUP BY observation.taxon_id";
|
||||
$stmt = $this->connection->prepare($sql);
|
||||
$stmt->bindValue(':date', $date);
|
||||
$result = $stmt->executeQuery();
|
||||
return $result->fetchAllAssociative();
|
||||
}
|
||||
|
||||
private function recorded_species_by_id_and_date($id, $date)
|
||||
{
|
||||
/* Get taxon even if there is no record this date */
|
||||
$sql = "SELECT * FROM `taxon` WHERE `taxon_id` = :id";
|
||||
$stmt = $this->connection->prepare($sql);
|
||||
$stmt->bindValue(':id', $id);
|
||||
$result = $stmt->executeQuery();
|
||||
$taxon = $result->fetchAssociative();
|
||||
if (!$taxon) {
|
||||
return [];
|
||||
}
|
||||
/* Get daily stats */
|
||||
$sql = "SELECT COUNT(*) AS `contact_count`, MAX(`confidence`) AS `max_confidence`
|
||||
FROM `taxon`
|
||||
INNER JOIN `observation`
|
||||
ON `taxon`.`taxon_id` = `observation`.`taxon_id`
|
||||
WHERE strftime('%Y-%m-%d', `observation`.`date`) = :date
|
||||
AND `observation`.`taxon_id` = :id";
|
||||
$stmt = $this->connection->prepare($sql);
|
||||
$stmt->bindValue(':id', $id);
|
||||
$stmt->bindValue(':date', $date);
|
||||
$result = $stmt->executeQuery();
|
||||
$stat = $result->fetchAllAssociative();
|
||||
$sql = "SELECT * FROM `observation`
|
||||
WHERE `taxon_id` = :id
|
||||
AND strftime('%Y-%m-%d', `observation`.`date`) = :date";
|
||||
$stmt = $this->connection->prepare($sql);
|
||||
$stmt->bindValue(':id', $id);
|
||||
$stmt->bindValue(':date', $date);
|
||||
$result = $stmt->executeQuery();
|
||||
$records = $result->fetchAllAssociative();
|
||||
return array("taxon" => $taxon, "stat" => $stat, "records" => $records);
|
||||
}
|
||||
|
||||
private function best_confidence_today($id, $date)
|
||||
{
|
||||
$sql = "SELECT MAX(`confidence`) AS confidence
|
||||
FROM `observation`
|
||||
WHERE strftime('%Y-%m-%d', `observation`.`date`) = :date
|
||||
AND `taxon_id` = :id";
|
||||
$stmt = $this->connection->prepare($sql);
|
||||
$stmt->bindValue(':id', $id);
|
||||
$stmt->bindValue(':date', $date);
|
||||
$result = $stmt->executeQuery();
|
||||
return $result->fetchAllAssociative();
|
||||
}
|
||||
}
|
0
www/src/Entity/.gitignore
vendored
Normal file
0
www/src/Entity/.gitignore
vendored
Normal file
0
www/src/Repository/.gitignore
vendored
Normal file
0
www/src/Repository/.gitignore
vendored
Normal file
@ -8,6 +8,33 @@
|
||||
"ref": "64d8583af5ea57b7afa4aba4b159907f3a148b05"
|
||||
}
|
||||
},
|
||||
"doctrine/doctrine-bundle": {
|
||||
"version": "2.7",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "2.4",
|
||||
"ref": "da713d006953b90d1c085c1be480ecdd6c4a95e0"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/doctrine.yaml",
|
||||
"src/Entity/.gitignore",
|
||||
"src/Repository/.gitignore"
|
||||
]
|
||||
},
|
||||
"doctrine/doctrine-migrations-bundle": {
|
||||
"version": "3.2",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "3.1",
|
||||
"ref": "ee609429c9ee23e22d6fa5728211768f51ed2818"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/doctrine_migrations.yaml",
|
||||
"migrations/.gitignore"
|
||||
]
|
||||
},
|
||||
"sensio/framework-extra-bundle": {
|
||||
"version": "6.2",
|
||||
"recipe": {
|
||||
@ -63,6 +90,15 @@
|
||||
"src/Kernel.php"
|
||||
]
|
||||
},
|
||||
"symfony/maker-bundle": {
|
||||
"version": "1.45",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
|
||||
}
|
||||
},
|
||||
"symfony/routing": {
|
||||
"version": "6.1",
|
||||
"recipe": {
|
||||
|
17
www/templates/today/index.html.twig
Normal file
17
www/templates/today/index.html.twig
Normal file
@ -0,0 +1,17 @@
|
||||
{% extends "base.html.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{{ "Today's Detected Species" | trans }}</h2>
|
||||
{# Display a list of records if any, else, print message #}
|
||||
{% if results[0] is defined and results[0] | length > 0 %}
|
||||
<ul>
|
||||
{% for sp in results %}
|
||||
<li class="species">
|
||||
<a href="./species/{{ sp['taxon_id'] }}">
|
||||
<span class="scientific-name">{{ sp["scientific_name"] }} (</span><span class="common-name">{{ sp["common_name"] }}</span>)
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endblock %}
|
72
www/templates/today/species.html.twig
Normal file
72
www/templates/today/species.html.twig
Normal file
@ -0,0 +1,72 @@
|
||||
{% extends "base.html.twig" %}
|
||||
|
||||
{% block content %}
|
||||
{% if results is defined and results | length > 0 %}
|
||||
{% set taxon = results["taxon"] %}
|
||||
<h2>
|
||||
{% set today = "now" | date("Y-m-d") %}
|
||||
{% if today == date %}
|
||||
{{ "Today's contact for" | trans }}
|
||||
{% else %}
|
||||
{{ "Contact on " | trans }}
|
||||
{{ date | format_datetime("full", "none") }}
|
||||
{{ " for " | trans }}
|
||||
{% endif %}
|
||||
<span class="scientific-name">
|
||||
{{ taxon["scientific_name"] }}
|
||||
</span>
|
||||
(<span class="common-name">
|
||||
{{ taxon["common_name"] }}
|
||||
</span>)
|
||||
</h2>
|
||||
<div class="stats">
|
||||
{% set stat = results["stat"][0] %}
|
||||
<h3>{{ "Stats" | trans }}</h3>
|
||||
<div class="contact-count">
|
||||
{{ "Contact count:" | trans }}
|
||||
<span class="counter">{{ stat["contact_count"] }}</span>
|
||||
</div>
|
||||
<div class="contact-confidence">
|
||||
{{ "Max confidence" | trans }}
|
||||
<span class="value">{{ stat["max_confidence"] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% set records = results["records"] %}
|
||||
<div class="records">
|
||||
<h3>{{ "Contact records" | trans }}</h3>
|
||||
{% if records is defined and records | length > 0 %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ "Filename" | trans }}</th>
|
||||
<th>{{ "Time" | trans }}</th>
|
||||
<th>{{ "Confidence" | trans }}</th>
|
||||
<th>{{ "Audio" | trans }}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for record in records %}
|
||||
<tr>
|
||||
<td>
|
||||
<a title="{{ "Download audio file" | trans }}" href="/media/records/{{ record['audio_file'] }}">
|
||||
{{ record["audio_file"] }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ record["date"] | date("H:m") }}</td>
|
||||
<td>{{ record["confidence"] }}</td>
|
||||
<td>
|
||||
<audio controls>
|
||||
<source src="/media/records/{{ record['audio_file'] }}" type="audio/wav">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>{{ "No records this day for this species" | trans }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user