Compare commits

...

4 Commits

Author SHA1 Message Date
Julien Rosset 4e299630b6 Add controller for material types 10 months ago
Julien Rosset 067786a3ea Fix routes for User 10 months ago
Julien Rosset 8fef71ef6c Preparing assets 10 months ago
Julien Rosset eca2464430 Migration to Symfony 7.3 10 months ago

@ -1,12 +1,18 @@
//region CSS
import './styles/app.scss';
//endregion
//region Bootstrap
import 'bootstrap';
//endregion
//region jQuery
import 'jqueryLocal'; // Declare $ as a global variable, accessible in all files
//endregion
//region Bootstrap
import '@popperjs/core';
import {Tooltip} from 'bootstrap';
//region Tooltips
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
[...tooltipTriggerList].map(tooltipTriggerEl => new Tooltip(tooltipTriggerEl));
//endregion
//endregion
//region FontAwesome
import 'fontawesome';
//endregion
import 'modalDynamic';

@ -1,4 +1,15 @@
{
"controllers": {},
"controllers": {
"@symfony/ux-turbo": {
"turbo-core": {
"enabled": true,
"fetch": "eager"
},
"mercure-turbo-stream": {
"enabled": false,
"fetch": "eager"
}
}
},
"entrypoints": []
}

@ -1,148 +0,0 @@
//region Datatables.net
import 'datatables.net';
import 'datatables.net-dt/css/dataTables.dataTables.min.css';
//endregion
//region Bootstrap 5 styling for Datatables.net
import 'datatables.net-bs5';
import 'datatables.net-bs5/css/dataTables.bootstrap5.min.css';
//endregion
//region Custom style for Datatables.net
import './styles/datatables.scss';
//endregion
import 'jqueryLocal';
$(function () {
$('table.table-datatable').each(function () {
const self = $(this);
//region Options du datatable
let options = {
paging: self.data('sortPaging', false),
pageLength: self.data('sortPerPage', 50),
processing: true,
layout: self.data(
'sortLayout',
{
topStart: null,
topEnd: null,
bottomStart: null,
bottomEnd: 'paging',
},
),
};
//endregion
//region Gestion tri initial
let initialSort = [];
$('> thead > tr > th[data-sort-onLoad]', this).each(function () {
let self = $(this);
initialSort.push(
{
index: self.index(),
priority: self.data('sortOnload'),
direction: self.data('sortOrder', 'asc').toLowerCase(),
},
);
});
initialSort.sort(function (sort1, sort2) {
if (sort1.priority === sort2.priority) {
return sort1.index - sort2.index;
}
return sort1.priority - sort2.priority;
});
if (initialSort.length > 0) {
options.order = [];
initialSort.forEach(function (sortOrder) {
options.order.push([sortOrder.index, sortOrder.direction]);
});
}
//endregion
//region Gestion sens de tri (1er clic ou initial si concerné)
let descSort = [];
$('> thead > tr > th[data-sort-order]', this).each(function () {
const self = $(this);
if (self.data('sortOrder', 'asc').toLowerCase() === 'desc') {
descSort.push(self.index());
}
});
if (descSort.length > 0) {
if (!options.hasOwnProperty('columnDefs')) {
options.columnDefs = [];
}
options.columnDefs.push(
{
targets: descSort,
orderSequence: ['desc', 'asc'],
},
);
}
//endregion
//region Gestion désactivation tri
let disabledSort = [];
$('> thead > tr > th[data-sort]', this).each(function () {
const self = $(this);
if (!self.data('sort', true)) {
disabledSort.push(self.index());
}
});
if (disabledSort.length > 0) {
if (!options.hasOwnProperty('columnDefs')) {
options.columnDefs = [];
}
options.columnDefs.push(
{
targets: disabledSort,
orderable: false,
},
);
}
//endregion
//region Gestion nom des colonnes
const columnsName = $('> thead > tr > th[data-sort-name]', this);
if (columnsName.length > 0) {
if (!options.hasOwnProperty('columnDefs')) {
options.columnDefs = [];
}
columnsName.each(function () {
const self = $(this);
const name = self.data('sortName', '');
if (name !== '') {
options.columnDefs.push(
{
targets: self.index(),
name: name,
},
);
}
});
}
//endregion
//region Gestion de AJAX
const ajaxUrl = self.data('sortAjax', false);
if (ajaxUrl !== false) {
options.serverSide = true;
options.ajax = {
url: ajaxUrl,
type: 'POST',
//createCDATASection: '',
cache: self.data('sortAjaxCache', false),
};
options.columns = [];
columnsName.each(function () {
const self = $(this);
const name = self.data('sortName', '');
if (name !== '') {
options.columns[self.index()] = {
data: name,
};
}
});
}
//endregion
self.DataTable(options);
});
});

