add progression
This commit is contained in:
17
.editorconfig
Normal file
17
.editorconfig
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[{compose.yaml,compose.*.yaml}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
57
.env
Normal file
57
.env
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# In all environments, the following files are loaded if they exist,
|
||||||
|
# the latter taking precedence over the former:
|
||||||
|
#
|
||||||
|
# * .env contains default values for the environment variables needed by the app
|
||||||
|
# * .env.local uncommitted file with local overrides
|
||||||
|
# * .env.$APP_ENV committed environment-specific defaults
|
||||||
|
# * .env.$APP_ENV.local uncommitted environment-specific overrides
|
||||||
|
#
|
||||||
|
# Real environment variables win over .env files.
|
||||||
|
#
|
||||||
|
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
|
||||||
|
# https://symfony.com/doc/current/configuration/secrets.html
|
||||||
|
#
|
||||||
|
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
|
||||||
|
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
|
||||||
|
|
||||||
|
###> symfony/framework-bundle ###
|
||||||
|
APP_ENV=dev
|
||||||
|
APP_SECRET=
|
||||||
|
###< symfony/framework-bundle ###
|
||||||
|
|
||||||
|
###> symfony/routing ###
|
||||||
|
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||||
|
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||||
|
DEFAULT_URI=http://localhost
|
||||||
|
###< symfony/routing ###
|
||||||
|
|
||||||
|
###> doctrine/doctrine-bundle ###
|
||||||
|
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
|
||||||
|
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
|
||||||
|
#
|
||||||
|
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db"
|
||||||
|
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DATABASE_URL="mysql://contrib_root:123abc@127.0.0.1:3306/contribV2?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
||||||
|
|
||||||
|
### DATABASE_URL="mysql://contrib_root:123abc@127.0.0.1:3306/contribV2?serverVersion=8.0"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
|
||||||
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|
||||||
|
###> symfony/messenger ###
|
||||||
|
# Choose one of the transports below
|
||||||
|
# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
|
||||||
|
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
|
||||||
|
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
|
||||||
|
###< symfony/messenger ###
|
||||||
|
|
||||||
|
###> symfony/mailer ###
|
||||||
|
MAILER_DSN=null://null
|
||||||
|
###< symfony/mailer ###
|
||||||
4
.env.dev
Normal file
4
.env.dev
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
###> symfony/framework-bundle ###
|
||||||
|
APP_SECRET=4c43629b815cf7d22c3785190913d8aa
|
||||||
|
###< symfony/framework-bundle ###
|
||||||
3
.env.test
Normal file
3
.env.test
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# define your env variables for the test env here
|
||||||
|
KERNEL_CLASS='App\Kernel'
|
||||||
|
APP_SECRET='$ecretf0rt3st'
|
||||||
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
###> symfony/framework-bundle ###
|
||||||
|
/.env.local
|
||||||
|
/.env.local.php
|
||||||
|
/.env.*.local
|
||||||
|
/config/secrets/prod/prod.decrypt.private.php
|
||||||
|
/public/bundles/
|
||||||
|
/var/
|
||||||
|
/vendor/
|
||||||
|
###< symfony/framework-bundle ###
|
||||||
|
|
||||||
|
###> phpunit/phpunit ###
|
||||||
|
/phpunit.xml
|
||||||
|
/.phpunit.cache/
|
||||||
|
###< phpunit/phpunit ###
|
||||||
|
|
||||||
|
###> symfony/asset-mapper ###
|
||||||
|
/public/assets/
|
||||||
|
/assets/vendor/
|
||||||
|
###< symfony/asset-mapper ###
|
||||||
3
_baseScripts/create user.sql
Normal file
3
_baseScripts/create user.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
create user 'appcontrib'@'%' identified by 'abc123';
|
||||||
|
grant all on contribV2.* to 'appcontrib'@'%';
|
||||||
|
flush privileges;
|
||||||
40
_baseScripts/structure.sql
Normal file
40
_baseScripts/structure.sql
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
create table membre(
|
||||||
|
id int auto_increment primary key,
|
||||||
|
nom varchar(50) not null,
|
||||||
|
prenom varchar(50) not null,
|
||||||
|
email varchar(100) not null unique
|
||||||
|
);
|
||||||
|
|
||||||
|
create table projet(
|
||||||
|
id int auto_increment primary key,
|
||||||
|
nom varchar(50) not null,
|
||||||
|
commentaire text,
|
||||||
|
date_lancement date,
|
||||||
|
date_cloture date,
|
||||||
|
statut varchar(20) not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table contribution(
|
||||||
|
id int auto_increment primary key,
|
||||||
|
membre_id int not null references membre(id),
|
||||||
|
projet_id int not null references projet(id),
|
||||||
|
date_contribution date not null,
|
||||||
|
commentaire text,
|
||||||
|
duree int default 0
|
||||||
|
);
|
||||||
|
|
||||||
|
create table assistant_ia(
|
||||||
|
id int auto_increment primary key,
|
||||||
|
nom varchar(50) not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table contrib_ia(
|
||||||
|
id int auto_increment primary key,
|
||||||
|
assistant_ia_id int not null references assistant_ia(id),
|
||||||
|
contribution_id int not null references contribution(id),
|
||||||
|
evaluation_pertinence int check (evaluation_pertinence >= 1 and evaluation_pertinence <= 5),
|
||||||
|
evaluation_temps int check (evaluation_temps >= 1 and evaluation_temps <= 5),
|
||||||
|
commentaire text
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
15
_projet/priorités.txt
Normal file
15
_projet/priorités.txt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
- ajouter bootstrap en mettant des liens vers les cdn
|
||||||
|
- créer une vue qui détaille le contenu d'un projet •
|
||||||
|
- liste des contribs, avec info sur usage de l'IA
|
||||||
|
- possibilité de trier cette liste (sur le nom du
|
||||||
|
- afficher "en plus- :
|
||||||
|
CSS et JS (dans tenplates/base.html.twig)
|
||||||
|
ou non
|
||||||
|
dev. la date de la contrib... )
|
||||||
|
- la durée totale des contribs (some des durées)
|
||||||
|
- la liste des développeurs ayant travaillé sur le projet
|
||||||
|
- ajout, modif, suppression d'un projet :
|
||||||
|
- on reste sur la page qui affiche la liste des projets
|
||||||
|
- les formulaires sont dans les modales
|
||||||
|
- attention : refuser de supprimer un projet s'il contient des contributions
|
||||||
|
=> pour l'ensemble : bootstrap, propre et lisible
|
||||||
10
assets/app.js
Normal file
10
assets/app.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import './bootstrap.js';
|
||||||
|
/*
|
||||||
|
* Welcome to your app's main JavaScript file!
|
||||||
|
*
|
||||||
|
* This file will be included onto the page via the importmap() Twig function,
|
||||||
|
* which should already be in your base.html.twig.
|
||||||
|
*/
|
||||||
|
import './styles/app.css';
|
||||||
|
|
||||||
|
console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉');
|
||||||
5
assets/bootstrap.js
vendored
Normal file
5
assets/bootstrap.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { startStimulusApp } from '@symfony/stimulus-bundle';
|
||||||
|
|
||||||
|
const app = startStimulusApp();
|
||||||
|
// register any custom, 3rd party controllers here
|
||||||
|
// app.register('some_controller_name', SomeImportedController);
|
||||||
15
assets/controllers.json
Normal file
15
assets/controllers.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"controllers": {
|
||||||
|
"@symfony/ux-turbo": {
|
||||||
|
"turbo-core": {
|
||||||
|
"enabled": true,
|
||||||
|
"fetch": "eager"
|
||||||
|
},
|
||||||
|
"mercure-turbo-stream": {
|
||||||
|
"enabled": false,
|
||||||
|
"fetch": "eager"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entrypoints": []
|
||||||
|
}
|
||||||
79
assets/controllers/csrf_protection_controller.js
Normal file
79
assets/controllers/csrf_protection_controller.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
const nameCheck = /^[-_a-zA-Z0-9]{4,22}$/;
|
||||||
|
const tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/;
|
||||||
|
|
||||||
|
// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager
|
||||||
|
document.addEventListener('submit', function (event) {
|
||||||
|
generateCsrfToken(event.target);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
// When @hotwired/turbo handles form submissions, send the CSRF token in a header in addition to a cookie
|
||||||
|
// The `framework.csrf_protection.check_header` config option needs to be enabled for the header to be checked
|
||||||
|
document.addEventListener('turbo:submit-start', function (event) {
|
||||||
|
const h = generateCsrfHeaders(event.detail.formSubmission.formElement);
|
||||||
|
Object.keys(h).map(function (k) {
|
||||||
|
event.detail.formSubmission.fetchRequest.headers[k] = h[k];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When @hotwired/turbo handles form submissions, remove the CSRF cookie once a form has been submitted
|
||||||
|
document.addEventListener('turbo:submit-end', function (event) {
|
||||||
|
removeCsrfToken(event.detail.formSubmission.formElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
export function generateCsrfToken (formElement) {
|
||||||
|
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
|
||||||
|
|
||||||
|
if (!csrfField) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
|
||||||
|
let csrfToken = csrfField.value;
|
||||||
|
|
||||||
|
if (!csrfCookie && nameCheck.test(csrfToken)) {
|
||||||
|
csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken);
|
||||||
|
csrfField.defaultValue = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18))));
|
||||||
|
}
|
||||||
|
csrfField.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
|
||||||
|
if (csrfCookie && tokenCheck.test(csrfToken)) {
|
||||||
|
const cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict';
|
||||||
|
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateCsrfHeaders (formElement) {
|
||||||
|
const headers = {};
|
||||||
|
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
|
||||||
|
|
||||||
|
if (!csrfField) {
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
|
||||||
|
|
||||||
|
if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
|
||||||
|
headers[csrfCookie] = csrfField.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeCsrfToken (formElement) {
|
||||||
|
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
|
||||||
|
|
||||||
|
if (!csrfField) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
|
||||||
|
|
||||||
|
if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
|
||||||
|
const cookie = csrfCookie + '_' + csrfField.value + '=0; path=/; samesite=strict; max-age=0';
|
||||||
|
|
||||||
|
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stimulusFetch: 'lazy' */
|
||||||
|
export default 'csrf-protection-controller';
|
||||||
16
assets/controllers/hello_controller.js
Normal file
16
assets/controllers/hello_controller.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Controller } from '@hotwired/stimulus';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is an example Stimulus controller!
|
||||||
|
*
|
||||||
|
* Any element with a data-controller="hello" attribute will cause
|
||||||
|
* this controller to be executed. The name "hello" comes from the filename:
|
||||||
|
* hello_controller.js -> "hello"
|
||||||
|
*
|
||||||
|
* Delete this file or adapt it for your use!
|
||||||
|
*/
|
||||||
|
export default class extends Controller {
|
||||||
|
connect() {
|
||||||
|
this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js';
|
||||||
|
}
|
||||||
|
}
|
||||||
3
assets/styles/app.css
Normal file
3
assets/styles/app.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
body {
|
||||||
|
background-color: skyblue;
|
||||||
|
}
|
||||||
21
bin/console
Normal file
21
bin/console
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Kernel;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||||
|
|
||||||
|
if (!is_dir(dirname(__DIR__).'/vendor')) {
|
||||||
|
throw new LogicException('Dependencies are missing. Try running "composer install".');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
|
||||||
|
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||||
|
|
||||||
|
return function (array $context) {
|
||||||
|
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||||
|
|
||||||
|
return new Application($kernel);
|
||||||
|
};
|
||||||
23
bin/phpunit
Normal file
23
bin/phpunit
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if (!ini_get('date.timezone')) {
|
||||||
|
ini_set('date.timezone', 'UTC');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
|
||||||
|
if (PHP_VERSION_ID >= 80000) {
|
||||||
|
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
|
||||||
|
} else {
|
||||||
|
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
|
||||||
|
require PHPUNIT_COMPOSER_INSTALL;
|
||||||
|
PHPUnit\TextUI\Command::main();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
|
||||||
|
echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
|
||||||
|
}
|
||||||
18
compose.override.yaml
Normal file
18
compose.override.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
services:
|
||||||
|
###> doctrine/doctrine-bundle ###
|
||||||
|
database:
|
||||||
|
ports:
|
||||||
|
- "5432"
|
||||||
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|
||||||
|
###> symfony/mailer ###
|
||||||
|
mailer:
|
||||||
|
image: axllent/mailpit
|
||||||
|
ports:
|
||||||
|
- "1025"
|
||||||
|
- "8025"
|
||||||
|
environment:
|
||||||
|
MP_SMTP_AUTH_ACCEPT_ANY: 1
|
||||||
|
MP_SMTP_AUTH_ALLOW_INSECURE: 1
|
||||||
|
###< symfony/mailer ###
|
||||||
25
compose.yaml
Normal file
25
compose.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
services:
|
||||||
|
###> doctrine/doctrine-bundle ###
|
||||||
|
database:
|
||||||
|
image: postgres:${POSTGRES_VERSION:-16}-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}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}"]
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 60s
|
||||||
|
volumes:
|
||||||
|
- database_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 ###
|
||||||
|
database_data:
|
||||||
|
###< doctrine/doctrine-bundle ###
|
||||||
108
composer.json
Normal file
108
composer.json
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
{
|
||||||
|
"type": "project",
|
||||||
|
"license": "proprietary",
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"prefer-stable": true,
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2",
|
||||||
|
"ext-ctype": "*",
|
||||||
|
"ext-iconv": "*",
|
||||||
|
"doctrine/dbal": "^3.10.3",
|
||||||
|
"doctrine/doctrine-bundle": "^2.18",
|
||||||
|
"doctrine/doctrine-migrations-bundle": "^3.5",
|
||||||
|
"doctrine/orm": "^3.5.2",
|
||||||
|
"phpdocumentor/reflection-docblock": "^5.6.3",
|
||||||
|
"phpstan/phpdoc-parser": "^2.3",
|
||||||
|
"symfony/asset": "7.3.*",
|
||||||
|
"symfony/asset-mapper": "7.3.*",
|
||||||
|
"symfony/console": "7.3.*",
|
||||||
|
"symfony/doctrine-messenger": "7.3.*",
|
||||||
|
"symfony/dotenv": "7.3.*",
|
||||||
|
"symfony/expression-language": "7.3.*",
|
||||||
|
"symfony/flex": "^2.8.2",
|
||||||
|
"symfony/form": "7.3.*",
|
||||||
|
"symfony/framework-bundle": "7.3.*",
|
||||||
|
"symfony/http-client": "7.3.*",
|
||||||
|
"symfony/intl": "7.3.*",
|
||||||
|
"symfony/mailer": "7.3.*",
|
||||||
|
"symfony/mime": "7.3.*",
|
||||||
|
"symfony/monolog-bundle": "^3.10",
|
||||||
|
"symfony/notifier": "7.3.*",
|
||||||
|
"symfony/process": "7.3.*",
|
||||||
|
"symfony/property-access": "7.3.*",
|
||||||
|
"symfony/property-info": "7.3.*",
|
||||||
|
"symfony/runtime": "7.3.*",
|
||||||
|
"symfony/security-bundle": "7.3.*",
|
||||||
|
"symfony/serializer": "7.3.*",
|
||||||
|
"symfony/stimulus-bundle": "^2.31",
|
||||||
|
"symfony/string": "7.3.*",
|
||||||
|
"symfony/translation": "7.3.*",
|
||||||
|
"symfony/twig-bundle": "7.3.*",
|
||||||
|
"symfony/ux-turbo": "^2.31",
|
||||||
|
"symfony/validator": "7.3.*",
|
||||||
|
"symfony/web-link": "7.3.*",
|
||||||
|
"symfony/yaml": "7.3.*",
|
||||||
|
"twig/extra-bundle": "^2.12|^3.21",
|
||||||
|
"twig/twig": "^2.12|^3.21.1"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"allow-plugins": {
|
||||||
|
"php-http/discovery": true,
|
||||||
|
"symfony/flex": true,
|
||||||
|
"symfony/runtime": true
|
||||||
|
},
|
||||||
|
"bump-after-update": true,
|
||||||
|
"sort-packages": true
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\Tests\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"replace": {
|
||||||
|
"symfony/polyfill-ctype": "*",
|
||||||
|
"symfony/polyfill-iconv": "*",
|
||||||
|
"symfony/polyfill-php72": "*",
|
||||||
|
"symfony/polyfill-php73": "*",
|
||||||
|
"symfony/polyfill-php74": "*",
|
||||||
|
"symfony/polyfill-php80": "*",
|
||||||
|
"symfony/polyfill-php81": "*",
|
||||||
|
"symfony/polyfill-php82": "*"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"auto-scripts": {
|
||||||
|
"cache:clear": "symfony-cmd",
|
||||||
|
"assets:install %PUBLIC_DIR%": "symfony-cmd",
|
||||||
|
"importmap:install": "symfony-cmd"
|
||||||
|
},
|
||||||
|
"post-install-cmd": [
|
||||||
|
"@auto-scripts"
|
||||||
|
],
|
||||||
|
"post-update-cmd": [
|
||||||
|
"@auto-scripts"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/symfony": "*"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"symfony": {
|
||||||
|
"allow-contrib": false,
|
||||||
|
"require": "7.3.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^10.5.58",
|
||||||
|
"symfony/browser-kit": "7.3.*",
|
||||||
|
"symfony/css-selector": "7.3.*",
|
||||||
|
"symfony/debug-bundle": "7.3.*",
|
||||||
|
"symfony/maker-bundle": "^1.64",
|
||||||
|
"symfony/stopwatch": "7.3.*",
|
||||||
|
"symfony/web-profiler-bundle": "7.3.*"
|
||||||
|
}
|
||||||
|
}
|
||||||
9994
composer.lock
generated
Normal file
9994
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
config/bundles.php
Normal file
16
config/bundles.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||||
|
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||||
|
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||||
|
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
|
||||||
|
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||||
|
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||||
|
Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
|
||||||
|
Symfony\UX\Turbo\TurboBundle::class => ['all' => true],
|
||||||
|
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
||||||
|
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||||
|
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
||||||
|
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||||
|
];
|
||||||
11
config/packages/asset_mapper.yaml
Normal file
11
config/packages/asset_mapper.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
framework:
|
||||||
|
asset_mapper:
|
||||||
|
# The paths to make available to the asset mapper.
|
||||||
|
paths:
|
||||||
|
- assets/
|
||||||
|
missing_import_mode: strict
|
||||||
|
|
||||||
|
when@prod:
|
||||||
|
framework:
|
||||||
|
asset_mapper:
|
||||||
|
missing_import_mode: warn
|
||||||
19
config/packages/cache.yaml
Normal file
19
config/packages/cache.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
framework:
|
||||||
|
cache:
|
||||||
|
# Unique name of your app: used to compute stable namespaces for cache keys.
|
||||||
|
#prefix_seed: your_vendor_name/app_name
|
||||||
|
|
||||||
|
# The "app" cache stores to the filesystem by default.
|
||||||
|
# The data in this cache should persist between deploys.
|
||||||
|
# Other options include:
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
#app: cache.adapter.redis
|
||||||
|
#default_redis_provider: redis://localhost
|
||||||
|
|
||||||
|
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
|
||||||
|
#app: cache.adapter.apcu
|
||||||
|
|
||||||
|
# Namespaced pools use the above "app" backend by default
|
||||||
|
#pools:
|
||||||
|
#my.dedicated.cache: null
|
||||||
11
config/packages/csrf.yaml
Normal file
11
config/packages/csrf.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Enable stateless CSRF protection for forms and logins/logouts
|
||||||
|
framework:
|
||||||
|
form:
|
||||||
|
csrf_protection:
|
||||||
|
token_id: submit
|
||||||
|
|
||||||
|
csrf_protection:
|
||||||
|
stateless_token_ids:
|
||||||
|
- submit
|
||||||
|
- authenticate
|
||||||
|
- logout
|
||||||
5
config/packages/debug.yaml
Normal file
5
config/packages/debug.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
when@dev:
|
||||||
|
debug:
|
||||||
|
# Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
|
||||||
|
# See the "server:dump" command to start a new server.
|
||||||
|
dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"
|
||||||
54
config/packages/doctrine.yaml
Normal file
54
config/packages/doctrine.yaml
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
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: '16'
|
||||||
|
|
||||||
|
profiling_collect_backtrace: '%kernel.debug%'
|
||||||
|
use_savepoints: true
|
||||||
|
orm:
|
||||||
|
auto_generate_proxy_classes: true
|
||||||
|
enable_lazy_ghost_objects: true
|
||||||
|
report_fields_where_declared: true
|
||||||
|
validate_xml_mapping: true
|
||||||
|
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||||
|
identity_generation_preferences:
|
||||||
|
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
|
||||||
|
auto_mapping: true
|
||||||
|
mappings:
|
||||||
|
App:
|
||||||
|
type: attribute
|
||||||
|
is_bundle: false
|
||||||
|
dir: '%kernel.project_dir%/src/Entity'
|
||||||
|
prefix: 'App\Entity'
|
||||||
|
alias: App
|
||||||
|
controller_resolver:
|
||||||
|
auto_mapping: false
|
||||||
|
|
||||||
|
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
|
||||||
|
proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies'
|
||||||
|
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
config/packages/doctrine_migrations.yaml
Normal file
6
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: false
|
||||||
15
config/packages/framework.yaml
Normal file
15
config/packages/framework.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||||
|
framework:
|
||||||
|
secret: '%env(APP_SECRET)%'
|
||||||
|
|
||||||
|
# Note that the session will be started ONLY if you read or write from it.
|
||||||
|
session: true
|
||||||
|
|
||||||
|
#esi: true
|
||||||
|
#fragments: true
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
framework:
|
||||||
|
test: true
|
||||||
|
session:
|
||||||
|
storage_factory_id: session.storage.factory.mock_file
|
||||||
3
config/packages/mailer.yaml
Normal file
3
config/packages/mailer.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
framework:
|
||||||
|
mailer:
|
||||||
|
dsn: '%env(MAILER_DSN)%'
|
||||||
29
config/packages/messenger.yaml
Normal file
29
config/packages/messenger.yaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
framework:
|
||||||
|
messenger:
|
||||||
|
failure_transport: failed
|
||||||
|
|
||||||
|
transports:
|
||||||
|
# https://symfony.com/doc/current/messenger.html#transport-configuration
|
||||||
|
async:
|
||||||
|
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
|
||||||
|
options:
|
||||||
|
use_notify: true
|
||||||
|
check_delayed_interval: 60000
|
||||||
|
retry_strategy:
|
||||||
|
max_retries: 3
|
||||||
|
multiplier: 2
|
||||||
|
failed: 'doctrine://default?queue_name=failed'
|
||||||
|
# sync: 'sync://'
|
||||||
|
|
||||||
|
default_bus: messenger.bus.default
|
||||||
|
|
||||||
|
buses:
|
||||||
|
messenger.bus.default: []
|
||||||
|
|
||||||
|
routing:
|
||||||
|
Symfony\Component\Mailer\Messenger\SendEmailMessage: async
|
||||||
|
Symfony\Component\Notifier\Message\ChatMessage: async
|
||||||
|
Symfony\Component\Notifier\Message\SmsMessage: async
|
||||||
|
|
||||||
|
# Route your messages to the transports
|
||||||
|
# 'App\Message\YourMessage': async
|
||||||
62
config/packages/monolog.yaml
Normal file
62
config/packages/monolog.yaml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
monolog:
|
||||||
|
channels:
|
||||||
|
- deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
|
||||||
|
|
||||||
|
when@dev:
|
||||||
|
monolog:
|
||||||
|
handlers:
|
||||||
|
main:
|
||||||
|
type: stream
|
||||||
|
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||||
|
level: debug
|
||||||
|
channels: ["!event"]
|
||||||
|
# uncomment to get logging in your browser
|
||||||
|
# you may have to allow bigger header sizes in your Web server configuration
|
||||||
|
#firephp:
|
||||||
|
# type: firephp
|
||||||
|
# level: info
|
||||||
|
#chromephp:
|
||||||
|
# type: chromephp
|
||||||
|
# level: info
|
||||||
|
console:
|
||||||
|
type: console
|
||||||
|
process_psr_3_messages: false
|
||||||
|
channels: ["!event", "!doctrine", "!console"]
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
monolog:
|
||||||
|
handlers:
|
||||||
|
main:
|
||||||
|
type: fingers_crossed
|
||||||
|
action_level: error
|
||||||
|
handler: nested
|
||||||
|
excluded_http_codes: [404, 405]
|
||||||
|
channels: ["!event"]
|
||||||
|
nested:
|
||||||
|
type: stream
|
||||||
|
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||||
|
level: debug
|
||||||
|
|
||||||
|
when@prod:
|
||||||
|
monolog:
|
||||||
|
handlers:
|
||||||
|
main:
|
||||||
|
type: fingers_crossed
|
||||||
|
action_level: error
|
||||||
|
handler: nested
|
||||||
|
excluded_http_codes: [404, 405]
|
||||||
|
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
|
||||||
|
nested:
|
||||||
|
type: stream
|
||||||
|
path: php://stderr
|
||||||
|
level: debug
|
||||||
|
formatter: monolog.formatter.json
|
||||||
|
console:
|
||||||
|
type: console
|
||||||
|
process_psr_3_messages: false
|
||||||
|
channels: ["!event", "!doctrine"]
|
||||||
|
deprecation:
|
||||||
|
type: stream
|
||||||
|
channels: [deprecation]
|
||||||
|
path: php://stderr
|
||||||
|
formatter: monolog.formatter.json
|
||||||
12
config/packages/notifier.yaml
Normal file
12
config/packages/notifier.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
framework:
|
||||||
|
notifier:
|
||||||
|
chatter_transports:
|
||||||
|
texter_transports:
|
||||||
|
channel_policy:
|
||||||
|
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
|
||||||
|
urgent: ['email']
|
||||||
|
high: ['email']
|
||||||
|
medium: ['email']
|
||||||
|
low: ['email']
|
||||||
|
admin_recipients:
|
||||||
|
- { email: admin@example.com }
|
||||||
3
config/packages/property_info.yaml
Normal file
3
config/packages/property_info.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
framework:
|
||||||
|
property_info:
|
||||||
|
with_constructor_extractor: true
|
||||||
10
config/packages/routing.yaml
Normal file
10
config/packages/routing.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
framework:
|
||||||
|
router:
|
||||||
|
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||||
|
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||||
|
default_uri: '%env(DEFAULT_URI)%'
|
||||||
|
|
||||||
|
when@prod:
|
||||||
|
framework:
|
||||||
|
router:
|
||||||
|
strict_requirements: null
|
||||||
39
config/packages/security.yaml
Normal file
39
config/packages/security.yaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
security:
|
||||||
|
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
||||||
|
password_hashers:
|
||||||
|
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
|
||||||
|
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
||||||
|
providers:
|
||||||
|
users_in_memory: { memory: null }
|
||||||
|
firewalls:
|
||||||
|
dev:
|
||||||
|
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||||
|
security: false
|
||||||
|
main:
|
||||||
|
lazy: true
|
||||||
|
provider: users_in_memory
|
||||||
|
|
||||||
|
# activate different ways to authenticate
|
||||||
|
# https://symfony.com/doc/current/security.html#the-firewall
|
||||||
|
|
||||||
|
# https://symfony.com/doc/current/security/impersonating_user.html
|
||||||
|
# switch_user: true
|
||||||
|
|
||||||
|
# Easy way to control access for large sections of your site
|
||||||
|
# Note: Only the *first* access control that matches will be used
|
||||||
|
access_control:
|
||||||
|
# - { path: ^/admin, roles: ROLE_ADMIN }
|
||||||
|
# - { path: ^/profile, roles: ROLE_USER }
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
security:
|
||||||
|
password_hashers:
|
||||||
|
# By default, password hashers are resource intensive and take time. This is
|
||||||
|
# important to generate secure password hashes. In tests however, secure hashes
|
||||||
|
# are not important, waste resources and increase test times. The following
|
||||||
|
# reduces the work factor to the lowest possible values.
|
||||||
|
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
|
||||||
|
algorithm: auto
|
||||||
|
cost: 4 # Lowest possible value for bcrypt
|
||||||
|
time_cost: 3 # Lowest possible value for argon
|
||||||
|
memory_cost: 10 # Lowest possible value for argon
|
||||||
5
config/packages/translation.yaml
Normal file
5
config/packages/translation.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
framework:
|
||||||
|
default_locale: en
|
||||||
|
translator:
|
||||||
|
default_path: '%kernel.project_dir%/translations'
|
||||||
|
providers:
|
||||||
6
config/packages/twig.yaml
Normal file
6
config/packages/twig.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
twig:
|
||||||
|
file_name_pattern: '*.twig'
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
twig:
|
||||||
|
strict_variables: true
|
||||||
4
config/packages/ux_turbo.yaml
Normal file
4
config/packages/ux_turbo.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Enable stateless CSRF protection for forms and logins/logouts
|
||||||
|
framework:
|
||||||
|
csrf_protection:
|
||||||
|
check_header: true
|
||||||
11
config/packages/validator.yaml
Normal file
11
config/packages/validator.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
framework:
|
||||||
|
validation:
|
||||||
|
# Enables validator auto-mapping support.
|
||||||
|
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
|
||||||
|
#auto_mapping:
|
||||||
|
# App\Entity\: []
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
framework:
|
||||||
|
validation:
|
||||||
|
not_compromised_password: false
|
||||||
13
config/packages/web_profiler.yaml
Normal file
13
config/packages/web_profiler.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
when@dev:
|
||||||
|
web_profiler:
|
||||||
|
toolbar: true
|
||||||
|
|
||||||
|
framework:
|
||||||
|
profiler:
|
||||||
|
collect_serializer_data: true
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
framework:
|
||||||
|
profiler:
|
||||||
|
collect: false
|
||||||
|
collect_serializer_data: true
|
||||||
5
config/preload.php
Normal file
5
config/preload.php
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
|
||||||
|
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
|
||||||
|
}
|
||||||
5
config/routes.yaml
Normal file
5
config/routes.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
controllers:
|
||||||
|
resource:
|
||||||
|
path: ../src/Controller/
|
||||||
|
namespace: App\Controller
|
||||||
|
type: attribute
|
||||||
4
config/routes/framework.yaml
Normal file
4
config/routes/framework.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
when@dev:
|
||||||
|
_errors:
|
||||||
|
resource: '@FrameworkBundle/Resources/config/routing/errors.php'
|
||||||
|
prefix: /_error
|
||||||
3
config/routes/security.yaml
Normal file
3
config/routes/security.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
_security_logout:
|
||||||
|
resource: security.route_loader.logout
|
||||||
|
type: service
|
||||||
8
config/routes/web_profiler.yaml
Normal file
8
config/routes/web_profiler.yaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
when@dev:
|
||||||
|
web_profiler_wdt:
|
||||||
|
resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'
|
||||||
|
prefix: /_wdt
|
||||||
|
|
||||||
|
web_profiler_profiler:
|
||||||
|
resource: '@WebProfilerBundle/Resources/config/routing/profiler.php'
|
||||||
|
prefix: /_profiler
|
||||||
20
config/services.yaml
Normal file
20
config/services.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# This file is the entry point to configure your own services.
|
||||||
|
# Files in the packages/ subdirectory configure your dependencies.
|
||||||
|
|
||||||
|
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||||
|
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||||
|
parameters:
|
||||||
|
|
||||||
|
services:
|
||||||
|
# default configuration for services in *this* file
|
||||||
|
_defaults:
|
||||||
|
autowire: true # Automatically injects dependencies in your services.
|
||||||
|
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||||
|
|
||||||
|
# makes classes in src/ available to be used as services
|
||||||
|
# this creates a service per class whose id is the fully-qualified class name
|
||||||
|
App\:
|
||||||
|
resource: '../src/'
|
||||||
|
|
||||||
|
# add more service definitions when explicit configuration is needed
|
||||||
|
# please note that last definitions always *replace* previous ones
|
||||||
28
importmap.php
Normal file
28
importmap.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the importmap for this application.
|
||||||
|
*
|
||||||
|
* - "path" is a path inside the asset mapper system. Use the
|
||||||
|
* "debug:asset-map" command to see the full list of paths.
|
||||||
|
*
|
||||||
|
* - "entrypoint" (JavaScript only) set to true for any module that will
|
||||||
|
* be used as an "entrypoint" (and passed to the importmap() Twig function).
|
||||||
|
*
|
||||||
|
* The "importmap:require" command can be used to add new entries to this file.
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'app' => [
|
||||||
|
'path' => './assets/app.js',
|
||||||
|
'entrypoint' => true,
|
||||||
|
],
|
||||||
|
'@hotwired/stimulus' => [
|
||||||
|
'version' => '3.2.2',
|
||||||
|
],
|
||||||
|
'@symfony/stimulus-bundle' => [
|
||||||
|
'path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js',
|
||||||
|
],
|
||||||
|
'@hotwired/turbo' => [
|
||||||
|
'version' => '7.3.0',
|
||||||
|
],
|
||||||
|
];
|
||||||
0
migrations/.gitignore
vendored
Normal file
0
migrations/.gitignore
vendored
Normal file
44
phpunit.dist.xml
Normal file
44
phpunit.dist.xml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
|
||||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||||
|
colors="true"
|
||||||
|
failOnDeprecation="true"
|
||||||
|
failOnNotice="true"
|
||||||
|
failOnWarning="true"
|
||||||
|
bootstrap="tests/bootstrap.php"
|
||||||
|
cacheDirectory=".phpunit.cache"
|
||||||
|
>
|
||||||
|
<php>
|
||||||
|
<ini name="display_errors" value="1" />
|
||||||
|
<ini name="error_reporting" value="-1" />
|
||||||
|
<server name="APP_ENV" value="test" force="true" />
|
||||||
|
<server name="SHELL_VERBOSITY" value="-1" />
|
||||||
|
</php>
|
||||||
|
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Project Test Suite">
|
||||||
|
<directory>tests</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<source ignoreSuppressionOfDeprecations="true"
|
||||||
|
ignoreIndirectDeprecations="true"
|
||||||
|
restrictNotices="true"
|
||||||
|
restrictWarnings="true"
|
||||||
|
>
|
||||||
|
<include>
|
||||||
|
<directory>src</directory>
|
||||||
|
</include>
|
||||||
|
|
||||||
|
<deprecationTrigger>
|
||||||
|
<method>Doctrine\Deprecations\Deprecation::trigger</method>
|
||||||
|
<method>Doctrine\Deprecations\Deprecation::delegateTriggerToBackend</method>
|
||||||
|
<function>trigger_deprecation</function>
|
||||||
|
</deprecationTrigger>
|
||||||
|
</source>
|
||||||
|
|
||||||
|
<extensions>
|
||||||
|
</extensions>
|
||||||
|
</phpunit>
|
||||||
94
public/css/styles.css
Normal file
94
public/css/styles.css
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/* Buttons */
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #0d6efd;
|
||||||
|
border-color: #0d6efd;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #0b5ed7;
|
||||||
|
border-color: #0a58ca;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background-color: #198754;
|
||||||
|
border-color: #198754;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.btn-success:hover {
|
||||||
|
background-color: #157347;
|
||||||
|
border-color: #146c43;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background-color: #dc3545;
|
||||||
|
border-color: #dc3545;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.btn-danger:hover {
|
||||||
|
background-color: #bb2d3b;
|
||||||
|
border-color: #b02a37;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning {
|
||||||
|
background-color: #ffc107;
|
||||||
|
border-color: #ffc107;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
.btn-warning:hover {
|
||||||
|
background-color: #e0a800;
|
||||||
|
border-color: #d39e00;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tables */
|
||||||
|
.table {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: #212529;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th, .table td {
|
||||||
|
padding: 0.75rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-top: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead th {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody tr:nth-child(even) {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container */
|
||||||
|
.container {
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modals */
|
||||||
|
.modal-content {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form inputs */
|
||||||
|
input.form-control, select.form-control, textarea.form-control {
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.form-control:focus, select.form-control:focus, textarea.form-control:focus {
|
||||||
|
border-color: #0d6efd;
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
|
||||||
|
}
|
||||||
9
public/index.php
Normal file
9
public/index.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Kernel;
|
||||||
|
|
||||||
|
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||||
|
|
||||||
|
return function (array $context) {
|
||||||
|
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||||
|
};
|
||||||
0
src/Controller/.gitignore
vendored
Normal file
0
src/Controller/.gitignore
vendored
Normal file
72
src/Controller/AssistantIaController.php
Normal file
72
src/Controller/AssistantIaController.php
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\AssistantIa;
|
||||||
|
use App\Form\AssistantIaType;
|
||||||
|
use App\Repository\AssistantIaRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
#[Route('/assistantia')]
|
||||||
|
class AssistantIaController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/', name: 'assistantia_index', methods: ['GET'])]
|
||||||
|
public function index(AssistantIaRepository $repo): Response
|
||||||
|
{
|
||||||
|
return $this->render('assistantia/crud.html.twig', [
|
||||||
|
'assistants' => $repo->findAll(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/new', name: 'assistantia_new', methods: ['GET','POST'])]
|
||||||
|
public function new(Request $request, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$assistant = new AssistantIa();
|
||||||
|
$form = $this->createForm(AssistantIaType::class, $assistant);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$em->persist($assistant);
|
||||||
|
$em->flush();
|
||||||
|
return $this->index($em->getRepository(AssistantIa::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('assistantia/_form.html.twig', [
|
||||||
|
'form' => $form->createView(),
|
||||||
|
'action' => $this->generateUrl('assistantia_new'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/{id}/edit', name: 'assistantia_edit', methods: ['GET','POST'])]
|
||||||
|
public function edit(Request $request, AssistantIa $assistant, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$form = $this->createForm(AssistantIaType::class, $assistant);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$em->flush();
|
||||||
|
return $this->index($em->getRepository(AssistantIa::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('assistantia/_form.html.twig', [
|
||||||
|
'form' => $form->createView(),
|
||||||
|
'action' => $this->generateUrl('assistantia_edit', ['id' => $assistant->getId()]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/delete/{id}', name: 'assistantia_delete', methods: ['POST'])]
|
||||||
|
public function delete(AssistantIa $assistant, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
if ($assistant->getContribIas()->count() === 0) {
|
||||||
|
$em->remove($assistant);
|
||||||
|
$em->flush();
|
||||||
|
} else {
|
||||||
|
$this->addFlash('error', 'Impossible de supprimer un assistant avec des contributions.');
|
||||||
|
}
|
||||||
|
return $this->index($em->getRepository(AssistantIa::class));
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/Controller/ContribIaController.php
Normal file
68
src/Controller/ContribIaController.php
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\ContribIa;
|
||||||
|
use App\Form\ContribIaType;
|
||||||
|
use App\Repository\ContribIaRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
#[Route('/contribia')]
|
||||||
|
class ContribIaController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/', name: 'contribia_index', methods: ['GET'])]
|
||||||
|
public function index(ContribIaRepository $repo): Response
|
||||||
|
{
|
||||||
|
return $this->render('contribia/crud.html.twig', [
|
||||||
|
'contribIas' => $repo->findAll(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/new', name: 'contribia_new', methods: ['GET','POST'])]
|
||||||
|
public function new(Request $request, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$contrib = new ContribIa();
|
||||||
|
$form = $this->createForm(ContribIaType::class, $contrib);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$em->persist($contrib);
|
||||||
|
$em->flush();
|
||||||
|
return $this->index($em->getRepository(ContribIa::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('contribia/_form.html.twig', [
|
||||||
|
'form' => $form->createView(),
|
||||||
|
'action' => $this->generateUrl('contribia_new'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/{id}/edit', name: 'contribia_edit', methods: ['GET','POST'])]
|
||||||
|
public function edit(Request $request, ContribIa $contrib, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$form = $this->createForm(ContribIaType::class, $contrib);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$em->flush();
|
||||||
|
return $this->index($em->getRepository(ContribIa::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('contribia/_form.html.twig', [
|
||||||
|
'form' => $form->createView(),
|
||||||
|
'action' => $this->generateUrl('contribia_edit', ['id' => $contrib->getId()]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/delete/{id}', name: 'contribia_delete', methods: ['POST'])]
|
||||||
|
public function delete(ContribIa $contrib, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$em->remove($contrib);
|
||||||
|
$em->flush();
|
||||||
|
return $this->index($em->getRepository(ContribIa::class));
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/Controller/ContributionController.php
Normal file
31
src/Controller/ContributionController.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\Contribution;
|
||||||
|
use App\Repository\ContributionRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
#[Route('/contribution')]
|
||||||
|
class ContributionController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/', name: 'contribution_index', methods: ['GET'])]
|
||||||
|
public function index(ContributionRepository $repo): Response
|
||||||
|
{
|
||||||
|
return $this->render('contribution/index.html.twig', [
|
||||||
|
'contributions' => $repo->findAll(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/{id}', name: 'contribution_show', methods: ['GET'])]
|
||||||
|
public function show(Contribution $contribution): Response
|
||||||
|
{
|
||||||
|
return $this->render('contribution/show.html.twig', [
|
||||||
|
'contribution' => $contribution,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/Controller/HomeController.php
Normal file
16
src/Controller/HomeController.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
class HomeController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/', name: 'home')]
|
||||||
|
public function index(): Response
|
||||||
|
{
|
||||||
|
return $this->render('home/index.html.twig');
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/Controller/MembreController.php
Normal file
31
src/Controller/MembreController.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\Membre;
|
||||||
|
use App\Repository\MembreRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
#[Route('/membre')]
|
||||||
|
class MembreController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/', name: 'membre_index', methods: ['GET'])]
|
||||||
|
public function index(MembreRepository $repo): Response
|
||||||
|
{
|
||||||
|
return $this->render('membre/index.html.twig', [
|
||||||
|
'membres' => $repo->findAll(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/{id}', name: 'membre_show', methods: ['GET'])]
|
||||||
|
public function show(Membre $membre): Response
|
||||||
|
{
|
||||||
|
return $this->render('membre/show.html.twig', [
|
||||||
|
'membre' => $membre,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
73
src/Controller/ProjetController.php
Normal file
73
src/Controller/ProjetController.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\Projet;
|
||||||
|
use App\Form\ProjetType;
|
||||||
|
use App\Repository\ProjetRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
#[Route('/projet')]
|
||||||
|
class ProjetController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/', name: 'projet_index', methods: ['GET'])]
|
||||||
|
public function index(ProjetRepository $repo): Response
|
||||||
|
{
|
||||||
|
return $this->render('projet/crud.html.twig', [
|
||||||
|
'projets' => $repo->findAll(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/new', name: 'projet_new', methods: ['GET','POST'])]
|
||||||
|
public function new(Request $request, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$projet = new Projet();
|
||||||
|
$form = $this->createForm(ProjetType::class, $projet);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$em->persist($projet);
|
||||||
|
$em->flush();
|
||||||
|
return $this->redirectToRoute('projet_index');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('projet/_form.html.twig', [
|
||||||
|
'form' => $form->createView(),
|
||||||
|
'action' => $this->generateUrl('projet_new'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/{id}/edit', name: 'projet_edit', methods: ['GET','POST'])]
|
||||||
|
public function edit(Request $request, Projet $projet, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$form = $this->createForm(ProjetType::class, $projet);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$em->flush();
|
||||||
|
return $this->redirectToRoute('projet_index');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('projet/_form.html.twig', [
|
||||||
|
'form' => $form->createView(),
|
||||||
|
'action' => $this->generateUrl('projet_edit', ['id' => $projet->getId()]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/delete/{id}', name: 'projet_delete', methods: ['POST'])]
|
||||||
|
public function delete(Projet $projet, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
if ($projet->getContributions()->count() === 0) {
|
||||||
|
$em->remove($projet);
|
||||||
|
$em->flush();
|
||||||
|
} else {
|
||||||
|
$this->addFlash('error', 'Impossible de supprimer un projet avec des contributions.');
|
||||||
|
}
|
||||||
|
return $this->redirectToRoute('projet_index');
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/Entity/.gitignore
vendored
Normal file
0
src/Entity/.gitignore
vendored
Normal file
128
src/Entity/AssistantIa.php
Normal file
128
src/Entity/AssistantIa.php
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\AssistantIaRepository;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: AssistantIaRepository::class)]
|
||||||
|
#[ORM\Table(name: 'assistant_ia')]
|
||||||
|
class AssistantIa
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column(type: 'integer')]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'string', length: 50)]
|
||||||
|
#[Assert\NotBlank(message: 'Le nom de l\'assistant IA est obligatoire.')]
|
||||||
|
#[Assert\Length(max: 50, maxMessage: 'Le nom ne peut pas dépasser {{ limit }} caractères.')]
|
||||||
|
private ?string $nom = null;
|
||||||
|
|
||||||
|
#[ORM\OneToMany(targetEntity: ContribIa::class, mappedBy: 'assistantIa', cascade: ['persist'], orphanRemoval: true)]
|
||||||
|
private Collection $contribIas;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->contribIas = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNom(): ?string
|
||||||
|
{
|
||||||
|
return $this->nom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setNom(string $nom): static
|
||||||
|
{
|
||||||
|
$this->nom = $nom;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, ContribIa>
|
||||||
|
*/
|
||||||
|
public function getContribIas(): Collection
|
||||||
|
{
|
||||||
|
return $this->contribIas;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addContribIa(ContribIa $contribIa): static
|
||||||
|
{
|
||||||
|
if (!$this->contribIas->contains($contribIa)) {
|
||||||
|
$this->contribIas->add($contribIa);
|
||||||
|
$contribIa->setAssistantIa($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeContribIa(ContribIa $contribIa): static
|
||||||
|
{
|
||||||
|
if ($this->contribIas->removeElement($contribIa)) {
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($contribIa->getAssistantIa() === $this) {
|
||||||
|
$contribIa->setAssistantIa(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule la moyenne des évaluations de pertinence
|
||||||
|
*/
|
||||||
|
public function getMoyennePertinence(): ?float
|
||||||
|
{
|
||||||
|
$total = 0;
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
foreach ($this->contribIas as $contribIa) {
|
||||||
|
if ($contribIa->getEvaluationPertinence() !== null) {
|
||||||
|
$total += $contribIa->getEvaluationPertinence();
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $count > 0 ? round($total / $count, 2) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule la moyenne des évaluations de temps
|
||||||
|
*/
|
||||||
|
public function getMoyenneTemps(): ?float
|
||||||
|
{
|
||||||
|
$total = 0;
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
foreach ($this->contribIas as $contribIa) {
|
||||||
|
if ($contribIa->getEvaluationTemps() !== null) {
|
||||||
|
$total += $contribIa->getEvaluationTemps();
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $count > 0 ? round($total / $count, 2) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne le nombre total de contributions IA
|
||||||
|
*/
|
||||||
|
public function getNombreContributions(): int
|
||||||
|
{
|
||||||
|
return $this->contribIas->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->nom ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
186
src/Entity/ContribIa.php
Normal file
186
src/Entity/ContribIa.php
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\ContribIaRepository;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: ContribIaRepository::class)]
|
||||||
|
#[ORM\Table(name: 'contrib_ia')]
|
||||||
|
#[ORM\CheckConstraints([
|
||||||
|
new ORM\CheckConstraint(
|
||||||
|
name: 'check_evaluation_pertinence',
|
||||||
|
expression: 'evaluation_pertinence >= 1 AND evaluation_pertinence <= 5'
|
||||||
|
),
|
||||||
|
new ORM\CheckConstraint(
|
||||||
|
name: 'check_evaluation_temps',
|
||||||
|
expression: 'evaluation_temps >= 1 AND evaluation_temps <= 5'
|
||||||
|
),
|
||||||
|
])]
|
||||||
|
class ContribIa
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column(type: 'integer')]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(targetEntity: AssistantIa::class, inversedBy: 'contribIas')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
#[Assert\NotNull(message: 'L\'assistant IA est obligatoire.')]
|
||||||
|
private ?AssistantIa $assistantIa = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(targetEntity: Contribution::class, inversedBy: 'contribIas')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
#[Assert\NotNull(message: 'La contribution est obligatoire.')]
|
||||||
|
private ?Contribution $contribution = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'integer', nullable: true)]
|
||||||
|
#[Assert\Range(
|
||||||
|
min: 1,
|
||||||
|
max: 5,
|
||||||
|
notInRangeMessage: 'L\'évaluation de pertinence doit être comprise entre {{ min }} et {{ max }}.'
|
||||||
|
)]
|
||||||
|
private ?int $evaluationPertinence = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'integer', nullable: true)]
|
||||||
|
#[Assert\Range(
|
||||||
|
min: 1,
|
||||||
|
max: 5,
|
||||||
|
notInRangeMessage: 'L\'évaluation de temps doit être comprise entre {{ min }} et {{ max }}.'
|
||||||
|
)]
|
||||||
|
private ?int $evaluationTemps = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||||
|
private ?string $commentaire = null;
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAssistantIa(): ?AssistantIa
|
||||||
|
{
|
||||||
|
return $this->assistantIa;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAssistantIa(?AssistantIa $assistantIa): static
|
||||||
|
{
|
||||||
|
$this->assistantIa = $assistantIa;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContribution(): ?Contribution
|
||||||
|
{
|
||||||
|
return $this->contribution;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setContribution(?Contribution $contribution): static
|
||||||
|
{
|
||||||
|
$this->contribution = $contribution;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEvaluationPertinence(): ?int
|
||||||
|
{
|
||||||
|
return $this->evaluationPertinence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEvaluationPertinence(?int $evaluationPertinence): static
|
||||||
|
{
|
||||||
|
$this->evaluationPertinence = $evaluationPertinence;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEvaluationTemps(): ?int
|
||||||
|
{
|
||||||
|
return $this->evaluationTemps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEvaluationTemps(?int $evaluationTemps): static
|
||||||
|
{
|
||||||
|
$this->evaluationTemps = $evaluationTemps;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCommentaire(): ?string
|
||||||
|
{
|
||||||
|
return $this->commentaire;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCommentaire(?string $commentaire): static
|
||||||
|
{
|
||||||
|
$this->commentaire = $commentaire;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne la moyenne des deux évaluations
|
||||||
|
*/
|
||||||
|
public function getMoyenneEvaluation(): ?float
|
||||||
|
{
|
||||||
|
if ($this->evaluationPertinence === null && $this->evaluationTemps === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
$total = 0;
|
||||||
|
|
||||||
|
if ($this->evaluationPertinence !== null) {
|
||||||
|
$total += $this->evaluationPertinence;
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->evaluationTemps !== null) {
|
||||||
|
$total += $this->evaluationTemps;
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return round($total / $count, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne un libellé pour l'évaluation de pertinence
|
||||||
|
*/
|
||||||
|
public function getLibellePertinence(): string
|
||||||
|
{
|
||||||
|
return match ($this->evaluationPertinence) {
|
||||||
|
1 => 'Très faible',
|
||||||
|
2 => 'Faible',
|
||||||
|
3 => 'Moyen',
|
||||||
|
4 => 'Bon',
|
||||||
|
5 => 'Excellent',
|
||||||
|
default => 'Non évalué',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne un libellé pour l'évaluation de temps
|
||||||
|
*/
|
||||||
|
public function getLibelleTemps(): string
|
||||||
|
{
|
||||||
|
return match ($this->evaluationTemps) {
|
||||||
|
1 => 'Très lent',
|
||||||
|
2 => 'Lent',
|
||||||
|
3 => 'Moyen',
|
||||||
|
4 => 'Rapide',
|
||||||
|
5 => 'Très rapide',
|
||||||
|
default => 'Non évalué',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'IA: %s - Contribution: %s',
|
||||||
|
$this->assistantIa ? $this->assistantIa->getNom() : '',
|
||||||
|
$this->contribution ? $this->contribution->__toString() : ''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
171
src/Entity/Contribution.php
Normal file
171
src/Entity/Contribution.php
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\ContributionRepository;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: ContributionRepository::class)]
|
||||||
|
#[ORM\Table(name: 'contribution')]
|
||||||
|
class Contribution
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column(type: 'integer')]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(targetEntity: Membre::class, inversedBy: 'contributions')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
#[Assert\NotNull(message: 'Le membre est obligatoire.')]
|
||||||
|
private ?Membre $membre = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(targetEntity: Projet::class, inversedBy: 'contributions')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
#[Assert\NotNull(message: 'Le projet est obligatoire.')]
|
||||||
|
private ?Projet $projet = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::DATE_MUTABLE)]
|
||||||
|
#[Assert\NotNull(message: 'La date de contribution est obligatoire.')]
|
||||||
|
private ?\DateTimeInterface $dateContribution = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||||
|
private ?string $commentaire = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'integer', options: ['default' => 0])]
|
||||||
|
#[Assert\PositiveOrZero(message: 'La durée doit être positive ou nulle.')]
|
||||||
|
private ?int $duree = 0;
|
||||||
|
|
||||||
|
#[ORM\OneToMany(targetEntity: ContribIa::class, mappedBy: 'contribution', cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||||
|
private Collection $contribIas;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->contribIas = new ArrayCollection();
|
||||||
|
$this->dateContribution = new \DateTime();
|
||||||
|
$this->duree = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMembre(): ?Membre
|
||||||
|
{
|
||||||
|
return $this->membre;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMembre(?Membre $membre): static
|
||||||
|
{
|
||||||
|
$this->membre = $membre;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProjet(): ?Projet
|
||||||
|
{
|
||||||
|
return $this->projet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setProjet(?Projet $projet): static
|
||||||
|
{
|
||||||
|
$this->projet = $projet;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateContribution(): ?\DateTimeInterface
|
||||||
|
{
|
||||||
|
return $this->dateContribution;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDateContribution(\DateTimeInterface $dateContribution): static
|
||||||
|
{
|
||||||
|
$this->dateContribution = $dateContribution;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCommentaire(): ?string
|
||||||
|
{
|
||||||
|
return $this->commentaire;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCommentaire(?string $commentaire): static
|
||||||
|
{
|
||||||
|
$this->commentaire = $commentaire;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDuree(): ?int
|
||||||
|
{
|
||||||
|
return $this->duree;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDuree(int $duree): static
|
||||||
|
{
|
||||||
|
$this->duree = $duree;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, ContribIa>
|
||||||
|
*/
|
||||||
|
public function getContribIas(): Collection
|
||||||
|
{
|
||||||
|
return $this->contribIas;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addContribIa(ContribIa $contribIa): static
|
||||||
|
{
|
||||||
|
if (!$this->contribIas->contains($contribIa)) {
|
||||||
|
$this->contribIas->add($contribIa);
|
||||||
|
$contribIa->setContribution($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeContribIa(ContribIa $contribIa): static
|
||||||
|
{
|
||||||
|
if ($this->contribIas->removeElement($contribIa)) {
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($contribIa->getContribution() === $this) {
|
||||||
|
$contribIa->setContribution(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne la durée formatée en heures et minutes
|
||||||
|
*/
|
||||||
|
public function getDureeFormatee(): string
|
||||||
|
{
|
||||||
|
$heures = floor($this->duree / 60);
|
||||||
|
$minutes = $this->duree % 60;
|
||||||
|
|
||||||
|
if ($heures > 0) {
|
||||||
|
return sprintf('%dh%02d', $heures, $minutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf('%d min', $minutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'%s - %s (%s)',
|
||||||
|
$this->membre ? $this->membre->__toString() : '',
|
||||||
|
$this->projet ? $this->projet->getNom() : '',
|
||||||
|
$this->dateContribution ? $this->dateContribution->format('d/m/Y') : ''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
121
src/Entity/Membre.php
Normal file
121
src/Entity/Membre.php
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\MembreRepository;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: MembreRepository::class)]
|
||||||
|
#[ORM\Table(name: 'membre')]
|
||||||
|
#[UniqueEntity(fields: ['email'], message: 'Cet email est déjà utilisé.')]
|
||||||
|
class Membre
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column(type: 'integer')]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'string', length: 50)]
|
||||||
|
#[Assert\NotBlank(message: 'Le nom est obligatoire.')]
|
||||||
|
#[Assert\Length(max: 50, maxMessage: 'Le nom ne peut pas dépasser {{ limit }} caractères.')]
|
||||||
|
private ?string $nom = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'string', length: 50)]
|
||||||
|
#[Assert\NotBlank(message: 'Le prénom est obligatoire.')]
|
||||||
|
#[Assert\Length(max: 50, maxMessage: 'Le prénom ne peut pas dépasser {{ limit }} caractères.')]
|
||||||
|
private ?string $prenom = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'string', length: 100, unique: true)]
|
||||||
|
#[Assert\NotBlank(message: 'L\'email est obligatoire.')]
|
||||||
|
#[Assert\Email(message: 'L\'email {{ value }} n\'est pas valide.')]
|
||||||
|
#[Assert\Length(max: 100, maxMessage: 'L\'email ne peut pas dépasser {{ limit }} caractères.')]
|
||||||
|
private ?string $email = null;
|
||||||
|
|
||||||
|
#[ORM\OneToMany(targetEntity: Contribution::class, mappedBy: 'membre', cascade: ['persist'], orphanRemoval: true)]
|
||||||
|
private Collection $contributions;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->contributions = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNom(): ?string
|
||||||
|
{
|
||||||
|
return $this->nom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setNom(string $nom): static
|
||||||
|
{
|
||||||
|
$this->nom = $nom;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPrenom(): ?string
|
||||||
|
{
|
||||||
|
return $this->prenom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPrenom(string $prenom): static
|
||||||
|
{
|
||||||
|
$this->prenom = $prenom;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEmail(): ?string
|
||||||
|
{
|
||||||
|
return $this->email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEmail(string $email): static
|
||||||
|
{
|
||||||
|
$this->email = $email;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, Contribution>
|
||||||
|
*/
|
||||||
|
public function getContributions(): Collection
|
||||||
|
{
|
||||||
|
return $this->contributions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addContribution(Contribution $contribution): static
|
||||||
|
{
|
||||||
|
if (!$this->contributions->contains($contribution)) {
|
||||||
|
$this->contributions->add($contribution);
|
||||||
|
$contribution->setMembre($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeContribution(Contribution $contribution): static
|
||||||
|
{
|
||||||
|
if ($this->contributions->removeElement($contribution)) {
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($contribution->getMembre() === $this) {
|
||||||
|
$contribution->setMembre(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->prenom . ' ' . $this->nom;
|
||||||
|
}
|
||||||
|
}
|
||||||
167
src/Entity/Projet.php
Normal file
167
src/Entity/Projet.php
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\ProjetRepository;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: ProjetRepository::class)]
|
||||||
|
#[ORM\Table(name: 'projet')]
|
||||||
|
class Projet
|
||||||
|
{
|
||||||
|
public const STATUT_EN_COURS = 'en_cours';
|
||||||
|
public const STATUT_TERMINE = 'termine';
|
||||||
|
public const STATUT_ANNULE = 'annule';
|
||||||
|
public const STATUT_EN_ATTENTE = 'en_attente';
|
||||||
|
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column(type: 'integer')]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'string', length: 50)]
|
||||||
|
#[Assert\NotBlank(message: 'Le nom du projet est obligatoire.')]
|
||||||
|
#[Assert\Length(max: 50, maxMessage: 'Le nom ne peut pas dépasser {{ limit }} caractères.')]
|
||||||
|
private ?string $nom = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||||
|
private ?string $commentaire = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::DATE_MUTABLE, nullable: true)]
|
||||||
|
private ?\DateTimeInterface $dateLancement = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::DATE_MUTABLE, nullable: true)]
|
||||||
|
#[Assert\GreaterThanOrEqual(propertyPath: 'dateLancement', message: 'La date de clôture doit être après la date de lancement.')]
|
||||||
|
private ?\DateTimeInterface $dateCloture = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'string', length: 20)]
|
||||||
|
#[Assert\NotBlank(message: 'Le statut est obligatoire.')]
|
||||||
|
#[Assert\Choice(
|
||||||
|
choices: [self::STATUT_EN_COURS, self::STATUT_TERMINE, self::STATUT_ANNULE, self::STATUT_EN_ATTENTE],
|
||||||
|
message: 'Le statut doit être valide.'
|
||||||
|
)]
|
||||||
|
private ?string $statut = null;
|
||||||
|
|
||||||
|
#[ORM\OneToMany(targetEntity: Contribution::class, mappedBy: 'projet', cascade: ['persist'], orphanRemoval: true)]
|
||||||
|
private Collection $contributions;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->contributions = new ArrayCollection();
|
||||||
|
$this->statut = self::STATUT_EN_ATTENTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNom(): ?string
|
||||||
|
{
|
||||||
|
return $this->nom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setNom(string $nom): static
|
||||||
|
{
|
||||||
|
$this->nom = $nom;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCommentaire(): ?string
|
||||||
|
{
|
||||||
|
return $this->commentaire;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCommentaire(?string $commentaire): static
|
||||||
|
{
|
||||||
|
$this->commentaire = $commentaire;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateLancement(): ?\DateTimeInterface
|
||||||
|
{
|
||||||
|
return $this->dateLancement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDateLancement(?\DateTimeInterface $dateLancement): static
|
||||||
|
{
|
||||||
|
$this->dateLancement = $dateLancement;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateCloture(): ?\DateTimeInterface
|
||||||
|
{
|
||||||
|
return $this->dateCloture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDateCloture(?\DateTimeInterface $dateCloture): static
|
||||||
|
{
|
||||||
|
$this->dateCloture = $dateCloture;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatut(): ?string
|
||||||
|
{
|
||||||
|
return $this->statut;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStatut(string $statut): static
|
||||||
|
{
|
||||||
|
$this->statut = $statut;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, Contribution>
|
||||||
|
*/
|
||||||
|
public function getContributions(): Collection
|
||||||
|
{
|
||||||
|
return $this->contributions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addContribution(Contribution $contribution): static
|
||||||
|
{
|
||||||
|
if (!$this->contributions->contains($contribution)) {
|
||||||
|
$this->contributions->add($contribution);
|
||||||
|
$contribution->setProjet($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeContribution(Contribution $contribution): static
|
||||||
|
{
|
||||||
|
if ($this->contributions->removeElement($contribution)) {
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($contribution->getProjet() === $this) {
|
||||||
|
$contribution->setProjet(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->nom ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getStatutChoices(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'En attente' => self::STATUT_EN_ATTENTE,
|
||||||
|
'En cours' => self::STATUT_EN_COURS,
|
||||||
|
'Terminé' => self::STATUT_TERMINE,
|
||||||
|
'Annulé' => self::STATUT_ANNULE,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/Form/AssistantIaType.php
Normal file
25
src/Form/AssistantIaType.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\AssistantIa;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class AssistantIaType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('nom', TextType::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => AssistantIa::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/Form/ContribIaType.php
Normal file
51
src/Form/ContribIaType.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\ContribIa;
|
||||||
|
use App\Entity\AssistantIa;
|
||||||
|
use App\Entity\Contribution;
|
||||||
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class ContribIaType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('assistantIa', EntityType::class, [
|
||||||
|
'class' => AssistantIa::class,
|
||||||
|
'choice_label' => 'nom',
|
||||||
|
'placeholder' => 'Choisir un assistant IA',
|
||||||
|
'required' => true,
|
||||||
|
])
|
||||||
|
->add('contribution', EntityType::class, [
|
||||||
|
'class' => Contribution::class,
|
||||||
|
'choice_label' => 'titre',
|
||||||
|
'placeholder' => 'Choisir une contribution',
|
||||||
|
'required' => true,
|
||||||
|
])
|
||||||
|
->add('evaluationPertinence', IntegerType::class, [
|
||||||
|
'required' => false,
|
||||||
|
'attr' => ['min' => 1, 'max' => 5],
|
||||||
|
])
|
||||||
|
->add('evaluationTemps', IntegerType::class, [
|
||||||
|
'required' => false,
|
||||||
|
'attr' => ['min' => 1, 'max' => 5],
|
||||||
|
])
|
||||||
|
->add('commentaire', TextareaType::class, [
|
||||||
|
'required' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => ContribIa::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/Form/ContributionType.php
Normal file
37
src/Form/ContributionType.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\Contribution;
|
||||||
|
use App\Entity\Projet;
|
||||||
|
use App\Entity\Membre;
|
||||||
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class ContributionType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('titre', TextType::class)
|
||||||
|
->add('projet', EntityType::class, [
|
||||||
|
'class' => Projet::class,
|
||||||
|
'choice_label' => 'nom',
|
||||||
|
])
|
||||||
|
->add('membre', EntityType::class, [
|
||||||
|
'class' => Membre::class,
|
||||||
|
'choice_label' => 'nom',
|
||||||
|
])
|
||||||
|
// Ajoute ici les autres champs de l'entité
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => Contribution::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/Form/MembreType.php
Normal file
26
src/Form/MembreType.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\Membre;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class MembreType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('nom', TextType::class)
|
||||||
|
// Ajoute ici les autres champs de l'entité
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => Membre::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/Form/ProjetType.php
Normal file
33
src/Form/ProjetType.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\Projet;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class ProjetType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('nom', TextType::class)
|
||||||
|
->add('commentaire', TextareaType::class, ['required' => false])
|
||||||
|
->add('dateLancement', DateType::class, ['widget' => 'single_text', 'required' => false])
|
||||||
|
->add('dateCloture', DateType::class, ['widget' => 'single_text', 'required' => false])
|
||||||
|
->add('statut', ChoiceType::class, [
|
||||||
|
'choices' => Projet::getStatutChoices(),
|
||||||
|
'placeholder' => 'Choisir un statut',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults(['data_class' => Projet::class]);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/Kernel.php
Normal file
11
src/Kernel.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
||||||
|
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
|
||||||
|
|
||||||
|
class Kernel extends BaseKernel
|
||||||
|
{
|
||||||
|
use MicroKernelTrait;
|
||||||
|
}
|
||||||
0
src/Repository/.gitignore
vendored
Normal file
0
src/Repository/.gitignore
vendored
Normal file
15
src/Repository/AssistantIaRepository.php
Normal file
15
src/Repository/AssistantIaRepository.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\AssistantIa;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
class AssistantIaRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, AssistantIa::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/Repository/ContribIaRepository.php
Normal file
15
src/Repository/ContribIaRepository.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\ContribIa;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
class ContribIaRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, ContribIa::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/Repository/ContributionRepository.php
Normal file
15
src/Repository/ContributionRepository.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\Contribution;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
class ContributionRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, Contribution::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/Repository/MembreRepository.php
Normal file
15
src/Repository/MembreRepository.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\Membre;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
class MembreRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, Membre::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/Repository/ProjetRepository.php
Normal file
28
src/Repository/ProjetRepository.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\Projet;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
class ProjetRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, Projet::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si un projet possède des contributions.
|
||||||
|
*/
|
||||||
|
public function hasContributions(Projet $projet): bool
|
||||||
|
{
|
||||||
|
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||||
|
$qb->select('count(c.id)')
|
||||||
|
->from('App\\Entity\\Contribution', 'c')
|
||||||
|
->where('c.projet = :projet')
|
||||||
|
->setParameter('projet', $projet);
|
||||||
|
return $qb->getQuery()->getSingleScalarResult() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
325
symfony.lock
Normal file
325
symfony.lock
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
{
|
||||||
|
"doctrine/deprecations": {
|
||||||
|
"version": "1.1",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "1.0",
|
||||||
|
"ref": "87424683adc81d7dc305eefec1fced883084aab9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"doctrine/doctrine-bundle": {
|
||||||
|
"version": "2.18",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "2.13",
|
||||||
|
"ref": "620b57f496f2e599a6015a9fa222c2ee0a32adcb"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/doctrine.yaml",
|
||||||
|
"./src/Entity/.gitignore",
|
||||||
|
"./src/Repository/.gitignore"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"doctrine/doctrine-migrations-bundle": {
|
||||||
|
"version": "3.5",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "3.1",
|
||||||
|
"ref": "1d01ec03c6ecbd67c3375c5478c9a423ae5d6a33"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/doctrine_migrations.yaml",
|
||||||
|
"./migrations/.gitignore"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"phpunit/phpunit": {
|
||||||
|
"version": "12.4",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "11.1",
|
||||||
|
"ref": "c6658a60fc9d594805370eacdf542c3d6b5c0869"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./.env.test",
|
||||||
|
"./phpunit.dist.xml",
|
||||||
|
"./tests/bootstrap.php",
|
||||||
|
"./bin/phpunit"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/asset-mapper": {
|
||||||
|
"version": "7.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "6.4",
|
||||||
|
"ref": "5ad1308aa756d58f999ffbe1540d1189f5d7d14a"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./assets/app.js",
|
||||||
|
"./assets/styles/app.css",
|
||||||
|
"./config/packages/asset_mapper.yaml",
|
||||||
|
"./importmap.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/console": {
|
||||||
|
"version": "7.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "5.3",
|
||||||
|
"ref": "1781ff40d8a17d87cf53f8d4cf0c8346ed2bb461"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./bin/console"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/debug-bundle": {
|
||||||
|
"version": "7.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "5.3",
|
||||||
|
"ref": "5aa8aa48234c8eb6dbdd7b3cd5d791485d2cec4b"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/debug.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/flex": {
|
||||||
|
"version": "2.8",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "2.4",
|
||||||
|
"ref": "52e9754527a15e2b79d9a610f98185a1fe46622a"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./.env",
|
||||||
|
"./.env.dev"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/form": {
|
||||||
|
"version": "7.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "7.2",
|
||||||
|
"ref": "7d86a6723f4a623f59e2bf966b6aad2fc461d36b"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/csrf.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/framework-bundle": {
|
||||||
|
"version": "7.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "7.3",
|
||||||
|
"ref": "5a1497d539f691b96afd45ae397ce5fe30beb4b9"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/cache.yaml",
|
||||||
|
"./config/packages/framework.yaml",
|
||||||
|
"./config/preload.php",
|
||||||
|
"./config/routes/framework.yaml",
|
||||||
|
"./config/services.yaml",
|
||||||
|
"./public/index.php",
|
||||||
|
"./src/Controller/.gitignore",
|
||||||
|
"./src/Kernel.php",
|
||||||
|
"./.editorconfig"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/mailer": {
|
||||||
|
"version": "7.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "4.3",
|
||||||
|
"ref": "09051cfde49476e3c12cd3a0e44289ace1c75a4f"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/mailer.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/maker-bundle": {
|
||||||
|
"version": "1.64",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "1.0",
|
||||||
|
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"symfony/messenger": {
|
||||||
|
"version": "7.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "6.0",
|
||||||
|
"ref": "ba1ac4e919baba5644d31b57a3284d6ba12d52ee"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/messenger.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/monolog-bundle": {
|
||||||
|
"version": "3.10",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "3.7",
|
||||||
|
"ref": "aff23899c4440dd995907613c1dd709b6f59503f"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/monolog.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/notifier": {
|
||||||
|
"version": "7.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "5.0",
|
||||||
|
"ref": "178877daf79d2dbd62129dd03612cb1a2cb407cc"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/notifier.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/property-info": {
|
||||||
|
"version": "7.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "7.3",
|
||||||
|
"ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/property_info.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/routing": {
|
||||||
|
"version": "7.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "7.0",
|
||||||
|
"ref": "ab1e60e2afd5c6f4a6795908f646e235f2564eb2"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/routing.yaml",
|
||||||
|
"./config/routes.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/security-bundle": {
|
||||||
|
"version": "7.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "6.4",
|
||||||
|
"ref": "2ae08430db28c8eb4476605894296c82a642028f"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/security.yaml",
|
||||||
|
"./config/routes/security.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/stimulus-bundle": {
|
||||||
|
"version": "2.30",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "2.20",
|
||||||
|
"ref": "c30f782a8910ae9f12e7a1399db607d172d23da6"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./assets/bootstrap.js",
|
||||||
|
"./assets/controllers.json",
|
||||||
|
"./assets/controllers/csrf_protection_controller.js",
|
||||||
|
"./assets/controllers/hello_controller.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/translation": {
|
||||||
|
"version": "7.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "6.3",
|
||||||
|
"ref": "620a1b84865ceb2ba304c8f8bf2a185fbf32a843"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/translation.yaml",
|
||||||
|
"./translations/.gitignore"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/twig-bundle": {
|
||||||
|
"version": "7.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "6.4",
|
||||||
|
"ref": "cab5fd2a13a45c266d45a7d9337e28dee6272877"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/twig.yaml",
|
||||||
|
"./templates/base.html.twig"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/ux-turbo": {
|
||||||
|
"version": "2.30",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "2.20",
|
||||||
|
"ref": "287f7c6eb6e9b65e422d34c00795b360a787380b"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/ux_turbo.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/validator": {
|
||||||
|
"version": "7.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "7.0",
|
||||||
|
"ref": "8c1c4e28d26a124b0bb273f537ca8ce443472bfd"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/validator.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/web-profiler-bundle": {
|
||||||
|
"version": "7.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "7.3",
|
||||||
|
"ref": "a363460c1b0b4a4d0242f2ce1a843ca0f6ac9026"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/web_profiler.yaml",
|
||||||
|
"./config/routes/web_profiler.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/webapp-pack": {
|
||||||
|
"version": "1.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "1.0",
|
||||||
|
"ref": "7d5c5e282f7e2c36a2c3bbb1504f78456c352407"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./config/packages/messenger.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"twig/extra-bundle": {
|
||||||
|
"version": "v3.21.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
templates/assistantia/_form.html.twig
Normal file
6
templates/assistantia/_form.html.twig
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{{ form_start(form, {'action': action, 'attr': {'class': 'ajax-form'}}) }}
|
||||||
|
{{ form_widget(form) }}
|
||||||
|
<div class="mt-3 text-end">
|
||||||
|
<button class="btn btn-success">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
{{ form_end(form) }}
|
||||||
19
templates/assistantia/_list.html.twig
Normal file
19
templates/assistantia/_list.html.twig
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr><th>ID</th><th>Nom</th><th>Actions</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for assistant in assistants %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ assistant.id }}</td>
|
||||||
|
<td>{{ assistant.nom }}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-warning btn-edit" data-url="{{ path('assistantia_edit', {'id': assistant.id}) }}">Modifier</button>
|
||||||
|
<form style="display:inline" method="post" action="{{ path('assistantia_delete', {'id': assistant.id}) }}">
|
||||||
|
<button class="btn btn-danger" onclick="return confirm('Supprimer ?')">Supprimer</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
44
templates/assistantia/crud.html.twig
Normal file
44
templates/assistantia/crud.html.twig
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
{% block title %}CRUD Assistant IA{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>CRUD Assistant IA</h1>
|
||||||
|
<div class="d-flex gap-3">
|
||||||
|
<div style="flex:1;">
|
||||||
|
<h2>Ajouter / Modifier</h2>
|
||||||
|
<button class="btn btn-primary mb-3" id="btnAdd">+ Nouvel assistant IA</button>
|
||||||
|
{% if form is defined %}
|
||||||
|
{% include 'assistantia/_form.html.twig' with {'form': form, 'action': path('assistantia_new')} %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div style="flex:2;">
|
||||||
|
<h2>Liste</h2>
|
||||||
|
{% include 'assistantia/_list.html.twig' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="crudModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog"><div class="modal-content">
|
||||||
|
<div class="modal-header"><h5 class="modal-title">Formulaire</h5></div>
|
||||||
|
<div class="modal-body" id="modal-body"></div>
|
||||||
|
</div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('click', async e => {
|
||||||
|
if(e.target.id === 'btnAdd'){
|
||||||
|
const res = await fetch('{{ path("assistantia_new") }}');
|
||||||
|
const html = await res.text();
|
||||||
|
document.getElementById('modal-body').innerHTML = html;
|
||||||
|
new bootstrap.Modal(document.getElementById('crudModal')).show();
|
||||||
|
}
|
||||||
|
if(e.target.classList.contains('btn-edit')){
|
||||||
|
const url = e.target.dataset.url;
|
||||||
|
const res = await fetch(url);
|
||||||
|
const html = await res.text();
|
||||||
|
document.getElementById('modal-body').innerHTML = html;
|
||||||
|
new bootstrap.Modal(document.getElementById('crudModal')).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
39
templates/base.html.twig
Normal file
39
templates/base.html.twig
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{% block title %}My Application{% endblock %}</title>
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="{{ asset('css/styles.css') }}">
|
||||||
|
{% block stylesheets %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header class="bg-light mb-4 shadow-sm">
|
||||||
|
<nav class="container py-3">
|
||||||
|
<ul class="nav justify-content-center">
|
||||||
|
<li class="nav-item"><a class="nav-link btn btn-primary me-2" href="{{ path('home') }}">Accueil</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link btn btn-primary me-2" href="{{ path('assistantia_index') }}">Assistant IA</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link btn btn-primary me-2" href="{{ path('contribia_index') }}">Contrib IA</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link btn btn-primary me-2" href="{{ path('contribution_index') }}">Contribution</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link btn btn-primary me-2" href="{{ path('membre_index') }}">Membre</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link btn btn-primary" href="{{ path('projet_index') }}">Projet</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container my-4">
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="bg-light text-center py-3 mt-auto shadow-top">
|
||||||
|
<p class="mb-0">© {{ "now"|date("Y") }} My Application</p>
|
||||||
|
{% block footer %}{% endblock %}
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="{{ asset('js/scripts.js') }}"></script>
|
||||||
|
{% block javascripts %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
0
templates/contribia/_form.html.twig
Normal file
0
templates/contribia/_form.html.twig
Normal file
22
templates/contribia/_list.html.twig
Normal file
22
templates/contribia/_list.html.twig
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr><th>ID</th><th>Assistant IA</th><th>Contribution</th><th>Pertinence</th><th>Temps</th><th>Actions</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for contrib in contribIas %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ contrib.id }}</td>
|
||||||
|
<td>{{ contrib.assistantIa.nom }}</td>
|
||||||
|
<td>{{ contrib.contribution.titre }}</td>
|
||||||
|
<td>{{ contrib.getLibellePertinence() }}</td>
|
||||||
|
<td>{{ contrib.getLibelleTemps() }}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-warning btn-edit" data-url="{{ path('contribia_edit', {'id': contrib.id}) }}">Modifier</button>
|
||||||
|
<form style="display:inline" method="post" action="{{ path('contribia_delete', {'id': contrib.id}) }}">
|
||||||
|
<button class="btn btn-danger" onclick="return confirm('Supprimer ?')">Supprimer</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
44
templates/contribia/crud.html.twig
Normal file
44
templates/contribia/crud.html.twig
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
{% block title %}CRUD Contrib IA{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>CRUD Contrib IA</h1>
|
||||||
|
<div class="d-flex gap-3">
|
||||||
|
<div style="flex:1;">
|
||||||
|
<h2>Ajouter / Modifier</h2>
|
||||||
|
<button class="btn btn-primary mb-3" id="btnAdd">+ Nouvelle contribution IA</button>
|
||||||
|
{% if form is defined %}
|
||||||
|
{% include 'contribia/_form.html.twig' with {'form': form, 'action': path('contribia_new')} %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div style="flex:2;">
|
||||||
|
<h2>Liste</h2>
|
||||||
|
{% include 'contribia/_list.html.twig' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="crudModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog"><div class="modal-content">
|
||||||
|
<div class="modal-header"><h5 class="modal-title">Formulaire</h5></div>
|
||||||
|
<div class="modal-body" id="modal-body"></div>
|
||||||
|
</div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('click', async e => {
|
||||||
|
if(e.target.id === 'btnAdd'){
|
||||||
|
const res = await fetch('{{ path("contribia_new") }}');
|
||||||
|
const html = await res.text();
|
||||||
|
document.getElementById('modal-body').innerHTML = html;
|
||||||
|
new bootstrap.Modal(document.getElementById('crudModal')).show();
|
||||||
|
}
|
||||||
|
if(e.target.classList.contains('btn-edit')){
|
||||||
|
const url = e.target.dataset.url;
|
||||||
|
const res = await fetch(url);
|
||||||
|
const html = await res.text();
|
||||||
|
document.getElementById('modal-body').innerHTML = html;
|
||||||
|
new bootstrap.Modal(document.getElementById('crudModal')).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
12
templates/contribution/index.html.twig
Normal file
12
templates/contribution/index.html.twig
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
{% block title %}Liste des Contributions{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<h2>Liste des Contributions</h2>
|
||||||
|
<ul>
|
||||||
|
{% for c in contributions %}
|
||||||
|
<li><a href="{{ path('contribution_show', {'id': c.id}) }}">Contribution #{{ c.id }}</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li>Aucune contribution trouvée.</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
||||||
7
templates/contribution/show.html.twig
Normal file
7
templates/contribution/show.html.twig
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
{% block title %}Détail Contribution{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<h2>Détail Contribution</h2>
|
||||||
|
<pre>{{ dump(contribution) }}</pre>
|
||||||
|
<a href="{{ path('contribution_index') }}">Retour à la liste</a>
|
||||||
|
{% endblock %}
|
||||||
8
templates/home/index.html.twig
Normal file
8
templates/home/index.html.twig
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Accueil{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>Bienvenue sur la page d'accueil</h1>
|
||||||
|
<p>Utilisez la barre de navigation pour accéder aux différentes sections.</p>
|
||||||
|
{% endblock %}
|
||||||
12
templates/membre/index.html.twig
Normal file
12
templates/membre/index.html.twig
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
{% block title %}Liste des Membres{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<h2>Liste des Membres</h2>
|
||||||
|
<ul>
|
||||||
|
{% for m in membres %}
|
||||||
|
<li><a href="{{ path('membre_show', {'id': m.id}) }}">Membre #{{ m.id }}</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li>Aucun membre trouvé.</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
||||||
7
templates/membre/show.html.twig
Normal file
7
templates/membre/show.html.twig
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
{% block title %}Détail Membre{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<h2>Détail Membre</h2>
|
||||||
|
<pre>{{ dump(membre) }}</pre>
|
||||||
|
<a href="{{ path('membre_index') }}">Retour à la liste</a>
|
||||||
|
{% endblock %}
|
||||||
6
templates/projet/_form.html.twig
Normal file
6
templates/projet/_form.html.twig
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{{ form_start(form, {'action': action}) }}
|
||||||
|
{{ form_widget(form) }}
|
||||||
|
<div class="mt-3 text-end">
|
||||||
|
<button class="btn btn-success">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
{{ form_end(form) }}
|
||||||
14
templates/projet/_list.html.twig
Normal file
14
templates/projet/_list.html.twig
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<table class="table">
|
||||||
|
<thead><tr><th>ID</th><th>Nom</th><th>Actions</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for projet in projets %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ projet.id }}</td>
|
||||||
|
<td>{{ projet.nom }}</td>
|
||||||
|
<td>
|
||||||
|
<button data-bs-toggle="modal" data-bs-target="#modal" data-action="{{ path('projet_edit', {'id': projet.id}) }}">Edit</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
51
templates/projet/crud.html.twig
Normal file
51
templates/projet/crud.html.twig
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
{% block body %}
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h1>Gestion des projets</h1>
|
||||||
|
<button class="btn btn-primary mb-3" id="btnAdd">+ Nouveau projet</button>
|
||||||
|
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead><tr><th>ID</th><th>Nom</th><th>Statut</th><th>Actions</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for projet in projets %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ projet.id }}</td>
|
||||||
|
<td>{{ projet.nom }}</td>
|
||||||
|
<td>{{ projet.statut }}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-warning btn-edit" data-url="{{ path('projet_edit', {'id': projet.id}) }}">Modifier</button>
|
||||||
|
<form style="display:inline" method="post" action="{{ path('projet_delete', {'id': projet.id}) }}">
|
||||||
|
<button class="btn btn-danger" onclick="return confirm('Supprimer ?')">Supprimer</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="crudModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog"><div class="modal-content">
|
||||||
|
<div class="modal-header"><h5 class="modal-title">Formulaire</h5></div>
|
||||||
|
<div class="modal-body" id="modal-body"></div>
|
||||||
|
</div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('click', async e => {
|
||||||
|
if(e.target.id === 'btnAdd'){
|
||||||
|
const res = await fetch('{{ path("projet_new") }}');
|
||||||
|
const html = await res.text();
|
||||||
|
document.getElementById('modal-body').innerHTML = html;
|
||||||
|
new bootstrap.Modal(document.getElementById('crudModal')).show();
|
||||||
|
}
|
||||||
|
if(e.target.classList.contains('btn-edit')){
|
||||||
|
const url = e.target.dataset.url;
|
||||||
|
const res = await fetch(url);
|
||||||
|
const html = await res.text();
|
||||||
|
document.getElementById('modal-body').innerHTML = html;
|
||||||
|
new bootstrap.Modal(document.getElementById('crudModal')).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
13
tests/bootstrap.php
Normal file
13
tests/bootstrap.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\Dotenv\Dotenv;
|
||||||
|
|
||||||
|
require dirname(__DIR__).'/vendor/autoload.php';
|
||||||
|
|
||||||
|
if (method_exists(Dotenv::class, 'bootEnv')) {
|
||||||
|
(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['APP_DEBUG']) {
|
||||||
|
umask(0000);
|
||||||
|
}
|
||||||
0
translations/.gitignore
vendored
Normal file
0
translations/.gitignore
vendored
Normal file
Reference in New Issue
Block a user