diff --git a/assets/app.js b/assets/app.js index 10e5fcb..9576c2e 100644 --- a/assets/app.js +++ b/assets/app.js @@ -7,3 +7,12 @@ import './styles/app.css'; import { Tooltip } from './vendor/bootstrap/bootstrap.index.js'; const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]') const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new Tooltip(tooltipTriggerEl)) + +document.addEventListener('click', function (event) { + if (event.target.matches('a[href*="remove"]')) { + if (!confirm(event.target.innerText + ' ?')) { + event.preventDefault(); + return false; + } + } +}); diff --git a/assets/controllers/josm_controller.js b/assets/controllers/josm_controller.js index 7817295..b2a2842 100644 --- a/assets/controllers/josm_controller.js +++ b/assets/controllers/josm_controller.js @@ -2,8 +2,17 @@ import { Controller } from '@hotwired/stimulus'; export default class extends Controller { static values = { + imagery: String, importurl: String, layername: String, + sendosm: Boolean, + bottom: Number, + top: Number, + left: Number, + right: Number, + comment: String, + source: String, + hashtags: String, } remoteControl() { @@ -18,26 +27,44 @@ export default class extends Controller { .then(function (json) { console.log('JOSM v' + json.version); url = baseurl + '/imagery?' + (new URLSearchParams({ - 'id': 'osmfr', // 'fr.orthohr', // cf + 'id': _this.imageryValue, })); fetch(url) .then(function (response) { return response.text(); }) .then(function (text) { - console.log(text); + console.log('JOSM imagery ' + text); }); - url = baseurl + '/import?' + (new URLSearchParams({ - 'new_layer': true, - 'layer_name': _this.layernameValue, - 'url': _this.importurlValue, + if (_this.sendosmValue) { + url = baseurl + '/import?' + (new URLSearchParams({ + 'new_layer': true, + 'layer_name': _this.layernameValue, + 'url': _this.importurlValue, + })); + fetch(url) + .then(function (response) { + return response.text(); + }) + .then(function (text) { + console.log('JOSM import ' + text); + }); + } + url = baseurl + '/zoom?' + (new URLSearchParams({ + 'bottom': _this.bottomValue, + 'top': _this.topValue, + 'left': _this.leftValue, + 'right': _this.rightValue, + 'changeset_comment': _this.commentValue, + 'changeset_source': _this.sourceValue, + 'changeset_hashtags': _this.hashtagsValue, })); fetch(url) .then(function (response) { return response.text(); }) .then(function (text) { - console.log(text); + console.log('JOSM zoom ' + text); }); }); } diff --git a/composer.json b/composer.json index cbbb31f..0b73479 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ "knpuniversity/oauth2-client-bundle": "^2.18", "league/commonmark": "^2.4", "league/html-to-markdown": "^5.1", + "phayes/geophp": "^1.2", "stof/doctrine-extensions-bundle": "^1.12", "symfony/asset": "7.1.*", "symfony/asset-mapper": "7.1.*", @@ -25,6 +26,7 @@ "symfony/flex": "^2", "symfony/form": "7.1.*", "symfony/framework-bundle": "7.1.*", + "symfony/mime": "7.1.*", "symfony/runtime": "7.1.*", "symfony/security-bundle": "7.1.*", "symfony/stimulus-bundle": "^2.18", diff --git a/composer.lock b/composer.lock index 8c600af..d508f53 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d673dbc41a6c7573a7676813f6beb0c1", + "content-hash": "705ac1a078cbdb34254dbd216eaeb8c4", "packages": [ { "name": "behat/transliterator", @@ -2853,6 +2853,46 @@ "time": "2020-10-15T08:29:30+00:00" }, { + "name": "phayes/geophp", + "version": "1.2", + "source": { + "type": "git", + "url": "https://github.com/phayes/geoPHP.git", + "reference": "015404e85b602e0df1f91441f8db0f9e98f7e567" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phayes/geoPHP/zipball/015404e85b602e0df1f91441f8db0f9e98f7e567", + "reference": "015404e85b602e0df1f91441f8db0f9e98f7e567", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "4.1.*" + }, + "type": "library", + "autoload": { + "classmap": [ + "geoPHP.inc" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2 or New-BSD" + ], + "authors": [ + { + "name": "Patrick Hayes" + } + ], + "description": "GeoPHP is a open-source native PHP library for doing geometry operations. It is written entirely in PHP and can therefore run on shared hosts. It can read and write a wide variety of formats: WKT (including EWKT), WKB (including EWKB), GeoJSON, KML, GPX, GeoRSS). It works with all Simple-Feature geometries (Point, LineString, Polygon, GeometryCollection etc.) and can be used to get centroids, bounding-boxes, area, and a wide variety of other useful information.", + "homepage": "https://github.com/phayes/geoPHP", + "support": { + "issues": "https://github.com/phayes/geoPHP/issues", + "source": "https://github.com/phayes/geoPHP/tree/master" + }, + "time": "2014-12-02T06:11:22+00:00" + }, + { "name": "psr/cache", "version": "3.0.0", "source": { @@ -5398,6 +5438,90 @@ "time": "2024-05-31T14:57:53+00:00" }, { + "name": "symfony/mime", + "version": "v7.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "26a00b85477e69a4bab63b66c5dce64f18b0cbfc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/26a00b85477e69a4bab63b66c5dce64f18b0cbfc", + "reference": "26a00b85477e69a4bab63b66c5dce64f18b0cbfc", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v7.1.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-28T10:03:55+00:00" + }, + { "name": "symfony/options-resolver", "version": "v7.1.1", "source": { @@ -5699,6 +5823,90 @@ "time": "2024-05-31T15:07:36+00:00" }, { + "name": "symfony/polyfill-intl-idn", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", + "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { "name": "symfony/polyfill-intl-normalizer", "version": "v1.30.0", "source": { diff --git a/migrations/Version20240726180444.php b/migrations/Version20240726180444.php new file mode 100644 index 0000000..1643090 --- /dev/null +++ b/migrations/Version20240726180444.php @@ -0,0 +1,43 @@ +addSql('CREATE TEMPORARY TABLE __temp__comment AS SELECT id, task_id, content FROM comment'); + $this->addSql('DROP TABLE comment'); + $this->addSql('CREATE TABLE comment (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, task_id INTEGER NOT NULL, created_by_id INTEGER NOT NULL, content CLOB NOT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable) + , CONSTRAINT FK_9474526C8DB60186 FOREIGN KEY (task_id) REFERENCES task (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_9474526CB03A8386 FOREIGN KEY (created_by_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO comment (id, task_id, content) SELECT id, task_id, content FROM __temp__comment'); + $this->addSql('DROP TABLE __temp__comment'); + $this->addSql('CREATE INDEX IDX_9474526C8DB60186 ON comment (task_id)'); + $this->addSql('CREATE INDEX IDX_9474526CB03A8386 ON comment (created_by_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TEMPORARY TABLE __temp__comment AS SELECT id, task_id, content FROM comment'); + $this->addSql('DROP TABLE comment'); + $this->addSql('CREATE TABLE comment (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, task_id INTEGER NOT NULL, content CLOB NOT NULL, CONSTRAINT FK_9474526C8DB60186 FOREIGN KEY (task_id) REFERENCES task (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO comment (id, task_id, content) SELECT id, task_id, content FROM __temp__comment'); + $this->addSql('DROP TABLE __temp__comment'); + $this->addSql('CREATE INDEX IDX_9474526C8DB60186 ON comment (task_id)'); + } +} diff --git a/migrations/Version20240726191510.php b/migrations/Version20240726191510.php new file mode 100644 index 0000000..527a656 --- /dev/null +++ b/migrations/Version20240726191510.php @@ -0,0 +1,51 @@ +addSql('CREATE TEMPORARY TABLE __temp__task AS SELECT id, project_id, created_by_id, locked_by_id, name, slug, urgent, important, status, geojson, osm, description, created_at, locked_at FROM task'); + $this->addSql('DROP TABLE task'); + $this->addSql('CREATE TABLE task (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, project_id INTEGER NOT NULL, created_by_id INTEGER NOT NULL, locked_by_id INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL, urgent INTEGER DEFAULT NULL, important INTEGER DEFAULT NULL, status VARCHAR(255) NOT NULL, geojson CLOB NOT NULL, osm CLOB DEFAULT \'\' NOT NULL, description CLOB DEFAULT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable) + , locked_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) + , CONSTRAINT FK_527EDB25166D1F9C FOREIGN KEY (project_id) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_527EDB25B03A8386 FOREIGN KEY (created_by_id) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_527EDB257A88E00 FOREIGN KEY (locked_by_id) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO task (id, project_id, created_by_id, locked_by_id, name, slug, urgent, important, status, geojson, osm, description, created_at, locked_at) SELECT id, project_id, created_by_id, locked_by_id, name, slug, urgent, important, status, geojson, osm, description, created_at, locked_at FROM __temp__task'); + $this->addSql('DROP TABLE __temp__task'); + $this->addSql('CREATE INDEX IDX_527EDB257A88E00 ON task (locked_by_id)'); + $this->addSql('CREATE INDEX IDX_527EDB25B03A8386 ON task (created_by_id)'); + $this->addSql('CREATE INDEX IDX_527EDB25166D1F9C ON task (project_id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_527EDB25989D9B62 ON task (slug)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TEMPORARY TABLE __temp__task AS SELECT id, project_id, created_by_id, locked_by_id, name, slug, urgent, important, status, geojson, osm, description, created_at, locked_at FROM task'); + $this->addSql('DROP TABLE task'); + $this->addSql('CREATE TABLE task (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, project_id INTEGER NOT NULL, created_by_id INTEGER NOT NULL, locked_by_id INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL, urgent INTEGER DEFAULT NULL, important INTEGER DEFAULT NULL, status VARCHAR(255) NOT NULL, geojson CLOB NOT NULL, osm CLOB NOT NULL, description CLOB DEFAULT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable) + , locked_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) + , CONSTRAINT FK_527EDB25166D1F9C FOREIGN KEY (project_id) REFERENCES project (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_527EDB25B03A8386 FOREIGN KEY (created_by_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_527EDB257A88E00 FOREIGN KEY (locked_by_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO task (id, project_id, created_by_id, locked_by_id, name, slug, urgent, important, status, geojson, osm, description, created_at, locked_at) SELECT id, project_id, created_by_id, locked_by_id, name, slug, urgent, important, status, geojson, osm, description, created_at, locked_at FROM __temp__task'); + $this->addSql('DROP TABLE __temp__task'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_527EDB25989D9B62 ON task (slug)'); + $this->addSql('CREATE INDEX IDX_527EDB25166D1F9C ON task (project_id)'); + $this->addSql('CREATE INDEX IDX_527EDB25B03A8386 ON task (created_by_id)'); + $this->addSql('CREATE INDEX IDX_527EDB257A88E00 ON task (locked_by_id)'); + } +} diff --git a/migrations/Version20240726191624.php b/migrations/Version20240726191624.php new file mode 100644 index 0000000..d8d9817 --- /dev/null +++ b/migrations/Version20240726191624.php @@ -0,0 +1,51 @@ +addSql('CREATE TEMPORARY TABLE __temp__task AS SELECT id, project_id, created_by_id, locked_by_id, name, slug, urgent, important, status, geojson, osm, description, created_at, locked_at FROM task'); + $this->addSql('DROP TABLE task'); + $this->addSql('CREATE TABLE task (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, project_id INTEGER NOT NULL, created_by_id INTEGER NOT NULL, locked_by_id INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL, urgent INTEGER DEFAULT NULL, important INTEGER DEFAULT NULL, status VARCHAR(255) NOT NULL, geojson CLOB NOT NULL, osm CLOB DEFAULT NULL, description CLOB DEFAULT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable) + , locked_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) + , CONSTRAINT FK_527EDB25166D1F9C FOREIGN KEY (project_id) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_527EDB25B03A8386 FOREIGN KEY (created_by_id) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_527EDB257A88E00 FOREIGN KEY (locked_by_id) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO task (id, project_id, created_by_id, locked_by_id, name, slug, urgent, important, status, geojson, osm, description, created_at, locked_at) SELECT id, project_id, created_by_id, locked_by_id, name, slug, urgent, important, status, geojson, osm, description, created_at, locked_at FROM __temp__task'); + $this->addSql('DROP TABLE __temp__task'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_527EDB25989D9B62 ON task (slug)'); + $this->addSql('CREATE INDEX IDX_527EDB25166D1F9C ON task (project_id)'); + $this->addSql('CREATE INDEX IDX_527EDB25B03A8386 ON task (created_by_id)'); + $this->addSql('CREATE INDEX IDX_527EDB257A88E00 ON task (locked_by_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TEMPORARY TABLE __temp__task AS SELECT id, project_id, created_by_id, locked_by_id, name, slug, urgent, important, status, geojson, osm, description, created_at, locked_at FROM task'); + $this->addSql('DROP TABLE task'); + $this->addSql('CREATE TABLE task (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, project_id INTEGER NOT NULL, created_by_id INTEGER NOT NULL, locked_by_id INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL, urgent INTEGER DEFAULT NULL, important INTEGER DEFAULT NULL, status VARCHAR(255) NOT NULL, geojson CLOB NOT NULL, osm CLOB DEFAULT \'\' NOT NULL, description CLOB DEFAULT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable) + , locked_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) + , CONSTRAINT FK_527EDB25166D1F9C FOREIGN KEY (project_id) REFERENCES project (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_527EDB25B03A8386 FOREIGN KEY (created_by_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_527EDB257A88E00 FOREIGN KEY (locked_by_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO task (id, project_id, created_by_id, locked_by_id, name, slug, urgent, important, status, geojson, osm, description, created_at, locked_at) SELECT id, project_id, created_by_id, locked_by_id, name, slug, urgent, important, status, geojson, osm, description, created_at, locked_at FROM __temp__task'); + $this->addSql('DROP TABLE __temp__task'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_527EDB25989D9B62 ON task (slug)'); + $this->addSql('CREATE INDEX IDX_527EDB25166D1F9C ON task (project_id)'); + $this->addSql('CREATE INDEX IDX_527EDB25B03A8386 ON task (created_by_id)'); + $this->addSql('CREATE INDEX IDX_527EDB257A88E00 ON task (locked_by_id)'); + } +} diff --git a/migrations/Version20240727205448.php b/migrations/Version20240727205448.php new file mode 100644 index 0000000..9582ccb --- /dev/null +++ b/migrations/Version20240727205448.php @@ -0,0 +1,39 @@ +addSql('ALTER TABLE project ADD COLUMN hashtags VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE project ADD COLUMN source VARCHAR(255) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TEMPORARY TABLE __temp__project AS SELECT id, created_by_id, name, description, slug, created_at FROM project'); + $this->addSql('DROP TABLE project'); + $this->addSql('CREATE TABLE project (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, created_by_id INTEGER NOT NULL, name VARCHAR(255) NOT NULL, description CLOB DEFAULT NULL, slug VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable) + , CONSTRAINT FK_2FB3D0EEB03A8386 FOREIGN KEY (created_by_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO project (id, created_by_id, name, description, slug, created_at) SELECT id, created_by_id, name, description, slug, created_at FROM __temp__project'); + $this->addSql('DROP TABLE __temp__project'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_2FB3D0EE989D9B62 ON project (slug)'); + $this->addSql('CREATE INDEX IDX_2FB3D0EEB03A8386 ON project (created_by_id)'); + } +} diff --git a/migrations/Version20240728194704.php b/migrations/Version20240728194704.php new file mode 100644 index 0000000..247cf31 --- /dev/null +++ b/migrations/Version20240728194704.php @@ -0,0 +1,38 @@ +addSql('ALTER TABLE project ADD COLUMN imagery VARCHAR(255) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TEMPORARY TABLE __temp__project AS SELECT id, created_by_id, name, description, slug, created_at, hashtags, source FROM project'); + $this->addSql('DROP TABLE project'); + $this->addSql('CREATE TABLE project (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, created_by_id INTEGER NOT NULL, name VARCHAR(255) NOT NULL, description CLOB DEFAULT NULL, slug VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable) + , hashtags VARCHAR(255) DEFAULT NULL, source VARCHAR(255) DEFAULT NULL, CONSTRAINT FK_2FB3D0EEB03A8386 FOREIGN KEY (created_by_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO project (id, created_by_id, name, description, slug, created_at, hashtags, source) SELECT id, created_by_id, name, description, slug, created_at, hashtags, source FROM __temp__project'); + $this->addSql('DROP TABLE __temp__project'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_2FB3D0EE989D9B62 ON project (slug)'); + $this->addSql('CREATE INDEX IDX_2FB3D0EEB03A8386 ON project (created_by_id)'); + } +} diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php index 5dbae99..a9ec399 100644 --- a/src/Controller/HomeController.php +++ b/src/Controller/HomeController.php @@ -34,11 +34,13 @@ class HomeController extends AbstractController { $client = $clientRegistry->getClient('openstreetmap'); - try { - $resourceOwner = $client->fetchUser(); + if ($request->query->has('error')) { + $this->addFlash('danger', sprintf( + 'Échec de l’authentification (%s)', + $request->query->has('error_description') ? $request->query->get('error_description') : $request->query->get('error') + )); + } else { $this->addFlash('success', 'Authentification OSM réussie !'); - } catch (IdentityProviderException $exception) { - $this->addFlash('danger', 'Échec de l’authentification OSM !'); } return $this->redirectToRoute('app_home'); diff --git a/src/Controller/ProjectController.php b/src/Controller/ProjectController.php index 2fd5c0e..6eec49e 100644 --- a/src/Controller/ProjectController.php +++ b/src/Controller/ProjectController.php @@ -2,8 +2,10 @@ namespace App\Controller; +use App\Entity\Comment; use App\Entity\Project; use App\Entity\Task; +use App\Form\CsvType; use App\Form\ProjectType; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -21,11 +23,6 @@ class ProjectController extends AbstractController { $projects = $entityManager->getRepository(Project::class)->findAll(); - $this->addFlash( - 'info', - sprintf('%s projet%s trouvé%s', count($projects), (count($projects) > 1 ? 's' : ''), (count($projects) > 1 ? 's' : '')) - ); - return $this->render('project/index.html.twig', [ 'projects' => $projects, ]); @@ -72,29 +69,68 @@ class ProjectController extends AbstractController public function show(EntityManagerInterface $entityManager, $slug): Response { $project = $entityManager->getRepository(Project::class)->findOneBySlug($slug); - $tasks = $entityManager->getRepository(Task::class)->findByProjectPaginated($project); - if (!$project) { - $this->addFlash( - 'warning', - 'Projet non trouvé !' - ); + $this->addFlash('warning', 'Projet non trouvé !'); return $this->redirectToRoute('app_project'); } - $count = count($project->getTasks()); - $this->addFlash( - 'info', - sprintf('%s tâche%s trouvée%s', $count, ($count > 1 ? 's' : ''), ($count > 1 ? 's' : '')) - ); + $tasks = $entityManager->getRepository(Task::class)->findByProjectPaginated($project); + $randomTask = $entityManager->getRepository(Task::class)->findRandomByProject($project, Task::STATUS_TODO); + + $csvForm = $this->createForm(CsvType::class, null, [ + 'action' => $this->generateUrl('app_project_import', ['slug' => $slug]) + ]); + $csvForm->add('submit', SubmitType::class, ['label' => 'Importer']); + + $comments = $entityManager->getRepository(Comment::class)->findLatestByProject($project); return $this->render('project/show.html.twig', [ 'project' => $project, 'tasks' => $tasks, + 'csv_form' => $csvForm, + 'comments' => $comments, + 'randomTask' => $randomTask, ]); } + #[Route('/{slug}/import', name: 'app_project_import')] + public function import(Request $request, EntityManagerInterface $entityManager, $slug): Response + { + $project = $entityManager->getRepository(Project::class)->findOneBySlug($slug); + if (!$project) { + $this->addFlash('warning', 'Projet non trouvé !'); + + return $this->redirectToRoute('app_project'); + } + + $csvForm = $this->createForm(CsvType::class, null, [ + 'allow_extra_fields' => true, + ]); + $csvForm->handleRequest($request); + if ($csvForm->isSubmitted() and $csvForm->isValid()) { + $csvFile = $csvForm->get('csv')->getData(); + + $csv = fopen($csvFile->getPathName(), 'r'); + $col = array_flip(fgetcsv($csv)); + while ($row = fgetcsv($csv)) { + + $task = new Task(); + $task->setCreatedBy($this->getUser()); + $task->setProject($project); + $task->setName($row[$col['name']]); + $task->setDescription($row[$col['description']]); + $task->setOsm($row[$col['osm']]); + $task->setGeojson(json_decode($row[$col['geojson']], true)); + $task->setStatus(Task::STATUS_TODO); + $entityManager->persist($task); + } + $entityManager->flush(); + } + + return $this->redirectToRoute('app_project_show', ['slug' => $slug]); + } + #[Route('/{slug}/update', name: 'app_project_update')] public function update(Request $request, EntityManagerInterface $entityManager, $slug): Response { @@ -128,7 +164,7 @@ class ProjectController extends AbstractController 'Projet modifié !' ); - return $this->redirectToRoute('app_project'); + return $this->redirectToRoute('app_project_show', ['slug' => $slug]); } catch (\Exception $exception) { $this->addFlash( 'danger', @@ -162,11 +198,6 @@ class ProjectController extends AbstractController $entityManager->remove($project); $entityManager->flush(); - $this->addFlash( - 'success', - 'Projet supprimé !' - ); - return $this->redirectToRoute('app_project'); } catch (\Exception $exception) { $this->addFlash( diff --git a/src/Controller/TaskController.php b/src/Controller/TaskController.php index 04a0496..93668d5 100644 --- a/src/Controller/TaskController.php +++ b/src/Controller/TaskController.php @@ -11,6 +11,7 @@ use App\Service\GeoJsonManager; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\HttpFoundation\HeaderUtils; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -21,17 +22,20 @@ use Symfony\Component\Workflow\WorkflowInterface; class TaskController extends AbstractController { #[Route('/create', name: 'app_task_create')] - public function create(Request $request, EntityManagerInterface $entityManager, $slug): Response + public function create(Request $request, EntityManagerInterface $entityManager): Response { + if (!$request->query->has('slug')) { + $this->addFlash( 'warning', 'Projet non spécifié !'); + return $this->redirectToRoute('app_project'); + } + + $slug = $request->query->get('slug'); + $repository = $entityManager->getRepository(Project::class); $project = $repository->findOneBySlug($slug); if (!$project) { - $this->addFlash( - 'warning', - 'Projet non trouvé !' - ); - + $this->addFlash( 'warning', 'Projet non trouvé !'); return $this->redirectToRoute('app_project'); } @@ -58,10 +62,7 @@ class TaskController extends AbstractController return $this->redirectToRoute('app_project_show', ['slug' => $slug]); } catch (\Exception $exception) { - $this->addFlash( - 'danger', - 'Impossible de créer la tâche !' - ); + $this->addFlash('danger', 'Impossible de créer la tâche !'); } } @@ -84,9 +85,14 @@ class TaskController extends AbstractController 'Tâche non trouvée !' ); + if (!$request->headers->has('Referer')) { + throw $this->createNotFoundException('Task not found'); + } return $this->redirect($request->headers->get('Referer')); } + $nextTask = $repository->findNextOne($task); + $comment = new Comment(); $commentForm = $this->createForm(CommentType::class, $comment, [ 'action' => $this->generateUrl('app_task_comment', ['slug' => $slug]), @@ -95,10 +101,15 @@ class TaskController extends AbstractController 'label' => 'Commenter', ]); + $geom = \geoPHP::load($task->getGeojson(), 'json'); + $bbox = $geom->getBBox(); + return $this->render('task/show.html.twig', [ 'task' => $task, 'project' => $task->getProject(), 'commentForm' => $commentForm, + 'nextTask' => $nextTask, + 'bbox' => $bbox, ]); } @@ -118,6 +129,7 @@ class TaskController extends AbstractController } $comment = new Comment(); + $comment->setCreatedBy($this->getUser()); $comment->setTask($task); $commentForm = $this->createForm(CommentType::class, $comment); $commentForm->add('submit', SubmitType::class, [ @@ -132,14 +144,10 @@ class TaskController extends AbstractController $entityManager->persist($comment); $entityManager->flush(); - $this->addFlash( - 'success', - 'Commentaire ajouté !' - ); } catch (\Exception $exception) { $this->addFlash( 'danger', - 'Impossible de commenter !' + 'Impossible de commenter ! ' . $exception->getMessage() ); } } @@ -175,11 +183,6 @@ class TaskController extends AbstractController $entityManager->persist($task); $entityManager->flush(); - $this->addFlash( - 'success', - 'Tâche modifiée !' - ); - return $this->redirectToRoute('app_task_show', ['slug' => $slug]); } catch (\Exception $exception) { $this->addFlash( @@ -217,11 +220,6 @@ class TaskController extends AbstractController $entityManager->remove($task); $entityManager->flush(); - $this->addFlash( - 'success', - 'Tâche supprimée !' - ); - return $this->redirectToRoute('app_project_show', ['slug' => $project->getSlug()]); } catch (\Exception $exception) { $this->addFlash( @@ -233,7 +231,7 @@ class TaskController extends AbstractController return $this->redirectToRoute('app_project_show', ['slug' => $slug]); } - private function transition(WorkflowInterface $taskLifecycleStateMachine, EntityManagerInterface $entityManager, $slug, $transitionName): Response + private function transition(WorkflowInterface $taskLifecycleStateMachine, EntityManagerInterface $entityManager, $slug, $transitionName, $commentTemplate): Response { $repository = $entityManager->getRepository(Task::class); $task = $repository->findOneBySlug($slug); @@ -265,14 +263,18 @@ class TaskController extends AbstractController } elseif ($shouldTransitionUnlock) { $task->unlock(); } - $entityManager->persist($task); - $entityManager->flush(); - $this->addFlash( - 'success', - 'La tâche est modifiée !' - ); + $comment = new Comment(); + $comment->setTask($task); + $comment->setCreatedBy($this->getUser()); + $comment->setContent($this->renderView($commentTemplate, [ + 'user' => $this->getUser(), + 'task' => $task, + ])); + $entityManager->persist($comment); + + $entityManager->flush(); } catch (Exception $exception) { $this->addFlash( 'warning', @@ -286,28 +288,28 @@ class TaskController extends AbstractController #[Route('/{slug}/start', name: 'app_task_start')] public function start(WorkflowInterface $taskLifecycleStateMachine, EntityManagerInterface $entityManager, $slug): Response { - return $this->transition($taskLifecycleStateMachine, $entityManager, $slug, Task::TRANSITION_START); + return $this->transition($taskLifecycleStateMachine, $entityManager, $slug, Task::TRANSITION_START, 'comment/start.md.twig'); } #[Route('/{slug}/finish', name: 'app_task_finish')] public function finish(WorkflowInterface $taskLifecycleStateMachine, EntityManagerInterface $entityManager, $slug): Response { - return $this->transition($taskLifecycleStateMachine, $entityManager, $slug, Task::TRANSITION_FINISH); + return $this->transition($taskLifecycleStateMachine, $entityManager, $slug, Task::TRANSITION_FINISH, 'comment/finish.md.twig'); } #[Route('/{slug}/cancel', name: 'app_task_cancel')] public function cancel(WorkflowInterface $taskLifecycleStateMachine, EntityManagerInterface $entityManager, $slug): Response { - return $this->transition($taskLifecycleStateMachine, $entityManager, $slug, Task::TRANSITION_CANCEL); + return $this->transition($taskLifecycleStateMachine, $entityManager, $slug, Task::TRANSITION_CANCEL, 'comment/cancel.md.twig'); } #[Route('/{slug}/reset', name: 'app_task_reset')] public function reset(WorkflowInterface $taskLifecycleStateMachine, EntityManagerInterface $entityManager, $slug): Response { - return $this->transition($taskLifecycleStateMachine, $entityManager, $slug, Task::TRANSITION_RESET); + return $this->transition($taskLifecycleStateMachine, $entityManager, $slug, Task::TRANSITION_RESET, 'comment/reset.md.twig'); } - #[Route('/{slug}.geojson', name: 'app_task_geojson')] + #[Route('/download/{slug}.geojson', name: 'app_task_geojson')] public function geojson(Request $request, EntityManagerInterface $entityManager, $slug): Response { $repository = $entityManager->getRepository(Task::class); @@ -318,13 +320,52 @@ class TaskController extends AbstractController 'warning', 'Tâche non trouvée !' ); + // TODO faire pareil ailleurs où il y a des referer + if (!$request->headers->has('Referer')) { + throw $this->createNotFoundException('Task not found'); + } + return $this->redirect($request->headers->get('Referer')); + } + + $response = JsonResponse::fromJsonString($task->getGeojson()); + + $response->headers->set('Content-Disposition', HeaderUtils::makeDisposition( + HeaderUtils::DISPOSITION_ATTACHMENT, + sprintf('%s.geojson', $task->getSlug()) + )); + + return $response; + } + + #[Route('/download/{slug}.gpx', name: 'app_task_gpx')] + public function gpx(Request $request, EntityManagerInterface $entityManager, $slug): Response + { + $repository = $entityManager->getRepository(Task::class); + $task = $repository->findOneBySlug($slug); + + if (!$task) { + $this->addFlash('warning', 'Tâche non trouvée !'); + if (!$request->headers->has('Referer')) { + throw $this->createNotFoundException('Task not found'); + } return $this->redirect($request->headers->get('Referer')); } - return JsonResponse::fromJsonString($task->getGeojson()); + $geom = \geoPHP::load($task->getGeojson(), 'json'); + $gpx = $geom->out('gpx'); + + $response = new Response($gpx); + + $response->headers->set('Content-Type', 'application/xml'); + $response->headers->set('Content-Disposition', HeaderUtils::makeDisposition( + HeaderUtils::DISPOSITION_ATTACHMENT, + sprintf('%s.gpx', $task->getSlug()) + )); + + return $response; } - #[Route('/{slug}.osm', name: 'app_task_osm')] + #[Route('/download/{slug}.osm', name: 'app_task_osm')] public function osm(Request $request, EntityManagerInterface $entityManager, $slug): Response { $repository = $entityManager->getRepository(Task::class); @@ -340,8 +381,15 @@ class TaskController extends AbstractController $xml = new \DOMDocument(); $xml->loadXml($task->getOsm()); + $response = new Response($xml->saveXML()); + $response->headers->set('Content-Type', 'application/xml'); + $response->headers->set('Content-Disposition', HeaderUtils::makeDisposition( + HeaderUtils::DISPOSITION_ATTACHMENT, + sprintf('%s.osm', $task->getSlug()) + )); + return $response; } diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index 1ab590d..69dc949 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -13,6 +13,22 @@ class AppFixtures extends Fixture { public function load(ObjectManager $manager): void { + + $user = new User(); + $user->setUsername('Ptigrouick'); + $user->setOsmId(195175); + $manager->persist($user); + + $user = new User(); + $user->setUsername('pyrog'); + $user->setOsmId(270456); + $manager->persist($user); + + $user = new User(); + $user->setUsername('AdrienHegy'); + $user->setOsmId(19054951); + $manager->persist($user); + $user = new User(); $user->setUsername('caboulot'); $user->setOsmId(80638); diff --git a/src/Entity/Comment.php b/src/Entity/Comment.php index 4e7717d..52fbc50 100644 --- a/src/Entity/Comment.php +++ b/src/Entity/Comment.php @@ -5,6 +5,7 @@ namespace App\Entity; use App\Repository\CommentRepository; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use Gedmo\Mapping\Annotation as Gedmo; #[ORM\Entity(repositoryClass: CommentRepository::class)] class Comment @@ -21,6 +22,14 @@ class Comment #[ORM\JoinColumn(nullable: false)] private ?Task $task = null; + #[ORM\ManyToOne] + #[ORM\JoinColumn(nullable: false)] + private ?User $createdBy = null; + + #[ORM\Column] + #[Gedmo\Timestampable(on: 'create')] + private ?\DateTimeImmutable $createdAt = null; + public function getId(): ?int { return $this->id; @@ -49,4 +58,28 @@ class Comment return $this; } + + public function getCreatedBy(): ?User + { + return $this->createdBy; + } + + public function setCreatedBy(?User $createdBy): static + { + $this->createdBy = $createdBy; + + return $this; + } + + public function getCreatedAt(): ?\DateTimeImmutable + { + return $this->createdAt; + } + + public function setCreatedAt(\DateTimeImmutable $createdAt): static + { + $this->createdAt = $createdAt; + + return $this; + } } diff --git a/src/Entity/Project.php b/src/Entity/Project.php index d7468df..85f99f9 100644 --- a/src/Entity/Project.php +++ b/src/Entity/Project.php @@ -47,6 +47,15 @@ class Project #[Gedmo\Timestampable(on: 'create')] private ?\DateTimeImmutable $createdAt = null; + #[ORM\Column(length: 255, nullable: true)] + private ?string $hashtags = null; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $source = null; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $imagery = null; + public function __construct() { $this->tags = new ArrayCollection(); @@ -165,4 +174,40 @@ class Project return $this; } + public function getHashtags(): ?string + { + return $this->hashtags; + } + + public function setHashtags(?string $hashtags): static + { + $this->hashtags = $hashtags; + + return $this; + } + + public function getSource(): ?string + { + return $this->source; + } + + public function setSource(?string $source): static + { + $this->source = $source; + + return $this; + } + + public function getImagery(): ?string + { + return $this->imagery; + } + + public function setImagery(?string $imagery): static + { + $this->imagery = $imagery; + + return $this; + } + } diff --git a/src/Entity/Task.php b/src/Entity/Task.php index d307744..a636500 100644 --- a/src/Entity/Task.php +++ b/src/Entity/Task.php @@ -49,7 +49,7 @@ class Task #[ORM\Column(type: Types::TEXT)] private ?string $geojson = null; - #[ORM\Column(type: Types::TEXT)] + #[ORM\Column(type: Types::TEXT, nullable: true)] private ?string $osm = null; /** diff --git a/src/Form/CsvType.php b/src/Form/CsvType.php new file mode 100644 index 0000000..1adb138 --- /dev/null +++ b/src/Form/CsvType.php @@ -0,0 +1,32 @@ +add('csv', FileType::class, [ + 'mapped' => false, + 'label' => 'Fichier CSV', + 'constraints' => [ + new File([ + 'maxSize' => '10M', + 'mimeTypes' => [ + 'text/csv', + 'text/plain', + ], + 'mimeTypesMessage' => 'Type MIME inattendu', + ]) + ], + ]) + ; + } +} diff --git a/src/Form/ProjectType.php b/src/Form/ProjectType.php index d896c5d..550817b 100644 --- a/src/Form/ProjectType.php +++ b/src/Form/ProjectType.php @@ -16,6 +16,13 @@ class ProjectType extends AbstractType $builder ->add('name', null, ['label' => 'Nom']) ->add('description', null, ['label' => 'Description']) + ->add('hashtags', null, ['label' => 'Hashtags']) + ->add('source', null, ['label' => 'Source']) + ->add('imagery', null, [ + 'label' => 'Imagerie', + 'help_html' => true, + 'help' => 'Cf le wiki', + ]) ->add('tags', EntityType::class, [ 'label' => 'Étiquettes', 'class' => Tag::class, diff --git a/src/Form/TaskType.php b/src/Form/TaskType.php index 8cb9cc5..06019c8 100644 --- a/src/Form/TaskType.php +++ b/src/Form/TaskType.php @@ -17,10 +17,10 @@ class TaskType extends AbstractType ->add('name', null, ['label' => 'Nom']) ->add('description', null, ['label' => 'Description']) ->add('geojson', TextareaType::class, ['label' => 'GeoJSON']) - ->add('osm', TextareaType::class, ['label' => 'OSM']) + ->add('osm', TextareaType::class, ['label' => 'OSM', 'required' => false]) ->add('status', TaskLifecycleType::class, ['label' => 'État']) - ->add('urgent', null, ['label' => 'Urgence']) - ->add('important', null, ['label' => 'Importance']) + ->add('urgent', null, ['label' => 'Urgence', 'required' => false]) + ->add('important', null, ['label' => 'Importance', 'required' => false]) ; } diff --git a/src/Repository/CommentRepository.php b/src/Repository/CommentRepository.php index 47ea76e..b644660 100644 --- a/src/Repository/CommentRepository.php +++ b/src/Repository/CommentRepository.php @@ -2,6 +2,8 @@ namespace App\Repository; +use App\Entity\Task; +use App\Entity\Project; use App\Entity\Comment; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; @@ -16,20 +18,20 @@ class CommentRepository extends ServiceEntityRepository parent::__construct($registry, Comment::class); } - // /** - // * @return Comment[] Returns an array of Comment objects - // */ - // public function findByExampleField($value): array - // { - // return $this->createQueryBuilder('c') - // ->andWhere('c.exampleField = :val') - // ->setParameter('val', $value) - // ->orderBy('c.id', 'ASC') - // ->setMaxResults(10) - // ->getQuery() - // ->getResult() - // ; - // } + /** + * @return Comment[] Returns an array of Comment objects + */ + public function findLatestByProject(Project $project): array + { + return $this->createQueryBuilder('c') + ->join(Task::class, 't') + ->andWhere('t.project = :project') + ->setParameter('project', $project) + ->orderBy('c.createdAt', 'ASC') + ->getQuery() + ->getResult() + ; + } // public function findOneBySomeField($value): ?Comment // { diff --git a/src/Repository/TaskRepository.php b/src/Repository/TaskRepository.php index c9a67c2..cac1993 100644 --- a/src/Repository/TaskRepository.php +++ b/src/Repository/TaskRepository.php @@ -65,4 +65,38 @@ class TaskRepository extends ServiceEntityRepository ); } + public function findNextOne(Task $task, $order = 'ASC') + { + return $this->createQueryBuilder('t') + ->andWhere('t.project = :project') + ->andWhere('t.id > :id') + ->setParameter('project', $task->getProject()) + ->setParameter('id', $task->getId()) + ->orderBy('t.id', $order) + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult() + ; + } + + public function findRandomByProject(Project $project, $status = null) + { + $qb = $this->createQueryBuilder('t') + ->andWhere('t.project = :project') + ->setParameter('project', $project); + + if (!is_null($status)) { + $qb->andWhere('t.status = :status') + ->setParameter('status', $status); + } + + $tasks = $qb->getQuery() + ->getResult() + ; + + shuffle($tasks); + return reset($tasks); + } + + } diff --git a/src/Service/GeoJsonManager.php b/src/Service/GeoJsonManager.php index b2a1033..1362bb2 100644 --- a/src/Service/GeoJsonManager.php +++ b/src/Service/GeoJsonManager.php @@ -20,11 +20,15 @@ class GeoJsonManager private function getFullGeoJson(Task $task) { $data = json_decode($task->getGeojson(), true); + if (isset($data['features'][0]['properties'])) { $data['features'][0]['properties'] = array_merge($data['features'][0]['properties'], [ 'name' => $task->getName(), 'url' => $this->router->generate('app_task_show', ['slug' => $task->getSlug()], UrlGeneratorInterface::ABSOLUTE_URL), 'color' => $this->taskLifecycleStateMachine->getMetadataStore()->getPlaceMetadata($task->getStatus())['color'], ]); + } else { + dump($data); + } return $data; } diff --git a/templates/_footer.html.twig b/templates/_footer.html.twig deleted file mode 100644 index 0654e75..0000000 --- a/templates/_footer.html.twig +++ /dev/null @@ -1,7 +0,0 @@ -
-
-
-

-
-
-
diff --git a/templates/base.html.twig b/templates/base.html.twig index 39ff413..3458302 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -45,7 +45,7 @@ {% endblock %} - {% endblock %} + {% block modals %}{% endblock %} diff --git a/templates/comment/cancel.md.twig b/templates/comment/cancel.md.twig new file mode 100644 index 0000000..a909604 --- /dev/null +++ b/templates/comment/cancel.md.twig @@ -0,0 +1 @@ +{{ user.username }} abandonne la tâche [{{ task.name }}]({{ path('app_task_show', {'slug': task.slug}) }}) diff --git a/templates/comment/finish.md.twig b/templates/comment/finish.md.twig new file mode 100644 index 0000000..bd4e3dd --- /dev/null +++ b/templates/comment/finish.md.twig @@ -0,0 +1 @@ +{{ user.username }} termine la tâche [{{ task.name }}]({{ path('app_task_show', {'slug': task.slug}) }}) diff --git a/templates/comment/reset.md.twig b/templates/comment/reset.md.twig new file mode 100644 index 0000000..52f2a04 --- /dev/null +++ b/templates/comment/reset.md.twig @@ -0,0 +1 @@ +{{ user.username }} recommence la tâche [{{ task.name }}]({{ path('app_task_show', {'slug': task.slug}) }}) diff --git a/templates/comment/start.md.twig b/templates/comment/start.md.twig new file mode 100644 index 0000000..49b4489 --- /dev/null +++ b/templates/comment/start.md.twig @@ -0,0 +1 @@ +{{ user.username }} commence la tâche [{{ task.name }}]({{ path('app_task_show', {'slug': task.slug}) }}) diff --git a/templates/partials/_comment-metadata.html.twig b/templates/partials/_comment-metadata.html.twig new file mode 100644 index 0000000..d76f8a5 --- /dev/null +++ b/templates/partials/_comment-metadata.html.twig @@ -0,0 +1,12 @@ + + + + + {{ comment.createdBy.username }} + + + + + + {{ comment.createdAt|format_datetime('short', 'short', locale='fr') }} + diff --git a/templates/project/create.html.twig b/templates/project/create.html.twig index 98d960c..aabc8ce 100644 --- a/templates/project/create.html.twig +++ b/templates/project/create.html.twig @@ -2,10 +2,10 @@ {% block breadcrumb %} - + {% endblock %} -{% block page_title %}Créer un nouveau projet{% endblock %} +{% block page_title %}Créer un projet{% endblock %} {% block page_content %} {{ form(create_form) }} diff --git a/templates/project/index.html.twig b/templates/project/index.html.twig index 42248ca..6022a1c 100644 --- a/templates/project/index.html.twig +++ b/templates/project/index.html.twig @@ -37,5 +37,9 @@ {% endfor %} +{% else %} +{% if not is_granted('IS_AUTHENTICATED_FULLY') %} +

Il n’y a pas encore de projet. Connectez-vous pour pouvoir en créer un.

+{% endif %} {% endif %} {% endblock %} diff --git a/templates/project/show.html.twig b/templates/project/show.html.twig index eefe445..e6bf6b8 100644 --- a/templates/project/show.html.twig +++ b/templates/project/show.html.twig @@ -23,8 +23,12 @@ Modifier le projet Supprimer le projet {% endif %} + Créer une tâche {% endif %} + {% if randomTask %} + Piocher une tâche + {% endif %} @@ -41,12 +45,14 @@ {% endif %} +{% if project.tasks is not empty %}

Carte

{{ macro.map(project) }}
+{% endif %} {% if project.tasks is not empty %} {% set dummy = tasks.setParam('_fragment', 'tasks') %} @@ -89,4 +95,42 @@ {% endif %} + +{% if comments is not empty %} +

Commentaires

+
+
+ {% for comment in comments %} +
+ {{ comment.content|markdown_to_html }} +
+ + {% endfor %} +
+
+{% endif %} +{% endblock %} + +{% block modals %} +{{ form_start(csv_form) }} + +{{ form_end(csv_form) }} {% endblock %} diff --git a/templates/project/update.html.twig b/templates/project/update.html.twig index 187850d..3b7a3c4 100644 --- a/templates/project/update.html.twig +++ b/templates/project/update.html.twig @@ -2,10 +2,10 @@ {% block breadcrumb %} - + {% endblock %} -{% block page_title %}Modifier le projet {{ project.name }}{% endblock %} +{% block page_title %}Modifier le projet{% endblock %} {% block page_content %} {{ form(update_form) }} diff --git a/templates/task/create.html.twig b/templates/task/create.html.twig index 8053269..8590ae2 100644 --- a/templates/task/create.html.twig +++ b/templates/task/create.html.twig @@ -3,10 +3,10 @@ {% block breadcrumb %} - + {% endblock %} -{% block page_title %}Créer une nouvelle tâche{% endblock %} +{% block page_title %}Créer une tâche{% endblock %} {% block page_content %} {{ form(create_form) }} diff --git a/templates/task/show.html.twig b/templates/task/show.html.twig index 7a68937..f1a2d5d 100644 --- a/templates/task/show.html.twig +++ b/templates/task/show.html.twig @@ -32,11 +32,32 @@ Télécharger la tâche - + + {% endif %} {% endif %} + {% if nextTask %} + Tâche suivante {% endif %} @@ -66,7 +87,9 @@ +{% if not (task.comments is empty or not is_granted('IS_AUTHENTICATED_FULLY')) %}

Commentaires

+{% endif %} {% if task.comments is not empty %}
@@ -74,13 +97,19 @@
{{ comment.content|markdown_to_html }}
+ + {% endfor %}
{% endif %} +{% if is_granted('IS_AUTHENTICATED_FULLY') %}
{{ form(commentForm) }}
+{% endif %} {% endblock %} diff --git a/templates/task/update.html.twig b/templates/task/update.html.twig index c678b93..62dc915 100644 --- a/templates/task/update.html.twig +++ b/templates/task/update.html.twig @@ -6,7 +6,7 @@ {% endblock %} -{% block page_title %}Modifier la tâche {{ task.name }}{% endblock %} +{% block page_title %}Modifier la tâche{% endblock %} {% block page_content %} {{ form(update_form) }}