@ -0,0 +1,329 @@
//region Datatables.net
import 'datatables.net';
import 'datatables.net-dt/css/dataTables.dataTables.min.css';
//endregion
//region Bootstrap 5 styling for Datatables.net
import 'datatables.net-bs5';
import 'datatables.net-bs5/css/dataTables.bootstrap5.min.css';
//endregion
//region Custom style for Datatables.net
import './styles/datatables2.scss';
//endregion
import 'jqueryLocal';
import Utils from 'utils';
/**
* Chargement automatique des éléments : tableaux, {@link https://datatables.net/ DataTables (v2)}
*
* Valide seulement si le JS "DataTable" est bien présent\
* S'instancie automatiquement sur les <table class="table-datatable2">
*
* Options :
* * table :
* * data-sort-paging : boolean\
*     Pagination gérée ?\
*     Défaut : faux
* * data-sort-per-page : integer\
*     Nombre d'éléments par défaut par page\
*     Défaut : 50
* * data-sort-layout : string (JSON)\
*     Le JSON du modèle du tableau (https://datatables.net/reference/option/layout)\
*     Défaut : pagination (si active) en bas à droite
* * data-sort-ajax : string|false\
*     Faux si pas de requête AJAX géré\
*     Sinon l'URL AJAX appelée\
*         Bien penser à définir les "data-sort-name" sur les <th>\
*     Défaut : false
* * data-sort-ajax-type : string\
*     Le type de la requête AJAX (GET, POST, etc.)\
*     Défaut : POST
* * data-sort-ajax-cache : boolean\
*     Mise en cache du résultat des requêtes AJAX ?\
*     Défaut : faux
* * data-sort-lang : string (JSON)\
*     Le JSON des traductions
* * data-sort-lang-url : string (url)\
*     L'URL renvoyant le JSON des traductions\
*     Ignore si "data-sort-lang" renseigné
* * data-sort-lang-code : string\
*     Code langue (plugin, voir https://datatables.net/plug-ins/i18n/French.html#Browser-loading-/-CDN)\
*         Exemple avec "fr-FR" : chargera "//cdn.datatables.net/plug-ins/2.2.2/i18n/fr-FR.json"\
*     Ignoré si "data-sort-lang" ou "data-sort-lang-url" est renseigné
* * data-sort-child : string (HTML)|false\
*     Faux si pas de lignes enfants gérées\
*     Sinon le contenu HTML de la ligne enfant\
*     Ignoré si supplanté par la version sur les <tr>\
*     Défaut : false
* * data-sort-child-ajax : string (url)|false\
*     Faux si pas de lignes enfants gérées\
*     L'URL des requêtes AJAX pour les lignes enfants\
*     Ignoré si "data-sort-child" est renseigné ou supplanté par la version sur les <tr>\
*     Défaut : false
* * data-sort-child-ajax-type : string\
*     Le type de la requête AJAX pour les lignes enfants (GET, POST, etc.)\
*     Ignoré si supplanté par la version sur les <tr>\
*     Défaut : POST
* * data-sort-child-ajax-cache : boolean\
*     Mise en cache du résultat des requêtes AJAX pour les lignes enfants ?\
*     Ignoré si supplanté par la version sur les <tr>\
*     Défaut : faux
* * thead th :
* * data-sort-onLoad : integer\
*     Ordre de tri de la colonne pour le tri initial/au chargement
* * data-sort-order : string ("asc", "desc")\
*     Sens du tri de la colonne lors du 1er clic (et lors du tri initial/au chargement si "data-sort-onLoad" également renseigné)
* * data-sort : boolean\
*     Activer le tri sur la colonne ?\
*     Défaut : vrai
* * data-sort-name : string\
*     Le nom de la colonne dans la source de données (AJAX : cf. data-sort-ajax)
* * data-sort-name-value : boolean\
*     La colonne à une valeur de tri différente de son affichage ?\
*     Si vrai attends une paire display/sort dans le JSON (exemple : https://datatables.net/examples/ajax/orthogonal-data.html)\
*         Cf. SolulogSAS\BaseSite\Web\DataTables2_ColumnWithSortValue\
*     Défaut : faux
* * data-sort-child : string (HTML)|false\
*     Faux si pas de lignes enfants gérées\
*     Sinon le contenu HTML de la ligne enfant\
*     Défaut : false
* * data-sort-child-ajax : string (url)|false\
*     Faux si pas de lignes enfants gérées\
*     L'URL des requêtes AJAX pour les lignes enfants\
*     Ignoré si "data-sort-child" est renseigné\
*     Défaut : false
* * data-sort-child-ajax-id : any\
*     L'ID ajax de la ligne enfant (transmis dans "ajaxId")\
*     Défaut : {néant}
* * data-sort-child-ajax-type : string\
*     Le type de la requête AJAX pour les lignes enfants (GET, POST, etc.)\
*     Défaut : POST
* * data-sort-child-ajax-cache : boolean\
*     Mise en cache du résultat des requêtes AJAX pour les lignes enfants ?\
*     Défaut : faux
* * tbody td :
* * data-order : any\
*     La valeur utilisée pour le tri
* * data-filter : any\
*     La valeur utilisée pour le filtrage
*
* @param {Document|jQuery} page
*/
function datatables2 (page) {
$('table.table-datatable2', page).each(function () {
const self = $(this);
//region Options du datatable
let options = {
paging: self.dataDefault('sortPaging', false),
pageLength: self.dataDefault('sortPerPage', 50),
processing: true,
layout:
self.dataDefault('sortLayout',
{
topStart: null,
topEnd: null,
bottomStart: null,
bottomEnd: 'paging',
},
),
};
//endregion
//region Gestion traduction
let lang = self.dataDefault('sortLang', undefined);
if (Utils.isUndefined(lang)) {
let langUrl = self.dataDefault('sortLangUrl', undefined);
if (Utils.isUndefined(langUrl)) {
const langCode = self.dataDefault('sortLangCode', undefined);
if (!Utils.isUndefined(langCode)) {
langUrl = '//cdn.datatables.net/plug-ins/2.2.2/i18n/' + langCode + '.json';
}
}
if (!Utils.isUndefined(langUrl)) {
lang = {url: langUrl};
}
}
if (!Utils.isUndefined(lang)) {
options.language = lang;
}
//endregion
//region Gestion tri initial
let initialSort = [];
$('> thead > tr > th[data-sort-onLoad]', self).each(function () {
let self = $(this);
initialSort.push(
{
index: self.index(),
priority: self.dataDefault('sortOnload', 0),
direction: self.dataDefault('sortOrder', 'asc').toLowerCase(),
},
);
});
initialSort.sort(function (sort1, sort2) {
if (sort1.priority === sort2.priority) {
return sort1.index - sort2.index;
}
return sort1.priority - sort2.priority;
});
if (initialSort.length > 0) {
options.order = [];
initialSort.forEach(function (sortOrder) {
options.order.push([sortOrder.index, sortOrder.direction]);
});
}
//endregion
//region Gestion sens de tri (1er clic ou initial si concerné)
let descSort = [];
$('> thead > tr > th[data-sort-order]', self).each(function () {
const self = $(this);
if (self.dataDefault('sortOrder', 'asc').toLowerCase() === 'desc') {
descSort.push(self.index());
}
});
if (descSort.length > 0) {
if (!options.hasOwnProperty('columnDefs')) {
options.columnDefs = [];
}
options.columnDefs.push(
{
targets: descSort,
orderSequence: ['desc', 'asc'],
},
);
}
//endregion
//region Gestion désactivation tri
let disabledSort = [];
$('> thead > tr > th[data-sort]', self).each(function () {
const self = $(this);
if (!self.dataDefault('sort', true)) {
disabledSort.push(self.index());
}
});
if (disabledSort.length > 0) {
if (!options.hasOwnProperty('columnDefs')) {
options.columnDefs = [];
}
options.columnDefs.push(
{
targets: disabledSort,
orderable: false,
},
);
}
//endregion
//region Gestion nom des colonnes
const columnsName = $('> thead > tr > th[data-sort-name]', self);
if (columnsName.length > 0) {
if (!options.hasOwnProperty('columnDefs')) {
options.columnDefs = [];
}
columnsName.each(function () {
const self = $(this);
const name = self.dataDefault('sortName', '');
if (name !== '') {
options.columnDefs.push(
{
targets: self.index(),
name: name,
},
);
}
});
}
//endregion
//region Gestion de AJAX
const ajaxUrl = self.dataDefault('sortAjax', false);
if (ajaxUrl !== false) {
options.serverSide = true;
options.ajax = {
url: ajaxUrl,
type: self.dataDefault('sortAjaxType', 'POST'),
cache: self.dataDefault('sortAjaxCache', false),
};
options.columns = [];
columnsName.each(function () {
const self = $(this);
const name = self.dataDefault('sortName', '');
const nameValue = self.dataDefault('sortNameValue', false);
if (name !== '') {
options.columns[self.index()] = {
data: nameValue
? {_: name + '.display', sort: name + '.sort'}
: name,
};
}
});
}
//endregion
const datatable = self.DataTable(options);
//region Gestion lignes enfants
const child = self.dataDefault('sortChild', false);
const childAjaxUrl = self.dataDefault('sortChildAjax', false);
const childAjaxType = self.dataDefault('sortChildAjaxType', 'POST');
const childAjaxCache = self.dataDefault('sortChildAjaxCache', false);
if (child === true || childAjaxUrl !== false) {
const showData = function (dtRow, childData) {
const parentHtml = '<div></div>';
const childJquery = $(parentHtml).append(childData);
datatables2(childJquery);
//noinspection JSUnresolvedReference
dtRow.child(childJquery).show();
};
self.on('click', '> tbody > tr:not([data-dt-row!=""][data-dt-row]) > td', function () {
//region Obtention ligne
const row = $(this).parents('tr').first();
const dtRow = datatable.row(row);
//endregion
//region Lignes enfants déjà affichées ? Les masques
//noinspection JSUnresolvedReference
if (dtRow.child.isShown()) {
//noinspection JSUnresolvedReference
dtRow.child.hide();
return;
}
//endregion
//region Données enfants directement spécifiées
const childData = row.dataDefault('sortChild', false);
if (childData !== false) {
showData(dtRow, childData);
return;
}
//endregion
//region Données enfants via AJAX
const rowChildAjaxUrl = row.dataDefault('sortChildAjax', childAjaxUrl);
if (Utils.isUndefined(rowChildAjaxUrl) || rowChildAjaxUrl === false) {
return;
}
$.ajax(
{
type: row.dataDefault('sortChildAjaxType', childAjaxType),
url: rowChildAjaxUrl,
data: {
ajaxId: row.dataDefault('sortChildAjaxId', undefined),
},
cache: row.dataDefault('sortChildAjaxCache', childAjaxCache),
},
)
.done(childData => {
showData(dtRow, childData);
});
//endregion
});
}
//endregion
});
}
$(function () {
datatables2(document);
});

