ode-ngjs-front 1.4.13-develop-integration.202507301459 → 1.4.13-develop-integration.202508041121

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"cantine-widget.widget.d.ts","sourceRoot":"","sources":["../../../../src/ts/widgets/cantine-widget/cantine-widget.widget.ts"],"names":[],"mappings":"AAGA,OAAO,kBAAkB,CAAC;AAwN1B,eAAO,MAAM,aAAa,2BAA2B,CAAC"}
1
+ {"version":3,"file":"cantine-widget.widget.d.ts","sourceRoot":"","sources":["../../../../src/ts/widgets/cantine-widget/cantine-widget.widget.ts"],"names":[],"mappings":"AAGA,OAAO,kBAAkB,CAAC;AAmO1B,eAAO,MAAM,aAAa,2BAA2B,CAAC"}
package/dist/version.txt CHANGED
@@ -1 +1 @@
1
- ode-ngjs-front 30/07/2025 15:00:25
1
+ ode-ngjs-front 04/08/2025 11:22:05
@@ -1,2 +1,2 @@
1
- "use strict";(self.webpackChunkode_ngjs_front=self.webpackChunkode_ngjs_front||[]).push([[91],{4798:(e,t,i)=>{i.d(t,{Z:()=>n});const n='<style>.cantine-menu-card{color:#333;justify-content:center;align-items:center;padding:5%;border-radius:10px;box-shadow:0 0 10px rgba(0,0,0,.1)}.cantine-select-wrapper{display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:20px;font-family:Roboto,sans-serif;color:#333}.cantine-select-wrapper label{font-weight:700;font-size:1em}.cantine-select-wrapper select{padding:6px 10px;border-radius:5px;border:1px solid #ccc;font-size:.95em;font-family:Roboto,sans-serif;background-color:#fff;color:#333;box-shadow:0 2px 5px rgba(0,0,0,.05);transition:border-color .3s ease}.cantine-select-wrapper select:focus{outline:0;border-color:#f7955c}.cantine-select{padding:8px 12px;font-size:1em;border-radius:5px;border:1px solid #ccc;font-family:Roboto,sans-serif;background-color:#fff;color:#333;box-shadow:0 0 5px rgba(0,0,0,.05);max-width:100%}.menu-error-message{padding:1rem;color:#721c24;background-color:#f8d7da;border:1px solid #f5c6cb;border-radius:4px;margin-top:1rem}.cantine-separator{border:none;height:1px;background-color:#ccc;margin:3% 0}.cantine-section{margin-bottom:3%}.cantine-section-title{font-weight:700;font-size:1.1em;display:flex;align-items:center}.cantine-item{margin-left:1%;font-family:Roboto;color:#4a4a4a;font-size:medium}.cantine-item{align-items:center;gap:5px}.cantine-items-container{border-left:2px solid #ccc;margin-left:.8%;padding-left:3%}.cantine-icon{width:1em;height:1em;margin-left:5px;vertical-align:middle;display:inline-block;margin-bottom:10px}.cantine-dot{height:.6em;width:.6em;border-radius:50%;display:inline-block;margin-right:2%}.cantine-arrow-button{background:0 0;border:none;font-size:1.5em;cursor:pointer;opacity:.6;transition:opacity .3s ease}.cantine-arrow-button:hover{opacity:1}.cantine-arrow-button:focus{outline:0}.cantine-allergen-collapse{background-color:#f9f9f9;border-radius:5px;padding:0;display:none;transition:all .3s ease-in-out;font-size:.9em}.cantine-allergen-collapse.open{display:block}.cantine-collapse-toggle{background-color:#f7955c;border:none;padding:0 10px;border-radius:5px;font-weight:700;cursor:pointer;display:block;margin-top:0;color:#fff;font-size:.8em}.cantine-collapse-toggle:hover{background-color:#e6b848}.cantine-red{background-color:red}.cantine-green{background-color:green}.cantine-blue{background-color:#00f}.cantine-pink{background-color:pink}.cantine-yellow{background-color:orange}</style> <div class="widget-dashboard"> <div class="cantine-menu-card"> <h2 style="color:#333;font-family:Roboto,sans-serif;display:flex;align-items:center;justify-content:center;gap:10px"> <i18n>cantine.title</i18n> </h2> <div class="cantine-separator"></div> <div ng-if="ctrl.showUaiDropdown"> <select id="uaiSelect" class="form-control" ng-model="ctrl.selectedUai" ng-options="option.uai as option.name for option in ctrl.schoolOptions" ng-change="ctrl.onUaiSelected()"> <option value="" disabled="disabled" selected="selected" hidden> <i18n>cantine.selectEtablissement</i18n> </option> </select> </div> <div class="cantine-date-navigation" style="display:flex;align-items:center;justify-content:center;gap:10px;position:relative"> <button class="cantine-arrow-button" ng-click="ctrl.changeDate(-1)">&#8592;</button> <span style="font-size:1.2em;font-weight:700;cursor:pointer" ng-show="!ctrl.showDatepicker" ng-click="ctrl.toggleDatepicker(true)">{{ ctrl.currentDate }}</span> <input type="date" ng-model="ctrl.pickerDate" ng-blur="ctrl.onDateBlur()" ng-show="ctrl.showDatepicker" style="font-size:1.2em;font-weight:700;cursor:pointer;text-align:center" ng-click="ctrl.toggleDatepicker(true)"/> <button class="cantine-arrow-button" ng-click="ctrl.changeDate(1)">&#8594;</button> </div> <div ng-if="ctrl.selectedUai"> <div ng-if="!ctrl.menuUnavailable"> <div class="cantine-section"> <div class="cantine-section-title"> <span class="cantine-dot cantine-red"></span> <i18n>cantine.entree</i18n> </div> <div class="cantine-items-container"> <div class="cantine-item" ng-repeat="item in ctrl.data.entrees"> {{ item.designationMenu && item.designationMenu.trim() !== \'\' ? item.designationMenu : item.nom }} <img ng-if="item.bio" src="/assets/widgets/cantine-widget/cantine-logos/bio-logo.png" alt="Bio" class="cantine-icon" title="Agriculture biologique"> <img ng-if="item.faitmaison" src="/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png" alt="Fait Maison" class="cantine-icon" title="Fait maison"> <img ng-if="item.vegetarien" src="/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png" alt="Vegetarian" class="cantine-icon" title="Végétarien"> <img ng-if="item.local" src="/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png" alt="Local Product" class="cantine-icon" title="Produit local"> <button class="cantine-collapse-toggle" ng-click="item.showAllergies = !item.showAllergies" ng-if="ctrl.getAllergies(item).length > 0"> <i18n>cantine.allergenes</i18n> </button> <div class="cantine-allergen-collapse" ng-class="{\'open\': item.showAllergies}" ng-if="ctrl.getAllergies(item).length > 0"> <span>{{ ctrl.getAllergies(item).join(\', \') }}</span> </div> </div> </div> </div> <div class="cantine-section"> <div class="cantine-section-title"> <span class="cantine-dot cantine-green"></span> <i18n>cantine.plat</i18n> </div> <div class="cantine-items-container"> <div class="cantine-item" ng-repeat="item in ctrl.data.plats"> {{ item.designationMenu && item.designationMenu.trim() !== \'\' ? item.designationMenu : item.nom }} <img ng-if="item.bio" src="/assets/widgets/cantine-widget/cantine-logos/bio-logo.png" alt="Bio" class="cantine-icon" title="Agriculture biologique"> <img ng-if="item.faitmaison" src="/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png" alt="Fait Maison" class="cantine-icon" title="Fait maison"> <img ng-if="item.vegetarien" src="/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png" alt="Vegetarian" class="cantine-icon" title="Végétarien"> <img ng-if="item.local" src="/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png" alt="Local Product" class="cantine-icon" title="Produit local"> <button class="cantine-collapse-toggle" ng-click="item.showAllergies = !item.showAllergies" ng-if="ctrl.getAllergies(item).length > 0"> <i18n>cantine.allergenes</i18n> </button> <div class="cantine-allergen-collapse" ng-class="{\'open\': item.showAllergies}" ng-if="ctrl.getAllergies(item).length > 0"> <span>{{ ctrl.getAllergies(item).join(\', \') }}</span> </div> </div> </div> </div> <div class="cantine-section"> <div class="cantine-section-title"> <span class="cantine-dot cantine-blue"></span> <i18n>cantine.accompagnement</i18n> </div> <div class="cantine-items-container"> <div class="cantine-item" ng-repeat="item in ctrl.data.accompagnements"> {{ item.designationMenu && item.designationMenu.trim() !== \'\' ? item.designationMenu : item.nom }} <img ng-if="item.bio" src="/assets/widgets/cantine-widget/cantine-logos/bio-logo.png" alt="Bio" class="cantine-icon" title="Agriculture biologique"> <img ng-if="item.faitmaison" src="/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png" alt="Fait Maison" class="cantine-icon" title="Fait maison"> <img ng-if="item.vegetarien" src="/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png" alt="Vegetarian" class="cantine-icon" title="Végétarien"> <img ng-if="item.local" src="/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png" alt="Local Product" class="cantine-icon" title="Produit local"> <button class="cantine-collapse-toggle" ng-click="item.showAllergies = !item.showAllergies" ng-if="ctrl.getAllergies(item).length > 0"> <i18n>cantine.allergenes</i18n> </button> <div class="cantine-allergen-collapse" ng-class="{\'open\': item.showAllergies}" ng-if="ctrl.getAllergies(item).length > 0"> <span>{{ ctrl.getAllergies(item).join(\', \') }}</span> </div> </div> </div> </div> <div class="cantine-section"> <div class="cantine-section-title"> <span class="cantine-dot cantine-pink"></span> <i18n>cantine.laitage</i18n> </div> <div class="cantine-items-container"> <div class="cantine-item" ng-repeat="item in ctrl.data.laitage"> {{ item.designationMenu && item.designationMenu.trim() !== \'\' ? item.designationMenu : item.nom }} <img ng-if="item.bio" src="/assets/widgets/cantine-widget/cantine-logos/bio-logo.png" alt="Bio" class="cantine-icon" title="Agriculture biologique"> <img ng-if="item.faitmaison" src="/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png" alt="Fait Maison" class="cantine-icon" title="Fait maison"> <img ng-if="item.vegetarien" src="/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png" alt="Vegetarian" class="cantine-icon" title="Végétarien"> <img ng-if="item.local" src="/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png" alt="Local Product" class="cantine-icon" title="Produit local"> <button class="cantine-collapse-toggle" ng-click="item.showAllergies = !item.showAllergies" ng-if="ctrl.getAllergies(item).length > 0"> <i18n>cantine.allergenes</i18n> </button> <div class="cantine-allergen-collapse" ng-class="{\'open\': item.showAllergies}" ng-if="ctrl.getAllergies(item).length > 0"> <span>{{ ctrl.getAllergies(item).join(\', \') }}</span> </div> </div> </div> </div> <div class="cantine-section"> <div class="cantine-section-title"> <span class="cantine-dot cantine-yellow"></span> <i18n>cantine.dessert</i18n> </div> <div class="cantine-items-container"> <div class="cantine-item" ng-repeat="item in ctrl.data.desserts"> {{ item.designationMenu && item.designationMenu.trim() !== \'\' ? item.designationMenu : item.nom }} <img ng-if="item.bio" src="/assets/widgets/cantine-widget/cantine-logos/bio-logo.png" alt="Bio" class="cantine-icon" title="Agriculture biologique"> <img ng-if="item.faitmaison" src="/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png" alt="Fait Maison" class="cantine-icon" title="Fait maison"> <img ng-if="item.vegetarien" src="/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png" alt="Vegetarian" class="cantine-icon" title="Végétarien"> <img ng-if="item.local" src="/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png" alt="Local Product" class="cantine-icon" title="Produit local"> <button class="cantine-collapse-toggle" ng-click="item.showAllergies = !item.showAllergies" ng-if="ctrl.getAllergies(item).length > 0"> <i18n>cantine.allergenes</i18n> </button> <div class="cantine-allergen-collapse" ng-class="{\'open\': item.showAllergies}" ng-if="ctrl.getAllergies(item).length > 0"> <span>{{ ctrl.getAllergies(item).join(\', \') }}</span> </div> </div> </div> </div> </div> <div ng-if="ctrl.menuUnavailable" class="menu-error-message"> {{ ctrl.unavailableMessage }} </div> </div> </div> </div>'},8544:function(e,t,i){var n=this&&this.__awaiter||function(e,t,i,n){return new(i||(i=Promise))(function(a,s){function o(e){try{c(n.next(e))}catch(e){s(e)}}function l(e){try{c(n.throw(e))}catch(e){s(e)}}function c(e){var t;e.done?a(e.value):(t=e.value,t instanceof i?t:new i(function(e){e(t)})).then(o,l)}c((n=n.apply(e,t||[])).next())})},a=this&&this.__generator||function(e,t){var i,n,a,s,o={label:0,sent:function(){if(1&a[0])throw a[1];return a[1]},trys:[],ops:[]};return s={next:l(0),throw:l(1),return:l(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function l(s){return function(l){return function(s){if(i)throw new TypeError("Generator is already executing.");for(;o;)try{if(i=1,n&&(a=2&s[0]?n.return:s[0]?n.throw||((a=n.return)&&a.call(n),0):n.next)&&!(a=a.call(n,s[1])).done)return a;switch(n=0,a&&(s=[2&s[0],a.value]),s[0]){case 0:case 1:a=s;break;case 4:return o.label++,{value:s[1],done:!1};case 5:o.label++,n=s[1],s=[0];continue;case 7:s=o.ops.pop(),o.trys.pop();continue;default:if(!(a=o.trys,(a=a.length>0&&a[a.length-1])||6!==s[0]&&2!==s[0])){o=0;continue}if(3===s[0]&&(!a||s[1]>a[0]&&s[1]<a[3])){o.label=s[1];break}if(6===s[0]&&o.label<a[1]){o.label=a[1],a=s;break}if(a&&o.label<a[2]){o.label=a[2],o.ops.push(s);break}a[2]&&o.ops.pop(),o.trys.pop();continue}s=t.call(e,o)}catch(e){s=[6,e],n=0}finally{i=a=0}if(5&s[0])throw s[1];return{value:s[0]?s[1]:void 0,done:!0}}([s,l])}}},s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.odeModuleName=void 0;var o=s(i(4202)),l=i(9419),c=s(i(381));i(4470);var r=s(i(9669)),g=function(){function e(e){this.$scope=e,this.data={},this.error=null,this.uaiList=[],this.selectedUai="",this.schoolOptions=[],this.menuUnavailable=!1,this.unavailableMessage="",this.currentDate="",this.pickerDate=new Date,this.showDatepicker=!1,this.previousApiDate="";var t=(0,l.session)().user;this.apiDate=(0,c.default)(this.pickerDate).format("YYYY-MM-DD"),this.updateCurrentDateDisplay();var i=t.uai;if(Array.isArray(i)&&i.length>1)this.loadUserStructures(t.userId);else if("string"==typeof i||Array.isArray(i)&&1===i.length){this.uaiList=Array.isArray(i)?i:[i];var n=Array.isArray(t.structureNames)?t.structureNames:[];this.schoolOptions=this.uaiList.map(function(e,t){return{uai:e,name:n[t]||e}}),this.setupUaiDropdown()}else this.uaiList=[],this.schoolOptions=[],this.selectedUai=""}return e.prototype.onUaiSelected=function(){this.selectedUai&&this.fetchMenu()},Object.defineProperty(e.prototype,"showUaiDropdown",{get:function(){return this.schoolOptions.length>1},enumerable:!1,configurable:!0}),e.prototype.loadUserStructures=function(e){var t;return n(this,void 0,void 0,function(){var i;return a(this,function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,r.default.get("/directory/user/".concat(e))];case 1:return(i=n.sent().data)&&Array.isArray(i.structureNodes)?(this.schoolOptions=i.structureNodes.map(function(e){return{uai:e.UAI,name:e.feederName||e.name||e.UAI}}),this.uaiList=this.schoolOptions.map(function(e){return e.uai}),this.setupUaiDropdown(),null===(t=this.apply)||void 0===t||t.call(this)):this.error="No structures found for user.",[3,3];case 2:return n.sent(),this.error="Failed to load user structures.",[3,3];case 3:return[2]}})})},e.prototype.changeDate=function(e){this.pickerDate.setDate(this.pickerDate.getDate()+e),this.updateDateAndFetch()},e.prototype.processDateSelection=function(){this.updateDateAndFetch(),this.showDatepicker=!1},e.prototype.onDateBlur=function(){this.pickerDate instanceof Date&&!isNaN(this.pickerDate.getTime())&&(this.processDateSelection(),this.$scope.$apply())},e.prototype.toggleDatepicker=function(e){void 0===e&&(e=null),this.showDatepicker=null!==e?e:!this.showDatepicker},e.prototype.updateDateAndFetch=function(){this.apiDate=(0,c.default)(this.pickerDate).format("YYYY-MM-DD"),this.apiDate!==this.previousApiDate&&(this.previousApiDate=this.apiDate,this.updateCurrentDateDisplay(),this.fetchMenu())},e.prototype.updateCurrentDateDisplay=function(){this.currentDate=this.pickerDate.toLocaleDateString("fr-FR",{weekday:"long",day:"2-digit",month:"long"}),this.currentDate=this.currentDate.charAt(0).toUpperCase()+this.currentDate.slice(1)},e.prototype.fetchMenu=function(){var e,t=this;if(!this.selectedUai)return this.data={entrees:[],plats:[],accompagnements:[],desserts:[],laitage:[]},this.menuUnavailable=!0,this.unavailableMessage="Veuillez sélectionner un établissement.",void(null===(e=this.apply)||void 0===e||e.call(this));r.default.get("/appregistry/".concat(this.selectedUai,"/cantine/menu?date=").concat(this.apiDate)).then(function(e){var i,n,a=null===(i=e.data)||void 0===i?void 0:i.menu;Array.isArray(a)&&a.length>0?(t.data={entrees:a.filter(function(e){return"entree"===e.type}),plats:a.filter(function(e){return"plat"===e.type}),accompagnements:a.filter(function(e){return"accompagnement"===e.type}),laitage:a.filter(function(e){return"laitage"===e.type}),desserts:a.filter(function(e){return"dessert"===e.type})},t.menuUnavailable=!1,t.error=null,t.unavailableMessage=""):(t.data={entrees:[],plats:[],accompagnements:[],desserts:[],laitage:[]},t.menuUnavailable=!0,t.unavailableMessage="Menu non disponible pour cette date.",t.error=null),null===(n=t.apply)||void 0===n||n.call(t)}).catch(function(e){var i,n;t.data={entrees:[],plats:[],accompagnements:[],desserts:[],laitage:[]},400===(null===(i=e.response)||void 0===i?void 0:i.status)?(t.menuUnavailable=!0,t.unavailableMessage="Menu indisponible pour cet établissement.",t.error=null):(t.menuUnavailable=!0,t.unavailableMessage="Une erreur est survenue lors du chargement du menu.",t.error=e.message||"Unknown error"),null===(n=t.apply)||void 0===n||n.call(t)})},e.prototype.getAllergies=function(e){return e?Object.keys(e).filter(function(t){return t.startsWith("allerg_")&&0!==e[t]&&void 0!==e[t]}).map(function(e){return e.replace("allerg_","").replace("_"," ")}):[]},e.prototype.setupUaiDropdown=function(){this.schoolOptions.length>1?(this.schoolOptions.unshift({uai:"",name:"Établissement"}),this.selectedUai=""):1===this.schoolOptions.length?(this.selectedUai=this.schoolOptions[0].uai,this.fetchMenu()):this.selectedUai=""},e}(),d=function(){function e(){this.restrict="E",this.template=i(4798).Z,this.scope={},this.bindToController=!0,this.controller=[g],this.controllerAs="ctrl",this.require=["odeCantineWidget"]}return e.prototype.link=function(e,t,i,s){return n(this,void 0,void 0,function(){var t;return a(this,function(i){return(t=null==s?void 0:s[0])&&(t.apply=function(){return e.$apply()}),[2]})})},e}();(0,l.notif)().onLangReady().promise.then(function(){(0,l.conf)().Platform.idiom.addKeys(i(4848))}),t.odeModuleName="odeCantineWidgetModule",o.default.module(t.odeModuleName,[]).directive("odeCantineWidget",function(){return new d})},4848:e=>{e.exports=JSON.parse('{"cantine.title":"Menu du jour","cantine.entree":"Entrée","cantine.laitage":"Laitage","cantine.plat":"Plat","cantine.accompagnement":"Accompagnement","cantine.dessert":"Dessert","cantine.allergenes":"⚠️ Allergènes","cantine.selectUai":"Établissement"}')}}]);
1
+ "use strict";(self.webpackChunkode_ngjs_front=self.webpackChunkode_ngjs_front||[]).push([[91],{4798:(e,t,i)=>{i.d(t,{Z:()=>n});const n='<style>.cantine-menu-card{color:#333;justify-content:center;align-items:center;padding:5%;border-radius:10px;box-shadow:0 0 10px rgba(0,0,0,.1)}.cantine-select-wrapper{display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:20px;font-family:Roboto,sans-serif;color:#333}.cantine-select-wrapper label{font-weight:700;font-size:1em}.cantine-select-wrapper select{padding:6px 10px;border-radius:5px;border:1px solid #ccc;font-size:.95em;font-family:Roboto,sans-serif;background-color:#fff;color:#333;box-shadow:0 2px 5px rgba(0,0,0,.05);transition:border-color .3s ease}.cantine-select-wrapper select:focus{outline:0;border-color:#f7955c}.cantine-select{padding:8px 12px;font-size:1em;border-radius:5px;border:1px solid #ccc;font-family:Roboto,sans-serif;background-color:#fff;color:#333;box-shadow:0 0 5px rgba(0,0,0,.05);max-width:100%}.menu-error-message{padding:1rem;color:#721c24;background-color:#f8d7da;border:1px solid #f5c6cb;border-radius:4px;margin-top:1rem}.cantine-separator{border:none;height:1px;background-color:#ccc;margin:3% 0}.cantine-section{margin-bottom:3%}.cantine-section-title{font-weight:700;font-size:1.1em;display:flex;align-items:center}.cantine-item{margin-left:1%;font-family:Roboto;color:#4a4a4a;font-size:medium}.cantine-item{align-items:center;gap:5px}.cantine-items-container{border-left:2px solid #ccc;margin-left:.8%;padding-left:3%}.cantine-icon{width:1em;height:1em;margin-left:5px;vertical-align:middle;display:inline-block;margin-bottom:10px}.cantine-dot{height:.6em;width:.6em;border-radius:50%;display:inline-block;margin-right:2%}.cantine-arrow-button{background:0 0;border:none;font-size:1.5em;cursor:pointer;opacity:.6;transition:opacity .3s ease}.cantine-arrow-button:hover{opacity:1}.cantine-arrow-button:focus{outline:0}.cantine-allergen-collapse{background-color:#f9f9f9;border-radius:5px;padding:0;display:none;transition:all .3s ease-in-out;font-size:.9em}.cantine-allergen-collapse.open{display:block}.cantine-collapse-toggle{background-color:#f7955c;border:none;padding:0 10px;border-radius:5px;font-weight:700;cursor:pointer;display:block;margin-top:0;color:#fff;font-size:.8em}.cantine-collapse-toggle:hover{background-color:#e6b848}.cantine-red{background-color:red}.cantine-green{background-color:green}.cantine-blue{background-color:#00f}.cantine-pink{background-color:pink}.cantine-yellow{background-color:orange}</style> <div class="widget-dashboard"> <div class="cantine-menu-card"> <h2 style="color:#333;font-family:Roboto,sans-serif;display:flex;align-items:center;justify-content:center;gap:10px"> <i18n>cantine.title</i18n> </h2> <div class="cantine-separator"></div> <div ng-if="ctrl.noStructuresError" class="menu-error-message"> <i18n>cantine.error.noStructures</i18n> </div> <div ng-if="ctrl.loadStructuresError" class="menu-error-message"> <i18n>cantine.error.loadStructuresFailed</i18n> </div> <div ng-if="ctrl.showUaiDropdown"> <select id="uaiSelect" class="form-control" ng-model="ctrl.selectedUai" ng-options="option.uai as option.name for option in ctrl.schoolOptions" ng-change="ctrl.onUaiSelected()"> <option value="" disabled="disabled" selected="selected" hidden> <i18n>cantine.selectEtablissement</i18n> </option> </select> </div> <div class="cantine-date-navigation" style="display:flex;align-items:center;justify-content:center;gap:10px;position:relative"> <button class="cantine-arrow-button" ng-click="ctrl.changeDate(-1)">&#8592;</button> <span style="font-size:1.2em;font-weight:700;cursor:pointer" ng-show="!ctrl.showDatepicker" ng-click="ctrl.toggleDatepicker(true)">{{ ctrl.currentDate }}</span> <input type="date" ng-model="ctrl.pickerDate" ng-blur="ctrl.onDateBlur()" ng-show="ctrl.showDatepicker" style="font-size:1.2em;font-weight:700;cursor:pointer;text-align:center" ng-click="ctrl.toggleDatepicker(true)"/> <button class="cantine-arrow-button" ng-click="ctrl.changeDate(1)">&#8594;</button> </div> <div ng-if="ctrl.selectedUai"> <div ng-if="!ctrl.menuUnavailable"> <div class="cantine-section"> <div class="cantine-section-title"> <span class="cantine-dot cantine-red"></span> <i18n>cantine.entree</i18n> </div> <div class="cantine-items-container"> <div class="cantine-item" ng-repeat="item in ctrl.data.entrees"> {{ item.designationMenu && item.designationMenu.trim() !== \'\' ? item.designationMenu : item.nom }} <img ng-if="item.bio" src="/assets/widgets/cantine-widget/cantine-logos/bio-logo.png" alt="Bio" class="cantine-icon" title="Agriculture biologique"> <img ng-if="item.faitmaison" src="/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png" alt="Fait Maison" class="cantine-icon" title="Fait maison"> <img ng-if="item.vegetarien" src="/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png" alt="Vegetarian" class="cantine-icon" title="Végétarien"> <img ng-if="item.local" src="/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png" alt="Local Product" class="cantine-icon" title="Produit local"> <button class="cantine-collapse-toggle" ng-click="item.showAllergies = !item.showAllergies" ng-if="ctrl.getAllergies(item).length > 0"> <i18n>cantine.allergenes</i18n> </button> <div class="cantine-allergen-collapse" ng-class="{\'open\': item.showAllergies}" ng-if="ctrl.getAllergies(item).length > 0"> <span>{{ ctrl.getAllergies(item).join(\', \') }}</span> </div> </div> </div> </div> <div class="cantine-section"> <div class="cantine-section-title"> <span class="cantine-dot cantine-green"></span> <i18n>cantine.plat</i18n> </div> <div class="cantine-items-container"> <div class="cantine-item" ng-repeat="item in ctrl.data.plats"> {{ item.designationMenu && item.designationMenu.trim() !== \'\' ? item.designationMenu : item.nom }} <img ng-if="item.bio" src="/assets/widgets/cantine-widget/cantine-logos/bio-logo.png" alt="Bio" class="cantine-icon" title="Agriculture biologique"> <img ng-if="item.faitmaison" src="/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png" alt="Fait Maison" class="cantine-icon" title="Fait maison"> <img ng-if="item.vegetarien" src="/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png" alt="Vegetarian" class="cantine-icon" title="Végétarien"> <img ng-if="item.local" src="/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png" alt="Local Product" class="cantine-icon" title="Produit local"> <button class="cantine-collapse-toggle" ng-click="item.showAllergies = !item.showAllergies" ng-if="ctrl.getAllergies(item).length > 0"> <i18n>cantine.allergenes</i18n> </button> <div class="cantine-allergen-collapse" ng-class="{\'open\': item.showAllergies}" ng-if="ctrl.getAllergies(item).length > 0"> <span>{{ ctrl.getAllergies(item).join(\', \') }}</span> </div> </div> </div> </div> <div class="cantine-section"> <div class="cantine-section-title"> <span class="cantine-dot cantine-blue"></span> <i18n>cantine.accompagnement</i18n> </div> <div class="cantine-items-container"> <div class="cantine-item" ng-repeat="item in ctrl.data.accompagnements"> {{ item.designationMenu && item.designationMenu.trim() !== \'\' ? item.designationMenu : item.nom }} <img ng-if="item.bio" src="/assets/widgets/cantine-widget/cantine-logos/bio-logo.png" alt="Bio" class="cantine-icon" title="Agriculture biologique"> <img ng-if="item.faitmaison" src="/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png" alt="Fait Maison" class="cantine-icon" title="Fait maison"> <img ng-if="item.vegetarien" src="/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png" alt="Vegetarian" class="cantine-icon" title="Végétarien"> <img ng-if="item.local" src="/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png" alt="Local Product" class="cantine-icon" title="Produit local"> <button class="cantine-collapse-toggle" ng-click="item.showAllergies = !item.showAllergies" ng-if="ctrl.getAllergies(item).length > 0"> <i18n>cantine.allergenes</i18n> </button> <div class="cantine-allergen-collapse" ng-class="{\'open\': item.showAllergies}" ng-if="ctrl.getAllergies(item).length > 0"> <span>{{ ctrl.getAllergies(item).join(\', \') }}</span> </div> </div> </div> </div> <div class="cantine-section"> <div class="cantine-section-title"> <span class="cantine-dot cantine-pink"></span> <i18n>cantine.laitage</i18n> </div> <div class="cantine-items-container"> <div class="cantine-item" ng-repeat="item in ctrl.data.laitage"> {{ item.designationMenu && item.designationMenu.trim() !== \'\' ? item.designationMenu : item.nom }} <img ng-if="item.bio" src="/assets/widgets/cantine-widget/cantine-logos/bio-logo.png" alt="Bio" class="cantine-icon" title="Agriculture biologique"> <img ng-if="item.faitmaison" src="/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png" alt="Fait Maison" class="cantine-icon" title="Fait maison"> <img ng-if="item.vegetarien" src="/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png" alt="Vegetarian" class="cantine-icon" title="Végétarien"> <img ng-if="item.local" src="/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png" alt="Local Product" class="cantine-icon" title="Produit local"> <button class="cantine-collapse-toggle" ng-click="item.showAllergies = !item.showAllergies" ng-if="ctrl.getAllergies(item).length > 0"> <i18n>cantine.allergenes</i18n> </button> <div class="cantine-allergen-collapse" ng-class="{\'open\': item.showAllergies}" ng-if="ctrl.getAllergies(item).length > 0"> <span>{{ ctrl.getAllergies(item).join(\', \') }}</span> </div> </div> </div> </div> <div class="cantine-section"> <div class="cantine-section-title"> <span class="cantine-dot cantine-yellow"></span> <i18n>cantine.dessert</i18n> </div> <div class="cantine-items-container"> <div class="cantine-item" ng-repeat="item in ctrl.data.desserts"> {{ item.designationMenu && item.designationMenu.trim() !== \'\' ? item.designationMenu : item.nom }} <img ng-if="item.bio" src="/assets/widgets/cantine-widget/cantine-logos/bio-logo.png" alt="Bio" class="cantine-icon" title="Agriculture biologique"> <img ng-if="item.faitmaison" src="/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png" alt="Fait Maison" class="cantine-icon" title="Fait maison"> <img ng-if="item.vegetarien" src="/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png" alt="Vegetarian" class="cantine-icon" title="Végétarien"> <img ng-if="item.local" src="/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png" alt="Local Product" class="cantine-icon" title="Produit local"> <button class="cantine-collapse-toggle" ng-click="item.showAllergies = !item.showAllergies" ng-if="ctrl.getAllergies(item).length > 0"> <i18n>cantine.allergenes</i18n> </button> <div class="cantine-allergen-collapse" ng-class="{\'open\': item.showAllergies}" ng-if="ctrl.getAllergies(item).length > 0"> <span>{{ ctrl.getAllergies(item).join(\', \') }}</span> </div> </div> </div> </div> </div> <div ng-if="ctrl.menuUnavailable" class="menu-error-message"> <div ng-if="ctrl.selectInstitutionError"> <i18n>cantine.error.selectInstitution</i18n> </div> <div ng-if="ctrl.menuNotAvailableError"> <i18n>cantine.error.menuNotAvailable</i18n> </div> <div ng-if="ctrl.menuUnavailableForInstitutionError"> <i18n>cantine.error.menuUnavailableForInstitution</i18n> </div> <div ng-if="ctrl.loadMenuError"> <i18n>cantine.error.loadMenuFailed</i18n> </div> </div> </div> </div> </div>'},8544:function(e,t,i){var n=this&&this.__awaiter||function(e,t,i,n){return new(i||(i=Promise))(function(a,o){function s(e){try{l(n.next(e))}catch(e){o(e)}}function r(e){try{l(n.throw(e))}catch(e){o(e)}}function l(e){var t;e.done?a(e.value):(t=e.value,t instanceof i?t:new i(function(e){e(t)})).then(s,r)}l((n=n.apply(e,t||[])).next())})},a=this&&this.__generator||function(e,t){var i,n,a,o,s={label:0,sent:function(){if(1&a[0])throw a[1];return a[1]},trys:[],ops:[]};return o={next:r(0),throw:r(1),return:r(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function r(o){return function(r){return function(o){if(i)throw new TypeError("Generator is already executing.");for(;s;)try{if(i=1,n&&(a=2&o[0]?n.return:o[0]?n.throw||((a=n.return)&&a.call(n),0):n.next)&&!(a=a.call(n,o[1])).done)return a;switch(n=0,a&&(o=[2&o[0],a.value]),o[0]){case 0:case 1:a=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,n=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(a=s.trys,(a=a.length>0&&a[a.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!a||o[1]>a[0]&&o[1]<a[3])){s.label=o[1];break}if(6===o[0]&&s.label<a[1]){s.label=a[1],a=o;break}if(a&&s.label<a[2]){s.label=a[2],s.ops.push(o);break}a[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],n=0}finally{i=a=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,r])}}},o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.odeModuleName=void 0;var s=o(i(4202)),r=i(9419),l=o(i(381));i(4470);var c=o(i(9669)),g=function(){function e(e){this.$scope=e,this.data={},this.error=null,this.uaiList=[],this.selectedUai="",this.schoolOptions=[],this.menuUnavailable=!1,this.noStructuresError=!1,this.loadStructuresError=!1,this.selectInstitutionError=!1,this.menuNotAvailableError=!1,this.menuUnavailableForInstitutionError=!1,this.loadMenuError=!1,this.currentDate="",this.pickerDate=new Date,this.showDatepicker=!1,this.previousApiDate="";var t=(0,r.session)().user;this.apiDate=(0,l.default)(this.pickerDate).format("YYYY-MM-DD"),this.updateCurrentDateDisplay();var i=t.uai;if(Array.isArray(i)&&i.length>1)this.loadUserStructures(t.userId);else if("string"==typeof i||Array.isArray(i)&&1===i.length){this.uaiList=Array.isArray(i)?i:[i];var n=Array.isArray(t.structureNames)?t.structureNames:[];this.schoolOptions=this.uaiList.map(function(e,t){return{uai:e,name:n[t]||e}}),this.setupUaiDropdown()}else this.uaiList=[],this.schoolOptions=[],this.selectedUai=""}return e.prototype.onUaiSelected=function(){this.selectedUai&&this.fetchMenu()},Object.defineProperty(e.prototype,"showUaiDropdown",{get:function(){return this.schoolOptions.length>1},enumerable:!1,configurable:!0}),e.prototype.loadUserStructures=function(e){var t;return n(this,void 0,void 0,function(){var i;return a(this,function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,c.default.get("/directory/user/".concat(e))];case 1:return(i=n.sent().data)&&Array.isArray(i.structureNodes)?(this.schoolOptions=i.structureNodes.map(function(e){return{uai:e.UAI,name:e.feederName||e.name||e.UAI}}),this.uaiList=this.schoolOptions.map(function(e){return e.uai}),this.setupUaiDropdown(),null===(t=this.apply)||void 0===t||t.call(this)):this.noStructuresError=!0,[3,3];case 2:return n.sent(),this.loadStructuresError=!0,[3,3];case 3:return[2]}})})},e.prototype.changeDate=function(e){this.pickerDate.setDate(this.pickerDate.getDate()+e),this.updateDateAndFetch()},e.prototype.processDateSelection=function(){this.updateDateAndFetch(),this.showDatepicker=!1},e.prototype.onDateBlur=function(){this.pickerDate instanceof Date&&!isNaN(this.pickerDate.getTime())&&(this.processDateSelection(),this.$scope.$apply())},e.prototype.toggleDatepicker=function(e){void 0===e&&(e=null),this.showDatepicker=null!==e?e:!this.showDatepicker},e.prototype.updateDateAndFetch=function(){this.apiDate=(0,l.default)(this.pickerDate).format("YYYY-MM-DD"),this.apiDate!==this.previousApiDate&&(this.previousApiDate=this.apiDate,this.updateCurrentDateDisplay(),this.fetchMenu())},e.prototype.updateCurrentDateDisplay=function(){this.currentDate=this.pickerDate.toLocaleDateString("fr-FR",{weekday:"long",day:"2-digit",month:"long"}),this.currentDate=this.currentDate.charAt(0).toUpperCase()+this.currentDate.slice(1)},e.prototype.fetchMenu=function(){var e,t=this;if(!this.selectedUai)return this.data={entrees:[],plats:[],accompagnements:[],desserts:[],laitage:[]},this.menuUnavailable=!0,this.selectInstitutionError=!0,void(null===(e=this.apply)||void 0===e||e.call(this));c.default.get("/appregistry/".concat(this.selectedUai,"/cantine/menu?date=").concat(this.apiDate)).then(function(e){var i,n,a=null===(i=e.data)||void 0===i?void 0:i.menu;Array.isArray(a)&&a.length>0?(t.data={entrees:a.filter(function(e){return"entree"===e.type}),plats:a.filter(function(e){return"plat"===e.type}),accompagnements:a.filter(function(e){return"accompagnement"===e.type}),laitage:a.filter(function(e){return"laitage"===e.type}),desserts:a.filter(function(e){return"dessert"===e.type})},t.menuUnavailable=!1,t.error=null,t.selectInstitutionError=!1,t.menuNotAvailableError=!1,t.menuUnavailableForInstitutionError=!1,t.loadMenuError=!1,t.noStructuresError=!1,t.loadStructuresError=!1):(t.data={entrees:[],plats:[],accompagnements:[],desserts:[],laitage:[]},t.menuUnavailable=!0,t.menuNotAvailableError=!0,t.error=null),null===(n=t.apply)||void 0===n||n.call(t)}).catch(function(e){var i,n;t.data={entrees:[],plats:[],accompagnements:[],desserts:[],laitage:[]},400===(null===(i=e.response)||void 0===i?void 0:i.status)?(t.menuUnavailable=!0,t.menuUnavailableForInstitutionError=!0,t.error=null):(t.menuUnavailable=!0,t.loadMenuError=!0,t.error=e.message||"Unknown error"),null===(n=t.apply)||void 0===n||n.call(t)})},e.prototype.getAllergies=function(e){return e?Object.keys(e).filter(function(t){return t.startsWith("allerg_")&&0!==e[t]&&void 0!==e[t]}).map(function(e){return e.replace("allerg_","").replace("_"," ")}):[]},e.prototype.setupUaiDropdown=function(){this.schoolOptions.length>1?(this.schoolOptions.unshift({uai:"",name:"Établissement"}),this.selectedUai=""):1===this.schoolOptions.length?(this.selectedUai=this.schoolOptions[0].uai,this.fetchMenu()):this.selectedUai=""},e}(),u=function(){function e(){this.restrict="E",this.template=i(4798).Z,this.scope={},this.bindToController=!0,this.controller=[g],this.controllerAs="ctrl",this.require=["odeCantineWidget"]}return e.prototype.link=function(e,t,i,o){return n(this,void 0,void 0,function(){var t;return a(this,function(i){return(t=null==o?void 0:o[0])&&(t.apply=function(){return e.$apply()}),[2]})})},e}();(0,r.notif)().onLangReady().promise.then(function(){(0,r.conf)().Platform.idiom.addKeys(i(4848)),(0,r.conf)().Platform.idiom.addKeys(i(4884))}),t.odeModuleName="odeCantineWidgetModule",s.default.module(t.odeModuleName,[]).directive("odeCantineWidget",function(){return new u})},4884:e=>{e.exports=JSON.parse('{"cantine.title":"Today\'s Menu","cantine.entree":"Starter","cantine.laitage":"Dairy","cantine.plat":"Main Course","cantine.accompagnement":"Side Dish","cantine.dessert":"Dessert","cantine.allergenes":"⚠️ Allergens","cantine.selectUai":"Institution","cantine.error.noStructures":"No structures found for user.","cantine.error.loadStructuresFailed":"Failed to load user structures.","cantine.error.selectInstitution":"Please select an institution.","cantine.error.menuNotAvailable":"Menu not available for this date.","cantine.error.menuUnavailableForInstitution":"Menu unavailable for this institution.","cantine.error.loadMenuFailed":"An error occurred while loading the menu."}')},4848:e=>{e.exports=JSON.parse('{"cantine.title":"Menu du jour","cantine.entree":"Entrée","cantine.laitage":"Laitage","cantine.plat":"Plat","cantine.accompagnement":"Accompagnement","cantine.dessert":"Dessert","cantine.allergenes":"⚠️ Allergènes","cantine.selectUai":"Établissement","cantine.error.noStructures":"Aucune structure trouvée pour l\'utilisateur.","cantine.error.loadStructuresFailed":"Échec du chargement des structures utilisateur.","cantine.error.selectInstitution":"Veuillez sélectionner un établissement.","cantine.error.menuNotAvailable":"Menu non disponible pour cette date.","cantine.error.menuUnavailableForInstitution":"Menu indisponible pour cet établissement.","cantine.error.loadMenuFailed":"Une erreur est survenue lors du chargement du menu."}')}}]);
2
2
  //# sourceMappingURL=cantine-widget.widget.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"widgets/cantine-widget/cantine-widget.widget.js","mappings":"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","sources":["webpack://ode-ngjs-front/./src/ts/widgets/cantine-widget/cantine-widget.widget.html","webpack://ode-ngjs-front/./src/ts/widgets/cantine-widget/cantine-widget.widget.ts"],"sourcesContent":["// Module\nvar code = \"<style>.cantine-menu-card{color:#333;justify-content:center;align-items:center;padding:5%;border-radius:10px;box-shadow:0 0 10px rgba(0,0,0,.1)}.cantine-select-wrapper{display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:20px;font-family:Roboto,sans-serif;color:#333}.cantine-select-wrapper label{font-weight:700;font-size:1em}.cantine-select-wrapper select{padding:6px 10px;border-radius:5px;border:1px solid #ccc;font-size:.95em;font-family:Roboto,sans-serif;background-color:#fff;color:#333;box-shadow:0 2px 5px rgba(0,0,0,.05);transition:border-color .3s ease}.cantine-select-wrapper select:focus{outline:0;border-color:#f7955c}.cantine-select{padding:8px 12px;font-size:1em;border-radius:5px;border:1px solid #ccc;font-family:Roboto,sans-serif;background-color:#fff;color:#333;box-shadow:0 0 5px rgba(0,0,0,.05);max-width:100%}.menu-error-message{padding:1rem;color:#721c24;background-color:#f8d7da;border:1px solid #f5c6cb;border-radius:4px;margin-top:1rem}.cantine-separator{border:none;height:1px;background-color:#ccc;margin:3% 0}.cantine-section{margin-bottom:3%}.cantine-section-title{font-weight:700;font-size:1.1em;display:flex;align-items:center}.cantine-item{margin-left:1%;font-family:Roboto;color:#4a4a4a;font-size:medium}.cantine-item{align-items:center;gap:5px}.cantine-items-container{border-left:2px solid #ccc;margin-left:.8%;padding-left:3%}.cantine-icon{width:1em;height:1em;margin-left:5px;vertical-align:middle;display:inline-block;margin-bottom:10px}.cantine-dot{height:.6em;width:.6em;border-radius:50%;display:inline-block;margin-right:2%}.cantine-arrow-button{background:0 0;border:none;font-size:1.5em;cursor:pointer;opacity:.6;transition:opacity .3s ease}.cantine-arrow-button:hover{opacity:1}.cantine-arrow-button:focus{outline:0}.cantine-allergen-collapse{background-color:#f9f9f9;border-radius:5px;padding:0;display:none;transition:all .3s ease-in-out;font-size:.9em}.cantine-allergen-collapse.open{display:block}.cantine-collapse-toggle{background-color:#f7955c;border:none;padding:0 10px;border-radius:5px;font-weight:700;cursor:pointer;display:block;margin-top:0;color:#fff;font-size:.8em}.cantine-collapse-toggle:hover{background-color:#e6b848}.cantine-red{background-color:red}.cantine-green{background-color:green}.cantine-blue{background-color:#00f}.cantine-pink{background-color:pink}.cantine-yellow{background-color:orange}</style> <div class=\\\"widget-dashboard\\\"> <div class=\\\"cantine-menu-card\\\"> <h2 style=\\\"color:#333;font-family:Roboto,sans-serif;display:flex;align-items:center;justify-content:center;gap:10px\\\"> <i18n>cantine.title</i18n> </h2> <div class=\\\"cantine-separator\\\"></div> <div ng-if=\\\"ctrl.showUaiDropdown\\\"> <select id=\\\"uaiSelect\\\" class=\\\"form-control\\\" ng-model=\\\"ctrl.selectedUai\\\" ng-options=\\\"option.uai as option.name for option in ctrl.schoolOptions\\\" ng-change=\\\"ctrl.onUaiSelected()\\\"> <option value=\\\"\\\" disabled=\\\"disabled\\\" selected=\\\"selected\\\" hidden> <i18n>cantine.selectEtablissement</i18n> </option> </select> </div> <div class=\\\"cantine-date-navigation\\\" style=\\\"display:flex;align-items:center;justify-content:center;gap:10px;position:relative\\\"> <button class=\\\"cantine-arrow-button\\\" ng-click=\\\"ctrl.changeDate(-1)\\\">&#8592;</button> <span style=\\\"font-size:1.2em;font-weight:700;cursor:pointer\\\" ng-show=\\\"!ctrl.showDatepicker\\\" ng-click=\\\"ctrl.toggleDatepicker(true)\\\">{{ ctrl.currentDate }}</span> <input type=\\\"date\\\" ng-model=\\\"ctrl.pickerDate\\\" ng-blur=\\\"ctrl.onDateBlur()\\\" ng-show=\\\"ctrl.showDatepicker\\\" style=\\\"font-size:1.2em;font-weight:700;cursor:pointer;text-align:center\\\" ng-click=\\\"ctrl.toggleDatepicker(true)\\\"/> <button class=\\\"cantine-arrow-button\\\" ng-click=\\\"ctrl.changeDate(1)\\\">&#8594;</button> </div> <div ng-if=\\\"ctrl.selectedUai\\\"> <div ng-if=\\\"!ctrl.menuUnavailable\\\"> <div class=\\\"cantine-section\\\"> <div class=\\\"cantine-section-title\\\"> <span class=\\\"cantine-dot cantine-red\\\"></span> <i18n>cantine.entree</i18n> </div> <div class=\\\"cantine-items-container\\\"> <div class=\\\"cantine-item\\\" ng-repeat=\\\"item in ctrl.data.entrees\\\"> {{ item.designationMenu && item.designationMenu.trim() !== '' ? item.designationMenu : item.nom }} <img ng-if=\\\"item.bio\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/bio-logo.png\\\" alt=\\\"Bio\\\" class=\\\"cantine-icon\\\" title=\\\"Agriculture biologique\\\"> <img ng-if=\\\"item.faitmaison\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png\\\" alt=\\\"Fait Maison\\\" class=\\\"cantine-icon\\\" title=\\\"Fait maison\\\"> <img ng-if=\\\"item.vegetarien\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png\\\" alt=\\\"Vegetarian\\\" class=\\\"cantine-icon\\\" title=\\\"Végétarien\\\"> <img ng-if=\\\"item.local\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png\\\" alt=\\\"Local Product\\\" class=\\\"cantine-icon\\\" title=\\\"Produit local\\\"> <button class=\\\"cantine-collapse-toggle\\\" ng-click=\\\"item.showAllergies = !item.showAllergies\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <i18n>cantine.allergenes</i18n> </button> <div class=\\\"cantine-allergen-collapse\\\" ng-class=\\\"{'open': item.showAllergies}\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <span>{{ ctrl.getAllergies(item).join(', ') }}</span> </div> </div> </div> </div> <div class=\\\"cantine-section\\\"> <div class=\\\"cantine-section-title\\\"> <span class=\\\"cantine-dot cantine-green\\\"></span> <i18n>cantine.plat</i18n> </div> <div class=\\\"cantine-items-container\\\"> <div class=\\\"cantine-item\\\" ng-repeat=\\\"item in ctrl.data.plats\\\"> {{ item.designationMenu && item.designationMenu.trim() !== '' ? item.designationMenu : item.nom }} <img ng-if=\\\"item.bio\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/bio-logo.png\\\" alt=\\\"Bio\\\" class=\\\"cantine-icon\\\" title=\\\"Agriculture biologique\\\"> <img ng-if=\\\"item.faitmaison\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png\\\" alt=\\\"Fait Maison\\\" class=\\\"cantine-icon\\\" title=\\\"Fait maison\\\"> <img ng-if=\\\"item.vegetarien\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png\\\" alt=\\\"Vegetarian\\\" class=\\\"cantine-icon\\\" title=\\\"Végétarien\\\"> <img ng-if=\\\"item.local\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png\\\" alt=\\\"Local Product\\\" class=\\\"cantine-icon\\\" title=\\\"Produit local\\\"> <button class=\\\"cantine-collapse-toggle\\\" ng-click=\\\"item.showAllergies = !item.showAllergies\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <i18n>cantine.allergenes</i18n> </button> <div class=\\\"cantine-allergen-collapse\\\" ng-class=\\\"{'open': item.showAllergies}\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <span>{{ ctrl.getAllergies(item).join(', ') }}</span> </div> </div> </div> </div> <div class=\\\"cantine-section\\\"> <div class=\\\"cantine-section-title\\\"> <span class=\\\"cantine-dot cantine-blue\\\"></span> <i18n>cantine.accompagnement</i18n> </div> <div class=\\\"cantine-items-container\\\"> <div class=\\\"cantine-item\\\" ng-repeat=\\\"item in ctrl.data.accompagnements\\\"> {{ item.designationMenu && item.designationMenu.trim() !== '' ? item.designationMenu : item.nom }} <img ng-if=\\\"item.bio\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/bio-logo.png\\\" alt=\\\"Bio\\\" class=\\\"cantine-icon\\\" title=\\\"Agriculture biologique\\\"> <img ng-if=\\\"item.faitmaison\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png\\\" alt=\\\"Fait Maison\\\" class=\\\"cantine-icon\\\" title=\\\"Fait maison\\\"> <img ng-if=\\\"item.vegetarien\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png\\\" alt=\\\"Vegetarian\\\" class=\\\"cantine-icon\\\" title=\\\"Végétarien\\\"> <img ng-if=\\\"item.local\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png\\\" alt=\\\"Local Product\\\" class=\\\"cantine-icon\\\" title=\\\"Produit local\\\"> <button class=\\\"cantine-collapse-toggle\\\" ng-click=\\\"item.showAllergies = !item.showAllergies\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <i18n>cantine.allergenes</i18n> </button> <div class=\\\"cantine-allergen-collapse\\\" ng-class=\\\"{'open': item.showAllergies}\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <span>{{ ctrl.getAllergies(item).join(', ') }}</span> </div> </div> </div> </div> <div class=\\\"cantine-section\\\"> <div class=\\\"cantine-section-title\\\"> <span class=\\\"cantine-dot cantine-pink\\\"></span> <i18n>cantine.laitage</i18n> </div> <div class=\\\"cantine-items-container\\\"> <div class=\\\"cantine-item\\\" ng-repeat=\\\"item in ctrl.data.laitage\\\"> {{ item.designationMenu && item.designationMenu.trim() !== '' ? item.designationMenu : item.nom }} <img ng-if=\\\"item.bio\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/bio-logo.png\\\" alt=\\\"Bio\\\" class=\\\"cantine-icon\\\" title=\\\"Agriculture biologique\\\"> <img ng-if=\\\"item.faitmaison\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png\\\" alt=\\\"Fait Maison\\\" class=\\\"cantine-icon\\\" title=\\\"Fait maison\\\"> <img ng-if=\\\"item.vegetarien\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png\\\" alt=\\\"Vegetarian\\\" class=\\\"cantine-icon\\\" title=\\\"Végétarien\\\"> <img ng-if=\\\"item.local\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png\\\" alt=\\\"Local Product\\\" class=\\\"cantine-icon\\\" title=\\\"Produit local\\\"> <button class=\\\"cantine-collapse-toggle\\\" ng-click=\\\"item.showAllergies = !item.showAllergies\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <i18n>cantine.allergenes</i18n> </button> <div class=\\\"cantine-allergen-collapse\\\" ng-class=\\\"{'open': item.showAllergies}\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <span>{{ ctrl.getAllergies(item).join(', ') }}</span> </div> </div> </div> </div> <div class=\\\"cantine-section\\\"> <div class=\\\"cantine-section-title\\\"> <span class=\\\"cantine-dot cantine-yellow\\\"></span> <i18n>cantine.dessert</i18n> </div> <div class=\\\"cantine-items-container\\\"> <div class=\\\"cantine-item\\\" ng-repeat=\\\"item in ctrl.data.desserts\\\"> {{ item.designationMenu && item.designationMenu.trim() !== '' ? item.designationMenu : item.nom }} <img ng-if=\\\"item.bio\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/bio-logo.png\\\" alt=\\\"Bio\\\" class=\\\"cantine-icon\\\" title=\\\"Agriculture biologique\\\"> <img ng-if=\\\"item.faitmaison\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png\\\" alt=\\\"Fait Maison\\\" class=\\\"cantine-icon\\\" title=\\\"Fait maison\\\"> <img ng-if=\\\"item.vegetarien\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png\\\" alt=\\\"Vegetarian\\\" class=\\\"cantine-icon\\\" title=\\\"Végétarien\\\"> <img ng-if=\\\"item.local\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png\\\" alt=\\\"Local Product\\\" class=\\\"cantine-icon\\\" title=\\\"Produit local\\\"> <button class=\\\"cantine-collapse-toggle\\\" ng-click=\\\"item.showAllergies = !item.showAllergies\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <i18n>cantine.allergenes</i18n> </button> <div class=\\\"cantine-allergen-collapse\\\" ng-class=\\\"{'open': item.showAllergies}\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <span>{{ ctrl.getAllergies(item).join(', ') }}</span> </div> </div> </div> </div> </div> <div ng-if=\\\"ctrl.menuUnavailable\\\" class=\\\"menu-error-message\\\"> {{ ctrl.unavailableMessage }} </div> </div> </div> </div>\";\n// Exports\nexport default code;","import angular, { IAttributes, IController, IDirective, IScope } from \"angular\";\nimport { conf, notif, session } from \"../../utils\";\nimport moment from \"moment\";\nimport 'moment/locale/fr';\nimport axios from 'axios';\n\nclass Controller implements IController {\n data: any = {};\n error: string | null = null;\n uaiList: string[] = [];\n selectedUai: string = '';\n schoolOptions: { uai: string; name: string }[] = [];\n\n menuUnavailable: boolean = false;\n unavailableMessage: string = '';\n\n apiDate: string;\n currentDate: string = '';\n pickerDate: Date = new Date();\n showDatepicker: boolean = false;\n previousApiDate: string = '';\n\n public apply?: () => void;\n\n constructor(private $scope: IScope) {\n const user = session().user;\n\n this.apiDate = moment(this.pickerDate).format('YYYY-MM-DD');\n this.updateCurrentDateDisplay();\n\n const uaiFromSession = user.uai;\n if (Array.isArray(uaiFromSession) && uaiFromSession.length > 1) {\n this.loadUserStructures(user.userId);\n } else if (typeof uaiFromSession === 'string' || (Array.isArray(uaiFromSession) && uaiFromSession.length === 1)) {\n this.uaiList = Array.isArray(uaiFromSession) ? uaiFromSession : [uaiFromSession];\n const nameList = Array.isArray(user.structureNames) ? user.structureNames : [];\n\n this.schoolOptions = this.uaiList.map((uai, i) => ({ uai, name: nameList[i] || uai }));\n\n this.setupUaiDropdown(); // 🔹 call helper\n } else {\n this.uaiList = [];\n this.schoolOptions = [];\n this.selectedUai = '';\n }\n }\n\n onUaiSelected() {\n if (this.selectedUai) {\n this.fetchMenu();\n }\n }\n\n get showUaiDropdown(): boolean {\n return this.schoolOptions.length > 1;\n }\n\n async loadUserStructures(userId: string) {\n try {\n const { data } = await axios.get(`/directory/user/${userId}`);\n if (data && Array.isArray(data.structureNodes)) {\n this.schoolOptions = data.structureNodes.map((node: any) => ({\n uai: node.UAI,\n name: node.feederName || node.name || node.UAI,\n }));\n this.uaiList = this.schoolOptions.map(opt => opt.uai);\n\n this.setupUaiDropdown(); // 🔹 call helper\n this.apply?.();\n } else {\n this.error = \"No structures found for user.\";\n }\n } catch {\n this.error = \"Failed to load user structures.\";\n }\n }\n\n changeDate(delta: number): void {\n this.pickerDate.setDate(this.pickerDate.getDate() + delta);\n this.updateDateAndFetch();\n }\n\n processDateSelection(): void {\n this.updateDateAndFetch();\n this.showDatepicker = false;\n }\n\n onDateBlur(): void {\n if (this.pickerDate instanceof Date && !isNaN(this.pickerDate.getTime())) {\n this.processDateSelection();\n this.$scope.$apply();\n }\n }\n\n toggleDatepicker(forceOpen: boolean | null = null): void {\n if (forceOpen !== null) {\n this.showDatepicker = forceOpen;\n } else {\n this.showDatepicker = !this.showDatepicker;\n }\n }\n\n private updateDateAndFetch() {\n this.apiDate = moment(this.pickerDate).format('YYYY-MM-DD');\n if (this.apiDate !== this.previousApiDate) {\n this.previousApiDate = this.apiDate;\n this.updateCurrentDateDisplay();\n this.fetchMenu();\n }\n }\n\n private updateCurrentDateDisplay() {\n const options: Intl.DateTimeFormatOptions = {\n weekday: 'long',\n day: '2-digit',\n month: 'long',\n };\n this.currentDate = this.pickerDate.toLocaleDateString('fr-FR', options);\n this.currentDate = this.currentDate.charAt(0).toUpperCase() + this.currentDate.slice(1);\n }\n\n fetchMenu() {\n if (!this.selectedUai) {\n this.data = { entrees: [], plats: [], accompagnements: [], desserts: [], laitage: [] };\n this.menuUnavailable = true;\n this.unavailableMessage = \"Veuillez sélectionner un établissement.\";\n this.apply?.();\n return;\n }\n\n axios.get(`/appregistry/${this.selectedUai}/cantine/menu?date=${this.apiDate}`)\n .then(response => {\n const menu = response.data?.menu;\n\n if (Array.isArray(menu) && menu.length > 0) {\n this.data = {\n entrees: menu.filter((item: any) => item.type === \"entree\"),\n plats: menu.filter((item: any) => item.type === \"plat\"),\n accompagnements: menu.filter((item: any) => item.type === \"accompagnement\"),\n laitage: menu.filter((item: any) => item.type === \"laitage\"),\n desserts: menu.filter((item: any) => item.type === \"dessert\"),\n };\n this.menuUnavailable = false;\n this.error = null;\n this.unavailableMessage = '';\n } else {\n this.data = { entrees: [], plats: [], accompagnements: [], desserts: [], laitage: [] };\n this.menuUnavailable = true;\n this.unavailableMessage = \"Menu non disponible pour cette date.\";\n this.error = null;\n }\n\n this.apply?.();\n })\n .catch(error => {\n this.data = { entrees: [], plats: [], accompagnements: [], desserts: [], laitage: [] };\n\n if (error.response?.status === 400) {\n this.menuUnavailable = true;\n this.unavailableMessage = \"Menu indisponible pour cet établissement.\";\n this.error = null;\n } else {\n this.menuUnavailable = true;\n this.unavailableMessage = \"Une erreur est survenue lors du chargement du menu.\";\n this.error = error.message || \"Unknown error\";\n }\n\n this.apply?.();\n });\n }\n\n getAllergies(item: any): string[] {\n if (!item) return [];\n return Object.keys(item)\n .filter(k => k.startsWith(\"allerg_\") && item[k] !== 0 && item[k] !== undefined)\n .map(k => k.replace(\"allerg_\", \"\").replace(\"_\", \" \"));\n }\n\n // 🔹 New helper method\n private setupUaiDropdown() {\n if (this.schoolOptions.length > 1) {\n this.schoolOptions.unshift({ uai: '', name: 'Établissement' });\n this.selectedUai = '';\n } else if (this.schoolOptions.length === 1) {\n this.selectedUai = this.schoolOptions[0].uai;\n this.fetchMenu();\n } else {\n this.selectedUai = '';\n }\n }\n}\n\nclass Directive implements IDirective<IScope, JQLite, IAttributes, IController[]> {\n restrict = \"E\";\n template = require(\"./cantine-widget.widget.html\").default;\n scope = {};\n bindToController = true;\n controller = [Controller];\n controllerAs = \"ctrl\";\n require = [\"odeCantineWidget\"];\n\n async link(scope: IScope, elem: JQLite, attrs: IAttributes, controllers?: IController[]) {\n const ctrl = controllers?.[0] as Controller | undefined;\n if (ctrl) {\n ctrl.apply = () => scope.$apply();\n }\n }\n}\n\nfunction DirectiveFactory() {\n return new Directive();\n}\n\nnotif()\n .onLangReady()\n .promise.then(() => {\n conf().Platform.idiom.addKeys(require(\"./i18n/fr.json\"));\n });\n\nexport const odeModuleName = \"odeCantineWidgetModule\";\nangular.module(odeModuleName, []).directive(\"odeCantineWidget\", DirectiveFactory);\n"],"names":["$scope","data","error","uaiList","selectedUai","schoolOptions","menuUnavailable","unavailableMessage","currentDate","pickerDate","Date","showDatepicker","previousApiDate","user","session","this","apiDate","format","updateCurrentDateDisplay","uaiFromSession","uai","Array","isArray","length","loadUserStructures","userId","structureNames","map","i","name","setupUaiDropdown","onUaiSelected","fetchMenu","get","structureNodes","node","UAI","feederName","opt","apply","changeDate","delta","setDate","getDate","updateDateAndFetch","processDateSelection","onDateBlur","isNaN","getTime","$apply","toggleDatepicker","forceOpen","toLocaleDateString","weekday","day","month","charAt","toUpperCase","slice","entrees","plats","accompagnements","desserts","laitage","then","response","menu","filter","item","type","catch","status","message","getAllergies","Object","keys","k","startsWith","undefined","replace","unshift","restrict","template","scope","bindToController","controller","Controller","controllerAs","require","link","elem","attrs","controllers","ctrl","notif","onLangReady","promise","conf","Platform","idiom","addKeys","odeModuleName","module","directive","Directive"],"sourceRoot":""}
1
+ {"version":3,"file":"widgets/cantine-widget/cantine-widget.widget.js","mappings":"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","sources":["webpack://ode-ngjs-front/./src/ts/widgets/cantine-widget/cantine-widget.widget.html","webpack://ode-ngjs-front/./src/ts/widgets/cantine-widget/cantine-widget.widget.ts"],"sourcesContent":["// Module\nvar code = \"<style>.cantine-menu-card{color:#333;justify-content:center;align-items:center;padding:5%;border-radius:10px;box-shadow:0 0 10px rgba(0,0,0,.1)}.cantine-select-wrapper{display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:20px;font-family:Roboto,sans-serif;color:#333}.cantine-select-wrapper label{font-weight:700;font-size:1em}.cantine-select-wrapper select{padding:6px 10px;border-radius:5px;border:1px solid #ccc;font-size:.95em;font-family:Roboto,sans-serif;background-color:#fff;color:#333;box-shadow:0 2px 5px rgba(0,0,0,.05);transition:border-color .3s ease}.cantine-select-wrapper select:focus{outline:0;border-color:#f7955c}.cantine-select{padding:8px 12px;font-size:1em;border-radius:5px;border:1px solid #ccc;font-family:Roboto,sans-serif;background-color:#fff;color:#333;box-shadow:0 0 5px rgba(0,0,0,.05);max-width:100%}.menu-error-message{padding:1rem;color:#721c24;background-color:#f8d7da;border:1px solid #f5c6cb;border-radius:4px;margin-top:1rem}.cantine-separator{border:none;height:1px;background-color:#ccc;margin:3% 0}.cantine-section{margin-bottom:3%}.cantine-section-title{font-weight:700;font-size:1.1em;display:flex;align-items:center}.cantine-item{margin-left:1%;font-family:Roboto;color:#4a4a4a;font-size:medium}.cantine-item{align-items:center;gap:5px}.cantine-items-container{border-left:2px solid #ccc;margin-left:.8%;padding-left:3%}.cantine-icon{width:1em;height:1em;margin-left:5px;vertical-align:middle;display:inline-block;margin-bottom:10px}.cantine-dot{height:.6em;width:.6em;border-radius:50%;display:inline-block;margin-right:2%}.cantine-arrow-button{background:0 0;border:none;font-size:1.5em;cursor:pointer;opacity:.6;transition:opacity .3s ease}.cantine-arrow-button:hover{opacity:1}.cantine-arrow-button:focus{outline:0}.cantine-allergen-collapse{background-color:#f9f9f9;border-radius:5px;padding:0;display:none;transition:all .3s ease-in-out;font-size:.9em}.cantine-allergen-collapse.open{display:block}.cantine-collapse-toggle{background-color:#f7955c;border:none;padding:0 10px;border-radius:5px;font-weight:700;cursor:pointer;display:block;margin-top:0;color:#fff;font-size:.8em}.cantine-collapse-toggle:hover{background-color:#e6b848}.cantine-red{background-color:red}.cantine-green{background-color:green}.cantine-blue{background-color:#00f}.cantine-pink{background-color:pink}.cantine-yellow{background-color:orange}</style> <div class=\\\"widget-dashboard\\\"> <div class=\\\"cantine-menu-card\\\"> <h2 style=\\\"color:#333;font-family:Roboto,sans-serif;display:flex;align-items:center;justify-content:center;gap:10px\\\"> <i18n>cantine.title</i18n> </h2> <div class=\\\"cantine-separator\\\"></div> <div ng-if=\\\"ctrl.noStructuresError\\\" class=\\\"menu-error-message\\\"> <i18n>cantine.error.noStructures</i18n> </div> <div ng-if=\\\"ctrl.loadStructuresError\\\" class=\\\"menu-error-message\\\"> <i18n>cantine.error.loadStructuresFailed</i18n> </div> <div ng-if=\\\"ctrl.showUaiDropdown\\\"> <select id=\\\"uaiSelect\\\" class=\\\"form-control\\\" ng-model=\\\"ctrl.selectedUai\\\" ng-options=\\\"option.uai as option.name for option in ctrl.schoolOptions\\\" ng-change=\\\"ctrl.onUaiSelected()\\\"> <option value=\\\"\\\" disabled=\\\"disabled\\\" selected=\\\"selected\\\" hidden> <i18n>cantine.selectEtablissement</i18n> </option> </select> </div> <div class=\\\"cantine-date-navigation\\\" style=\\\"display:flex;align-items:center;justify-content:center;gap:10px;position:relative\\\"> <button class=\\\"cantine-arrow-button\\\" ng-click=\\\"ctrl.changeDate(-1)\\\">&#8592;</button> <span style=\\\"font-size:1.2em;font-weight:700;cursor:pointer\\\" ng-show=\\\"!ctrl.showDatepicker\\\" ng-click=\\\"ctrl.toggleDatepicker(true)\\\">{{ ctrl.currentDate }}</span> <input type=\\\"date\\\" ng-model=\\\"ctrl.pickerDate\\\" ng-blur=\\\"ctrl.onDateBlur()\\\" ng-show=\\\"ctrl.showDatepicker\\\" style=\\\"font-size:1.2em;font-weight:700;cursor:pointer;text-align:center\\\" ng-click=\\\"ctrl.toggleDatepicker(true)\\\"/> <button class=\\\"cantine-arrow-button\\\" ng-click=\\\"ctrl.changeDate(1)\\\">&#8594;</button> </div> <div ng-if=\\\"ctrl.selectedUai\\\"> <div ng-if=\\\"!ctrl.menuUnavailable\\\"> <div class=\\\"cantine-section\\\"> <div class=\\\"cantine-section-title\\\"> <span class=\\\"cantine-dot cantine-red\\\"></span> <i18n>cantine.entree</i18n> </div> <div class=\\\"cantine-items-container\\\"> <div class=\\\"cantine-item\\\" ng-repeat=\\\"item in ctrl.data.entrees\\\"> {{ item.designationMenu && item.designationMenu.trim() !== '' ? item.designationMenu : item.nom }} <img ng-if=\\\"item.bio\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/bio-logo.png\\\" alt=\\\"Bio\\\" class=\\\"cantine-icon\\\" title=\\\"Agriculture biologique\\\"> <img ng-if=\\\"item.faitmaison\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png\\\" alt=\\\"Fait Maison\\\" class=\\\"cantine-icon\\\" title=\\\"Fait maison\\\"> <img ng-if=\\\"item.vegetarien\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png\\\" alt=\\\"Vegetarian\\\" class=\\\"cantine-icon\\\" title=\\\"Végétarien\\\"> <img ng-if=\\\"item.local\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png\\\" alt=\\\"Local Product\\\" class=\\\"cantine-icon\\\" title=\\\"Produit local\\\"> <button class=\\\"cantine-collapse-toggle\\\" ng-click=\\\"item.showAllergies = !item.showAllergies\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <i18n>cantine.allergenes</i18n> </button> <div class=\\\"cantine-allergen-collapse\\\" ng-class=\\\"{'open': item.showAllergies}\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <span>{{ ctrl.getAllergies(item).join(', ') }}</span> </div> </div> </div> </div> <div class=\\\"cantine-section\\\"> <div class=\\\"cantine-section-title\\\"> <span class=\\\"cantine-dot cantine-green\\\"></span> <i18n>cantine.plat</i18n> </div> <div class=\\\"cantine-items-container\\\"> <div class=\\\"cantine-item\\\" ng-repeat=\\\"item in ctrl.data.plats\\\"> {{ item.designationMenu && item.designationMenu.trim() !== '' ? item.designationMenu : item.nom }} <img ng-if=\\\"item.bio\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/bio-logo.png\\\" alt=\\\"Bio\\\" class=\\\"cantine-icon\\\" title=\\\"Agriculture biologique\\\"> <img ng-if=\\\"item.faitmaison\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png\\\" alt=\\\"Fait Maison\\\" class=\\\"cantine-icon\\\" title=\\\"Fait maison\\\"> <img ng-if=\\\"item.vegetarien\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png\\\" alt=\\\"Vegetarian\\\" class=\\\"cantine-icon\\\" title=\\\"Végétarien\\\"> <img ng-if=\\\"item.local\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png\\\" alt=\\\"Local Product\\\" class=\\\"cantine-icon\\\" title=\\\"Produit local\\\"> <button class=\\\"cantine-collapse-toggle\\\" ng-click=\\\"item.showAllergies = !item.showAllergies\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <i18n>cantine.allergenes</i18n> </button> <div class=\\\"cantine-allergen-collapse\\\" ng-class=\\\"{'open': item.showAllergies}\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <span>{{ ctrl.getAllergies(item).join(', ') }}</span> </div> </div> </div> </div> <div class=\\\"cantine-section\\\"> <div class=\\\"cantine-section-title\\\"> <span class=\\\"cantine-dot cantine-blue\\\"></span> <i18n>cantine.accompagnement</i18n> </div> <div class=\\\"cantine-items-container\\\"> <div class=\\\"cantine-item\\\" ng-repeat=\\\"item in ctrl.data.accompagnements\\\"> {{ item.designationMenu && item.designationMenu.trim() !== '' ? item.designationMenu : item.nom }} <img ng-if=\\\"item.bio\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/bio-logo.png\\\" alt=\\\"Bio\\\" class=\\\"cantine-icon\\\" title=\\\"Agriculture biologique\\\"> <img ng-if=\\\"item.faitmaison\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png\\\" alt=\\\"Fait Maison\\\" class=\\\"cantine-icon\\\" title=\\\"Fait maison\\\"> <img ng-if=\\\"item.vegetarien\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png\\\" alt=\\\"Vegetarian\\\" class=\\\"cantine-icon\\\" title=\\\"Végétarien\\\"> <img ng-if=\\\"item.local\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png\\\" alt=\\\"Local Product\\\" class=\\\"cantine-icon\\\" title=\\\"Produit local\\\"> <button class=\\\"cantine-collapse-toggle\\\" ng-click=\\\"item.showAllergies = !item.showAllergies\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <i18n>cantine.allergenes</i18n> </button> <div class=\\\"cantine-allergen-collapse\\\" ng-class=\\\"{'open': item.showAllergies}\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <span>{{ ctrl.getAllergies(item).join(', ') }}</span> </div> </div> </div> </div> <div class=\\\"cantine-section\\\"> <div class=\\\"cantine-section-title\\\"> <span class=\\\"cantine-dot cantine-pink\\\"></span> <i18n>cantine.laitage</i18n> </div> <div class=\\\"cantine-items-container\\\"> <div class=\\\"cantine-item\\\" ng-repeat=\\\"item in ctrl.data.laitage\\\"> {{ item.designationMenu && item.designationMenu.trim() !== '' ? item.designationMenu : item.nom }} <img ng-if=\\\"item.bio\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/bio-logo.png\\\" alt=\\\"Bio\\\" class=\\\"cantine-icon\\\" title=\\\"Agriculture biologique\\\"> <img ng-if=\\\"item.faitmaison\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png\\\" alt=\\\"Fait Maison\\\" class=\\\"cantine-icon\\\" title=\\\"Fait maison\\\"> <img ng-if=\\\"item.vegetarien\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png\\\" alt=\\\"Vegetarian\\\" class=\\\"cantine-icon\\\" title=\\\"Végétarien\\\"> <img ng-if=\\\"item.local\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png\\\" alt=\\\"Local Product\\\" class=\\\"cantine-icon\\\" title=\\\"Produit local\\\"> <button class=\\\"cantine-collapse-toggle\\\" ng-click=\\\"item.showAllergies = !item.showAllergies\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <i18n>cantine.allergenes</i18n> </button> <div class=\\\"cantine-allergen-collapse\\\" ng-class=\\\"{'open': item.showAllergies}\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <span>{{ ctrl.getAllergies(item).join(', ') }}</span> </div> </div> </div> </div> <div class=\\\"cantine-section\\\"> <div class=\\\"cantine-section-title\\\"> <span class=\\\"cantine-dot cantine-yellow\\\"></span> <i18n>cantine.dessert</i18n> </div> <div class=\\\"cantine-items-container\\\"> <div class=\\\"cantine-item\\\" ng-repeat=\\\"item in ctrl.data.desserts\\\"> {{ item.designationMenu && item.designationMenu.trim() !== '' ? item.designationMenu : item.nom }} <img ng-if=\\\"item.bio\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/bio-logo.png\\\" alt=\\\"Bio\\\" class=\\\"cantine-icon\\\" title=\\\"Agriculture biologique\\\"> <img ng-if=\\\"item.faitmaison\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/fait-maison-logo.png\\\" alt=\\\"Fait Maison\\\" class=\\\"cantine-icon\\\" title=\\\"Fait maison\\\"> <img ng-if=\\\"item.vegetarien\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/vegetarien-logo.png\\\" alt=\\\"Vegetarian\\\" class=\\\"cantine-icon\\\" title=\\\"Végétarien\\\"> <img ng-if=\\\"item.local\\\" src=\\\"/assets/widgets/cantine-widget/cantine-logos/produit-local-logo.png\\\" alt=\\\"Local Product\\\" class=\\\"cantine-icon\\\" title=\\\"Produit local\\\"> <button class=\\\"cantine-collapse-toggle\\\" ng-click=\\\"item.showAllergies = !item.showAllergies\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <i18n>cantine.allergenes</i18n> </button> <div class=\\\"cantine-allergen-collapse\\\" ng-class=\\\"{'open': item.showAllergies}\\\" ng-if=\\\"ctrl.getAllergies(item).length > 0\\\"> <span>{{ ctrl.getAllergies(item).join(', ') }}</span> </div> </div> </div> </div> </div> <div ng-if=\\\"ctrl.menuUnavailable\\\" class=\\\"menu-error-message\\\"> <div ng-if=\\\"ctrl.selectInstitutionError\\\"> <i18n>cantine.error.selectInstitution</i18n> </div> <div ng-if=\\\"ctrl.menuNotAvailableError\\\"> <i18n>cantine.error.menuNotAvailable</i18n> </div> <div ng-if=\\\"ctrl.menuUnavailableForInstitutionError\\\"> <i18n>cantine.error.menuUnavailableForInstitution</i18n> </div> <div ng-if=\\\"ctrl.loadMenuError\\\"> <i18n>cantine.error.loadMenuFailed</i18n> </div> </div> </div> </div> </div>\";\n// Exports\nexport default code;","import angular, { IAttributes, IController, IDirective, IScope } from \"angular\";\nimport { conf, notif, session } from \"../../utils\";\nimport moment from \"moment\";\nimport 'moment/locale/fr';\nimport axios from 'axios';\n\nclass Controller implements IController {\n data: any = {};\n error: string | null = null;\n uaiList: string[] = [];\n selectedUai: string = '';\n schoolOptions: { uai: string; name: string }[] = [];\n\n menuUnavailable: boolean = false;\n noStructuresError: boolean = false;\n loadStructuresError: boolean = false;\n selectInstitutionError: boolean = false;\n menuNotAvailableError: boolean = false;\n menuUnavailableForInstitutionError: boolean = false;\n loadMenuError: boolean = false;\n\n apiDate: string;\n currentDate: string = '';\n pickerDate: Date = new Date();\n showDatepicker: boolean = false;\n previousApiDate: string = '';\n\n public apply?: () => void;\n\n constructor(private $scope: IScope) {\n const user = session().user;\n\n this.apiDate = moment(this.pickerDate).format('YYYY-MM-DD');\n this.updateCurrentDateDisplay();\n\n const uaiFromSession = user.uai;\n if (Array.isArray(uaiFromSession) && uaiFromSession.length > 1) {\n this.loadUserStructures(user.userId);\n } else if (typeof uaiFromSession === 'string' || (Array.isArray(uaiFromSession) && uaiFromSession.length === 1)) {\n this.uaiList = Array.isArray(uaiFromSession) ? uaiFromSession : [uaiFromSession];\n const nameList = Array.isArray(user.structureNames) ? user.structureNames : [];\n\n this.schoolOptions = this.uaiList.map((uai, i) => ({ uai, name: nameList[i] || uai }));\n\n this.setupUaiDropdown(); // 🔹 call helper\n } else {\n this.uaiList = [];\n this.schoolOptions = [];\n this.selectedUai = '';\n }\n }\n\n onUaiSelected() {\n if (this.selectedUai) {\n this.fetchMenu();\n }\n }\n\n get showUaiDropdown(): boolean {\n return this.schoolOptions.length > 1;\n }\n\n async loadUserStructures(userId: string) {\n try {\n const { data } = await axios.get(`/directory/user/${userId}`);\n if (data && Array.isArray(data.structureNodes)) {\n this.schoolOptions = data.structureNodes.map((node: any) => ({\n uai: node.UAI,\n name: node.feederName || node.name || node.UAI,\n }));\n this.uaiList = this.schoolOptions.map(opt => opt.uai);\n\n this.setupUaiDropdown(); // 🔹 call helper\n this.apply?.();\n } else {\n this.noStructuresError = true;\n }\n } catch {\n this.loadStructuresError = true;\n }\n }\n\n changeDate(delta: number): void {\n this.pickerDate.setDate(this.pickerDate.getDate() + delta);\n this.updateDateAndFetch();\n }\n\n processDateSelection(): void {\n this.updateDateAndFetch();\n this.showDatepicker = false;\n }\n\n onDateBlur(): void {\n if (this.pickerDate instanceof Date && !isNaN(this.pickerDate.getTime())) {\n this.processDateSelection();\n this.$scope.$apply();\n }\n }\n\n toggleDatepicker(forceOpen: boolean | null = null): void {\n if (forceOpen !== null) {\n this.showDatepicker = forceOpen;\n } else {\n this.showDatepicker = !this.showDatepicker;\n }\n }\n\n private updateDateAndFetch() {\n this.apiDate = moment(this.pickerDate).format('YYYY-MM-DD');\n if (this.apiDate !== this.previousApiDate) {\n this.previousApiDate = this.apiDate;\n this.updateCurrentDateDisplay();\n this.fetchMenu();\n }\n }\n\n private updateCurrentDateDisplay() {\n const options: Intl.DateTimeFormatOptions = {\n weekday: 'long',\n day: '2-digit',\n month: 'long',\n };\n this.currentDate = this.pickerDate.toLocaleDateString('fr-FR', options);\n this.currentDate = this.currentDate.charAt(0).toUpperCase() + this.currentDate.slice(1);\n }\n\n fetchMenu() {\n if (!this.selectedUai) {\n this.data = { entrees: [], plats: [], accompagnements: [], desserts: [], laitage: [] };\n this.menuUnavailable = true;\n this.selectInstitutionError = true;\n this.apply?.();\n return;\n }\n\n axios.get(`/appregistry/${this.selectedUai}/cantine/menu?date=${this.apiDate}`)\n .then(response => {\n const menu = response.data?.menu;\n\n if (Array.isArray(menu) && menu.length > 0) {\n this.data = {\n entrees: menu.filter((item: any) => item.type === \"entree\"),\n plats: menu.filter((item: any) => item.type === \"plat\"),\n accompagnements: menu.filter((item: any) => item.type === \"accompagnement\"),\n laitage: menu.filter((item: any) => item.type === \"laitage\"),\n desserts: menu.filter((item: any) => item.type === \"dessert\"),\n };\n this.menuUnavailable = false;\n this.error = null;\n this.selectInstitutionError = false;\n this.menuNotAvailableError = false;\n this.menuUnavailableForInstitutionError = false;\n this.loadMenuError = false;\n this.noStructuresError = false;\n this.loadStructuresError = false;\n } else {\n this.data = { entrees: [], plats: [], accompagnements: [], desserts: [], laitage: [] };\n this.menuUnavailable = true;\n this.menuNotAvailableError = true;\n this.error = null;\n }\n\n this.apply?.();\n })\n .catch(error => {\n this.data = { entrees: [], plats: [], accompagnements: [], desserts: [], laitage: [] };\n\n if (error.response?.status === 400) {\n this.menuUnavailable = true;\n this.menuUnavailableForInstitutionError = true;\n this.error = null;\n } else {\n this.menuUnavailable = true;\n this.loadMenuError = true;\n this.error = error.message || \"Unknown error\";\n }\n\n this.apply?.();\n });\n }\n\n getAllergies(item: any): string[] {\n if (!item) return [];\n return Object.keys(item)\n .filter(k => k.startsWith(\"allerg_\") && item[k] !== 0 && item[k] !== undefined)\n .map(k => k.replace(\"allerg_\", \"\").replace(\"_\", \" \"));\n }\n\n // 🔹 New helper method\n private setupUaiDropdown() {\n if (this.schoolOptions.length > 1) {\n this.schoolOptions.unshift({ uai: '', name: 'Établissement' });\n this.selectedUai = '';\n } else if (this.schoolOptions.length === 1) {\n this.selectedUai = this.schoolOptions[0].uai;\n this.fetchMenu();\n } else {\n this.selectedUai = '';\n }\n }\n}\n\nclass Directive implements IDirective<IScope, JQLite, IAttributes, IController[]> {\n restrict = \"E\";\n template = require(\"./cantine-widget.widget.html\").default;\n scope = {};\n bindToController = true;\n controller = [Controller];\n controllerAs = \"ctrl\";\n require = [\"odeCantineWidget\"];\n\n async link(scope: IScope, elem: JQLite, attrs: IAttributes, controllers?: IController[]) {\n const ctrl = controllers?.[0] as Controller | undefined;\n if (ctrl) {\n ctrl.apply = () => scope.$apply();\n }\n }\n}\n\nfunction DirectiveFactory() {\n return new Directive();\n}\n\nnotif()\n .onLangReady()\n .promise.then(() => {\n conf().Platform.idiom.addKeys(require(\"./i18n/fr.json\"));\n conf().Platform.idiom.addKeys(require(\"./i18n/en.json\"));\n });\n\nexport const odeModuleName = \"odeCantineWidgetModule\";\nangular.module(odeModuleName, []).directive(\"odeCantineWidget\", DirectiveFactory);\n"],"names":["$scope","data","error","uaiList","selectedUai","schoolOptions","menuUnavailable","noStructuresError","loadStructuresError","selectInstitutionError","menuNotAvailableError","menuUnavailableForInstitutionError","loadMenuError","currentDate","pickerDate","Date","showDatepicker","previousApiDate","user","session","this","apiDate","format","updateCurrentDateDisplay","uaiFromSession","uai","Array","isArray","length","loadUserStructures","userId","structureNames","map","i","name","setupUaiDropdown","onUaiSelected","fetchMenu","get","structureNodes","node","UAI","feederName","opt","apply","changeDate","delta","setDate","getDate","updateDateAndFetch","processDateSelection","onDateBlur","isNaN","getTime","$apply","toggleDatepicker","forceOpen","toLocaleDateString","weekday","day","month","charAt","toUpperCase","slice","entrees","plats","accompagnements","desserts","laitage","then","response","menu","filter","item","type","catch","status","message","getAllergies","Object","keys","k","startsWith","undefined","replace","unshift","restrict","template","scope","bindToController","controller","Controller","controllerAs","require","link","elem","attrs","controllers","ctrl","notif","onLangReady","promise","conf","Platform","idiom","addKeys","odeModuleName","module","directive","Directive"],"sourceRoot":""}
@@ -1,2 +1,2 @@
1
- "use strict";(self.webpackChunkode_ngjs_front=self.webpackChunkode_ngjs_front||[]).push([[202],{5688:(e,t,o)=>{o.d(t,{Z:()=>r});const r='<style>.screen-time-summary{padding:16px;font-family:Roboto,sans-serif;display:flex;flex-direction:column;gap:12px}.title{padding-bottom:6px;font-size:16px;color:#4a4a4a;font-weight:700;border-bottom:1px solid #e0e0e0}.modal-body{margin:1rem 0!important}.summary-row{display:flex;gap:12px}.summary-row:nth-of-type(2){margin-top:10px}.summary-block{flex:1;background:#e5f5ff;border-radius:12px;padding:12px;box-shadow:0 1px 3px rgba(0,0,0,.1)}.summary-block .label{font-size:16px;color:#4a4a4a;font-weight:700;margin-bottom:4px}.summary-block .value{font-size:32px;color:#333}.percentage-bar-container{width:100%;height:10px;background-color:#e0e0e0;border-radius:5px;margin-top:5px;display:flex;overflow:hidden}.percentage-bar{height:100%;transition:width .5s ease-in-out;border-radius:5px}.legend{display:flex;gap:16px;align-items:center;margin-top:10px;font-size:10px;color:#333}.legend-item{display:flex;align-items:center;gap:6px}.legend-square{width:16px;height:16px;border-radius:4px}.custom-select{appearance:none;border:none;padding:8px 32px 8px 12px;border-radius:6px;font-size:16px;color:#333;font-family:Roboto,sans-serif;background:#fff url("data:image/svg+xml,%3Csvg fill=\'none\' stroke=\'%23666\' stroke-width=\'2\' viewBox=\'0 0 24 24\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cpath stroke-linecap=\'round\' stroke-linejoin=\'round\' d=\'M19 9l-7 7-7-7\'%3E%3C/path%3E%3C/svg%3E") no-repeat right 10px center;background-size:16px;cursor:pointer;min-width:150px}.custom-select:focus{outline:0;box-shadow:0 0 0 2px rgba(42,156,200,.3)}.see-more{display:flex;justify-content:flex-end;margin-top:8px;padding-left:4px}.see-more button{background-color:transparent;border:none;font-family:Roboto,sans-serif;font-weight:600;font-size:16px;line-height:24px;padding:6px 12px;cursor:pointer;color:#2a9cc8;border-radius:4px;transition:background-color .2s ease}.see-more button:hover{background-color:#f0f8fc}.centered-controls{margin:0 auto 20px auto;width:fit-content;text-align:center}.view-controls{display:flex;width:100%;flex-direction:row;justify-content:flex-end;margin-bottom:16px}.control-group{display:flex;align-items:center}.close-button-container{display:flex;justify-content:flex-end;margin-top:20px}.close-button{width:84px;height:40px;padding:8px 16px;background:#ff8d2e;color:#fff;font-size:16px;font-weight:700;border:none;border-radius:8px;cursor:pointer;transition:background .3s ease}.close-button:hover{background:#e67c24}.date-controls{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.nav-button{background:0 0;border:none;font-size:24px;cursor:pointer}.date-navigation{display:flex;align-items:center;justify-content:center;gap:1rem}.date-navigation button{background:0 0;border:none;font-size:1.5rem;cursor:pointer}.date-navigation button:disabled{color:#d3d3d3;cursor:not-allowed}.selected-date{cursor:pointer;font-weight:700}.error-message{background-color:#ffe5e5;color:#c00;border:1px solid #c00;padding:12px;border-radius:6px;font-size:14px;font-weight:500}.info-popover{position:absolute;z-index:10;display:block;width:90%;background-color:#fff;border:1px solid #e0e0e0;box-shadow:0 4px 6px 0 rgba(0,0,0,.08);font-size:1.5rem;line-height:1.8;padding:1rem;border-radius:6px}</style> <div class="screen-time-widget-container"> <div class="screen-time-summary"> <div class="title"> <div class="label"> <i18n>screenTime.title.uppercase</i18n> </div> </div> <div ng-if="ctrl.isParent"> <select id="userSelect" class="custom-select" ng-model="ctrl.selectedUser" ng-options="child.userId as child.name for child in ctrl.children" ng-change="ctrl.fetchDataForCurrentUser()"> </select> </div> <div class="error-message" ng-if="ctrl.errorMessage"> {{ ctrl.errorMessage }} </div> <div ng-if="!ctrl.errorMessage"> <div class="summary-row"> <div class="summary-block"> <div class="label"> <i18n>screenTime.today</i18n> </div> <div class="value">{{ ctrl.todayTotal | duration }}</div> </div> </div> <div class="summary-row"> <div class="summary-block"> <div class="label"> <i18n>screenTime.weekAverage</i18n> </div> <div class="value">{{ ctrl.weeklyTotalAverage | duration }}</div> </div> </div> <div class="legend"> <div class="see-more"> <button ng-click="ctrl.toggleLightbox(true)"> <i18n>screenTime.displayDetails</i18n> </button> </div> </div> </div> </div> <ode-modal visible="ctrl.showLightbox" on-close="ctrl.toggleLightbox(false)" size="lg" dnd-nodrag> <ode-modal-title> <h2> <i18n>screenTime.title.normal</i18n> <popover> <popover-opener><i class="fa-solid fa-circle-info"></i></popover-opener> <popover-content class="info-popover"> <ul> <li> <i18n>screenTime.information.first</i18n> </li> <li> <i18n>screenTime.information.second</i18n> </li> <li> <i18n>screenTime.information.third</i18n> </li> <li> <i18n>screenTime.information.fourth</i18n> </li> </ul> </popover-content> </popover> </h2> <div class="view-controls"> <div class="control-group"> <label style="font-size:14px" for="userSelect"> <i18n>screenTime.user</i18n> :</label> <div ng-if="ctrl.isParent"> <select id="userSelect" class="custom-select" ng-model="ctrl.selectedChildHistogram" ng-options="child.userId as child.name for child in ctrl.children"> </select> </div> </div> <div class="control-group"> <label for="viewMode"> <i18n>screenTime.viewMode</i18n> :</label> <select id="viewMode" class="custom-select" ng-model="ctrl.viewMode" ng-change="ctrl.updateChart()"> <option value="weekly"> [[ctrl.lang.translate("screenTime.viewMode.weekly")]] </option> <option value="daily"> [[ctrl.lang.translate("screenTime.viewMode.daily")]] </option> </select> </div> </div> </ode-modal-title> <ode-modal-body class="modal-body"> <div class="date-selectors centered-controls" ng-if="ctrl.showLightbox"> <div class="date-navigation" ng-if="ctrl.viewMode === \'daily\'"> <button ng-click="ctrl.changeDay(-1)" ng-disabled="!ctrl.canGoToPreviousDay()">←</button> <div ng-if="!ctrl.showDatePicker" class="selected-date" ng-click="ctrl.toggleDatePicker(true)"> {{ ctrl.selectedDailyDateObj | date:\'fullDate\' }} </div> <input type="date" ng-if="ctrl.showDatePicker" ng-model="ctrl.selectedDailyDate" ng-change="ctrl.setDateFromPicker(ctrl.selectedDailyDate)" ng-blur="ctrl.toggleDatePicker(false)" max="{{ ctrl.selectedDailyDate }}"/> <button ng-click="ctrl.changeDay(1)" ng-disabled="!ctrl.canGoToNextDay()">→</button> </div> <div class="date-navigation" ng-if="ctrl.viewMode === \'weekly\'"> <button ng-click="ctrl.changeWeek(-1)" ng-disabled="!ctrl.canGoToPreviousWeek()">←</button> <div class="selected-date">{{ ctrl.weekLabel }}</div> <button ng-click="ctrl.changeWeek(1)" ng-disabled="!ctrl.canGoToNextWeek()">→</button> </div> </div> <canvas id="myChart" style="width:100%;height:500px;max-height:500px"></canvas> </ode-modal-body> </ode-modal> </div>'},58:function(e,t,o){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.odeModuleName=t.Controller=void 0;var a=r(o(4202)),i=o(9419),n=o(5668),s=r(o(381));o(4470),n.Chart.register.apply(n.Chart,n.registerables),s.default.locale("fr");var l=function(){function e(){this.platform=(0,i.conf)().Platform,this.lang=this.platform.idiom,this.selectedUser="",this.viewMode="weekly",this.userData={},this.selectedDailyDate=(0,s.default)().format("YYYY-MM-DD"),this.weekStart=(0,s.default)().startOf("isoWeek"),this.weekEnd=(0,s.default)().endOf("isoWeek"),this.updateChart=function(){},this.todayOnCampus=0,this.todayOffCampus=0,this.todayTotal=0,this.weeklyAvgOnCampus=0,this.weeklyAvgOffCampus=0,this.weeklyTotalAverage=0,this.weeklyAvgSchoolUsePercentage=0,this.weeklyAvgOutOfSchoolPercentage=0,this.fixedTodayOnCampus=0,this.fixedTodayOffCampus=0,this.fixedTodayTotal=0,this.fixedWeeklyAvgOnCampus=0,this.fixedWeeklyAvgOffCampus=0,this.fixedWeeklyTotalAverage=0,this.fixedWeeklyAvgSchoolUsePercentage=0,this.fixedWeeklyAvgOutOfSchoolPercentage=0,this.customWeekMode=!1,this.customStartDate="",this.customEndDate="",this.isParent=!1,this.hasError=!1,this.errorMessage="",this.fetchDataForCurrentUser=function(){},this.fetchLightboxData=function(){},this.showLightbox=!1,this.showDatePicker=!1,this.children=[],this.selectedChild=null,this.selectedChildHistogram=""}return e.prototype.toggleDatePicker=function(e){this.showDatePicker=e},e.prototype.setDateFromPicker=function(e){var t=(0,s.default)(e).format("YYYY-MM-DD");this.showDatePicker=!1,this.setSelectedDate(t),this.fetchLightboxData()},e.prototype.toggleLightbox=function(e){this.showLightbox=e,e&&(this.fixedTodayOnCampus=this.todayOnCampus,this.fixedTodayOffCampus=this.todayOffCampus,this.fixedTodayTotal=this.todayTotal,this.fixedWeeklyAvgOnCampus=this.weeklyAvgOnCampus,this.fixedWeeklyAvgOffCampus=this.weeklyAvgOffCampus,this.fixedWeeklyTotalAverage=this.weeklyTotalAverage,this.fixedWeeklyAvgSchoolUsePercentage=this.weeklyAvgSchoolUsePercentage,this.fixedWeeklyAvgOutOfSchoolPercentage=this.weeklyAvgOutOfSchoolPercentage,this.selectedDailyDate=(0,s.default)().format("YYYY-MM-DD"),this.weekStart=(0,s.default)().startOf("isoWeek"),this.weekEnd=(0,s.default)().endOf("isoWeek"),this.selectedChildHistogram=this.selectedUser)},e.prototype.getCurrentSchoolYear=function(){var e=(0,s.default)(),t=e.year(),o=e.month()<8?(0,s.default)({year:t-1,month:8,day:1}):(0,s.default)({year:t,month:8,day:1}),r=o.clone().add(1,"year").subtract(1,"day");return{schoolYearStart:o.startOf("day"),schoolYearEnd:r.endOf("day")}},e.prototype.setSelectedDate=function(e){var t=(0,s.default)(e);this.selectedDailyDate=t.format("YYYY-MM-DD"),this.weekStart=t.clone().startOf("isoWeek"),this.weekEnd=t.clone().endOf("isoWeek")},e.prototype.setSelectedWeek=function(e){this.weekStart=e.clone().startOf("isoWeek"),this.weekEnd=this.weekStart.clone().endOf("isoWeek"),this.selectedDailyDate=this.weekStart.format("YYYY-MM-DD")},e.prototype.canGoToPreviousWeek=function(){var e=this.weekStart.clone().subtract(1,"week"),t=this.getCurrentSchoolYear(),o=t.schoolYearStart,r=t.schoolYearEnd;return e.isBetween(o,r)},e.prototype.canGoToNextWeek=function(){var e=this.weekStart.clone().add(1,"week"),t=this.getCurrentSchoolYear(),o=t.schoolYearStart,r=t.schoolYearEnd;return e.isBetween(o,r)},e.prototype.canGoToPreviousDay=function(){var e=(0,s.default)(this.selectedDailyDate).subtract(1,"day"),t=this.getCurrentSchoolYear(),o=t.schoolYearStart,r=t.schoolYearEnd;return e.isBetween(o,r)},e.prototype.canGoToNextDay=function(){var e=(0,s.default)(this.selectedDailyDate).add(1,"day"),t=this.getCurrentSchoolYear(),o=t.schoolYearStart,r=t.schoolYearEnd;return e.isBetween(o,r)},e.prototype.changeDay=function(e){var t=(0,s.default)(this.selectedDailyDate).add(e,"days");this.setSelectedDate(t),this.fetchLightboxData()},e.prototype.changeWeek=function(e){var t=this.weekStart.clone().add(e,"weeks");this.setSelectedWeek(t),this.fetchLightboxData()},Object.defineProperty(e.prototype,"selectedDailyDateObj",{get:function(){return new Date(this.selectedDailyDate)},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"weekLabel",{get:function(){var e=this.weekStart.format("dddd D MMMM"),t=this.weekEnd.format("dddd D MMMM");return"du ".concat(e," au ").concat(t)},enumerable:!1,configurable:!0}),e}();function c(e,t,o,r,a,i){t.children=d(t);var n="/appregistry/screen-time/".concat(o,"/weekly?startDate=").concat(a,"&endDate=").concat(i),s="/appregistry/screen-time/".concat(o,"/daily?date=").concat(r);return Promise.all([e.get(n),e.get(s)]).then(function(e){var o=e[0],r=e[1];return t.hasError=!1,t.errorMessage="",{weekly:o.data.dailySummaries.reduce(function(e,t){return e[t.date]={duration:t.durationMinutes,schoolUsePercentage:t.schoolUsePercentage/100},e},{}),daily:r.data.durations.map(function(e){return{hour:e.hour,duration:e.durationMinutes,schoolUsePercentage:e.schoolUsePercentage/100}})}}).catch(function(e){return t.hasError=!0,404===(null==e?void 0:e.status)?t.errorMessage="Erreur lors de l’identification de l’utilisateur. Contactez l’administrateur de votre établissement.":t.errorMessage="Un problème technique est survenu. Si le problème persiste contactez l’administrateur de votre établissement.",console.error("Error fetching data for current user and dates:",e),Promise.reject(e)})}function d(e){var t=(0,i.session)().user.children;return e.isParent=!1,t&&Object.keys(t).length>0?(e.isParent=!0,Object.entries(t).map(function(e){var t=e[0],o=e[1];return{id:t,name:"".concat(o.firstName," ").concat(o.lastName),userId:t}})):[]}t.Controller=l;var u=function(){function e(){this.restrict="E",this.template=o(5688).Z,this.scope={},this.bindToController=!0,this.controller=[l],this.controllerAs="ctrl",this.require=["odeScreenTimeWidget"]}return e.prototype.link=function(e,t,o,r){var l=r?r[0]:null;if(l){var u=a.default.injector(["ng"]).get("$http"),f=(0,i.session)().user.userId;l.fetchDataForCurrentUser();var h=null,p=function(e,t,o,r){return"".concat(e,"_daily_").concat(t,"_weekly_").concat(o,"_").concat(r)};l.fetchDataForCurrentUser=function(){l.errorMessage="",l.hasError=!1;var t=p(l.selectedUser,(0,s.default)().format("YYYY-MM-DD"),(0,s.default)().startOf("isoWeek").format("YYYY-MM-DD"),(0,s.default)().endOf("isoWeek").format("YYYY-MM-DD"));c(u,l,l.selectedUser,(0,s.default)().format("YYYY-MM-DD"),(0,s.default)().startOf("isoWeek").format("YYYY-MM-DD"),(0,s.default)().endOf("isoWeek").format("YYYY-MM-DD")).then(function(o){var r,a,i,n,c,d,u,f,h,p;l.userData[t]={weekly:o.weekly,daily:o.daily},r=o.weekly,a=0,i=0,n=0,c=(0,s.default)().format("YYYY-MM-DD"),d=0,u=0,f=0,h=0,p=0,Object.keys(r).forEach(function(e){var t=r[e],o=t.duration||0,l=t.schoolUsePercentage||0,g=o*l,m=o*(1-l);a+=g,i+=m,n++,f+=g,h+=m,p+=o,(0,s.default)(e).format("YYYY-MM-DD")===c&&(d=g,u=m)}),l.todayOnCampus=d,l.todayOffCampus=u,l.todayTotal=d+u,l.weeklyAvgOnCampus=n>0?a/n:0,l.weeklyAvgOffCampus=n>0?i/n:0,l.weeklyTotalAverage=l.weeklyAvgOnCampus+l.weeklyAvgOffCampus,p>0?(l.weeklyAvgSchoolUsePercentage=f/p*100,l.weeklyAvgOutOfSchoolPercentage=h/p*100):(l.weeklyAvgSchoolUsePercentage=0,l.weeklyAvgOutOfSchoolPercentage=0),e.$applyAsync()}).catch(function(t){e.$applyAsync(),console.error("Error fetching initial data for current user:",t)})},l.fetchLightboxData=function(){var t=l.selectedChildHistogram||l.selectedUser;if(t){var o=p(t,l.selectedDailyDate,l.weekStart.format("YYYY-MM-DD"),l.weekEnd.format("YYYY-MM-DD"));c(u,l,t,l.selectedDailyDate,l.weekStart.format("YYYY-MM-DD"),l.weekEnd.format("YYYY-MM-DD")).then(function(t){l.userData[o]={weekly:t.weekly,daily:t.daily},l.showLightbox&&setTimeout(function(){return l.updateChart()},50),e.$applyAsync()}).catch(function(e){console.error("Error fetching data for lightbox:",e)})}},l.updateChart=function(){var e=t[0].querySelector("#myChart");if(e){var o=e.getContext("2d");if(o){var r=[],a=[],i=l.selectedChildHistogram||l.selectedUser,c=p(i,l.selectedDailyDate,l.weekStart.format("YYYY-MM-DD"),l.weekEnd.format("YYYY-MM-DD")),d=l.userData[c];if(d){var u="weekly"===l.viewMode?d.weekly:d.daily;if(u){if("weekly"===l.viewMode)Object.keys(u).sort().forEach(function(e){var t=u[e];r.push((0,s.default)(e).format("ddd D")),a.push(t.duration)});else u.forEach(function(e){r.push("".concat(e.hour,"h")),a.push(e.duration)});h?(h.data.labels=r,h.data.datasets[0].data=a,h.options={scales:{x:{stacked:!0},y:{stacked:!0,beginAtZero:!0,title:{display:!0,text:"weekly"===l.viewMode?"Heures":"Minutes"}}}},h.update()):h=new n.Chart(o,{type:"bar",data:{labels:r,datasets:[{label:"Usage total",data:a,backgroundColor:"#2A9CC8"}]},options:{plugins:{tooltip:{callbacks:{label:function(e){var t=e.raw;return"".concat(e.dataset.label,": ").concat(t," ").concat("weekly"===l.viewMode?"heures":"minutes")}}},legend:{display:!1,position:"bottom"}},responsive:!0,scales:{x:{stacked:!0},y:{stacked:!0,beginAtZero:!0,title:{display:!0,text:"weekly"===l.viewMode?"Heures":"Minutes"}}}}})}}}}},l.children=d(l),l.isParent&&l.children.length>0?(l.selectedUser||(l.selectedUser=l.children[0].userId),l.fetchDataForCurrentUser()):!l.isParent&&f?(l.selectedUser=f,l.fetchDataForCurrentUser()):(l.hasError=!0,l.errorMessage="Aucun utilisateur disponible pour afficher les données de temps d'écran.",e.$applyAsync()),e.$watch(function(){return l.viewMode},function(e,t){e!==t&&l.showLightbox&&setTimeout(function(){return l.updateChart()},50)}),e.$watch(function(){return l.selectedChildHistogram},function(e,t){e!==t&&l.showLightbox&&l.fetchLightboxData()}),e.$watch(function(){return l.showLightbox},function(e){e?(l.selectedChildHistogram=l.selectedUser,setTimeout(function(){l.updateChart()},100)):h&&(h.destroy(),h=null)})}},e}();t.odeModuleName="odeCantineWidgetModule",a.default.module(t.odeModuleName,[]).filter("duration",function(){return function(e){if(isNaN(e)||null===e||e<0)return"0m";var t=Math.floor(e),o=Math.floor(t/60),r=t%60,a="";return o>0&&(a+="".concat(o,"h")),(r>0||0===o&&0===t)&&(a+="".concat(r,"m")),a.trim()||"0m"}}).directive("odeScreenTimeWidget",function(){return new u}),(0,i.notif)().onLangReady().promise.then(function(e){(0,i.conf)().Platform.idiom.addKeys(o(1245))})},1245:e=>{e.exports=JSON.parse('{"screenTime.title.normal":"Temps d\'écran","screenTime.title.uppercase":"TEMPS D\'ÉCRAN","screenTime.today":"Aujourd\'hui","screenTime.weekAverage":"Moyenne de la semaine","screenTime.displayDetails":"Voir le détail","screenTime.user":"Utilisateur","screenTime.viewMode":"Affichage","screenTime.viewMode.weekly":"Hebdomadaire","screenTime.viewMode.daily":"Quotidien","screenTime.modal.close":"Fermer","screenTime.information.first":"\'Temps d\'écran\' est une fonctionnalité mise en place par le Département des Bouches-du-Rhône.","screenTime.information.second":"Elle permet de mesurer le temps d\'activité de l\'élève sur l\'ordinateur portable mis à disposition par le Département pendant le temps scolaire et en dehors du temps scolaire.","screenTime.information.third":"\'Temps d\'écran\' observe uniquement l\'ouverture et la fermeture de la session par l\'élève sur son ordinateur.","screenTime.information.fourth":"La fonctionnalité n\'enregistre aucune donnée personnelle d\'utilisation ou de navigation internet."}')}}]);
1
+ "use strict";(self.webpackChunkode_ngjs_front=self.webpackChunkode_ngjs_front||[]).push([[202],{5688:(e,t,r)=>{r.d(t,{Z:()=>o});const o='<style>.screen-time-summary{padding:16px;font-family:Roboto,sans-serif;display:flex;flex-direction:column;gap:12px}.title{padding-bottom:6px;font-size:16px;color:#4a4a4a;font-weight:700;border-bottom:1px solid #e0e0e0}.modal-title{display:block;width:100%}.modal-body{margin:1rem 0!important}.summary-row{display:flex;gap:12px}.summary-row:nth-of-type(2){margin-top:10px}.summary-block{flex:1;background:#e5f5ff;border-radius:12px;padding:12px;box-shadow:0 1px 3px rgba(0,0,0,.1)}.summary-block .label{font-size:16px;color:#4a4a4a;font-weight:700;margin-bottom:4px}.summary-block .value{font-size:32px;color:#333}.percentage-bar-container{width:100%;height:10px;background-color:#e0e0e0;border-radius:5px;margin-top:5px;display:flex;overflow:hidden}.percentage-bar{height:100%;transition:width .5s ease-in-out;border-radius:5px}.legend{display:flex;gap:16px;align-items:center;margin-top:10px;font-size:10px;color:#333}.legend-item{display:flex;align-items:center;gap:6px}.legend-square{width:16px;height:16px;border-radius:4px}.custom-select{appearance:none;border:none;padding:8px 32px 8px 12px;border-radius:6px;font-size:16px;color:#333;font-family:Roboto,sans-serif;background:#fff url("data:image/svg+xml,%3Csvg fill=\'none\' stroke=\'%23666\' stroke-width=\'2\' viewBox=\'0 0 24 24\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cpath stroke-linecap=\'round\' stroke-linejoin=\'round\' d=\'M19 9l-7 7-7-7\'%3E%3C/path%3E%3C/svg%3E") no-repeat right 10px center;background-size:16px;cursor:pointer;min-width:150px}.custom-select:focus{outline:0;box-shadow:0 0 0 2px rgba(42,156,200,.3)}.see-more{display:flex;justify-content:flex-end;margin-top:8px;padding-left:4px}.see-more button{background-color:transparent;border:none;font-family:Roboto,sans-serif;font-weight:600;font-size:16px;line-height:24px;padding:6px 12px;cursor:pointer;color:#2a9cc8;border-radius:4px;transition:background-color .2s ease}.see-more button:hover{background-color:#f0f8fc}.centered-controls{margin:0 auto 20px auto;width:fit-content;text-align:center}.view-controls{display:flex;width:100%;flex-direction:row;justify-content:space-between;margin-bottom:16px}.control-group{display:flex;align-items:center}.close-button-container{display:flex;justify-content:flex-end;margin-top:20px}.close-button{width:84px;height:40px;padding:8px 16px;background:#ff8d2e;color:#fff;font-size:16px;font-weight:700;border:none;border-radius:8px;cursor:pointer;transition:background .3s ease}.close-button:hover{background:#e67c24}.date-controls{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.nav-button{background:0 0;border:none;font-size:24px;cursor:pointer}.date-navigation{display:flex;align-items:center;justify-content:center;gap:1rem}.date-navigation button{background:0 0;border:none;font-size:1.5rem;cursor:pointer}.date-navigation button:disabled{color:#d3d3d3;cursor:not-allowed}.selected-date{cursor:pointer;font-weight:700}.error-message{background-color:#ffe5e5;color:#c00;border:1px solid #c00;padding:12px;border-radius:6px;font-size:14px;font-weight:500}.info-popover{position:absolute;z-index:10;display:block;width:90%;background-color:#fff;border:1px solid #e0e0e0;box-shadow:0 4px 6px 0 rgba(0,0,0,.08);font-size:1.5rem;line-height:1.8;padding:1rem;border-radius:6px}</style> <div class="screen-time-widget-container"> <div class="screen-time-summary"> <div class="title"> <div class="label"> <i18n>screenTime.title.uppercase</i18n> </div> </div> <div ng-if="ctrl.isParent"> <select id="userSelect" class="custom-select" ng-model="ctrl.selectedUser" ng-options="child.userId as child.name for child in ctrl.children" ng-change="ctrl.fetchDataForCurrentUser()"> </select> </div> <div class="error-message" ng-if="ctrl.errorMessage"> {{ ctrl.errorMessage }} </div> <div ng-if="!ctrl.errorMessage"> <div class="summary-row"> <div class="summary-block"> <div class="label"> <i18n>screenTime.today</i18n> </div> <div class="value">{{ ctrl.todayTotal | duration }}</div> </div> </div> <div class="summary-row"> <div class="summary-block"> <div class="label"> <i18n>screenTime.weekAverage</i18n> </div> <div class="value">{{ ctrl.weeklyTotalAverage | duration }}</div> </div> </div> <div class="legend"> <div class="see-more"> <button ng-click="ctrl.toggleLightbox(true)"> <i18n>screenTime.displayDetails</i18n> </button> </div> </div> </div> </div> <ode-modal visible="ctrl.showLightbox" on-close="ctrl.toggleLightbox(false)" size="lg" dnd-nodrag> <ode-modal-title class="modal-title"> <h2> <i18n>screenTime.title.normal</i18n> <popover> <popover-opener><i class="fa-solid fa-circle-info"></i></popover-opener> <popover-content class="info-popover"> <ul> <li> <i18n>screenTime.information.first</i18n> </li> <li> <i18n>screenTime.information.second</i18n> </li> <li> <i18n>screenTime.information.third</i18n> </li> <li> <i18n>screenTime.information.fourth</i18n> </li> </ul> </popover-content> </popover> </h2> <div class="view-controls"> <div class="control-group"> <label style="font-size:14px" for="userSelect"> <i18n>screenTime.user</i18n> :</label> <div ng-if="ctrl.isParent"> <select id="userSelect" class="custom-select" ng-model="ctrl.selectedChildHistogram" ng-options="child.userId as child.name for child in ctrl.children"> </select> </div> </div> <div class="control-group"> <label for="viewMode"> <i18n>screenTime.viewMode</i18n> :</label> <select id="viewMode" class="custom-select" ng-model="ctrl.viewMode" ng-change="ctrl.updateChart()"> <option value="weekly"> [[ctrl.lang.translate("screenTime.viewMode.weekly")]] </option> <option value="daily"> [[ctrl.lang.translate("screenTime.viewMode.daily")]] </option> </select> </div> </div> </ode-modal-title> <ode-modal-body class="modal-body"> <div class="date-selectors centered-controls" ng-if="ctrl.showLightbox"> <div class="date-navigation" ng-if="ctrl.viewMode === \'daily\'"> <button ng-click="ctrl.changeDay(-1)" ng-disabled="!ctrl.canGoToPreviousDay()">←</button> <div ng-if="!ctrl.showDatePicker" class="selected-date" ng-click="ctrl.toggleDatePicker(true)"> {{ ctrl.selectedDailyDateObj | date:\'fullDate\' }} </div> <input type="date" ng-if="ctrl.showDatePicker" ng-model="ctrl.selectedDailyDate" ng-change="ctrl.setDateFromPicker(ctrl.selectedDailyDate)" ng-blur="ctrl.toggleDatePicker(false)" max="{{ ctrl.selectedDailyDate }}"/> <button ng-click="ctrl.changeDay(1)" ng-disabled="!ctrl.canGoToNextDay()">→</button> </div> <div class="date-navigation" ng-if="ctrl.viewMode === \'weekly\'"> <button ng-click="ctrl.changeWeek(-1)" ng-disabled="!ctrl.canGoToPreviousWeek()">←</button> <div class="selected-date">{{ ctrl.weekLabel }}</div> <button ng-click="ctrl.changeWeek(1)" ng-disabled="!ctrl.canGoToNextWeek()">→</button> </div> </div> <canvas id="myChart" style="width:100%;height:500px;max-height:500px"></canvas> </ode-modal-body> </ode-modal> </div>'},58:function(e,t,r){var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.odeModuleName=t.Controller=void 0;var a=o(r(4202)),i=r(9419),n=r(5668),s=o(r(381));r(4470),n.Chart.register.apply(n.Chart,n.registerables),s.default.locale("fr");var l=function(){function e(){this.platform=(0,i.conf)().Platform,this.lang=this.platform.idiom,this.selectedUser="",this.viewMode="weekly",this.userData={},this.selectedDailyDate=(0,s.default)().format("YYYY-MM-DD"),this.weekStart=(0,s.default)().startOf("isoWeek"),this.weekEnd=(0,s.default)().endOf("isoWeek"),this.updateChart=function(){},this.todayOnCampus=0,this.todayOffCampus=0,this.todayTotal=0,this.weeklyAvgOnCampus=0,this.weeklyAvgOffCampus=0,this.weeklyTotalAverage=0,this.weeklyAvgSchoolUsePercentage=0,this.weeklyAvgOutOfSchoolPercentage=0,this.fixedTodayOnCampus=0,this.fixedTodayOffCampus=0,this.fixedTodayTotal=0,this.fixedWeeklyAvgOnCampus=0,this.fixedWeeklyAvgOffCampus=0,this.fixedWeeklyTotalAverage=0,this.fixedWeeklyAvgSchoolUsePercentage=0,this.fixedWeeklyAvgOutOfSchoolPercentage=0,this.customWeekMode=!1,this.customStartDate="",this.customEndDate="",this.isParent=!1,this.hasError=!1,this.errorMessage="",this.fetchDataForCurrentUser=function(){},this.fetchLightboxData=function(){},this.showLightbox=!1,this.showDatePicker=!1,this.children=[],this.selectedChild=null,this.selectedChildHistogram=""}return e.prototype.toggleDatePicker=function(e){this.showDatePicker=e},e.prototype.setDateFromPicker=function(e){var t=(0,s.default)(e).format("YYYY-MM-DD");this.showDatePicker=!1,this.setSelectedDate(t),this.fetchLightboxData()},e.prototype.toggleLightbox=function(e){this.showLightbox=e,e&&(this.fixedTodayOnCampus=this.todayOnCampus,this.fixedTodayOffCampus=this.todayOffCampus,this.fixedTodayTotal=this.todayTotal,this.fixedWeeklyAvgOnCampus=this.weeklyAvgOnCampus,this.fixedWeeklyAvgOffCampus=this.weeklyAvgOffCampus,this.fixedWeeklyTotalAverage=this.weeklyTotalAverage,this.fixedWeeklyAvgSchoolUsePercentage=this.weeklyAvgSchoolUsePercentage,this.fixedWeeklyAvgOutOfSchoolPercentage=this.weeklyAvgOutOfSchoolPercentage,this.selectedDailyDate=(0,s.default)().format("YYYY-MM-DD"),this.weekStart=(0,s.default)().startOf("isoWeek"),this.weekEnd=(0,s.default)().endOf("isoWeek"),this.selectedChildHistogram=this.selectedUser)},e.prototype.getCurrentSchoolYear=function(){var e=(0,s.default)(),t=e.year(),r=e.month()<8?(0,s.default)({year:t-1,month:8,day:1}):(0,s.default)({year:t,month:8,day:1}),o=r.clone().add(1,"year").subtract(1,"day");return{schoolYearStart:r.startOf("day"),schoolYearEnd:o.endOf("day")}},e.prototype.setSelectedDate=function(e){var t=(0,s.default)(e);this.selectedDailyDate=t.format("YYYY-MM-DD"),this.weekStart=t.clone().startOf("isoWeek"),this.weekEnd=t.clone().endOf("isoWeek")},e.prototype.setSelectedWeek=function(e){this.weekStart=e.clone().startOf("isoWeek"),this.weekEnd=this.weekStart.clone().endOf("isoWeek"),this.selectedDailyDate=this.weekStart.format("YYYY-MM-DD")},e.prototype.canGoToPreviousWeek=function(){var e=this.weekStart.clone().subtract(1,"week"),t=this.getCurrentSchoolYear(),r=t.schoolYearStart,o=t.schoolYearEnd;return e.isBetween(r,o)},e.prototype.canGoToNextWeek=function(){var e=this.weekStart.clone().add(1,"week"),t=this.getCurrentSchoolYear(),r=t.schoolYearStart,o=t.schoolYearEnd;return e.isBetween(r,o)},e.prototype.canGoToPreviousDay=function(){var e=(0,s.default)(this.selectedDailyDate).subtract(1,"day"),t=this.getCurrentSchoolYear(),r=t.schoolYearStart,o=t.schoolYearEnd;return e.isBetween(r,o)},e.prototype.canGoToNextDay=function(){var e=(0,s.default)(this.selectedDailyDate).add(1,"day"),t=this.getCurrentSchoolYear(),r=t.schoolYearStart,o=t.schoolYearEnd;return e.isBetween(r,o)},e.prototype.changeDay=function(e){var t=(0,s.default)(this.selectedDailyDate).add(e,"days");this.setSelectedDate(t),this.fetchLightboxData()},e.prototype.changeWeek=function(e){var t=this.weekStart.clone().add(e,"weeks");this.setSelectedWeek(t),this.fetchLightboxData()},Object.defineProperty(e.prototype,"selectedDailyDateObj",{get:function(){return new Date(this.selectedDailyDate)},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"weekLabel",{get:function(){var e=this.weekStart.format("dddd D MMMM"),t=this.weekEnd.format("dddd D MMMM");return"du ".concat(e," au ").concat(t)},enumerable:!1,configurable:!0}),e}();function c(e,t,r,o,a,i){t.children=d(t);var n="/appregistry/screen-time/".concat(r,"/weekly?startDate=").concat(a,"&endDate=").concat(i),s="/appregistry/screen-time/".concat(r,"/daily?date=").concat(o);return Promise.all([e.get(n),e.get(s)]).then(function(e){var r=e[0],o=e[1];return t.hasError=!1,t.errorMessage="",{weekly:r.data.dailySummaries.reduce(function(e,t){return e[t.date]={duration:t.durationMinutes,schoolUsePercentage:t.schoolUsePercentage/100},e},{}),daily:o.data.durations.map(function(e){return{hour:e.hour,duration:e.durationMinutes,schoolUsePercentage:e.schoolUsePercentage/100}})}}).catch(function(e){return t.hasError=!0,404===(null==e?void 0:e.status)?t.errorMessage=t.lang.translate("screenTime.error.404"):t.errorMessage=t.lang.translate("screenTime.error.generic"),console.error("Error fetching data for current user and dates:",e),Promise.reject(e)})}function d(e){var t=(0,i.session)().user.children;return e.isParent=!1,t&&Object.keys(t).length>0?(e.isParent=!0,Object.entries(t).map(function(e){var t=e[0],r=e[1];return{id:t,name:"".concat(r.firstName," ").concat(r.lastName),userId:t}})):[]}t.Controller=l;var u=function(){function e(){this.restrict="E",this.template=r(5688).Z,this.scope={},this.bindToController=!0,this.controller=[l],this.controllerAs="ctrl",this.require=["odeScreenTimeWidget"]}return e.prototype.link=function(e,t,r,o){var l=o?o[0]:null;if(l){var u=a.default.injector(["ng"]).get("$http"),f=(0,i.session)().user.userId;l.fetchDataForCurrentUser();var h=null,p=function(e,t,r,o){return"".concat(e,"_daily_").concat(t,"_weekly_").concat(r,"_").concat(o)};l.fetchDataForCurrentUser=function(){l.errorMessage="",l.hasError=!1;var t=p(l.selectedUser,(0,s.default)().format("YYYY-MM-DD"),(0,s.default)().startOf("isoWeek").format("YYYY-MM-DD"),(0,s.default)().endOf("isoWeek").format("YYYY-MM-DD"));c(u,l,l.selectedUser,(0,s.default)().format("YYYY-MM-DD"),(0,s.default)().startOf("isoWeek").format("YYYY-MM-DD"),(0,s.default)().endOf("isoWeek").format("YYYY-MM-DD")).then(function(r){var o,a,i,n,c,d,u,f,h,p;l.userData[t]={weekly:r.weekly,daily:r.daily},o=r.weekly,a=0,i=0,n=0,c=(0,s.default)().format("YYYY-MM-DD"),d=0,u=0,f=0,h=0,p=0,Object.keys(o).forEach(function(e){var t=o[e],r=t.duration||0,l=t.schoolUsePercentage||0,g=r*l,m=r*(1-l);a+=g,i+=m,n++,f+=g,h+=m,p+=r,(0,s.default)(e).format("YYYY-MM-DD")===c&&(d=g,u=m)}),l.todayOnCampus=d,l.todayOffCampus=u,l.todayTotal=d+u,l.weeklyAvgOnCampus=n>0?a/n:0,l.weeklyAvgOffCampus=n>0?i/n:0,l.weeklyTotalAverage=l.weeklyAvgOnCampus+l.weeklyAvgOffCampus,p>0?(l.weeklyAvgSchoolUsePercentage=f/p*100,l.weeklyAvgOutOfSchoolPercentage=h/p*100):(l.weeklyAvgSchoolUsePercentage=0,l.weeklyAvgOutOfSchoolPercentage=0),e.$applyAsync()}).catch(function(t){e.$applyAsync(),console.error("Error fetching initial data for current user:",t)})},l.fetchLightboxData=function(){var t=l.selectedChildHistogram||l.selectedUser;if(t){var r=p(t,l.selectedDailyDate,l.weekStart.format("YYYY-MM-DD"),l.weekEnd.format("YYYY-MM-DD"));c(u,l,t,l.selectedDailyDate,l.weekStart.format("YYYY-MM-DD"),l.weekEnd.format("YYYY-MM-DD")).then(function(t){l.userData[r]={weekly:t.weekly,daily:t.daily},l.showLightbox&&setTimeout(function(){return l.updateChart()},50),e.$applyAsync()}).catch(function(e){console.error("Error fetching data for lightbox:",e)})}},l.updateChart=function(){var e=t[0].querySelector("#myChart");if(e){var r=e.getContext("2d");if(r){var o=[],a=[],i=l.selectedChildHistogram||l.selectedUser,c=p(i,l.selectedDailyDate,l.weekStart.format("YYYY-MM-DD"),l.weekEnd.format("YYYY-MM-DD")),d=l.userData[c];if(d){var u="weekly"===l.viewMode?d.weekly:d.daily;if(u){if("weekly"===l.viewMode)Object.keys(u).sort().forEach(function(e){var t=u[e];o.push((0,s.default)(e).format("ddd D")),a.push(t.duration/60)});else u.forEach(function(e){o.push("".concat(e.hour,"h")),a.push(e.duration)});h?(h.data.labels=o,h.data.datasets[0].data=a,h.options={scales:{x:{stacked:!0},y:{stacked:!0,beginAtZero:!0,title:{display:!0,text:"weekly"===l.viewMode?"Heures":"Minutes"}}}},h.update()):h=new n.Chart(r,{type:"bar",data:{labels:o,datasets:[{label:"Usage total",data:a,backgroundColor:"#2A9CC8"}]},options:{plugins:{tooltip:{callbacks:{label:function(e){var t=e.raw;return"".concat(e.dataset.label,": ").concat(t," ").concat("weekly"===l.viewMode?"heures":"minutes")}}},legend:{display:!1,position:"bottom"}},responsive:!0,scales:{x:{stacked:!0},y:{stacked:!0,beginAtZero:!0,title:{display:!0,text:"weekly"===l.viewMode?"Heures":"Minutes"}}}}})}}}}},l.children=d(l),l.isParent&&l.children.length>0?(l.selectedUser||(l.selectedUser=l.children[0].userId),l.fetchDataForCurrentUser()):!l.isParent&&f?(l.selectedUser=f,l.fetchDataForCurrentUser()):(l.hasError=!0,l.errorMessage="Aucun utilisateur disponible pour afficher les données de temps d'écran.",e.$applyAsync()),e.$watch(function(){return l.viewMode},function(e,t){e!==t&&l.showLightbox&&setTimeout(function(){return l.updateChart()},50)}),e.$watch(function(){return l.selectedChildHistogram},function(e,t){e!==t&&l.showLightbox&&l.fetchLightboxData()}),e.$watch(function(){return l.showLightbox},function(e){e?(l.selectedChildHistogram=l.selectedUser,setTimeout(function(){l.updateChart()},100)):h&&(h.destroy(),h=null)})}},e}();t.odeModuleName="odeCantineWidgetModule",a.default.module(t.odeModuleName,[]).filter("duration",function(){return function(e){if(isNaN(e)||null===e||e<0)return"0m";var t=Math.floor(e),r=Math.floor(t/60),o=t%60,a="";return r>0&&(a+="".concat(r,"h")),(o>0||0===r&&0===t)&&(a+="".concat(o,"m")),a.trim()||"0m"}}).directive("odeScreenTimeWidget",function(){return new u}),(0,i.notif)().onLangReady().promise.then(function(e){(0,i.conf)().Platform.idiom.addKeys(r(1245))})},1245:e=>{e.exports=JSON.parse('{"screenTime.title.normal":"Temps d\'écran","screenTime.title.uppercase":"TEMPS D\'ÉCRAN","screenTime.today":"Aujourd\'hui","screenTime.weekAverage":"Moyenne de la semaine","screenTime.displayDetails":"Voir le détail","screenTime.user":"Utilisateur","screenTime.viewMode":"Affichage","screenTime.viewMode.weekly":"Hebdomadaire","screenTime.viewMode.daily":"Quotidien","screenTime.modal.close":"Fermer","screenTime.information.first":"\'Temps d\'écran\' est une fonctionnalité mise en place par le Département des Bouches-du-Rhône.","screenTime.information.second":"Elle permet de mesurer le temps d\'activité de l\'élève sur l\'ordinateur portable mis à disposition par le Département pendant le temps scolaire et en dehors du temps scolaire.","screenTime.information.third":"\'Temps d\'écran\' observe uniquement l\'ouverture et la fermeture de la session par l\'élève sur son ordinateur.","screenTime.information.fourth":"La fonctionnalité n\'enregistre aucune donnée personnelle d\'utilisation ou de navigation internet.","screenTime.error.generic":"Un problème technique est survenu. Si le problème persiste contactez l’administrateur de votre établissement.","screenTime.error.404":"Erreur lors de l’identification de l’utilisateur. Contactez l’administrateur de votre établissement."}')}}]);
2
2
  //# sourceMappingURL=screen-time-widget.widget.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"widgets/screen-time-widget/screen-time-widget.widget.js","mappings":"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","sources":["webpack://ode-ngjs-front/./src/ts/widgets/screen-time-widget/screen-time-widget.widget.html","webpack://ode-ngjs-front/./src/ts/widgets/screen-time-widget/screen-time-widget.widget.ts"],"sourcesContent":["// Module\nvar code = \"<style>.screen-time-summary{padding:16px;font-family:Roboto,sans-serif;display:flex;flex-direction:column;gap:12px}.title{padding-bottom:6px;font-size:16px;color:#4a4a4a;font-weight:700;border-bottom:1px solid #e0e0e0}.modal-body{margin:1rem 0!important}.summary-row{display:flex;gap:12px}.summary-row:nth-of-type(2){margin-top:10px}.summary-block{flex:1;background:#e5f5ff;border-radius:12px;padding:12px;box-shadow:0 1px 3px rgba(0,0,0,.1)}.summary-block .label{font-size:16px;color:#4a4a4a;font-weight:700;margin-bottom:4px}.summary-block .value{font-size:32px;color:#333}.percentage-bar-container{width:100%;height:10px;background-color:#e0e0e0;border-radius:5px;margin-top:5px;display:flex;overflow:hidden}.percentage-bar{height:100%;transition:width .5s ease-in-out;border-radius:5px}.legend{display:flex;gap:16px;align-items:center;margin-top:10px;font-size:10px;color:#333}.legend-item{display:flex;align-items:center;gap:6px}.legend-square{width:16px;height:16px;border-radius:4px}.custom-select{appearance:none;border:none;padding:8px 32px 8px 12px;border-radius:6px;font-size:16px;color:#333;font-family:Roboto,sans-serif;background:#fff url(\\\"data:image/svg+xml,%3Csvg fill='none' stroke='%23666' stroke-width='2' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E\\\") no-repeat right 10px center;background-size:16px;cursor:pointer;min-width:150px}.custom-select:focus{outline:0;box-shadow:0 0 0 2px rgba(42,156,200,.3)}.see-more{display:flex;justify-content:flex-end;margin-top:8px;padding-left:4px}.see-more button{background-color:transparent;border:none;font-family:Roboto,sans-serif;font-weight:600;font-size:16px;line-height:24px;padding:6px 12px;cursor:pointer;color:#2a9cc8;border-radius:4px;transition:background-color .2s ease}.see-more button:hover{background-color:#f0f8fc}.centered-controls{margin:0 auto 20px auto;width:fit-content;text-align:center}.view-controls{display:flex;width:100%;flex-direction:row;justify-content:flex-end;margin-bottom:16px}.control-group{display:flex;align-items:center}.close-button-container{display:flex;justify-content:flex-end;margin-top:20px}.close-button{width:84px;height:40px;padding:8px 16px;background:#ff8d2e;color:#fff;font-size:16px;font-weight:700;border:none;border-radius:8px;cursor:pointer;transition:background .3s ease}.close-button:hover{background:#e67c24}.date-controls{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.nav-button{background:0 0;border:none;font-size:24px;cursor:pointer}.date-navigation{display:flex;align-items:center;justify-content:center;gap:1rem}.date-navigation button{background:0 0;border:none;font-size:1.5rem;cursor:pointer}.date-navigation button:disabled{color:#d3d3d3;cursor:not-allowed}.selected-date{cursor:pointer;font-weight:700}.error-message{background-color:#ffe5e5;color:#c00;border:1px solid #c00;padding:12px;border-radius:6px;font-size:14px;font-weight:500}.info-popover{position:absolute;z-index:10;display:block;width:90%;background-color:#fff;border:1px solid #e0e0e0;box-shadow:0 4px 6px 0 rgba(0,0,0,.08);font-size:1.5rem;line-height:1.8;padding:1rem;border-radius:6px}</style> <div class=\\\"screen-time-widget-container\\\"> <div class=\\\"screen-time-summary\\\"> <div class=\\\"title\\\"> <div class=\\\"label\\\"> <i18n>screenTime.title.uppercase</i18n> </div> </div> <div ng-if=\\\"ctrl.isParent\\\"> <select id=\\\"userSelect\\\" class=\\\"custom-select\\\" ng-model=\\\"ctrl.selectedUser\\\" ng-options=\\\"child.userId as child.name for child in ctrl.children\\\" ng-change=\\\"ctrl.fetchDataForCurrentUser()\\\"> </select> </div> <div class=\\\"error-message\\\" ng-if=\\\"ctrl.errorMessage\\\"> {{ ctrl.errorMessage }} </div> <div ng-if=\\\"!ctrl.errorMessage\\\"> <div class=\\\"summary-row\\\"> <div class=\\\"summary-block\\\"> <div class=\\\"label\\\"> <i18n>screenTime.today</i18n> </div> <div class=\\\"value\\\">{{ ctrl.todayTotal | duration }}</div> </div> </div> <div class=\\\"summary-row\\\"> <div class=\\\"summary-block\\\"> <div class=\\\"label\\\"> <i18n>screenTime.weekAverage</i18n> </div> <div class=\\\"value\\\">{{ ctrl.weeklyTotalAverage | duration }}</div> </div> </div> <div class=\\\"legend\\\"> <div class=\\\"see-more\\\"> <button ng-click=\\\"ctrl.toggleLightbox(true)\\\"> <i18n>screenTime.displayDetails</i18n> </button> </div> </div> </div> </div> <ode-modal visible=\\\"ctrl.showLightbox\\\" on-close=\\\"ctrl.toggleLightbox(false)\\\" size=\\\"lg\\\" dnd-nodrag> <ode-modal-title> <h2> <i18n>screenTime.title.normal</i18n> <popover> <popover-opener><i class=\\\"fa-solid fa-circle-info\\\"></i></popover-opener> <popover-content class=\\\"info-popover\\\"> <ul> <li> <i18n>screenTime.information.first</i18n> </li> <li> <i18n>screenTime.information.second</i18n> </li> <li> <i18n>screenTime.information.third</i18n> </li> <li> <i18n>screenTime.information.fourth</i18n> </li> </ul> </popover-content> </popover> </h2> <div class=\\\"view-controls\\\"> <div class=\\\"control-group\\\"> <label style=\\\"font-size:14px\\\" for=\\\"userSelect\\\"> <i18n>screenTime.user</i18n> :</label> <div ng-if=\\\"ctrl.isParent\\\"> <select id=\\\"userSelect\\\" class=\\\"custom-select\\\" ng-model=\\\"ctrl.selectedChildHistogram\\\" ng-options=\\\"child.userId as child.name for child in ctrl.children\\\"> </select> </div> </div> <div class=\\\"control-group\\\"> <label for=\\\"viewMode\\\"> <i18n>screenTime.viewMode</i18n> :</label> <select id=\\\"viewMode\\\" class=\\\"custom-select\\\" ng-model=\\\"ctrl.viewMode\\\" ng-change=\\\"ctrl.updateChart()\\\"> <option value=\\\"weekly\\\"> [[ctrl.lang.translate(\\\"screenTime.viewMode.weekly\\\")]] </option> <option value=\\\"daily\\\"> [[ctrl.lang.translate(\\\"screenTime.viewMode.daily\\\")]] </option> </select> </div> </div> </ode-modal-title> <ode-modal-body class=\\\"modal-body\\\"> <div class=\\\"date-selectors centered-controls\\\" ng-if=\\\"ctrl.showLightbox\\\"> <div class=\\\"date-navigation\\\" ng-if=\\\"ctrl.viewMode === 'daily'\\\"> <button ng-click=\\\"ctrl.changeDay(-1)\\\" ng-disabled=\\\"!ctrl.canGoToPreviousDay()\\\">←</button> <div ng-if=\\\"!ctrl.showDatePicker\\\" class=\\\"selected-date\\\" ng-click=\\\"ctrl.toggleDatePicker(true)\\\"> {{ ctrl.selectedDailyDateObj | date:'fullDate' }} </div> <input type=\\\"date\\\" ng-if=\\\"ctrl.showDatePicker\\\" ng-model=\\\"ctrl.selectedDailyDate\\\" ng-change=\\\"ctrl.setDateFromPicker(ctrl.selectedDailyDate)\\\" ng-blur=\\\"ctrl.toggleDatePicker(false)\\\" max=\\\"{{ ctrl.selectedDailyDate }}\\\"/> <button ng-click=\\\"ctrl.changeDay(1)\\\" ng-disabled=\\\"!ctrl.canGoToNextDay()\\\">→</button> </div> <div class=\\\"date-navigation\\\" ng-if=\\\"ctrl.viewMode === 'weekly'\\\"> <button ng-click=\\\"ctrl.changeWeek(-1)\\\" ng-disabled=\\\"!ctrl.canGoToPreviousWeek()\\\">←</button> <div class=\\\"selected-date\\\">{{ ctrl.weekLabel }}</div> <button ng-click=\\\"ctrl.changeWeek(1)\\\" ng-disabled=\\\"!ctrl.canGoToNextWeek()\\\">→</button> </div> </div> <canvas id=\\\"myChart\\\" style=\\\"width:100%;height:500px;max-height:500px\\\"></canvas> </ode-modal-body> </ode-modal> </div>\";\n// Exports\nexport default code;","import angular, { IAttributes, IController, IDirective, IScope, IHttpService } from \"angular\";\nimport { conf, notif, session } from \"../../utils\";\nimport { Chart, registerables } from \"chart.js\";\nimport moment, { Moment } from \"moment\";\nimport 'moment/locale/fr';\n\nChart.register(...registerables);\nmoment.locale('fr');\n\n// Controller\nexport class Controller {\n public platform = conf().Platform;\n public lang = this.platform.idiom;\n public selectedUser: string = \"\";\n public viewMode: \"weekly\" | \"daily\" = \"weekly\";\n\n public userData: { [key: string]: { weekly: any, daily: any } } = {};\n public selectedDailyDate: string = moment().format('YYYY-MM-DD');\n public weekStart: Moment = moment().startOf('isoWeek'); // Monday\n public weekEnd: Moment = moment().endOf('isoWeek'); // Sunday\n\n public updateChart: () => void = () => { };\n\n public todayOnCampus = 0;\n public todayOffCampus = 0;\n public todayTotal = 0;\n\n public weeklyAvgOnCampus = 0;\n public weeklyAvgOffCampus = 0;\n public weeklyTotalAverage = 0;\n public weeklyAvgSchoolUsePercentage: number = 0;\n public weeklyAvgOutOfSchoolPercentage: number = 0;\n\n // new props\n public fixedTodayOnCampus = 0;\n public fixedTodayOffCampus = 0;\n public fixedTodayTotal = 0;\n\n public fixedWeeklyAvgOnCampus = 0;\n public fixedWeeklyAvgOffCampus = 0;\n public fixedWeeklyTotalAverage = 0;\n public fixedWeeklyAvgSchoolUsePercentage: number = 0;\n public fixedWeeklyAvgOutOfSchoolPercentage: number = 0;\n\n\n // end of new \n\n public customWeekMode: boolean = false;\n public customStartDate: string = \"\";\n public customEndDate: string = \"\";\n\n public isParent: boolean = false;\n\n public hasError: boolean = false;\n public errorMessage: string = \"\";\n\n public fetchDataForCurrentUser: () => void = () => { };\n public fetchLightboxData: () => void = () => { };\n\n public showLightbox: boolean = false;\n public showDatePicker: boolean = false;\n\n public children: { id: string; name: string; userId: string }[] = [];\n public selectedChild = null;\n public selectedChildHistogram = \"\";\n\n public toggleDatePicker(show: boolean) {\n this.showDatePicker = show;\n }\n\n public setDateFromPicker(date: string) {\n const newDate = moment(date).format('YYYY-MM-DD');\n this.showDatePicker = false;\n this.setSelectedDate(newDate);\n this.fetchLightboxData();\n }\n\n public toggleLightbox(show: boolean) {\n this.showLightbox = show;\n if (show) {\n // Store the current summary values when lightbox opens\n this.fixedTodayOnCampus = this.todayOnCampus;\n this.fixedTodayOffCampus = this.todayOffCampus;\n this.fixedTodayTotal = this.todayTotal;\n this.fixedWeeklyAvgOnCampus = this.weeklyAvgOnCampus;\n this.fixedWeeklyAvgOffCampus = this.weeklyAvgOffCampus;\n this.fixedWeeklyTotalAverage = this.weeklyTotalAverage;\n this.fixedWeeklyAvgSchoolUsePercentage = this.weeklyAvgSchoolUsePercentage;\n this.fixedWeeklyAvgOutOfSchoolPercentage = this.weeklyAvgOutOfSchoolPercentage;\n\n // Initialize lightbox dates to current date/week\n this.selectedDailyDate = moment().format('YYYY-MM-DD');\n this.weekStart = moment().startOf('isoWeek');\n this.weekEnd = moment().endOf('isoWeek');\n this.selectedChildHistogram = this.selectedUser;\n\n }\n }\n\n private getCurrentSchoolYear(): { schoolYearStart: moment.Moment, schoolYearEnd: moment.Moment } {\n const today = moment();\n const currentYear = today.year();\n const currentMonth = today.month();\n\n // If current month is less than september, school year is the previous one\n const schoolYearStart = currentMonth < 8\n ? moment({ year: currentYear - 1, month: 8, day: 1 }) // 1st september of previous school year\n : moment({ year: currentYear, month: 8, day: 1 }); // 1st september of current school year\n\n const schoolYearEnd = schoolYearStart\n .clone()\n .add(1, 'year')\n .subtract(1, 'day'); // 31 august of next school year\n\n return {\n schoolYearStart: schoolYearStart.startOf('day'),\n schoolYearEnd: schoolYearEnd.endOf('day'),\n };\n }\n\n private setSelectedDate(date: string | moment.Moment) {\n const momentDate = moment(date);\n\n this.selectedDailyDate = momentDate.format('YYYY-MM-DD');\n\n this.weekStart = momentDate.clone().startOf('isoWeek');\n this.weekEnd = momentDate.clone().endOf('isoWeek');\n }\n\n private setSelectedWeek(weekStart: moment.Moment) {\n this.weekStart = weekStart.clone().startOf('isoWeek');\n this.weekEnd = this.weekStart.clone().endOf('isoWeek');\n\n this.selectedDailyDate = this.weekStart.format('YYYY-MM-DD');\n }\n\n public canGoToPreviousWeek(): boolean {\n const offsetWeekStart = this.weekStart.clone().subtract(1, 'week');\n const { schoolYearStart, schoolYearEnd } = this.getCurrentSchoolYear();\n\n return offsetWeekStart.isBetween(schoolYearStart, schoolYearEnd);\n }\n\n public canGoToNextWeek(): boolean {\n const offsetWeekStart = this.weekStart.clone().add(1, 'week');\n const { schoolYearStart, schoolYearEnd } = this.getCurrentSchoolYear();\n\n return offsetWeekStart.isBetween(schoolYearStart, schoolYearEnd);\n }\n\n public canGoToPreviousDay(): boolean {\n const offsetDayStart = moment(this.selectedDailyDate).subtract(1, 'day');\n const { schoolYearStart, schoolYearEnd } = this.getCurrentSchoolYear();\n\n return offsetDayStart.isBetween(schoolYearStart, schoolYearEnd);\n }\n\n public canGoToNextDay(): boolean {\n const offsetDayStart = moment(this.selectedDailyDate).add(1, 'day');\n const { schoolYearStart, schoolYearEnd } = this.getCurrentSchoolYear();\n\n return offsetDayStart.isBetween(schoolYearStart, schoolYearEnd);\n }\n\n public changeDay(offset: number) {\n const newDate = moment(this.selectedDailyDate).add(offset, 'days');\n this.setSelectedDate(newDate);\n this.fetchLightboxData();\n }\n\n public changeWeek(offset: number) {\n const newWeekStart = this.weekStart.clone().add(offset, 'weeks');\n this.setSelectedWeek(newWeekStart);\n this.fetchLightboxData();\n }\n\n public get selectedDailyDateObj(): Date {\n return new Date(this.selectedDailyDate);\n }\n\n public get weekLabel(): string {\n const start = this.weekStart.format('dddd D MMMM');\n const end = this.weekEnd.format('dddd D MMMM');\n return `du ${start} au ${end}`;\n }\n}\n\nfunction fetchAllScreenTimeDataForUserAndDates($http: IHttpService, ctrl: Controller, userId: string, dailyDate: string, startDate: string, endDate: string): Promise<{ weekly: any, daily: any }> {\n ctrl.children = fetchChildren(ctrl);\n\n const weeklyEndpoint = `/appregistry/screen-time/${userId}/weekly?startDate=${startDate}&endDate=${endDate}`;\n const dailyEndpoint = `/appregistry/screen-time/${userId}/daily?date=${dailyDate}`;\n\n return Promise.all([\n $http.get(weeklyEndpoint),\n $http.get(dailyEndpoint)\n ]).then(([weeklyResponse, dailyResponse]: [any, any]) => {\n ctrl.hasError = false;\n ctrl.errorMessage = \"\";\n return {\n weekly: weeklyResponse.data.dailySummaries.reduce((acc: any, day: any) => {\n acc[day.date] = {\n duration: day.durationMinutes,\n schoolUsePercentage: day.schoolUsePercentage / 100 // convert to ratio\n };\n return acc;\n }, {}),\n daily: dailyResponse.data.durations.map((hour: any) => ({\n hour: hour.hour,\n duration: hour.durationMinutes,\n schoolUsePercentage: hour.schoolUsePercentage / 100 // convert to ratio\n }))\n };\n }).catch(error => {\n ctrl.hasError = true;\n\n if (error?.status === 404) {\n ctrl.errorMessage = \"Erreur lors de l’identification de l’utilisateur. Contactez l’administrateur de votre établissement.\";\n } else {\n ctrl.errorMessage = \"Un problème technique est survenu. Si le problème persiste contactez l’administrateur de votre établissement.\";\n }\n\n console.error(\"Error fetching data for current user and dates:\", error);\n return Promise.reject(error);\n });\n}\n\nfunction fetchChildren(ctrl: Controller) {\n const childrenObj = session().user.children;\n ctrl.isParent = false;\n\n if (childrenObj && Object.keys(childrenObj).length > 0) {\n ctrl.isParent = true;\n\n return Object.entries(childrenObj).map(([userId, childData]) => {\n const child = childData as { firstName: string; lastName: string };\n return {\n id: userId,\n name: `${child.firstName} ${child.lastName}`,\n userId: userId\n };\n });\n } else {\n return [];\n }\n}\n\nclass Directive implements IDirective<IScope, JQLite, IAttributes, IController[]> {\n restrict = \"E\";\n template = require(\"./screen-time-widget.widget.html\").default;\n scope = {};\n bindToController = true;\n controller = [Controller];\n controllerAs = \"ctrl\";\n require = [\"odeScreenTimeWidget\"];\n\n link(scope: IScope, elem: angular.IAugmentedJQuery, attrs: IAttributes, controllers?: IController[]) {\n const ctrl: Controller | null = controllers ? (controllers[0] as Controller) : null;\n if (!ctrl) return;\n\n const $http = angular.injector([\"ng\"]).get<IHttpService>(\"$http\");\n const currentUserId = session().user.userId;\n\n ctrl.fetchDataForCurrentUser();\n\n let chartInstance: Chart | null = null;\n\n // Function to update the summary values from the pre-fetched weekly data\n const updateSummary = (weeklyData: any) => {\n let totalOnCampus = 0;\n let totalOffCampus = 0;\n let daysCount = 0;\n const today = moment().format(\"YYYY-MM-DD\");\n let todayOnCampusTemp = 0;\n let todayOffCampusTemp = 0;\n\n let weeklyTotalSchoolUseDuration = 0;\n let weeklyTotalOutOfSchoolDuration = 0;\n let weeklyTotalOverallDuration = 0;\n\n Object.keys(weeklyData).forEach(dateStr => {\n const entry = weeklyData[dateStr];\n const duration = entry.duration || 0;\n const schoolUsePct = entry.schoolUsePercentage || 0;\n\n const onCampus = duration * schoolUsePct;\n const offCampus = duration * (1 - schoolUsePct);\n\n totalOnCampus += onCampus;\n totalOffCampus += offCampus;\n daysCount++;\n\n weeklyTotalSchoolUseDuration += onCampus;\n weeklyTotalOutOfSchoolDuration += offCampus;\n weeklyTotalOverallDuration += duration;\n\n if (moment(dateStr).format(\"YYYY-MM-DD\") === today) {\n todayOnCampusTemp = onCampus;\n todayOffCampusTemp = offCampus;\n }\n });\n\n ctrl.todayOnCampus = todayOnCampusTemp;\n ctrl.todayOffCampus = todayOffCampusTemp;\n ctrl.todayTotal = todayOnCampusTemp + todayOffCampusTemp;\n ctrl.weeklyAvgOnCampus = daysCount > 0 ? totalOnCampus / daysCount : 0;\n ctrl.weeklyAvgOffCampus = daysCount > 0 ? totalOffCampus / daysCount : 0;\n ctrl.weeklyTotalAverage = ctrl.weeklyAvgOnCampus + ctrl.weeklyAvgOffCampus;\n\n if (weeklyTotalOverallDuration > 0) {\n ctrl.weeklyAvgSchoolUsePercentage = (weeklyTotalSchoolUseDuration / weeklyTotalOverallDuration) * 100;\n ctrl.weeklyAvgOutOfSchoolPercentage = (weeklyTotalOutOfSchoolDuration / weeklyTotalOverallDuration) * 100;\n } else {\n ctrl.weeklyAvgSchoolUsePercentage = 0;\n ctrl.weeklyAvgOutOfSchoolPercentage = 0;\n }\n };\n\n // Helper to generate a unique key for caching data based on user and date range\n const generateDataKey = (userId: string, dailyDate: string, startDate: string, endDate: string) => {\n return `${userId}_daily_${dailyDate}_weekly_${startDate}_${endDate}`;\n };\n\n // New function assigned to ctrl.fetchDataForCurrentUser to be called from HTML\n ctrl.fetchDataForCurrentUser = () => {\n ctrl.errorMessage = \"\";\n ctrl.hasError = false;\n const currentKey = generateDataKey(\n ctrl.selectedUser,\n moment().format('YYYY-MM-DD'), // Always use today's date for initial load\n moment().startOf('isoWeek').format('YYYY-MM-DD'),\n moment().endOf('isoWeek').format('YYYY-MM-DD')\n );\n\n // Fetch initial summary data (today and current week)\n fetchAllScreenTimeDataForUserAndDates(\n $http,\n ctrl,\n ctrl.selectedUser,\n moment().format('YYYY-MM-DD'),\n moment().startOf('isoWeek').format('YYYY-MM-DD'),\n moment().endOf('isoWeek').format('YYYY-MM-DD')\n ).then((data) => {\n ctrl.userData[currentKey] = { weekly: data.weekly, daily: data.daily };\n updateSummary(data.weekly); // Update the main summary values\n scope.$applyAsync();\n }).catch(error => {\n scope.$applyAsync();\n console.error(\"Error fetching initial data for current user:\", error);\n });\n };\n\n ctrl.fetchLightboxData = () => {\n\n const userIdForLightbox = ctrl.selectedChildHistogram || ctrl.selectedUser;\n\n if (!userIdForLightbox) return;\n\n const currentKey = generateDataKey(\n userIdForLightbox,\n ctrl.selectedDailyDate,\n ctrl.weekStart.format('YYYY-MM-DD'),\n ctrl.weekEnd.format('YYYY-MM-DD')\n );\n\n fetchAllScreenTimeDataForUserAndDates(\n $http,\n ctrl,\n userIdForLightbox,\n ctrl.selectedDailyDate,\n ctrl.weekStart.format('YYYY-MM-DD'),\n ctrl.weekEnd.format('YYYY-MM-DD')\n ).then((data) => {\n ctrl.userData[currentKey] = { weekly: data.weekly, daily: data.daily };\n if (ctrl.showLightbox) {\n setTimeout(() => ctrl.updateChart(), 50);\n }\n scope.$applyAsync();\n }).catch(error => {\n console.error(\"Error fetching data for lightbox:\", error);\n });\n };\n\n ctrl.updateChart = () => {\n const canvas = elem[0].querySelector<HTMLCanvasElement>(\"#myChart\");\n if (!canvas) return;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n\n let labels: string[] = [];\n let totalTimes: number[] = [];\n\n const userIdForChart = ctrl.selectedChildHistogram || ctrl.selectedUser;\n\n // Use the dates from the lightbox controls for the chart data key\n const currentDataKey = generateDataKey(\n userIdForChart,\n ctrl.selectedDailyDate,\n ctrl.weekStart.format('YYYY-MM-DD'),\n ctrl.weekEnd.format('YYYY-MM-DD')\n );\n const currentUserAndDateData = ctrl.userData[currentDataKey];\n\n if (!currentUserAndDateData) return;\n\n const dataToUse: any = ctrl.viewMode === \"weekly\" ? currentUserAndDateData.weekly : currentUserAndDateData.daily;\n if (!dataToUse) return;\n\n if (ctrl.viewMode === \"weekly\") {\n const sortedDates = Object.keys(dataToUse).sort();\n\n sortedDates.forEach(dataStr => {\n const entry = dataToUse[dataStr];\n labels.push(moment(dataStr).format('ddd D'));\n totalTimes.push(entry.duration);\n })\n } else {\n dataToUse.forEach((hourData: any) => {\n labels.push(`${hourData.hour}h`);\n totalTimes.push(hourData.duration);\n });\n }\n\n // If the chart already exists, we simply update it\n if (chartInstance) {\n chartInstance.data.labels = labels;\n chartInstance.data.datasets[0].data = totalTimes;\n\n chartInstance.options = {\n scales: {\n x: {\n stacked: true\n },\n y: {\n stacked: true,\n beginAtZero: true,\n title: {\n display: true,\n text: ctrl.viewMode === \"weekly\" ? \"Heures\" : \"Minutes\"\n }\n }\n }\n }\n\n chartInstance.update();\n } else {\n chartInstance = new Chart(ctx, {\n type: \"bar\",\n data: {\n labels,\n datasets: [\n {\n label: \"Usage total\",\n data: totalTimes,\n backgroundColor: \"#2A9CC8\"\n }\n ]\n },\n options: {\n plugins: {\n tooltip: {\n callbacks: {\n label: function (context) {\n const value = context.raw;\n return `${context.dataset.label}: ${value} ${ctrl.viewMode === \"weekly\" ? \"heures\" : \"minutes\"}`;\n }\n }\n },\n legend: {\n display: false,\n position: \"bottom\"\n }\n },\n responsive: true,\n scales: {\n x: {\n stacked: true\n },\n y: {\n stacked: true,\n beginAtZero: true,\n title: {\n display: true,\n text: ctrl.viewMode === \"weekly\" ? \"Heures\" : \"Minutes\"\n }\n }\n }\n }\n });\n }\n };\n\n ctrl.children = fetchChildren(ctrl);\n\n // Determine the user whose data should be fetched on load\n if (ctrl.isParent && ctrl.children.length > 0) {\n // If a parent, and has children, default to the first child if no child is selected yet\n if (!ctrl.selectedUser) {\n ctrl.selectedUser = ctrl.children[0].userId;\n }\n ctrl.fetchDataForCurrentUser();\n } else if (!ctrl.isParent && currentUserId) {\n // If not a parent, and current user ID is available, fetch data for themselves\n ctrl.selectedUser = currentUserId; // Set selectedUser to the current logged-in user's ID\n ctrl.fetchDataForCurrentUser();\n } else {\n // No children AND no current user ID (or other error condition)\n ctrl.hasError = true;\n ctrl.errorMessage = \"Aucun utilisateur disponible pour afficher les données de temps d'écran.\";\n scope.$applyAsync(); // Ensure the error message is displayed\n }\n\n // Watch for changes in view mode (within lightbox): only updates the chart\n scope.$watch(() => ctrl.viewMode, (newVal, oldVal) => {\n if (newVal !== oldVal && ctrl.showLightbox) {\n setTimeout(() => ctrl.updateChart(), 50);\n }\n });\n // Watch for selectedUser changes within the lightbox to update chart data\n scope.$watch(() => ctrl.selectedChildHistogram, (newVal, oldVal) => { // Changed from ctrl.selectedUser\n if (newVal !== oldVal && ctrl.showLightbox) {\n ctrl.fetchLightboxData();\n }\n });\n\n // Watch for lightbox visibility changes\n scope.$watch(() => ctrl.showLightbox, (isVisible: boolean) => {\n if (isVisible) {\n // When lightbox opens:\n // 1. Set selectedChildHistogram. This will trigger the selectedChildHistogram watch,\n // which will then call fetchLightboxData() (the single desired API call).\n ctrl.selectedChildHistogram = ctrl.selectedUser;\n\n // 2. Schedule chart update after a short delay, allowing fetchLightboxData() to begin.\n setTimeout(() => {\n ctrl.updateChart();\n }, 100);\n } else {\n // When lightbox closes\n if (chartInstance) {\n chartInstance.destroy();\n chartInstance = null;\n }\n }\n });\n\n }\n}\n\n// Factory function for the directive\nfunction DirectiveFactory() {\n return new Directive();\n}\n\n// Define the module name\nexport const odeModuleName = \"odeCantineWidgetModule\";\n\n// Angular module definition\nangular\n .module(odeModuleName, [])\n // Define the custom 'duration' filter\n .filter('duration', function () {\n return function (input: number) {\n if (isNaN(input) || input === null || input < 0) {\n return '0m';\n }\n\n const totalMinutes = Math.floor(input);\n const hours = Math.floor(totalMinutes / 60);\n const minutes = totalMinutes % 60;\n\n let result = '';\n\n if (hours > 0) {\n result += `${hours}h`;\n }\n if (minutes > 0 || (hours === 0 && totalMinutes === 0)) {\n result += `${minutes}m`;\n }\n\n return result.trim() || '0m';\n };\n })\n\n // Register the directive\n .directive(\"odeScreenTimeWidget\", DirectiveFactory);\n\n// Internationalization setup\nnotif()\n .onLangReady()\n .promise.then((lang) => {\n switch (lang) {\n default:\n conf().Platform.idiom.addKeys(require(\"./i18n/fr.json\"));\n break;\n }\n });\n"],"names":["Chart","register","registerables","locale","platform","conf","Platform","lang","this","idiom","selectedUser","viewMode","userData","selectedDailyDate","format","weekStart","startOf","weekEnd","endOf","updateChart","todayOnCampus","todayOffCampus","todayTotal","weeklyAvgOnCampus","weeklyAvgOffCampus","weeklyTotalAverage","weeklyAvgSchoolUsePercentage","weeklyAvgOutOfSchoolPercentage","fixedTodayOnCampus","fixedTodayOffCampus","fixedTodayTotal","fixedWeeklyAvgOnCampus","fixedWeeklyAvgOffCampus","fixedWeeklyTotalAverage","fixedWeeklyAvgSchoolUsePercentage","fixedWeeklyAvgOutOfSchoolPercentage","customWeekMode","customStartDate","customEndDate","isParent","hasError","errorMessage","fetchDataForCurrentUser","fetchLightboxData","showLightbox","showDatePicker","children","selectedChild","selectedChildHistogram","toggleDatePicker","show","setDateFromPicker","date","newDate","setSelectedDate","toggleLightbox","getCurrentSchoolYear","today","currentYear","year","schoolYearStart","month","day","schoolYearEnd","clone","add","subtract","momentDate","setSelectedWeek","canGoToPreviousWeek","offsetWeekStart","isBetween","canGoToNextWeek","canGoToPreviousDay","offsetDayStart","canGoToNextDay","changeDay","offset","changeWeek","newWeekStart","Date","start","end","fetchAllScreenTimeDataForUserAndDates","$http","ctrl","userId","dailyDate","startDate","endDate","fetchChildren","weeklyEndpoint","dailyEndpoint","Promise","all","get","then","weeklyResponse","dailyResponse","weekly","data","dailySummaries","reduce","acc","duration","durationMinutes","schoolUsePercentage","daily","durations","map","hour","catch","error","status","console","reject","childrenObj","session","user","Object","keys","length","entries","child","id","name","firstName","lastName","Controller","restrict","template","scope","bindToController","controller","controllerAs","require","link","elem","attrs","controllers","injector","currentUserId","chartInstance","generateDataKey","currentKey","weeklyData","totalOnCampus","totalOffCampus","daysCount","todayOnCampusTemp","todayOffCampusTemp","weeklyTotalSchoolUseDuration","weeklyTotalOutOfSchoolDuration","weeklyTotalOverallDuration","forEach","dateStr","entry","schoolUsePct","onCampus","offCampus","$applyAsync","userIdForLightbox","setTimeout","canvas","querySelector","ctx","getContext","labels","totalTimes","userIdForChart","currentDataKey","currentUserAndDateData","dataToUse","sort","dataStr","push","hourData","datasets","options","scales","x","stacked","y","beginAtZero","title","display","text","update","type","label","backgroundColor","plugins","tooltip","callbacks","context","value","raw","dataset","legend","position","responsive","$watch","newVal","oldVal","isVisible","destroy","odeModuleName","module","filter","input","isNaN","totalMinutes","Math","floor","hours","minutes","result","trim","directive","Directive","notif","onLangReady","promise","addKeys"],"sourceRoot":""}
1
+ {"version":3,"file":"widgets/screen-time-widget/screen-time-widget.widget.js","mappings":"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","sources":["webpack://ode-ngjs-front/./src/ts/widgets/screen-time-widget/screen-time-widget.widget.html","webpack://ode-ngjs-front/./src/ts/widgets/screen-time-widget/screen-time-widget.widget.ts"],"sourcesContent":["// Module\nvar code = \"<style>.screen-time-summary{padding:16px;font-family:Roboto,sans-serif;display:flex;flex-direction:column;gap:12px}.title{padding-bottom:6px;font-size:16px;color:#4a4a4a;font-weight:700;border-bottom:1px solid #e0e0e0}.modal-title{display:block;width:100%}.modal-body{margin:1rem 0!important}.summary-row{display:flex;gap:12px}.summary-row:nth-of-type(2){margin-top:10px}.summary-block{flex:1;background:#e5f5ff;border-radius:12px;padding:12px;box-shadow:0 1px 3px rgba(0,0,0,.1)}.summary-block .label{font-size:16px;color:#4a4a4a;font-weight:700;margin-bottom:4px}.summary-block .value{font-size:32px;color:#333}.percentage-bar-container{width:100%;height:10px;background-color:#e0e0e0;border-radius:5px;margin-top:5px;display:flex;overflow:hidden}.percentage-bar{height:100%;transition:width .5s ease-in-out;border-radius:5px}.legend{display:flex;gap:16px;align-items:center;margin-top:10px;font-size:10px;color:#333}.legend-item{display:flex;align-items:center;gap:6px}.legend-square{width:16px;height:16px;border-radius:4px}.custom-select{appearance:none;border:none;padding:8px 32px 8px 12px;border-radius:6px;font-size:16px;color:#333;font-family:Roboto,sans-serif;background:#fff url(\\\"data:image/svg+xml,%3Csvg fill='none' stroke='%23666' stroke-width='2' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E\\\") no-repeat right 10px center;background-size:16px;cursor:pointer;min-width:150px}.custom-select:focus{outline:0;box-shadow:0 0 0 2px rgba(42,156,200,.3)}.see-more{display:flex;justify-content:flex-end;margin-top:8px;padding-left:4px}.see-more button{background-color:transparent;border:none;font-family:Roboto,sans-serif;font-weight:600;font-size:16px;line-height:24px;padding:6px 12px;cursor:pointer;color:#2a9cc8;border-radius:4px;transition:background-color .2s ease}.see-more button:hover{background-color:#f0f8fc}.centered-controls{margin:0 auto 20px auto;width:fit-content;text-align:center}.view-controls{display:flex;width:100%;flex-direction:row;justify-content:space-between;margin-bottom:16px}.control-group{display:flex;align-items:center}.close-button-container{display:flex;justify-content:flex-end;margin-top:20px}.close-button{width:84px;height:40px;padding:8px 16px;background:#ff8d2e;color:#fff;font-size:16px;font-weight:700;border:none;border-radius:8px;cursor:pointer;transition:background .3s ease}.close-button:hover{background:#e67c24}.date-controls{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.nav-button{background:0 0;border:none;font-size:24px;cursor:pointer}.date-navigation{display:flex;align-items:center;justify-content:center;gap:1rem}.date-navigation button{background:0 0;border:none;font-size:1.5rem;cursor:pointer}.date-navigation button:disabled{color:#d3d3d3;cursor:not-allowed}.selected-date{cursor:pointer;font-weight:700}.error-message{background-color:#ffe5e5;color:#c00;border:1px solid #c00;padding:12px;border-radius:6px;font-size:14px;font-weight:500}.info-popover{position:absolute;z-index:10;display:block;width:90%;background-color:#fff;border:1px solid #e0e0e0;box-shadow:0 4px 6px 0 rgba(0,0,0,.08);font-size:1.5rem;line-height:1.8;padding:1rem;border-radius:6px}</style> <div class=\\\"screen-time-widget-container\\\"> <div class=\\\"screen-time-summary\\\"> <div class=\\\"title\\\"> <div class=\\\"label\\\"> <i18n>screenTime.title.uppercase</i18n> </div> </div> <div ng-if=\\\"ctrl.isParent\\\"> <select id=\\\"userSelect\\\" class=\\\"custom-select\\\" ng-model=\\\"ctrl.selectedUser\\\" ng-options=\\\"child.userId as child.name for child in ctrl.children\\\" ng-change=\\\"ctrl.fetchDataForCurrentUser()\\\"> </select> </div> <div class=\\\"error-message\\\" ng-if=\\\"ctrl.errorMessage\\\"> {{ ctrl.errorMessage }} </div> <div ng-if=\\\"!ctrl.errorMessage\\\"> <div class=\\\"summary-row\\\"> <div class=\\\"summary-block\\\"> <div class=\\\"label\\\"> <i18n>screenTime.today</i18n> </div> <div class=\\\"value\\\">{{ ctrl.todayTotal | duration }}</div> </div> </div> <div class=\\\"summary-row\\\"> <div class=\\\"summary-block\\\"> <div class=\\\"label\\\"> <i18n>screenTime.weekAverage</i18n> </div> <div class=\\\"value\\\">{{ ctrl.weeklyTotalAverage | duration }}</div> </div> </div> <div class=\\\"legend\\\"> <div class=\\\"see-more\\\"> <button ng-click=\\\"ctrl.toggleLightbox(true)\\\"> <i18n>screenTime.displayDetails</i18n> </button> </div> </div> </div> </div> <ode-modal visible=\\\"ctrl.showLightbox\\\" on-close=\\\"ctrl.toggleLightbox(false)\\\" size=\\\"lg\\\" dnd-nodrag> <ode-modal-title class=\\\"modal-title\\\"> <h2> <i18n>screenTime.title.normal</i18n> <popover> <popover-opener><i class=\\\"fa-solid fa-circle-info\\\"></i></popover-opener> <popover-content class=\\\"info-popover\\\"> <ul> <li> <i18n>screenTime.information.first</i18n> </li> <li> <i18n>screenTime.information.second</i18n> </li> <li> <i18n>screenTime.information.third</i18n> </li> <li> <i18n>screenTime.information.fourth</i18n> </li> </ul> </popover-content> </popover> </h2> <div class=\\\"view-controls\\\"> <div class=\\\"control-group\\\"> <label style=\\\"font-size:14px\\\" for=\\\"userSelect\\\"> <i18n>screenTime.user</i18n> :</label> <div ng-if=\\\"ctrl.isParent\\\"> <select id=\\\"userSelect\\\" class=\\\"custom-select\\\" ng-model=\\\"ctrl.selectedChildHistogram\\\" ng-options=\\\"child.userId as child.name for child in ctrl.children\\\"> </select> </div> </div> <div class=\\\"control-group\\\"> <label for=\\\"viewMode\\\"> <i18n>screenTime.viewMode</i18n> :</label> <select id=\\\"viewMode\\\" class=\\\"custom-select\\\" ng-model=\\\"ctrl.viewMode\\\" ng-change=\\\"ctrl.updateChart()\\\"> <option value=\\\"weekly\\\"> [[ctrl.lang.translate(\\\"screenTime.viewMode.weekly\\\")]] </option> <option value=\\\"daily\\\"> [[ctrl.lang.translate(\\\"screenTime.viewMode.daily\\\")]] </option> </select> </div> </div> </ode-modal-title> <ode-modal-body class=\\\"modal-body\\\"> <div class=\\\"date-selectors centered-controls\\\" ng-if=\\\"ctrl.showLightbox\\\"> <div class=\\\"date-navigation\\\" ng-if=\\\"ctrl.viewMode === 'daily'\\\"> <button ng-click=\\\"ctrl.changeDay(-1)\\\" ng-disabled=\\\"!ctrl.canGoToPreviousDay()\\\">←</button> <div ng-if=\\\"!ctrl.showDatePicker\\\" class=\\\"selected-date\\\" ng-click=\\\"ctrl.toggleDatePicker(true)\\\"> {{ ctrl.selectedDailyDateObj | date:'fullDate' }} </div> <input type=\\\"date\\\" ng-if=\\\"ctrl.showDatePicker\\\" ng-model=\\\"ctrl.selectedDailyDate\\\" ng-change=\\\"ctrl.setDateFromPicker(ctrl.selectedDailyDate)\\\" ng-blur=\\\"ctrl.toggleDatePicker(false)\\\" max=\\\"{{ ctrl.selectedDailyDate }}\\\"/> <button ng-click=\\\"ctrl.changeDay(1)\\\" ng-disabled=\\\"!ctrl.canGoToNextDay()\\\">→</button> </div> <div class=\\\"date-navigation\\\" ng-if=\\\"ctrl.viewMode === 'weekly'\\\"> <button ng-click=\\\"ctrl.changeWeek(-1)\\\" ng-disabled=\\\"!ctrl.canGoToPreviousWeek()\\\">←</button> <div class=\\\"selected-date\\\">{{ ctrl.weekLabel }}</div> <button ng-click=\\\"ctrl.changeWeek(1)\\\" ng-disabled=\\\"!ctrl.canGoToNextWeek()\\\">→</button> </div> </div> <canvas id=\\\"myChart\\\" style=\\\"width:100%;height:500px;max-height:500px\\\"></canvas> </ode-modal-body> </ode-modal> </div>\";\n// Exports\nexport default code;","import angular, { IAttributes, IController, IDirective, IScope, IHttpService } from \"angular\";\nimport { conf, notif, session } from \"../../utils\";\nimport { Chart, registerables } from \"chart.js\";\nimport moment, { Moment } from \"moment\";\nimport 'moment/locale/fr';\n\nChart.register(...registerables);\nmoment.locale('fr');\n\n// Controller\nexport class Controller {\n public platform = conf().Platform;\n public lang = this.platform.idiom;\n public selectedUser: string = \"\";\n public viewMode: \"weekly\" | \"daily\" = \"weekly\";\n\n public userData: { [key: string]: { weekly: any, daily: any } } = {};\n public selectedDailyDate: string = moment().format('YYYY-MM-DD');\n public weekStart: Moment = moment().startOf('isoWeek'); // Monday\n public weekEnd: Moment = moment().endOf('isoWeek'); // Sunday\n\n public updateChart: () => void = () => { };\n\n public todayOnCampus = 0;\n public todayOffCampus = 0;\n public todayTotal = 0;\n\n public weeklyAvgOnCampus = 0;\n public weeklyAvgOffCampus = 0;\n public weeklyTotalAverage = 0;\n public weeklyAvgSchoolUsePercentage: number = 0;\n public weeklyAvgOutOfSchoolPercentage: number = 0;\n\n // new props\n public fixedTodayOnCampus = 0;\n public fixedTodayOffCampus = 0;\n public fixedTodayTotal = 0;\n\n public fixedWeeklyAvgOnCampus = 0;\n public fixedWeeklyAvgOffCampus = 0;\n public fixedWeeklyTotalAverage = 0;\n public fixedWeeklyAvgSchoolUsePercentage: number = 0;\n public fixedWeeklyAvgOutOfSchoolPercentage: number = 0;\n\n\n // end of new \n\n public customWeekMode: boolean = false;\n public customStartDate: string = \"\";\n public customEndDate: string = \"\";\n\n public isParent: boolean = false;\n\n public hasError: boolean = false;\n public errorMessage: string = \"\";\n\n public fetchDataForCurrentUser: () => void = () => { };\n public fetchLightboxData: () => void = () => { };\n\n public showLightbox: boolean = false;\n public showDatePicker: boolean = false;\n\n public children: { id: string; name: string; userId: string }[] = [];\n public selectedChild = null;\n public selectedChildHistogram = \"\";\n\n public toggleDatePicker(show: boolean) {\n this.showDatePicker = show;\n }\n\n public setDateFromPicker(date: string) {\n const newDate = moment(date).format('YYYY-MM-DD');\n this.showDatePicker = false;\n this.setSelectedDate(newDate);\n this.fetchLightboxData();\n }\n\n public toggleLightbox(show: boolean) {\n this.showLightbox = show;\n if (show) {\n // Store the current summary values when lightbox opens\n this.fixedTodayOnCampus = this.todayOnCampus;\n this.fixedTodayOffCampus = this.todayOffCampus;\n this.fixedTodayTotal = this.todayTotal;\n this.fixedWeeklyAvgOnCampus = this.weeklyAvgOnCampus;\n this.fixedWeeklyAvgOffCampus = this.weeklyAvgOffCampus;\n this.fixedWeeklyTotalAverage = this.weeklyTotalAverage;\n this.fixedWeeklyAvgSchoolUsePercentage = this.weeklyAvgSchoolUsePercentage;\n this.fixedWeeklyAvgOutOfSchoolPercentage = this.weeklyAvgOutOfSchoolPercentage;\n\n // Initialize lightbox dates to current date/week\n this.selectedDailyDate = moment().format('YYYY-MM-DD');\n this.weekStart = moment().startOf('isoWeek');\n this.weekEnd = moment().endOf('isoWeek');\n this.selectedChildHistogram = this.selectedUser;\n\n }\n }\n\n private getCurrentSchoolYear(): { schoolYearStart: moment.Moment, schoolYearEnd: moment.Moment } {\n const today = moment();\n const currentYear = today.year();\n const currentMonth = today.month();\n\n // If current month is less than september, school year is the previous one\n const schoolYearStart = currentMonth < 8\n ? moment({ year: currentYear - 1, month: 8, day: 1 }) // 1st september of previous school year\n : moment({ year: currentYear, month: 8, day: 1 }); // 1st september of current school year\n\n const schoolYearEnd = schoolYearStart\n .clone()\n .add(1, 'year')\n .subtract(1, 'day'); // 31 august of next school year\n\n return {\n schoolYearStart: schoolYearStart.startOf('day'),\n schoolYearEnd: schoolYearEnd.endOf('day'),\n };\n }\n\n private setSelectedDate(date: string | moment.Moment) {\n const momentDate = moment(date);\n\n this.selectedDailyDate = momentDate.format('YYYY-MM-DD');\n\n this.weekStart = momentDate.clone().startOf('isoWeek');\n this.weekEnd = momentDate.clone().endOf('isoWeek');\n }\n\n private setSelectedWeek(weekStart: moment.Moment) {\n this.weekStart = weekStart.clone().startOf('isoWeek');\n this.weekEnd = this.weekStart.clone().endOf('isoWeek');\n\n this.selectedDailyDate = this.weekStart.format('YYYY-MM-DD');\n }\n\n public canGoToPreviousWeek(): boolean {\n const offsetWeekStart = this.weekStart.clone().subtract(1, 'week');\n const { schoolYearStart, schoolYearEnd } = this.getCurrentSchoolYear();\n\n return offsetWeekStart.isBetween(schoolYearStart, schoolYearEnd);\n }\n\n public canGoToNextWeek(): boolean {\n const offsetWeekStart = this.weekStart.clone().add(1, 'week');\n const { schoolYearStart, schoolYearEnd } = this.getCurrentSchoolYear();\n\n return offsetWeekStart.isBetween(schoolYearStart, schoolYearEnd);\n }\n\n public canGoToPreviousDay(): boolean {\n const offsetDayStart = moment(this.selectedDailyDate).subtract(1, 'day');\n const { schoolYearStart, schoolYearEnd } = this.getCurrentSchoolYear();\n\n return offsetDayStart.isBetween(schoolYearStart, schoolYearEnd);\n }\n\n public canGoToNextDay(): boolean {\n const offsetDayStart = moment(this.selectedDailyDate).add(1, 'day');\n const { schoolYearStart, schoolYearEnd } = this.getCurrentSchoolYear();\n\n return offsetDayStart.isBetween(schoolYearStart, schoolYearEnd);\n }\n\n public changeDay(offset: number) {\n const newDate = moment(this.selectedDailyDate).add(offset, 'days');\n this.setSelectedDate(newDate);\n this.fetchLightboxData();\n }\n\n public changeWeek(offset: number) {\n const newWeekStart = this.weekStart.clone().add(offset, 'weeks');\n this.setSelectedWeek(newWeekStart);\n this.fetchLightboxData();\n }\n\n public get selectedDailyDateObj(): Date {\n return new Date(this.selectedDailyDate);\n }\n\n public get weekLabel(): string {\n const start = this.weekStart.format('dddd D MMMM');\n const end = this.weekEnd.format('dddd D MMMM');\n return `du ${start} au ${end}`;\n }\n}\n\nfunction fetchAllScreenTimeDataForUserAndDates($http: IHttpService, ctrl: Controller, userId: string, dailyDate: string, startDate: string, endDate: string): Promise<{ weekly: any, daily: any }> {\n ctrl.children = fetchChildren(ctrl);\n\n const weeklyEndpoint = `/appregistry/screen-time/${userId}/weekly?startDate=${startDate}&endDate=${endDate}`;\n const dailyEndpoint = `/appregistry/screen-time/${userId}/daily?date=${dailyDate}`;\n\n return Promise.all([\n $http.get(weeklyEndpoint),\n $http.get(dailyEndpoint)\n ]).then(([weeklyResponse, dailyResponse]: [any, any]) => {\n ctrl.hasError = false;\n ctrl.errorMessage = \"\";\n return {\n weekly: weeklyResponse.data.dailySummaries.reduce((acc: any, day: any) => {\n acc[day.date] = {\n duration: day.durationMinutes,\n schoolUsePercentage: day.schoolUsePercentage / 100 // convert to ratio\n };\n return acc;\n }, {}),\n daily: dailyResponse.data.durations.map((hour: any) => ({\n hour: hour.hour,\n duration: hour.durationMinutes,\n schoolUsePercentage: hour.schoolUsePercentage / 100 // convert to ratio\n }))\n };\n }).catch(error => {\n ctrl.hasError = true;\n\n if (error?.status === 404) {\n ctrl.errorMessage = ctrl.lang.translate(\"screenTime.error.404\");\n } else {\n ctrl.errorMessage = ctrl.lang.translate(\"screenTime.error.generic\");\n }\n\n console.error(\"Error fetching data for current user and dates:\", error);\n return Promise.reject(error);\n });\n}\n\nfunction fetchChildren(ctrl: Controller) {\n const childrenObj = session().user.children;\n ctrl.isParent = false;\n\n if (childrenObj && Object.keys(childrenObj).length > 0) {\n ctrl.isParent = true;\n\n return Object.entries(childrenObj).map(([userId, childData]) => {\n const child = childData as { firstName: string; lastName: string };\n return {\n id: userId,\n name: `${child.firstName} ${child.lastName}`,\n userId: userId\n };\n });\n } else {\n return [];\n }\n}\n\nclass Directive implements IDirective<IScope, JQLite, IAttributes, IController[]> {\n restrict = \"E\";\n template = require(\"./screen-time-widget.widget.html\").default;\n scope = {};\n bindToController = true;\n controller = [Controller];\n controllerAs = \"ctrl\";\n require = [\"odeScreenTimeWidget\"];\n\n link(scope: IScope, elem: angular.IAugmentedJQuery, attrs: IAttributes, controllers?: IController[]) {\n const ctrl: Controller | null = controllers ? (controllers[0] as Controller) : null;\n if (!ctrl) return;\n\n const $http = angular.injector([\"ng\"]).get<IHttpService>(\"$http\");\n const currentUserId = session().user.userId;\n\n ctrl.fetchDataForCurrentUser();\n\n let chartInstance: Chart | null = null;\n\n // Function to update the summary values from the pre-fetched weekly data\n const updateSummary = (weeklyData: any) => {\n let totalOnCampus = 0;\n let totalOffCampus = 0;\n let daysCount = 0;\n const today = moment().format(\"YYYY-MM-DD\");\n let todayOnCampusTemp = 0;\n let todayOffCampusTemp = 0;\n\n let weeklyTotalSchoolUseDuration = 0;\n let weeklyTotalOutOfSchoolDuration = 0;\n let weeklyTotalOverallDuration = 0;\n\n Object.keys(weeklyData).forEach(dateStr => {\n const entry = weeklyData[dateStr];\n const duration = entry.duration || 0;\n const schoolUsePct = entry.schoolUsePercentage || 0;\n\n const onCampus = duration * schoolUsePct;\n const offCampus = duration * (1 - schoolUsePct);\n\n totalOnCampus += onCampus;\n totalOffCampus += offCampus;\n daysCount++;\n\n weeklyTotalSchoolUseDuration += onCampus;\n weeklyTotalOutOfSchoolDuration += offCampus;\n weeklyTotalOverallDuration += duration;\n\n if (moment(dateStr).format(\"YYYY-MM-DD\") === today) {\n todayOnCampusTemp = onCampus;\n todayOffCampusTemp = offCampus;\n }\n });\n\n ctrl.todayOnCampus = todayOnCampusTemp;\n ctrl.todayOffCampus = todayOffCampusTemp;\n ctrl.todayTotal = todayOnCampusTemp + todayOffCampusTemp;\n ctrl.weeklyAvgOnCampus = daysCount > 0 ? totalOnCampus / daysCount : 0;\n ctrl.weeklyAvgOffCampus = daysCount > 0 ? totalOffCampus / daysCount : 0;\n ctrl.weeklyTotalAverage = ctrl.weeklyAvgOnCampus + ctrl.weeklyAvgOffCampus;\n\n if (weeklyTotalOverallDuration > 0) {\n ctrl.weeklyAvgSchoolUsePercentage = (weeklyTotalSchoolUseDuration / weeklyTotalOverallDuration) * 100;\n ctrl.weeklyAvgOutOfSchoolPercentage = (weeklyTotalOutOfSchoolDuration / weeklyTotalOverallDuration) * 100;\n } else {\n ctrl.weeklyAvgSchoolUsePercentage = 0;\n ctrl.weeklyAvgOutOfSchoolPercentage = 0;\n }\n };\n\n // Helper to generate a unique key for caching data based on user and date range\n const generateDataKey = (userId: string, dailyDate: string, startDate: string, endDate: string) => {\n return `${userId}_daily_${dailyDate}_weekly_${startDate}_${endDate}`;\n };\n\n // New function assigned to ctrl.fetchDataForCurrentUser to be called from HTML\n ctrl.fetchDataForCurrentUser = () => {\n ctrl.errorMessage = \"\";\n ctrl.hasError = false;\n const currentKey = generateDataKey(\n ctrl.selectedUser,\n moment().format('YYYY-MM-DD'), // Always use today's date for initial load\n moment().startOf('isoWeek').format('YYYY-MM-DD'),\n moment().endOf('isoWeek').format('YYYY-MM-DD')\n );\n\n // Fetch initial summary data (today and current week)\n fetchAllScreenTimeDataForUserAndDates(\n $http,\n ctrl,\n ctrl.selectedUser,\n moment().format('YYYY-MM-DD'),\n moment().startOf('isoWeek').format('YYYY-MM-DD'),\n moment().endOf('isoWeek').format('YYYY-MM-DD')\n ).then((data) => {\n ctrl.userData[currentKey] = { weekly: data.weekly, daily: data.daily };\n updateSummary(data.weekly); // Update the main summary values\n scope.$applyAsync();\n }).catch(error => {\n scope.$applyAsync();\n console.error(\"Error fetching initial data for current user:\", error);\n });\n };\n\n ctrl.fetchLightboxData = () => {\n\n const userIdForLightbox = ctrl.selectedChildHistogram || ctrl.selectedUser;\n\n if (!userIdForLightbox) return;\n\n const currentKey = generateDataKey(\n userIdForLightbox,\n ctrl.selectedDailyDate,\n ctrl.weekStart.format('YYYY-MM-DD'),\n ctrl.weekEnd.format('YYYY-MM-DD')\n );\n\n fetchAllScreenTimeDataForUserAndDates(\n $http,\n ctrl,\n userIdForLightbox,\n ctrl.selectedDailyDate,\n ctrl.weekStart.format('YYYY-MM-DD'),\n ctrl.weekEnd.format('YYYY-MM-DD')\n ).then((data) => {\n ctrl.userData[currentKey] = { weekly: data.weekly, daily: data.daily };\n if (ctrl.showLightbox) {\n setTimeout(() => ctrl.updateChart(), 50);\n }\n scope.$applyAsync();\n }).catch(error => {\n console.error(\"Error fetching data for lightbox:\", error);\n });\n };\n\n ctrl.updateChart = () => {\n const canvas = elem[0].querySelector<HTMLCanvasElement>(\"#myChart\");\n if (!canvas) return;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n\n let labels: string[] = [];\n let totalTimes: number[] = [];\n\n const userIdForChart = ctrl.selectedChildHistogram || ctrl.selectedUser;\n\n // Use the dates from the lightbox controls for the chart data key\n const currentDataKey = generateDataKey(\n userIdForChart,\n ctrl.selectedDailyDate,\n ctrl.weekStart.format('YYYY-MM-DD'),\n ctrl.weekEnd.format('YYYY-MM-DD')\n );\n const currentUserAndDateData = ctrl.userData[currentDataKey];\n\n if (!currentUserAndDateData) return;\n\n const dataToUse: any = ctrl.viewMode === \"weekly\" ? currentUserAndDateData.weekly : currentUserAndDateData.daily;\n if (!dataToUse) return;\n\n if (ctrl.viewMode === \"weekly\") {\n const sortedDates = Object.keys(dataToUse).sort();\n\n sortedDates.forEach(dataStr => {\n const entry = dataToUse[dataStr];\n labels.push(moment(dataStr).format('ddd D'));\n totalTimes.push(entry.duration / 60);\n })\n } else {\n dataToUse.forEach((hourData: any) => {\n labels.push(`${hourData.hour}h`);\n totalTimes.push(hourData.duration);\n });\n }\n\n // If the chart already exists, we simply update it\n if (chartInstance) {\n chartInstance.data.labels = labels;\n chartInstance.data.datasets[0].data = totalTimes;\n\n chartInstance.options = {\n scales: {\n x: {\n stacked: true\n },\n y: {\n stacked: true,\n beginAtZero: true,\n title: {\n display: true,\n text: ctrl.viewMode === \"weekly\" ? \"Heures\" : \"Minutes\"\n }\n }\n }\n }\n\n chartInstance.update();\n } else {\n chartInstance = new Chart(ctx, {\n type: \"bar\",\n data: {\n labels,\n datasets: [\n {\n label: \"Usage total\",\n data: totalTimes,\n backgroundColor: \"#2A9CC8\"\n }\n ]\n },\n options: {\n plugins: {\n tooltip: {\n callbacks: {\n label: function (context) {\n const value = context.raw;\n return `${context.dataset.label}: ${value} ${ctrl.viewMode === \"weekly\" ? \"heures\" : \"minutes\"}`;\n }\n }\n },\n legend: {\n display: false,\n position: \"bottom\"\n }\n },\n responsive: true,\n scales: {\n x: {\n stacked: true\n },\n y: {\n stacked: true,\n beginAtZero: true,\n title: {\n display: true,\n text: ctrl.viewMode === \"weekly\" ? \"Heures\" : \"Minutes\"\n }\n }\n }\n }\n });\n }\n };\n\n ctrl.children = fetchChildren(ctrl);\n\n // Determine the user whose data should be fetched on load\n if (ctrl.isParent && ctrl.children.length > 0) {\n // If a parent, and has children, default to the first child if no child is selected yet\n if (!ctrl.selectedUser) {\n ctrl.selectedUser = ctrl.children[0].userId;\n }\n ctrl.fetchDataForCurrentUser();\n } else if (!ctrl.isParent && currentUserId) {\n // If not a parent, and current user ID is available, fetch data for themselves\n ctrl.selectedUser = currentUserId; // Set selectedUser to the current logged-in user's ID\n ctrl.fetchDataForCurrentUser();\n } else {\n // No children AND no current user ID (or other error condition)\n ctrl.hasError = true;\n ctrl.errorMessage = \"Aucun utilisateur disponible pour afficher les données de temps d'écran.\";\n scope.$applyAsync(); // Ensure the error message is displayed\n }\n\n // Watch for changes in view mode (within lightbox): only updates the chart\n scope.$watch(() => ctrl.viewMode, (newVal, oldVal) => {\n if (newVal !== oldVal && ctrl.showLightbox) {\n setTimeout(() => ctrl.updateChart(), 50);\n }\n });\n // Watch for selectedUser changes within the lightbox to update chart data\n scope.$watch(() => ctrl.selectedChildHistogram, (newVal, oldVal) => { // Changed from ctrl.selectedUser\n if (newVal !== oldVal && ctrl.showLightbox) {\n ctrl.fetchLightboxData();\n }\n });\n\n // Watch for lightbox visibility changes\n scope.$watch(() => ctrl.showLightbox, (isVisible: boolean) => {\n if (isVisible) {\n // When lightbox opens:\n // 1. Set selectedChildHistogram. This will trigger the selectedChildHistogram watch,\n // which will then call fetchLightboxData() (the single desired API call).\n ctrl.selectedChildHistogram = ctrl.selectedUser;\n\n // 2. Schedule chart update after a short delay, allowing fetchLightboxData() to begin.\n setTimeout(() => {\n ctrl.updateChart();\n }, 100);\n } else {\n // When lightbox closes\n if (chartInstance) {\n chartInstance.destroy();\n chartInstance = null;\n }\n }\n });\n\n }\n}\n\n// Factory function for the directive\nfunction DirectiveFactory() {\n return new Directive();\n}\n\n// Define the module name\nexport const odeModuleName = \"odeCantineWidgetModule\";\n\n// Angular module definition\nangular\n .module(odeModuleName, [])\n // Define the custom 'duration' filter\n .filter('duration', function () {\n return function (input: number) {\n if (isNaN(input) || input === null || input < 0) {\n return '0m';\n }\n\n const totalMinutes = Math.floor(input);\n const hours = Math.floor(totalMinutes / 60);\n const minutes = totalMinutes % 60;\n\n let result = '';\n\n if (hours > 0) {\n result += `${hours}h`;\n }\n if (minutes > 0 || (hours === 0 && totalMinutes === 0)) {\n result += `${minutes}m`;\n }\n\n return result.trim() || '0m';\n };\n })\n\n // Register the directive\n .directive(\"odeScreenTimeWidget\", DirectiveFactory);\n\n// Internationalization setup\nnotif()\n .onLangReady()\n .promise.then((lang) => {\n switch (lang) {\n default:\n conf().Platform.idiom.addKeys(require(\"./i18n/fr.json\"));\n break;\n }\n });\n"],"names":["Chart","register","registerables","locale","platform","conf","Platform","lang","this","idiom","selectedUser","viewMode","userData","selectedDailyDate","format","weekStart","startOf","weekEnd","endOf","updateChart","todayOnCampus","todayOffCampus","todayTotal","weeklyAvgOnCampus","weeklyAvgOffCampus","weeklyTotalAverage","weeklyAvgSchoolUsePercentage","weeklyAvgOutOfSchoolPercentage","fixedTodayOnCampus","fixedTodayOffCampus","fixedTodayTotal","fixedWeeklyAvgOnCampus","fixedWeeklyAvgOffCampus","fixedWeeklyTotalAverage","fixedWeeklyAvgSchoolUsePercentage","fixedWeeklyAvgOutOfSchoolPercentage","customWeekMode","customStartDate","customEndDate","isParent","hasError","errorMessage","fetchDataForCurrentUser","fetchLightboxData","showLightbox","showDatePicker","children","selectedChild","selectedChildHistogram","toggleDatePicker","show","setDateFromPicker","date","newDate","setSelectedDate","toggleLightbox","getCurrentSchoolYear","today","currentYear","year","schoolYearStart","month","day","schoolYearEnd","clone","add","subtract","momentDate","setSelectedWeek","canGoToPreviousWeek","offsetWeekStart","isBetween","canGoToNextWeek","canGoToPreviousDay","offsetDayStart","canGoToNextDay","changeDay","offset","changeWeek","newWeekStart","Date","start","end","fetchAllScreenTimeDataForUserAndDates","$http","ctrl","userId","dailyDate","startDate","endDate","fetchChildren","weeklyEndpoint","dailyEndpoint","Promise","all","get","then","weeklyResponse","dailyResponse","weekly","data","dailySummaries","reduce","acc","duration","durationMinutes","schoolUsePercentage","daily","durations","map","hour","catch","error","status","translate","console","reject","childrenObj","session","user","Object","keys","length","entries","child","id","name","firstName","lastName","Controller","restrict","template","scope","bindToController","controller","controllerAs","require","link","elem","attrs","controllers","injector","currentUserId","chartInstance","generateDataKey","currentKey","weeklyData","totalOnCampus","totalOffCampus","daysCount","todayOnCampusTemp","todayOffCampusTemp","weeklyTotalSchoolUseDuration","weeklyTotalOutOfSchoolDuration","weeklyTotalOverallDuration","forEach","dateStr","entry","schoolUsePct","onCampus","offCampus","$applyAsync","userIdForLightbox","setTimeout","canvas","querySelector","ctx","getContext","labels","totalTimes","userIdForChart","currentDataKey","currentUserAndDateData","dataToUse","sort","dataStr","push","hourData","datasets","options","scales","x","stacked","y","beginAtZero","title","display","text","update","type","label","backgroundColor","plugins","tooltip","callbacks","context","value","raw","dataset","legend","position","responsive","$watch","newVal","oldVal","isVisible","destroy","odeModuleName","module","filter","input","isNaN","totalMinutes","Math","floor","hours","minutes","result","trim","directive","Directive","notif","onLangReady","promise","addKeys"],"sourceRoot":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ode-ngjs-front",
3
- "version": "1.4.13-develop-integration.202507301459",
3
+ "version": "1.4.13-develop-integration.202508041121",
4
4
  "description": "Open Digital Education Frontend Framework",
5
5
  "homepage": "https://github.com/opendigitaleducation/ode-ngjs-front#readme",
6
6
  "bugs": {