From 93086eba608a2f7f54a5aed15bc56468d0f064ce Mon Sep 17 00:00:00 2001
From: Tykayn
Date: Tue, 24 Jun 2025 13:16:48 +0200
Subject: [PATCH] =?UTF-8?q?bubble=20fraicheur=20des=20completions=20ajout?=
=?UTF-8?q?=C3=A9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
assets/app.js | 42 +++++++++++++++------
assets/dashboard-charts.js | 31 +++++++--------
assets/js/table-sortable.js | 8 ++++
assets/js/table-sortable.min.js | 0
assets/table-map-toggles.js | 8 +---
migrations/Version20250624103515.php | 35 +++++++++++++++++
package-lock.json | 17 +++++++++
package.json | 4 ++
src/Controller/AdminController.php | 36 ++++++++++++------
src/Entity/Place.php | 17 +++++++++
src/Service/Motocultrice.php | 27 ++++++-------
templates/admin/email_content.html.twig | 2 +
templates/admin/labourage_results.html.twig | 2 +-
templates/admin/stats.html.twig | 5 ++-
templates/public/closed_commerces.html.twig | 2 +-
templates/public/dashboard.html.twig | 3 +-
templates/public/latest_changes.html.twig | 4 +-
templates/public/places_with_note.html.twig | 2 +-
18 files changed, 179 insertions(+), 66 deletions(-)
create mode 100644 assets/js/table-sortable.js
create mode 100644 assets/js/table-sortable.min.js
create mode 100644 migrations/Version20250624103515.php
diff --git a/assets/app.js b/assets/app.js
index f290bca..97a417a 100644
--- a/assets/app.js
+++ b/assets/app.js
@@ -34,6 +34,12 @@ import {
updateMapHeightForLargeScreens
} from './utils.js';
import Tablesort from 'tablesort';
+import TableSort from 'table-sort-js/table-sort.js';
+import $ from 'jquery';
+window.$ = $;
+window.jQuery = $;
+// Charger table-sortable (version non minifiée locale)
+import '../assets/js/table-sortable.js';
window.Chart = Chart;
window.genererCouleurPastel = genererCouleurPastel;
@@ -44,6 +50,7 @@ window.ChartDataLabels = ChartDataLabels;
window.maplibregl = maplibregl;
window.toggleCompletionInfo = toggleCompletionInfo;
window.updateMapHeightForLargeScreens = updateMapHeightForLargeScreens;
+window.Tablesort = Tablesort;
Chart.register(ChartDataLabels);
@@ -112,11 +119,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
updateCompletionProgress();
- // Activer le tri sur tous les tableaux désignés
- document.querySelectorAll('.js-sort-table').forEach(table => {
- new Tablesort(table);
- });
-
// Focus sur le premier champ texte au chargement
// const firstTextInput = document.querySelector('input.form-control');
// if (firstTextInput) {
@@ -131,13 +133,6 @@ document.addEventListener('DOMContentLoaded', () => {
parseCuisine();
- // Tri automatique des tableaux
- // const tables = document.querySelectorAll('table');
- // tables.forEach(table => {
- // table.classList.add('js-sort-table');
- // });
-
-
// Modifier la fonction de recherche existante
const searchInput = document.getElementById('app_admin_labourer');
const suggestionList = document.getElementById('suggestionList');
@@ -201,4 +196,27 @@ document.addEventListener('DOMContentLoaded', () => {
enableLabourageForm();
adjustListGroupFontSize('.list-group-item');
+
+ // Activer le tri naturel sur tous les tableaux avec la classe table-sort
+ document.querySelectorAll('table.table-sort').forEach(table => {
+ new TableSort(table);
+ });
+
+ // Initialisation du tri et filtrage sur les tableaux du dashboard et de la page stats
+ if (document.querySelector('#dashboard-table')) {
+ $('#dashboard-table').tableSortable({
+ pagination: false,
+ showPaginationLabel: true,
+ searchField: '#dashboard-table-search',
+ responsive: false
+ });
+ }
+ if (document.querySelector('#stats-table')) {
+ $('#stats-table').tableSortable({
+ pagination: false,
+ showPaginationLabel: true,
+ searchField: '#stats-table-search',
+ responsive: false
+ });
+ }
});
diff --git a/assets/dashboard-charts.js b/assets/dashboard-charts.js
index 4b33eac..1e55dbd 100644
--- a/assets/dashboard-charts.js
+++ b/assets/dashboard-charts.js
@@ -47,16 +47,17 @@ function waitForChartAndDrawBubble() {
}
// Calcul de la régression linéaire (moindres carrés)
- const validPoints = bubbleChartData.filter(d => d.x > 0 && d.y > 0);
+ // On ne fait la régression que si on veut, mais l'axe X = fraicheur, Y = complétion
+ const validPoints = bubbleChartData.filter(d => d.x !== null && d.y !== null);
const n = validPoints.length;
let regressionLine = null, slope = 0, intercept = 0;
if (n >= 2) {
let sumX = 0, sumY = 0, sumXY = 0, sumXX = 0;
validPoints.forEach(d => {
- sumX += Math.log10(d.x);
+ sumX += d.x;
sumY += d.y;
- sumXY += Math.log10(d.x) * d.y;
- sumXX += Math.log10(d.x) * Math.log10(d.x);
+ sumXY += d.x * d.y;
+ sumXX += d.x * d.x;
});
const meanX = sumX / n;
const meanY = sumY / n;
@@ -65,8 +66,8 @@ function waitForChartAndDrawBubble() {
const xMin = Math.min(...validPoints.map(d => d.x));
const xMax = Math.max(...validPoints.map(d => d.x));
regressionLine = [
- { x: xMin, y: slope * Math.log10(xMin) + intercept },
- { x: xMax, y: slope * Math.log10(xMax) + intercept }
+ { x: xMin, y: slope * xMin + intercept },
+ { x: xMax, y: slope * xMax + intercept }
];
}
window.Chart.register(window.ChartDataLabels);
@@ -105,10 +106,8 @@ function waitForChartAndDrawBubble() {
].filter(Boolean)
},
options: {
- // responsive: true,
plugins: {
datalabels: {
- // Désactivé au niveau global, activé par dataset
display: false
},
legend: { display: true },
@@ -117,14 +116,14 @@ function waitForChartAndDrawBubble() {
label: (context) => {
const d = context.raw;
if (context.dataset.type === 'line') {
- return `Régression: y = ${slope.toFixed(2)} × log10(x) + ${intercept.toFixed(2)}`;
+ return `Régression: y = ${slope.toFixed(2)} × x + ${intercept.toFixed(2)}`;
}
return [
`${d.label}`,
- `Population: ${d.x.toLocaleString()}`,
- `Nombre de lieux: ${d.r.toFixed(2)}`,
- `Complétion: ${d.y.toFixed(2)}%`,
`Fraîcheur moyenne: ${d.freshnessDays ? d.freshnessDays.toLocaleString() + ' jours' : 'N/A'}`,
+ `Complétion: ${d.y.toFixed(2)}%`,
+ `Population: ${d.population ? d.population.toLocaleString() : 'N/A'}`,
+ `Nombre de lieux: ${d.r.toFixed(2)}`,
`Budget: ${d.budget ? d.budget.toLocaleString() + ' €' : 'N/A'}`,
`Budget/habitant: ${d.budgetParHabitant ? d.budgetParHabitant.toFixed(2) + ' €' : 'N/A'}`,
`Budget/lieu: ${d.budgetParLieu ? d.budgetParLieu.toFixed(2) + ' €' : 'N/A'}`
@@ -135,11 +134,13 @@ function waitForChartAndDrawBubble() {
},
scales: {
x: {
- type: 'logarithmic',
- title: { display: true, text: 'Population (échelle log)' }
+ type: 'linear',
+ title: { display: true, text: 'Fraîcheur moyenne (jours, plus petit = plus récent)' }
},
y: {
- title: { display: true, text: 'Completion' }
+ title: { display: true, text: 'Taux de complétion (%)' },
+ min: 0,
+ max: 100
}
}
}
diff --git a/assets/js/table-sortable.js b/assets/js/table-sortable.js
new file mode 100644
index 0000000..58ecd8f
--- /dev/null
+++ b/assets/js/table-sortable.js
@@ -0,0 +1,8 @@
+/*
+ * table-sortable
+ * version: 2.0.3
+ * release date: 4/2/2021
+ * (c) Ravi Dhiman https://ravid.dev
+ * For the full copyright and license information, please view the LICENSE
+*/
+!function(t){var e={};function n(a){if(e[a])return e[a].exports;var i=e[a]={i:a,l:!1,exports:{}};return t[a].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=t,n.c=e,n.d=function(t,e,a){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:a})},n.r=function(t){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"===typeof t&&t&&t.__esModule)return t;var a=Object.create(null);if(n.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(a,i,function(e){return t[e]}.bind(null,i));return a},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=1)}([function(t,e){t.exports=jQuery},function(t,e,n){t.exports=n(3)},function(t,e,n){},function(t,e,n){"use strict";n.r(e);var a={};function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function r(t,e){for(var n=0;n2?a-2:0),r=2;rparseFloat(e)?1:-1;if(g(t)){var n=new Date(t),a=new Date(e);return n.getTime()>a.getTime()?1:-1}return p(t)?t>e?1:-1:1}return 0},k=function(t,e){var n;return function(){var a=this,i=arguments;clearTimeout(n),n=window.setTimeout((function(){return t.apply(a,i)}),e)}},C=function(t){return p(t)?t.toLowerCase():String(t)},E=function(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[];if(!e&&!h(e))return!1;var a=m(t);c(n)||(n=a);for(var i=0,r=a.length,o=!1,s=C(e);i-1&&u.indexOf(s)>-1){o=!0;break}if(!n.length&&u.indexOf(s)>-1){o=!0;break}i+=1}return o},w=function(t,e){return c(e)&&e[0]<=t&&e[1]>t},D=function(){function t(){i(this,t),this._name="dataset",this.dataset=null,this._cachedData=null,this._datasetLen=0,this._outLen=10,this.sortDirection={ASC:"asc",DESC:"desc"}}return o(t,[{key:"_formatError",value:function(t,e,n){for(var i=arguments.length,r=new Array(i>3?i-3:0),o=3;oe),"get",'"from" cannot be greater than "to"'),t=Math.max(t,0),e=Math.min(e,this._datasetLen),this.dataset.slice(t,e)}},{key:"sort",value:function(t,e){this._hasDataset(),this._formatError(p(t),"sort",'Requires "column" type of string'),this._formatError(p(e),"sort",'Requires "direction" type of string'),this._formatError("asc"===e||"desc"===e,"sort",'"%s" is invalid sort direction. Use "dataset.sortDirection.ASC" or "dataset.sortDirection.DESC".',e);var n=this.top(1)[0];return this._formatError("undefined"!==typeof n[t],"sort",'Column name "%s" does not exist in collection',t),this.sortDirection.ASC===e?this.dataset.sort((function(e,n){return P(e[t],n[t])})):this.dataset.sort((function(e,n){return P(n[t],e[t])})),this.top(this._datasetLen)}},{key:"pushData",value:function(t){c(t)&&Array.prototype.push.apply(this.dataset,t)}},{key:"lookUp",value:function(t,e){if(p(t)||h(t)){var n=JSON.parse(this._cachedData);this.dataset=""===t?n:v(n,(function(n){return E(n,t,e)})),this._datasetLen=this.dataset.length}}}]),t}(),x=function(t,e,n){return{node:t,attrs:e,children:n}},S=function(t){var e=t.node,n=t.attrs;t.children;return function(t,e){if(!e)return t;for(var n=Object.keys(e),a=0;a").concat(e,">")),n)},U=function t(e,n,a){if(a||n.empty(),c(e)){for(var i=[],r=0;r\u25bc",desc:"\u25b2"},nextText:"Next",prevText:"Prev",tableWillMount:function(){},tableDidMount:function(){},tableWillUpdate:function(){},tableDidUpdate:function(){},tableWillUnmount:function(){},tableDidUnmount:function(){},onPaginationChange:null},this._styles=null,this._dataset=null,this._table=null,this._thead=null,this._tbody=null,this._isMounted=!1,this._isUpdating=!1,this._sorting={currentCol:"",dir:""},this._pagination={elm:null,currentPage:0,totalPages:1,visiblePageNumbers:5},this._cachedOption=null,this._cachedViewPort=-1,this.setData=function(t,e,a){n.logError(c(t),"setData","expect first argument as array of objects"),n.logError(d(e),"setData","expect second argument as objects"),n._isMounted&&t&&(a?n._dataset.pushData(t):n._dataset.fromCollection(t),e&&(n.options.columns=e),n.refresh())},this.getData=function(){return n._isMounted?n._dataset.top():[]},this.getCurrentPageData=function(){if(n._isMounted){var t=n.options.rowsPerPage,e=n._pagination.currentPage*t,a=e+t;return n._dataset.get(e,a)}return[]},this.refresh=function(t){t?(n.distroy(),n.create()):n._isMounted&&n.updateTable()},this.distroy=function(){n._isMounted&&(n.emitLifeCycles("tableWillUnmount"),n._table.remove(),n._styles&&n._styles.length&&(n._styles.remove(),n._styles=null),n._dataset=null,n._table=null,n._thead=null,n._tbody=null,n._pagination.elm&&n._pagination.elm.remove(),n._pagination={elm:null,currentPage:0,totalPages:0,visiblePageNumbers:5},n._isMounted=!1,n._isUpdating=!1,n._sorting={currentCol:"",dir:""},n._cachedViewPort=-1,n._cachedOption=null,n.emitLifeCycles("tableDidUnmount"))},this.create=function(){n._isMounted||n.init()},this.options=u.a.extend(this._defOptions,e),delete this._defOptions,this._rootElement=u()(this.options.element),this.engine=L(),this.optionDepreciation(),this.init(),this._debounceUpdateTable()}return o(t,[{key:"optionDepreciation",value:function(){var t=this.options;this.logWarn(t.columnsHtml,"columnsHtml","has been deprecated. Use formatHeader()"),this.logWarn(t.processHtml,"processHtml","has been deprecated. Use formatCell()"),this.logWarn(t.dateParsing,"dateParsing","has been deprecated. It is true by default."),this.logWarn(t.generateUniqueIds,"generateUniqueIds","has been deprecated. It is true by default."),this.logWarn(t.showPaginationLabel,"showPaginationLabel","has been deprecated. It is true by default."),this.logWarn(t.paginationLength,"paginationLength","has been deprecated. Use rowsPerPage")}},{key:"logError",value:function(t,e,n){for(var i=arguments.length,r=new Array(i>3?i-3:0),o=3;o1?n-1:0),i=1;i1&&void 0!==arguments[1]?arguments[1]:[],n=this.options.columns;this.logError(e&&c(e),"lookUp","second argument must be array of keys"),e.length||(e=n),this._pagination.currentPage=0,this._dataset.lookUp(t,m(e)),this.debounceUpdateTable()}},{key:"_bindSearchField",value:function(){var t=this,e=this.options.searchField;if(e){var n=u()(e);this.logError(n.length,"searchField",'"%s" is not a valid DOM element or string',n),n.on("input",(function(){var e=u()(this).val();t.lookUp(e)})),this.options.searchField=n}}},{key:"_validateRootElement",value:function(){this.logError(this._rootElement.length,"element",'"%s" is not a valid root element',this._rootElement)}},{key:"_createTable",value:function(){this._table=u()("").addClass("table gs-table")}},{key:"_initDataset",value:function(){var t=this.options.data;this.logError(c(t),"data","table-sortable only supports collections. Like: [{ key: value }, { key: value }]");var e=new D;e.fromCollection(t),this._dataset=e}},{key:"_validateColumns",value:function(){var t=this.options.columns;this.logError(d(t),"columns","Invalid column type, see docs")}},{key:"sortData",value:function(t){var e=this._sorting,n=e.dir,a=e.currentCol;t!==a&&(n=""),n?n===this._dataset.sortDirection.ASC?n=this._dataset.sortDirection.DESC:n===this._dataset.sortDirection.DESC&&(n=this._dataset.sortDirection.ASC):n=this._dataset.sortDirection.ASC,a=t,this._sorting={dir:n,currentCol:a},this._dataset.sort(a,n),this.updateCellHeader()}},{key:"_addColSorting",value:function(t,e){var n=this,a=this.options.sorting,i=this;return a?(a&&!c(a)&&((t=u()(t)).attr("role","button"),t.addClass("gs-button"),e===this._sorting.currentCol&&this._sorting.dir&&t.append(this.options.sortingIcons[this._sorting.dir]),t.click((function(t){i.sortData(e)}))),c(a)&&b(a,(function(a){e===a&&((t=u()(t)).attr("role","button"),t.addClass("gs-button"),e===n._sorting.currentCol&&n._sorting.dir&&t.append(n.options.sortingIcons[n._sorting.dir]),t.click((function(t){i.sortData(e)})))})),t):t}},{key:"getCurrentPageIndex",value:function(){var t=this._dataset._datasetLen,e=this.options,n=e.pagination,a=e.rowsPerPage,i=this._pagination.currentPage;if(!n)return{from:0};var r=i*a,o=Math.min(r+a,t);return{from:r=Math.min(r,o),to:o}}},{key:"_renderHeader",value:function(t){var e=this;t||(t=u()(''));var n=this.options,a=n.columns,i=n.formatHeader,r=[],o=m(a);b(o,(function(t,n){var o=a[t];f(i)&&(o=i(a[t],t,n)),o=e._addColSorting(u()("").html(o),t);var s=e.engine.createElement("th",{html:o});r.push(s)}));var s=this.engine.createElement("tr",null,r);return this.engine.render(s,t)}},{key:"_renderBody",value:function(t){t||(t=u()(''));var e=this.engine,n=this.options,a=n.columns,i=n.formatCell,r=this.getCurrentPageIndex(),o=r.from,s=r.to,l=[];l=void 0===s?this._dataset.top():this._dataset.get(o,s);var c=[],h=m(a);return b(l,(function(t,n){var a=[];b(h,(function(n){var r;void 0!==t[n]&&(r=f(i)?e.createElement("td",{html:i(t,n)}):e.createElement("td",{html:t[n]}),a.push(r))})),c.push(e.createElement("tr",null,a))})),e.render(c,t)}},{key:"_createCells",value:function(){return{thead:this._renderHeader(),tbody:this._renderBody()}}},{key:"onPaginationBtnClick",value:function(t,e){var n=this,a=this._pagination,i=a.totalPages,r=a.currentPage,o=this.options.onPaginationChange;"up"===t?r=0&&(r-=1);if(f(o)){var s=isNaN(e)?r:e;o.apply(this,[s,function(t){return n.setPage(t)}])}else this._pagination.currentPage=void 0!==e?e:r,this.updateTable()}},{key:"renderPagination",value:function(t){var e=this,n=this.engine,a=this.options,i=a.pagination,r=a.paginationContainer,o=a.prevText,s=a.nextText,l=this._pagination,c=l.currentPage,h=l.totalPages,d=l.visiblePageNumbers,f=Math.min(h,d),p=0,g=Math.min(h,p+f);if(c>f/2&&ch-f/2&&(p=h-f,g=h),t||(t=u()(''),u()(r).length?u()(r).append(t):this._table.after(t)),!i)return t;var _=[],m=n.createElement("button",{className:"btn btn-default",html:o,disabled:0===c,onClick:function(){return e.onPaginationBtnClick("down")}});_.push(m);var b=n.createElement("button",{className:"btn btn-default",disabled:!0,text:"..."});c>f/2&&_.push(b);for(var v=p;v=h-1,onClick:function(){return e.onPaginationBtnClick("up")}});_.push(P),t.append(_);var k=this.getCurrentPageIndex(),C=k.from,E=k.to,w=n.createElement("span",{text:"Showing ".concat(Math.min(E,C+1)," to ").concat(E," of ").concat(this._dataset._datasetLen," rows")}),D=n.createElement("div",{className:"col-md-6"},w),x=n.createElement("div",{className:"btn-group d-flex justify-content-end"},_),S=n.createElement("div",{className:"col-md-6"},x),U=n.createElement("div",{className:"row"},[D,S]);return n.render(U,t)}},{key:"createPagination",value:function(){var t=this.options,e=t.rowsPerPage,n=t.pagination,a=t.totalPages;if(!n)return!1;this.logError(e&&h(e),"rowsPerPage","should be a number greater than zero."),this.logError(h(a),"totalPages","should be a number greater than zero.");var i=a||Math.ceil(this._dataset._datasetLen/e);0>=i&&(i=1),this._pagination.totalPages=i,this._pagination.elm?this.renderPagination(this._pagination.elm):this._pagination.elm=this.renderPagination()}},{key:"_generateTable",value:function(t,e){this._table.html(""),this._table.append(t),this._table.append(e),this._thead=t,this._tbody=e}},{key:"_renderTable",value:function(){if(this._rootElement.is("table"))this._rootElement.html(this._table.html());else{var t=this.engine.createElement("div",{className:"gs-table-container",append:this._table});this._rootElement=this.engine.render(t,this._rootElement)}}},{key:"_initStyles",value:function(){if(!this.options.responsive){var t=u()("");t.attr("id","gs-table"),t.html(".gs-table-container .table{table-layout:fixed}@media(max-width:767px){.gs-table-container{overflow:auto;max-width:100%}}"),u()("head").append(t),this._styles=t}}},{key:"init",value:function(){this.emitLifeCycles("tableWillMount"),this._validateRootElement(),this._initDataset(),this._createTable(),this._validateColumns();var t=this._createCells(),e=t.thead,n=t.tbody;this._generateTable(e,n),this._renderTable(),this.createPagination(),this._bindSearchField(),this._initStyles(),this._isMounted=!0,this.emitLifeCycles("tableDidMount"),-1===this._cachedViewPort&&this.resizeSideEffect()}},{key:"_debounceUpdateTable",value:function(){this.debounceUpdateTable=k(this.updateTable,400)}},{key:"updateTable",value:function(){this._isUpdating||(this.emitLifeCycles("tableWillUpdate"),this._isUpdating=!0,this._renderHeader(this._thead),this._renderBody(this._tbody),this.createPagination(),this._isUpdating=!1,this.emitLifeCycles("tableDidUpdate"))}},{key:"updateCellHeader",value:function(){this._isUpdating||(this._isUpdating=!0,this.emitLifeCycles("tableWillUpdate"),this._renderHeader(this._thead),this._renderBody(this._tbody),this._isUpdating=!1,this.emitLifeCycles("tableDidUpdate"))}},{key:"resizeSideEffect",value:function(){var t=k(this.makeResponsive,500);window.addEventListener("resize",t.bind(this)),this.makeResponsive()}},{key:"makeResponsive",value:function(){var t,e=this.options.responsive,n=window.innerWidth,a=_(m(e),"desc");if(this.logError(d(e),"responsive",'Invalid type of responsive option provided: "%s"',e),b(a,(function(e){parseInt(e,10)>n&&(t=e)})),this._cachedViewPort!==t){this._cachedViewPort=t;var i=e[t];d(i)?(this._cachedOption||(this._cachedOption=u.a.extend({},this.options)),this.options=u.a.extend(this.options,i),this.refresh()):this._cachedOption&&(this.options=u.a.extend({},this._cachedOption),this._cachedOption=null,this._cachedViewPort=-1,this.refresh())}}}]),t}());window.Pret=L(),window.TableSortable=O,window.DataSet=D,(s=jQuery).fn.tableSortable=function(t){return t.element=s(this),new window.TableSortable(t)};e.default=O}]);
\ No newline at end of file
diff --git a/assets/js/table-sortable.min.js b/assets/js/table-sortable.min.js
new file mode 100644
index 0000000..e69de29
diff --git a/assets/table-map-toggles.js b/assets/table-map-toggles.js
index 1f1c6cf..5f4fcf4 100644
--- a/assets/table-map-toggles.js
+++ b/assets/table-map-toggles.js
@@ -1,11 +1,7 @@
// Gestion du tri des tableaux
-import Tablesort from 'tablesort';
+// import Tablesort from 'tablesort';
document.addEventListener('DOMContentLoaded', () => {
- document.querySelectorAll('.js-sort-table').forEach(table => {
- new Tablesort(table);
- });
-
// Gestion du toggle gouttes/ronds sur la carte
const toggle = document.getElementById('toggleMarkers');
if (toggle && window.updateMarkers) {
@@ -18,6 +14,6 @@ document.addEventListener('DOMContentLoaded', () => {
// Exposer une fonction pour (ré)appliquer le tri si besoin
export function applyTableSort() {
document.querySelectorAll('.js-sort-table').forEach(table => {
- new Tablesort(table);
+ new window.Tablesort(table);
});
}
\ No newline at end of file
diff --git a/migrations/Version20250624103515.php b/migrations/Version20250624103515.php
new file mode 100644
index 0000000..3a62e23
--- /dev/null
+++ b/migrations/Version20250624103515.php
@@ -0,0 +1,35 @@
+addSql(<<<'SQL'
+ ALTER TABLE place ADD email_content LONGTEXT CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_0900_ai_ci`, ADD place_count INT DEFAULT NULL
+ SQL);
+ }
+
+ public function down(Schema $schema): void
+ {
+ // this down() migration is auto-generated, please modify it to your needs
+ $this->addSql(<<<'SQL'
+ ALTER TABLE place DROP email_content, DROP place_count
+ SQL);
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 39b892c..953c42e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,6 +5,9 @@
"packages": {
"": {
"license": "UNLICENSED",
+ "dependencies": {
+ "jquery": "^3.7.1"
+ },
"devDependencies": {
"@babel/core": "^7.17.0",
"@babel/preset-env": "^7.16.0",
@@ -14,6 +17,7 @@
"core-js": "^3.38.0",
"maplibre-gl": "^5.6.0",
"regenerator-runtime": "^0.13.9",
+ "table-sort-js": "^1.22.2",
"tablesort": "^5.6.0",
"webpack": "^5.74.0",
"webpack-cli": "^5.1.0"
@@ -3904,6 +3908,12 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
+ "node_modules/jquery": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
+ "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
+ "license": "MIT"
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -5491,6 +5501,13 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
+ "node_modules/table-sort-js": {
+ "version": "1.22.2",
+ "resolved": "https://registry.npmjs.org/table-sort-js/-/table-sort-js-1.22.2.tgz",
+ "integrity": "sha512-KUpmoYWH1TCnyiylE0HMCtMeAisl0KYBFjZfBL3CPHOlnhA8jy+RFfZbH6DwCpXAvmK73vsDAX54hg9J4DhuRQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/tablesort": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/tablesort/-/tablesort-5.6.0.tgz",
diff --git a/package.json b/package.json
index d3f21c8..8e5215b 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"core-js": "^3.38.0",
"maplibre-gl": "^5.6.0",
"regenerator-runtime": "^0.13.9",
+ "table-sort-js": "^1.22.2",
"tablesort": "^5.6.0",
"webpack": "^5.74.0",
"webpack-cli": "^5.1.0"
@@ -19,5 +20,8 @@
"dev": "encore dev",
"watch": "encore dev --watch",
"build": "encore production --progress"
+ },
+ "dependencies": {
+ "jquery": "^3.7.1"
}
}
diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php
index c7d5aba..1a2915e 100644
--- a/src/Controller/AdminController.php
+++ b/src/Controller/AdminController.php
@@ -16,6 +16,7 @@ use Symfony\Component\HttpFoundation\Request;
use function uuid_create;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\JsonResponse;
+use Twig\Environment;
final class AdminController extends AbstractController
{
@@ -23,7 +24,8 @@ final class AdminController extends AbstractController
public function __construct(
private EntityManagerInterface $entityManager,
private Motocultrice $motocultrice,
- private BudgetService $budgetService
+ private BudgetService $budgetService,
+ private Environment $twig
) {
}
@@ -99,6 +101,10 @@ final class AdminController extends AbstractController
$this->entityManager->persist($place);
$stats->addPlace($place);
$processedCount++;
+
+ // Générer le contenu de l'email avec le template
+ $emailContent = $this->twig->render('admin/email_content.html.twig', ['place' => $place]);
+ $place->setEmailContent($emailContent);
} elseif ($updateExisting) {
// Mettre à jour les données depuis Overpass uniquement si updateExisting est true
$existingPlace->update_place_from_overpass_data($placeData);
@@ -443,6 +449,8 @@ final class AdminController extends AbstractController
$overpass_osm_ids = array_map(fn($place) => $place['id'], $places_overpass);
+ $batchSize = 200;
+ $i = 0;
foreach ($places_overpass as $placeData) {
// Vérifier si le lieu existe déjà (optimisé)
$existingPlace = $placesByOsmId[$placeData['id']] ?? null;
@@ -471,6 +479,10 @@ final class AdminController extends AbstractController
$this->entityManager->persist($place);
$stats->addPlace($place);
$processedCount++;
+
+ // Générer le contenu de l'email avec le template
+ $emailContent = $this->twig->render('admin/email_content.html.twig', ['place' => $place]);
+ $place->setEmailContent($emailContent);
} elseif ($updateExisting) {
$existingPlace->setDead(false);
$existingPlace->update_place_from_overpass_data($placeData);
@@ -478,19 +490,19 @@ final class AdminController extends AbstractController
$this->entityManager->persist($existingPlace);
$updatedCount++;
}
- }
-
- // Supprimer les lieux qui ne sont plus dans la réponse Overpass, si activé
- if ($deleteMissing) {
- $db_places = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]);
- foreach ($db_places as $db_place) {
- if (!in_array($db_place->getOsmId(), $overpass_osm_ids)) {
- $this->entityManager->remove($db_place);
- $deletedCount++;
- }
+ $i++;
+ // Flush/clear Doctrine tous les X lieux pour éviter l'explosion mémoire
+ if (($i % $batchSize) === 0) {
+ $this->entityManager->flush();
+ $this->entityManager->clear();
+ // Recharger les stats après clear
+ $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
}
}
-
+ // Flush final
+ $this->entityManager->flush();
+ $this->entityManager->clear();
+
// Récupérer tous les commerces de la zone qui n'ont pas été supprimés
$commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]);
diff --git a/src/Entity/Place.php b/src/Entity/Place.php
index 984415c..1f77238 100644
--- a/src/Entity/Place.php
+++ b/src/Entity/Place.php
@@ -120,6 +120,12 @@ class Place
#[ORM\Column(nullable: true)]
private ?int $osm_changeset = null;
+ #[ORM\Column(type: Types::TEXT, nullable: true, options: ['charset' => 'utf8mb4'])]
+ private ?string $emailContent = null;
+
+ #[ORM\Column(type: Types::INTEGER, nullable: true)]
+ private ?int $place_count = null;
+
public function getPlaceTypeName(): ?string
{
if ($this->main_tag == 'amenity=restaurant') {
@@ -734,4 +740,15 @@ class Place
return $this;
}
+
+ public function getEmailContent(): ?string
+ {
+ return $this->emailContent;
+ }
+
+ public function setEmailContent(?string $emailContent): static
+ {
+ $this->emailContent = $emailContent;
+ return $this;
+ }
}
diff --git a/src/Service/Motocultrice.php b/src/Service/Motocultrice.php
index 11ef320..a07f217 100644
--- a/src/Service/Motocultrice.php
+++ b/src/Service/Motocultrice.php
@@ -252,16 +252,21 @@ out meta;';
}
public function get_city_osm_from_zip_code($zip_code) {
+ // Détection spéciale pour Paris, Lyon, Marseille
+ if (preg_match('/^75(0[1-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|6[0-9]|7[0-9]|8[0-9]|9[0-9])$/', $zip_code)) {
+ $arr = intval(substr($zip_code, 2, 3));
+ return 'Paris ' . $arr . 'e arr.';
+ }
+ if (preg_match('/^69(0[1-9]|1[0-9]|2[0-9])$/', $zip_code)) {
+ $arr = intval(substr($zip_code, 2, 3));
+ return 'Lyon ' . $arr . 'e arr.';
+ }
+ if (preg_match('/^13(0[1-9]|1[0-6])$/', $zip_code)) {
+ $arr = intval(substr($zip_code, 2, 3));
+ return 'Marseille ' . $arr . 'e arr.';
+ }
// Requête Overpass pour obtenir la zone administrative de niveau 8 avec un nom
- $query = "[out:json][timeout:25];
- area[\"ref:INSEE\"=\"{$zip_code}\"]->.searchArea;
- (
- relation[\"admin_level\"=\"8\"][\"name\"][\"type\"=\"boundary\"][\"boundary\"=\"administrative\"](area.searchArea);
- );
- out body;
- >;
- out skel qt;";
-
+ $query = "[out:json][timeout:25];\n area[\"ref:INSEE\"=\"{$zip_code}\"]->.searchArea;\n (\n relation[\"admin_level\"=\"8\"][\"name\"][\"type\"=\"boundary\"][\"boundary\"=\"administrative\"](area.searchArea);\n );\n out body;\n >;\n out skel qt;";
$response = $this->client->request('POST', $this->overpassApiUrl, [
'body' => ['data' => $query]
]);
@@ -389,10 +394,6 @@ out meta;';
- public static function uuid_create_static() {
- return $this->uuid_create();
- }
-
public function uuid_create() {
return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
// 32 bits for "time_low"
diff --git a/templates/admin/email_content.html.twig b/templates/admin/email_content.html.twig
index 069507e..4179a36 100644
--- a/templates/admin/email_content.html.twig
+++ b/templates/admin/email_content.html.twig
@@ -28,5 +28,7 @@ En vous souhaitant une bonne journée.
+{% if place.id %}
Ne plus être sollicité pour mettre à jour mon commerce
+{% endif %}
\ No newline at end of file
diff --git a/templates/admin/labourage_results.html.twig b/templates/admin/labourage_results.html.twig
index 93de459..a3bc865 100644
--- a/templates/admin/labourage_results.html.twig
+++ b/templates/admin/labourage_results.html.twig
@@ -25,7 +25,7 @@ commerces existants déjà en base: {{ commerces|length }}
{# {{ dump(commerces[0]) }} #}
-
+
{% include 'admin/stats/table-head.html.twig' %}
diff --git a/templates/admin/stats.html.twig b/templates/admin/stats.html.twig
index b173126..d04dc71 100644
--- a/templates/admin/stats.html.twig
+++ b/templates/admin/stats.html.twig
@@ -54,7 +54,7 @@
{{ stats.name }} - {{ stats.completionPercent }}% complété
-
+
+
{% include 'admin/stats/table-head.html.twig' %}
{% for commerce in stats.places %}
diff --git a/templates/public/closed_commerces.html.twig b/templates/public/closed_commerces.html.twig
index b6fd256..c656507 100644
--- a/templates/public/closed_commerces.html.twig
+++ b/templates/public/closed_commerces.html.twig
@@ -18,7 +18,7 @@
Commerces fermés
Voici la liste des commerces fermés :
-
+
Nom du commerce |
diff --git a/templates/public/dashboard.html.twig b/templates/public/dashboard.html.twig
index 66a75cc..7b8aa51 100644
--- a/templates/public/dashboard.html.twig
+++ b/templates/public/dashboard.html.twig
@@ -137,8 +137,9 @@
Statistiques par ville
+
-
+
Ville |
diff --git a/templates/public/latest_changes.html.twig b/templates/public/latest_changes.html.twig
index 3710859..e8ea163 100644
--- a/templates/public/latest_changes.html.twig
+++ b/templates/public/latest_changes.html.twig
@@ -9,7 +9,7 @@
Lieux modifiés
-
+
Nom |
@@ -33,7 +33,7 @@
Lieux affichés
-
+
Nom |
diff --git a/templates/public/places_with_note.html.twig b/templates/public/places_with_note.html.twig
index 8fe169e..8f3c6d7 100644
--- a/templates/public/places_with_note.html.twig
+++ b/templates/public/places_with_note.html.twig
@@ -5,7 +5,7 @@
{% block body %}
Commerces avec une note
-