@ -8,18 +8,16 @@ window.jQuery = $; // Ensure global “jQuery” property available: necess
(function ($) {
//region .data
const jqueryDataOrig = $.fn.data;
/**
* Get a DOM "data" value
* Get a DOM data value, with a default value
*
* @param {string} key The "data" key
* @param {string} key The data key
* @param {any} defaultValue The default value
*
* @return {any} The "data" value
* @return {any} The data value
*/
$.fn.data = function (key, defaultValue = undefined) {
const value = jqueryDataOrig.apply(this, [key]);
$.fn.dataDefault = function (key, defaultValue = undefined) {
const value = this.data(key);
if (Utils.isUndefined(value)) {
return defaultValue;
}

@ -0,0 +1,26 @@
import 'bootstrap';
import 'jqueryLocal';
import Utils from 'utils';
$(function () {
$('.modal-dynamic').on('show.bs.modal', function (event) {
const dialog = $(this);
const sender = $(event.relatedTarget);
const title = sender.dataDefault('modalDynamicTitle');
const body = sender.dataDefault('modalDynamicBody');
const linkUrl = sender.dataDefault('modalDynamicLinkUrl', sender.matchTag('a') ? sender.attr('href') : undefined);
if (!Utils.isUndefined(title)) {
$('.modal-title', dialog).html(title);
}
if (!Utils.isUndefined(body)) {
$('.modal-body', dialog).html(body);
}
if (!Utils.isUndefined(linkUrl)) {
$('a.modal-confirm-link', dialog).attr('href', linkUrl);
}
return true;
});
});

@ -16,7 +16,56 @@ $alert-margin-bottom : 0.5rem;
@import '../../vendor/twbs/bootstrap/scss/variables-dark';
//endregion
//region Maps personnalisés
$utilities : (
//region Largeur max
'max-width': (
property: max-width,
class: mw,
values: (
25: 25%,
50: 50%,
75: 75%,
100: 100%,
)
),
//endregion
//region Hauteur max
'max-height': (
property: max-height,
class: mh,
values: (
25: 25%,
50: 50%,
75: 75%,
100: 100%,
)
),
//endregion
//region Largeur min
'min-width': (
property: min-width,
class: mnw,
values: (
25: 25%,
50: 50%,
75: 75%,
100: 100%,
)
),
//endregion
//region Hauteur min
'min-height': (
property: min-height,
class: mnh,
values: (
25: 25%,
50: 50%,
75: 75%,
100: 100%,
)
),
//endregion
);
//endregion
//region Reste de la configuration de Bootstrap
@import '../../vendor/twbs/bootstrap/scss/maps';

@ -5,3 +5,8 @@
.required:not(.form-check-label) {
color : var(--bs-red);
}
.fit-content {
white-space : nowrap;
width : 1px;
}

@ -1,36 +0,0 @@
table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order::after,
table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order::after,
table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order::after,
table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order::after,
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order::after,
table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order::after,
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order::after,
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order::after {
margin-top : 3px;
}
table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order,
table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order,
table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order,
table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order,
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order,
table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order,
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order,
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order {
position : relative;
left : 10px;
top : none;
bottom : none;
}
div.dt-container .dt-paging .dt-paging-button {
padding : 0;
margin : 0;
}
table.dataTable th.dt-type-numeric,
table.dataTable th.dt-type-date,
table.dataTable td.dt-type-numeric,
table.dataTable td.dt-type-date {
text-align : left;
}

@ -0,0 +1,35 @@
table.table-datatable2 {
& > thead > tr {
& > th.dt-orderable-asc span.dt-column-title {
margin-right : 5px;
}
& > th.dt-orderable-asc span.dt-column-order,
& > th.dt-orderable-desc span.dt-column-order {
position : relative;
left : 0;
top : 0;
bottom : 0;
&::after {
margin-top : 3px;
}
}
}
& > tbody > tr[data-dt-row]:not([data-dt-row='']) > td {
padding : 0.75rem;
}
div.dt-container .dt-paging .dt-paging-button {
padding : 0;
margin : 0;
}
th.dt-type-numeric,
th.dt-type-date,
td.dt-type-numeric,
td.dt-type-date {
text-align : left;
}
}

@ -13,37 +13,37 @@
"doctrine/orm": "^3.3",
"phpdocumentor/reflection-docblock": "^5.6",
"phpstan/phpdoc-parser": "^2.1",
"symfony/asset": "7.2.*",
"symfony/asset-mapper": "7.2.*",
"symfony/console": "7.2.*",
"symfony/doctrine-messenger": "7.2.*",
"symfony/dotenv": "7.2.*",
"symfony/expression-language": "7.2.*",
"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",
"symfony/form": "7.2.*",
"symfony/framework-bundle": "7.2.*",
"symfony/http-client": "7.2.*",
"symfony/intl": "7.2.*",
"symfony/mailer": "7.2.*",
"symfony/messenger": "7.2.*",
"symfony/mime": "7.2.*",
"symfony/form": "7.3.*",
"symfony/framework-bundle": "7.3.*",
"symfony/http-client": "7.3.*",
"symfony/intl": "7.3.*",
"symfony/mailer": "7.3.*",
"symfony/messenger": "7.3.*",
"symfony/mime": "7.3.*",
"symfony/monolog-bundle": "^3.0",
"symfony/notifier": "7.2.*",
"symfony/process": "7.2.*",
"symfony/property-access": "7.2.*",
"symfony/property-info": "7.2.*",
"symfony/rate-limiter": "7.2.*",
"symfony/runtime": "7.2.*",
"symfony/security-bundle": "7.2.*",
"symfony/serializer": "7.2.*",
"symfony/notifier": "7.3.*",
"symfony/process": "7.3.*",
"symfony/property-access": "7.3.*",
"symfony/property-info": "7.3.*",
"symfony/rate-limiter": "7.3.*",
"symfony/runtime": "7.3.*",
"symfony/security-bundle": "7.3.*",
"symfony/serializer": "7.3.*",
"symfony/stimulus-bundle": "^2.25",
"symfony/string": "7.2.*",
"symfony/translation": "7.2.*",
"symfony/twig-bundle": "7.2.*",
"symfony/string": "7.3.*",
"symfony/translation": "7.3.*",
"symfony/twig-bundle": "7.3.*",
"symfony/ux-turbo": "^2.25",
"symfony/validator": "7.2.*",
"symfony/web-link": "7.2.*",
"symfony/yaml": "7.2.*",
"symfony/validator": "7.3.*",
"symfony/web-link": "7.3.*",
"symfony/yaml": "7.3.*",
"symfonycasts/sass-bundle": "^0.8.2",
"twbs/bootstrap": "^5.3",
"twig/extra-bundle": "^2.12|^3.0",
@ -97,17 +97,17 @@
"extra": {
"symfony": {
"allow-contrib": false,
"require": "7.2.*"
"require": "7.3.*"
}
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"symfony/browser-kit": "7.2.*",
"symfony/css-selector": "7.2.*",
"symfony/debug-bundle": "7.2.*",
"symfony/browser-kit": "7.3.*",
"symfony/css-selector": "7.3.*",
"symfony/debug-bundle": "7.3.*",
"symfony/maker-bundle": "^1.0",
"symfony/phpunit-bridge": "^7.2",
"symfony/stopwatch": "7.2.*",
"symfony/web-profiler-bundle": "7.2.*"
"symfony/phpunit-bridge": "^7.3",
"symfony/stopwatch": "7.3.*",
"symfony/web-profiler-bundle": "7.3.*"
}
}

809
composer.lock generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,3 @@
framework:
property_info:
with_constructor_extractor: true

@ -0,0 +1,5 @@
symfonycasts_sass:
# Path to your Sass root file
root_sass:
- '%kernel.project_dir%/assets/styles/app.scss'
- '%kernel.project_dir%/assets/styles/datatables2.scss'

@ -16,10 +16,13 @@ return [
'path' => './assets/app.js',
'entrypoint' => true,
],
'datatables' => [
'path' => './assets/datatables.js',
'datatables2' => [
'path' => './assets/datatables2.js',
'entrypoint' => true,
],
'modalDynamic' => [
'path' => './assets/modalDynamic.js',
],
'jqueryLocal' => [
'path' => './assets/jqueryLocal.js',
],
@ -66,4 +69,13 @@ return [
'version' => '5.3.6',
'type' => 'css',
],
'@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,0 +1,89 @@
<?php
namespace App\Controller\Config;
use App\Entity\MaterialType;
use App\Form\Config\MaterialTypeEditForm;
use App\Misc\FlashType;
use App\Repository\MaterialTypeRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
/**
* Controllers for the configuration pages
*/
#[Route('/Config/MaterialType')]
class MaterialTypeController extends AbstractController {
private readonly EntityManagerInterface $entityManager;
/**
* @var MaterialTypeRepository The material type repository
*/
private readonly MaterialTypeRepository $materialTypeRepository;
/**
* Initialisation
*
* @param MaterialTypeRepository $materialTypeRepository The material type repository
*/
public function __construct (EntityManagerInterface $entityManager, MaterialTypeRepository $materialTypeRepository) {
$this->entityManager = $entityManager;
$this->materialTypeRepository = $materialTypeRepository;
}
/**
* List of material types
*
* @return Response The response
*/
#[Route('/', name: 'config_materialType_list', alias: 'config_materialType')]
public function list (): Response {
return $this->render(
'Config/MaterialType/List.html.twig',
[
'materialTypes' => $this->materialTypeRepository->findAll(),
]
);
}
/**
* Edit/Create a material type
*
* @return Response The response
*/
#[Route('/Create', name: 'config_materialType_create')]
#[Route('/Edit-{id}', name: 'config_materialType_edit')]
public function edit (Request $request, ?MaterialType $materialType = null): Response {
$materialType ??= new MaterialType();
$form = $this->createForm(MaterialTypeEditForm::class, $materialType);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->entityManager->persist($materialType);
$this->entityManager->flush();
$this->addFlash(FlashType::SUCCESS, 'Le type de matériau a bien été enregistré.');
return $this->redirectToRoute('config_materialType_list');
}
return $this->render('Config/MaterialType/Edit.html.twig', [
'form' => $form->createView(),
]);
}
/**
* Delete a material type
*
* @return Response The response
*/
#[Route('/Delete-{id}', name: 'config_materialType_delete')]
public function delete (MaterialType $materialType): Response {
$this->entityManager->remove($materialType);
$this->entityManager->flush();
$this->addFlash(FlashType::SUCCESS, 'Le type de matériau a bien été supprimé.');
return $this->redirectToRoute('config_materialType_list');
}
}

@ -26,7 +26,7 @@ use Throwable;
/**
* Controllers for user pages
*/
#[Route('/user')]
#[Route('/User')]
class UserController extends AbstractController {
/**
* @var TranslatorInterface The translator service
@ -59,7 +59,7 @@ class UserController extends AbstractController {
*
* @throws TransportExceptionInterface
*/
#[Route('/signUp', name: 'user_signUp')]
#[Route('/SignUp', name: 'user_signUp')]
public function signUp (Request $request, UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager): Response {
if (($response = $this->connectedUserService->checkNotConnected())) {
return $response;
@ -99,7 +99,7 @@ class UserController extends AbstractController {
*
* @return Response The response
*/
#[Route(path: '/signIn', name: 'user_signIn')]
#[Route(path: '/SignIn', name: 'user_signIn')]
public function login (AuthenticationUtils $authenticationUtils): Response {
if (($response = $this->connectedUserService->checkNotConnected())) {
return $response;
@ -123,7 +123,7 @@ class UserController extends AbstractController {
*
* @return void
*/
#[Route(path: '/signOut', name: 'user_signOut')]
#[Route(path: '/SignOut', name: 'user_signOut')]
public function logout (): void {
throw new LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
}

@ -161,10 +161,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Stringa
}
/**
* Removes sensitive data from the user
*
* @see UserInterface
* @inheritDoc
*/
public function eraseCredentials (): void {
// Nothing to do
}
}

@ -0,0 +1,45 @@
<?php
namespace App\Form\Config;
use App\Entity\MaterialType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* The form for editing a material type
*/
class MaterialTypeEditForm extends AbstractType {
/**
* @inheritDoc
*/
public function configureOptions (OptionsResolver $resolver): void {
$resolver->setDefaults(
[
'data_class' => MaterialType::class,
]
);
}
/**
* @inheritDoc
*/
public function buildForm (FormBuilderInterface $builder, array $options): void {
$builder
->add('name', null, [
'label' => 'Nom',
])
->add('stackName', null, [
'label' => 'Nom de la pile',
])
->add('stackSize', null, [
'label' => 'Taille de la pile',
])
->add('submit', SubmitType::class, [
'label' => 'Enregistrer',
]);
}
}

@ -1,4 +1,13 @@
{
"doctrine/deprecations": {
"version": "1.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.0",
"ref": "87424683adc81d7dc305eefec1fced883084aab9"
}
},
"doctrine/doctrine-bundle": {
"version": "2.14",
"recipe": {
@ -195,6 +204,18 @@
"./tests/bootstrap.php"
]
},
"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.2",
"recipe": {

@ -0,0 +1,8 @@
{% extends 'base.html.twig' %}
{% block title %}Modification type de matériau - {{ parent() }}{% endblock %}
{% block mainContent %}
<h1>Modification type de matériau</h1>
{{ form(form) }}
{% endblock %}

@ -0,0 +1,62 @@
{% extends '/base.html.twig' %}
{% block title %}Liste des types de matériaux - {{ parent() }}{% endblock %}
{% block importmap %}{{ importmap(['app', 'datatables2']) }}{% endblock %}
{% block mainContent %}
<h1>Liste des types de matériaux</h1>
<div class="d-flex">
<div class="table-responsive mnw-25">
<table class="table table-sm table-striped table-hover table-bordered table-datatable2">
<thead>
<tr>
<th scope="col" data-sort-onLoad="1" class="align-middle">Nom</th>
<th scope="col" data-sort="false" class="fit-content align-middle">
<a href="{{ path('config_materialType_create') }}" class="btn btn-primary" data-bs-toggle="tooltip" data-bs-title="Ajouter">
<i class="fa-solid fa-square-plus"></i>
</a>
</th>
</tr>
</thead>
<tbody>
{% for materialType in materialTypes %}
<tr>
<td>{{ materialType.name }}</td>
<td class="fit-content">
<a href="{{ path('config_materialType_edit', {id: materialType.id}) }}"
class="text-primary me-2"
data-bs-toggle="tooltip"
data-bs-title="Éditer"
><i class="fa-solid fa-pen"></i></a>
<a href="#" class="text-danger" id="btDelete" data-bs-toggle="tooltip" data-bs-title="Supprimer">
<span data-bs-toggle="modal"
data-bs-target="#deleteConfirmation"
data-modal-dynamic-link-url="{{ path('config_materialType_delete', {id: materialType.id}) }}"
><i class="fa-solid fa-xmark"></i></span>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="modal modal-dynamic fade" id="deleteConfirmation" tabindex="-1" aria-label="btDelete" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5">Suppression</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Êtes-vous sûr de vouloir supprimer ce type de matériau ?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Non</button>
<a href="#" class="modal-confirm-link btn btn-danger">Oui</a>
</div>
</div>
</div>
</div>
{% endblock %}

@ -5,7 +5,18 @@
{% block mainContent %}
<h1>Recipe Manager</h1>
{% if app.user %}
Bienvenu {{ app.user }}
<nav class="navbar navbar-expand-lg py-0">
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle py-0" id="dropdown-config" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Configuration
</a>
<ul class="dropdown-menu" aria-labelledby="dropdown-config">
<li><a href="{{ path('config_materialType_list') }}" class="dropdown-item">Types de matériaux</a></li>
</ul>
</li>
</ul>
</nav>
{% else %}
<p>Bienvenu sur Recipe Manager, le gestionnaire de recette de jeux vidéos.</p>
<p>Merci de vous <a href="{{ path('user_signIn') }}">connecter</a> ou <a href="{{ path('user_signOut') }}">créer un compte</a> pour commencer.</p>

Loading…
Cancel
Save