diff --git a/amd/build/dashboard.min.js b/amd/build/dashboard.min.js index a370285..9b91061 100644 --- a/amd/build/dashboard.min.js +++ b/amd/build/dashboard.min.js @@ -1,3 +1,3 @@ -define("local_sitsgradepush/dashboard",["exports","./push_tasks","core/notification"],(function(_exports,_push_tasks,_notification){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};async function pushgrade(button){try{let assessmentmappingid=button.getAttribute("data-assessmentmappingid"),result=await(0,_push_tasks.schedulePushTask)(assessmentmappingid);if(result.success){button.parentNode.parentNode.querySelector("span:last-child").innerHTML='',button.disabled=!0;let tooltipid=button.getAttribute("aria-describedby");null!==tooltipid&&null!==document.getElementById(tooltipid)&&document.getElementById(tooltipid).remove()}else{let errormessagerow=document.createElement("tr");errormessagerow.setAttribute("class","error-message-row"),errormessagerow.innerHTML='";let currentrow=button.closest("tr");null!==currentrow.nextElementSibling&¤trow.nextElementSibling.classList.contains("error-message-row")&¤trow.nextElementSibling.remove(),currentrow.insertAdjacentElement("afterend",errormessagerow)}return result.success}catch(error){return window.console.error(error),!1}}_exports.init=()=>{let page=document.getElementById("page"),tableSelector=document.getElementById("module-delivery-selector");tableSelector.addEventListener("change",(function(){let selectedTable=document.getElementById(tableSelector.value);if(selectedTable){let offset=-100,tablePosition=selectedTable.getBoundingClientRect().top,scrollPosition=page.scrollTop+tablePosition+offset;page.scrollTo({top:scrollPosition,behavior:"smooth"})}}));let backToTopButton=document.getElementById("backToTopButton");page.addEventListener("scroll",(function(){page.scrollTop>=100?backToTopButton.style.display="block":backToTopButton.style.display="none"})),backToTopButton.addEventListener("click",(function(){page.scrollTo({top:0,behavior:"smooth"}),tableSelector.selectedIndex=0})),document.querySelectorAll(".push-mark-button:not([disabled])").forEach((function(button){button.addEventListener("click",(function(){pushgrade(this)}))})),document.getElementById("push-all-button").addEventListener("click",(async function(){let mabpushbuttons=document.querySelectorAll(".push-mark-button:not([disabled])"),total=mabpushbuttons.length,count=0,promises=[];mabpushbuttons.forEach((function(button){let promise=pushgrade(button).then((function(result){return result&&(count+=1),result})).catch((function(error){window.console.error(error)}));promises.push(promise)})),await Promise.all(promises),page.scrollTo({top:0,behavior:"instant"}),_notification.default.addNotification({message:count+" of "+total+" push tasks have been scheduled.",type:count===total?"success":"warning"})}))}})); +define("local_sitsgradepush/dashboard",["exports","./sitsgradepush_helper","core/notification"],(function(_exports,_sitsgradepush_helper,_notification){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};async function pushMarks(button){try{let assessmentmappingid=button.getAttribute("data-assessmentmappingid"),result=await(0,_sitsgradepush_helper.schedulePushTask)(assessmentmappingid);if(result.success){button.parentNode.parentNode.querySelector("span:last-child").innerHTML='',button.disabled=!0;let tooltipid=button.getAttribute("aria-describedby");null!==tooltipid&&null!==document.getElementById(tooltipid)&&document.getElementById(tooltipid).remove()}else{let errormessagerow=document.createElement("tr");errormessagerow.setAttribute("class","error-message-row"),errormessagerow.innerHTML='";let currentrow=button.closest("tr");null!==currentrow.nextElementSibling&¤trow.nextElementSibling.classList.contains("error-message-row")&¤trow.nextElementSibling.remove(),currentrow.insertAdjacentElement("afterend",errormessagerow)}return result.success}catch(error){return window.console.error(error),!1}}_exports.init=()=>{!function(){let successMessage=localStorage.getItem("successMessage");successMessage&&(_notification.default.addNotification({message:successMessage,type:"success"}),localStorage.removeItem("successMessage"))}();let page=document.getElementById("page"),tableSelector=document.getElementById("module-delivery-selector");tableSelector.addEventListener("change",(function(){let selectedTable=document.getElementById(tableSelector.value);if(selectedTable){let offset=-100,tablePosition=selectedTable.getBoundingClientRect().top,scrollPosition=page.scrollTop+tablePosition+offset;page.scrollTo({top:scrollPosition,behavior:"smooth"})}}));let backToTopButton=document.getElementById("backToTopButton");page.addEventListener("scroll",(function(){page.scrollTop>=100?backToTopButton.style.display="block":backToTopButton.style.display="none"})),backToTopButton.addEventListener("click",(function(){page.scrollTo({top:0,behavior:"smooth"}),tableSelector.selectedIndex=0}));let changesourcebuttons=document.querySelectorAll(".change-source-button:not([disabled])");changesourcebuttons.length>0&&changesourcebuttons.forEach((function(button){button.addEventListener("click",(function(){window.location.href=button.getAttribute("data-url")}))}));let mabpushbuttons=document.querySelectorAll(".push-mark-button:not([disabled])");mabpushbuttons.length>0&&mabpushbuttons.forEach((function(button){button.addEventListener("click",(function(){pushMarks(this)}))})),document.getElementById("push-all-button").addEventListener("click",(async function(){let mabpushbuttons=document.querySelectorAll(".push-mark-button:not([disabled])"),total=mabpushbuttons.length,count=0,promises=[];mabpushbuttons.forEach((function(button){let promise=pushMarks(button).then((function(result){return result&&(count+=1),result})).catch((function(error){window.console.error(error)}));promises.push(promise)})),await Promise.all(promises),page.scrollTo({top:0,behavior:"instant"}),_notification.default.addNotification({message:count+" of "+total+" push tasks have been scheduled.",type:count===total?"success":"warning"})}))}})); //# sourceMappingURL=dashboard.min.js.map \ No newline at end of file diff --git a/amd/build/dashboard.min.js.map b/amd/build/dashboard.min.js.map index 45dae80..0396921 100644 --- a/amd/build/dashboard.min.js.map +++ b/amd/build/dashboard.min.js.map @@ -1 +1 @@ -{"version":3,"file":"dashboard.min.js","sources":["../src/dashboard.js"],"sourcesContent":["import {schedulePushTask} from \"./push_tasks\";\nimport notification from \"core/notification\";\n\nexport const init = () => {\n // Find the page element.\n let page = document.getElementById(\"page\");\n\n // Find the module delivery table selector.\n let tableSelector = document.getElementById(\"module-delivery-selector\");\n\n // Jump to the selected module delivery table when the user selects a module delivery.\n tableSelector.addEventListener(\"change\", function() {\n // Find the selected table by ID.\n let selectedTable = document.getElementById(tableSelector.value);\n\n // Calculate the scroll position to be 100 pixels above the table.\n if (selectedTable) {\n let offset = -100;\n let tablePosition = selectedTable.getBoundingClientRect().top;\n let scrollPosition = page.scrollTop + tablePosition + offset;\n\n // Scroll to the calculated position.\n page.scrollTo({\n top: scrollPosition,\n behavior: \"smooth\"\n });\n }\n });\n\n // Find the back to top button.\n let backToTopButton = document.getElementById(\"backToTopButton\");\n\n // Show the button when the user scrolls down 100 pixels from the top of the page.\n page.addEventListener(\"scroll\", function() {\n if (page.scrollTop >= 100) {\n backToTopButton.style.display = \"block\";\n } else {\n backToTopButton.style.display = \"none\";\n }\n });\n\n // Scroll to the top of the page when the button is clicked.\n backToTopButton.addEventListener(\"click\", function() {\n page.scrollTo({top: 0, behavior: \"smooth\"});\n tableSelector.selectedIndex = 0;\n });\n\n // Get all the push buttons that are not disabled.\n let mabpushbuttons = document.querySelectorAll(\".push-mark-button:not([disabled])\");\n\n // Push grades when the user clicks on each enabled push button.\n mabpushbuttons.forEach(function(button) {\n button.addEventListener(\"click\", function() {\n pushgrade(this);\n });\n });\n\n // Get the push all button.\n let pushallbutton = document.getElementById(\"push-all-button\");\n\n // Push grades for all the not disabled push buttons when the user clicks on the push all button.\n pushallbutton.addEventListener(\"click\", async function() {\n // Get the updated not disabled push buttons.\n let mabpushbuttons = document.querySelectorAll(\".push-mark-button:not([disabled])\");\n\n // Number of not disabled push buttons.\n let total = mabpushbuttons.length;\n let count = 0;\n\n // Create an array to hold all the Promises.\n let promises = [];\n\n // Push grades to SITS for each MAB.\n mabpushbuttons.forEach(function(button) {\n // Create a Promise for each button and push it into the array.\n let promise = pushgrade(button)\n .then(function(result) {\n if (result) {\n count = count + 1;\n }\n return result;\n }).catch(function(error) {\n window.console.error(error);\n });\n\n promises.push(promise);\n });\n\n // Wait for all Promises to resolve.\n await Promise.all(promises);\n\n // Scroll to the top of the page so that the user can see the notification.\n page.scrollTo({top: 0, behavior: \"instant\"});\n\n // Show the notification.\n notification.addNotification({\n message: count + ' of ' + total + ' push tasks have been scheduled.',\n type: (count === total) ? 'success' : 'warning'\n });\n });\n};\n\n/**\n * Schedule a push task when the user clicks on a push button.\n *\n * @param {HTMLElement} button The button element.\n * @return {Promise} Promise.\n */\nasync function pushgrade(button) {\n try {\n // Get the assessment mapping ID from the button.\n let assessmentmappingid = button.getAttribute(\"data-assessmentmappingid\");\n\n // Schedule a push task.\n let result = await schedulePushTask(assessmentmappingid);\n\n // Check if the push task is successfully scheduled.\n if (result.success) {\n // Update the icon.\n let icon = button.parentNode.parentNode.querySelector(\"span:last-child\");\n icon.innerHTML = '';\n\n // Disable the push button after scheduled push task successfully.\n button.disabled = true;\n\n // Remove the tooltip (for Firefox and Safari).\n let tooltipid = button.getAttribute(\"aria-describedby\");\n if (tooltipid !== null && document.getElementById(tooltipid) !== null) {\n document.getElementById(tooltipid).remove();\n }\n } else {\n // Create an error message row.\n let errormessagerow = document.createElement(\"tr\");\n\n // Set the class and content of the error message row.\n errormessagerow.setAttribute(\"class\", \"error-message-row\");\n errormessagerow.innerHTML =\n '\">' +\n '
' + result.message + '
' +\n '';\n\n // Find the closest row to the button.\n let currentrow = button.closest(\"tr\");\n\n // Remove the existing error message row if it exists.\n if (currentrow.nextElementSibling !== null &&\n currentrow.nextElementSibling.classList.contains(\"error-message-row\")) {\n currentrow.nextElementSibling.remove();\n }\n\n // Insert the error message row after the current row.\n currentrow.insertAdjacentElement(\"afterend\", errormessagerow);\n }\n\n return result.success;\n } catch (error) {\n window.console.error(error);\n return false;\n }\n}\n"],"names":["pushgrade","button","assessmentmappingid","getAttribute","result","success","parentNode","querySelector","innerHTML","status","disabled","tooltipid","document","getElementById","remove","errormessagerow","createElement","setAttribute","message","currentrow","closest","nextElementSibling","classList","contains","insertAdjacentElement","error","window","console","page","tableSelector","addEventListener","selectedTable","value","offset","tablePosition","getBoundingClientRect","top","scrollPosition","scrollTop","scrollTo","behavior","backToTopButton","style","display","selectedIndex","querySelectorAll","forEach","this","async","mabpushbuttons","total","length","count","promises","promise","then","catch","push","Promise","all","addNotification","type"],"mappings":"4SA4GeA,UAAUC,gBAGbC,oBAAsBD,OAAOE,aAAa,4BAG1CC,aAAe,gCAAiBF,wBAGhCE,OAAOC,QAAS,CAELJ,OAAOK,WAAWA,WAAWC,cAAc,mBACjDC,UAAY,4FACDJ,OAAOK,OAAS,SAGhCR,OAAOS,UAAW,MAGdC,UAAYV,OAAOE,aAAa,oBAClB,OAAdQ,WAA6D,OAAvCC,SAASC,eAAeF,YAC9CC,SAASC,eAAeF,WAAWG,aAEpC,KAECC,gBAAkBH,SAASI,cAAc,MAG7CD,gBAAgBE,aAAa,QAAS,qBACtCF,gBAAgBP,UACZ,iEACkDJ,OAAOc,QADzD,kBAKAC,WAAalB,OAAOmB,QAAQ,MAGM,OAAlCD,WAAWE,oBACXF,WAAWE,mBAAmBC,UAAUC,SAAS,sBACjDJ,WAAWE,mBAAmBP,SAIlCK,WAAWK,sBAAsB,WAAYT,wBAG1CX,OAAOC,QAChB,MAAOoB,cACLC,OAAOC,QAAQF,MAAMA,QACd,iBA3JK,SAEZG,KAAOhB,SAASC,eAAe,QAG/BgB,cAAgBjB,SAASC,eAAe,4BAG5CgB,cAAcC,iBAAiB,UAAU,eAEjCC,cAAgBnB,SAASC,eAAegB,cAAcG,UAGtDD,cAAe,KACXE,QAAU,IACVC,cAAgBH,cAAcI,wBAAwBC,IACtDC,eAAiBT,KAAKU,UAAYJ,cAAgBD,OAGtDL,KAAKW,SAAS,CACVH,IAAKC,eACLG,SAAU,mBAMlBC,gBAAkB7B,SAASC,eAAe,mBAG9Ce,KAAKE,iBAAiB,UAAU,WACxBF,KAAKU,WAAa,IAClBG,gBAAgBC,MAAMC,QAAU,QAEhCF,gBAAgBC,MAAMC,QAAU,UAKxCF,gBAAgBX,iBAAiB,SAAS,WACtCF,KAAKW,SAAS,CAACH,IAAK,EAAGI,SAAU,WACjCX,cAAce,cAAgB,KAIbhC,SAASiC,iBAAiB,qCAGhCC,SAAQ,SAAS7C,QAC5BA,OAAO6B,iBAAiB,SAAS,WAC7B9B,UAAU+C,YAKEnC,SAASC,eAAe,mBAG9BiB,iBAAiB,SAASkB,qBAEhCC,eAAiBrC,SAASiC,iBAAiB,qCAG3CK,MAAQD,eAAeE,OACvBC,MAAQ,EAGRC,SAAW,GAGfJ,eAAeH,SAAQ,SAAS7C,YAExBqD,QAAUtD,UAAUC,QACnBsD,MAAK,SAASnD,eACPA,SACAgD,OAAgB,GAEbhD,UACRoD,OAAM,SAAS/B,OACdC,OAAOC,QAAQF,MAAMA,UAG7B4B,SAASI,KAAKH,kBAIZI,QAAQC,IAAIN,UAGlBzB,KAAKW,SAAS,CAACH,IAAK,EAAGI,SAAU,kCAGpBoB,gBAAgB,CACzB1C,QAASkC,MAAQ,OAASF,MAAQ,mCAClCW,KAAOT,QAAUF,MAAS,UAAY"} \ No newline at end of file +{"version":3,"file":"dashboard.min.js","sources":["../src/dashboard.js"],"sourcesContent":["import {schedulePushTask} from \"./sitsgradepush_helper\";\nimport notification from \"core/notification\";\n\nexport const init = () => {\n // If there is a saved message by successfully mapped an assessment in localStorage, display it.\n displayNotification();\n\n // Find the page element.\n let page = document.getElementById(\"page\");\n\n // Find the module delivery table selector.\n let tableSelector = document.getElementById(\"module-delivery-selector\");\n\n // Jump to the selected module delivery table when the user selects a module delivery.\n tableSelector.addEventListener(\"change\", function() {\n // Find the selected table by ID.\n let selectedTable = document.getElementById(tableSelector.value);\n\n // Calculate the scroll position to be 100 pixels above the table.\n if (selectedTable) {\n let offset = -100;\n let tablePosition = selectedTable.getBoundingClientRect().top;\n let scrollPosition = page.scrollTop + tablePosition + offset;\n\n // Scroll to the calculated position.\n page.scrollTo({\n top: scrollPosition,\n behavior: \"smooth\"\n });\n }\n });\n\n // Find the back to top button.\n let backToTopButton = document.getElementById(\"backToTopButton\");\n\n // Show the button when the user scrolls down 100 pixels from the top of the page.\n page.addEventListener(\"scroll\", function() {\n if (page.scrollTop >= 100) {\n backToTopButton.style.display = \"block\";\n } else {\n backToTopButton.style.display = \"none\";\n }\n });\n\n // Scroll to the top of the page when the button is clicked.\n backToTopButton.addEventListener(\"click\", function() {\n page.scrollTo({top: 0, behavior: \"smooth\"});\n tableSelector.selectedIndex = 0;\n });\n\n // Get all change source buttons.\n let changesourcebuttons = document.querySelectorAll(\".change-source-button:not([disabled])\");\n\n // Add event listener to each change source button.\n // When the user clicks on each change source button, redirect to the select source page.\n if (changesourcebuttons.length > 0) {\n changesourcebuttons.forEach(function(button) {\n button.addEventListener(\"click\", function() {\n // Redirect to the change source page.\n window.location.href = button.getAttribute(\"data-url\");\n });\n });\n }\n\n // Get all the push buttons that are not disabled.\n let mabpushbuttons = document.querySelectorAll(\".push-mark-button:not([disabled])\");\n\n if (mabpushbuttons.length > 0) {\n // Push grades when the user clicks on each enabled push button.\n mabpushbuttons.forEach(function(button) {\n button.addEventListener(\"click\", function() {\n pushMarks(this);\n });\n });\n }\n\n // Get the push all button.\n let pushallbutton = document.getElementById(\"push-all-button\");\n\n // Push grades for all the not disabled push buttons when the user clicks on the push all button.\n pushallbutton.addEventListener(\"click\", async function() {\n // Get the updated not disabled push buttons.\n let mabpushbuttons = document.querySelectorAll(\".push-mark-button:not([disabled])\");\n\n // Number of not disabled push buttons.\n let total = mabpushbuttons.length;\n let count = 0;\n\n // Create an array to hold all the Promises.\n let promises = [];\n\n // Push grades to SITS for each component grade.\n mabpushbuttons.forEach(function(button) {\n // Create a Promise for each button and push it into the array.\n let promise = pushMarks(button)\n .then(function(result) {\n if (result) {\n count = count + 1;\n }\n return result;\n }).catch(function(error) {\n window.console.error(error);\n });\n\n promises.push(promise);\n });\n\n // Wait for all Promises to resolve.\n await Promise.all(promises);\n\n // Scroll to the top of the page so that the user can see the notification.\n page.scrollTo({top: 0, behavior: \"instant\"});\n\n // Show the notification.\n notification.addNotification({\n message: count + ' of ' + total + ' push tasks have been scheduled.',\n type: (count === total) ? 'success' : 'warning'\n });\n });\n};\n\n/**\n * Schedule a push task when the user clicks on a push button.\n *\n * @param {HTMLElement} button The button element.\n * @return {Promise} Promise.\n */\nasync function pushMarks(button) {\n try {\n // Get the assessment mapping ID from the button.\n let assessmentmappingid = button.getAttribute(\"data-assessmentmappingid\");\n\n // Schedule a push task.\n let result = await schedulePushTask(assessmentmappingid);\n\n // Check if the push task is successfully scheduled.\n if (result.success) {\n // Update the icon.\n let icon = button.parentNode.parentNode.querySelector(\"span:last-child\");\n icon.innerHTML = '';\n\n // Disable the push button after scheduled push task successfully.\n button.disabled = true;\n\n // Remove the tooltip (for Firefox and Safari).\n let tooltipid = button.getAttribute(\"aria-describedby\");\n if (tooltipid !== null && document.getElementById(tooltipid) !== null) {\n document.getElementById(tooltipid).remove();\n }\n } else {\n // Create an error message row.\n let errormessagerow = document.createElement(\"tr\");\n\n // Set the class and content of the error message row.\n errormessagerow.setAttribute(\"class\", \"error-message-row\");\n errormessagerow.innerHTML =\n '\">' +\n '
' + result.message + '
' +\n '';\n\n // Find the closest row to the button.\n let currentrow = button.closest(\"tr\");\n\n // Remove the existing error message row if it exists.\n if (currentrow.nextElementSibling !== null &&\n currentrow.nextElementSibling.classList.contains(\"error-message-row\")) {\n currentrow.nextElementSibling.remove();\n }\n\n // Insert the error message row after the current row.\n currentrow.insertAdjacentElement(\"afterend\", errormessagerow);\n }\n\n return result.success;\n } catch (error) {\n window.console.error(error);\n return false;\n }\n}\n\n/**\n * Display a notification if a success message is available in localStorage.\n */\nfunction displayNotification() {\n // Retrieve the success message from localStorage.\n let successMessage = localStorage.getItem('successMessage');\n\n // Check if a success message is available.\n if (successMessage) {\n // Display the success message using a notification library or other means.\n notification.addNotification({\n message: successMessage,\n type: 'success'\n });\n\n // Remove the success message from localStorage to avoid showing it again.\n localStorage.removeItem('successMessage');\n }\n}\n"],"names":["pushMarks","button","assessmentmappingid","getAttribute","result","success","parentNode","querySelector","innerHTML","status","disabled","tooltipid","document","getElementById","remove","errormessagerow","createElement","setAttribute","message","currentrow","closest","nextElementSibling","classList","contains","insertAdjacentElement","error","window","console","successMessage","localStorage","getItem","addNotification","type","removeItem","displayNotification","page","tableSelector","addEventListener","selectedTable","value","offset","tablePosition","getBoundingClientRect","top","scrollPosition","scrollTop","scrollTo","behavior","backToTopButton","style","display","selectedIndex","changesourcebuttons","querySelectorAll","length","forEach","location","href","mabpushbuttons","this","async","total","count","promises","promise","then","catch","push","Promise","all"],"mappings":"gUA+HeA,UAAUC,gBAGbC,oBAAsBD,OAAOE,aAAa,4BAG1CC,aAAe,0CAAiBF,wBAGhCE,OAAOC,QAAS,CAELJ,OAAOK,WAAWA,WAAWC,cAAc,mBACjDC,UAAY,4FACDJ,OAAOK,OAAS,SAGhCR,OAAOS,UAAW,MAGdC,UAAYV,OAAOE,aAAa,oBAClB,OAAdQ,WAA6D,OAAvCC,SAASC,eAAeF,YAC9CC,SAASC,eAAeF,WAAWG,aAEpC,KAECC,gBAAkBH,SAASI,cAAc,MAG7CD,gBAAgBE,aAAa,QAAS,qBACtCF,gBAAgBP,UACZ,iEACkDJ,OAAOc,QADzD,kBAKAC,WAAalB,OAAOmB,QAAQ,MAGM,OAAlCD,WAAWE,oBACXF,WAAWE,mBAAmBC,UAAUC,SAAS,sBACjDJ,WAAWE,mBAAmBP,SAIlCK,WAAWK,sBAAsB,WAAYT,wBAG1CX,OAAOC,QAChB,MAAOoB,cACLC,OAAOC,QAAQF,MAAMA,QACd,iBA9KK,qBAuLZG,eAAiBC,aAAaC,QAAQ,kBAGtCF,uCAEaG,gBAAgB,CACzBb,QAASU,eACTI,KAAM,YAIVH,aAAaI,WAAW,mBAhM5BC,OAGIC,KAAOvB,SAASC,eAAe,QAG/BuB,cAAgBxB,SAASC,eAAe,4BAG5CuB,cAAcC,iBAAiB,UAAU,eAEjCC,cAAgB1B,SAASC,eAAeuB,cAAcG,UAGtDD,cAAe,KACXE,QAAU,IACVC,cAAgBH,cAAcI,wBAAwBC,IACtDC,eAAiBT,KAAKU,UAAYJ,cAAgBD,OAGtDL,KAAKW,SAAS,CACVH,IAAKC,eACLG,SAAU,mBAMlBC,gBAAkBpC,SAASC,eAAe,mBAG9CsB,KAAKE,iBAAiB,UAAU,WACxBF,KAAKU,WAAa,IAClBG,gBAAgBC,MAAMC,QAAU,QAEhCF,gBAAgBC,MAAMC,QAAU,UAKxCF,gBAAgBX,iBAAiB,SAAS,WACtCF,KAAKW,SAAS,CAACH,IAAK,EAAGI,SAAU,WACjCX,cAAce,cAAgB,SAI9BC,oBAAsBxC,SAASyC,iBAAiB,yCAIhDD,oBAAoBE,OAAS,GAC7BF,oBAAoBG,SAAQ,SAAStD,QACjCA,OAAOoC,iBAAiB,SAAS,WAE7BX,OAAO8B,SAASC,KAAOxD,OAAOE,aAAa,sBAMnDuD,eAAiB9C,SAASyC,iBAAiB,qCAE3CK,eAAeJ,OAAS,GAExBI,eAAeH,SAAQ,SAAStD,QAC5BA,OAAOoC,iBAAiB,SAAS,WAC7BrC,UAAU2D,YAMF/C,SAASC,eAAe,mBAG9BwB,iBAAiB,SAASuB,qBAEhCF,eAAiB9C,SAASyC,iBAAiB,qCAG3CQ,MAAQH,eAAeJ,OACvBQ,MAAQ,EAGRC,SAAW,GAGfL,eAAeH,SAAQ,SAAStD,YAExB+D,QAAUhE,UAAUC,QACnBgE,MAAK,SAAS7D,eACPA,SACA0D,OAAgB,GAEb1D,UACR8D,OAAM,SAASzC,OACdC,OAAOC,QAAQF,MAAMA,UAG7BsC,SAASI,KAAKH,kBAIZI,QAAQC,IAAIN,UAGlB5B,KAAKW,SAAS,CAACH,IAAK,EAAGI,SAAU,kCAGpBhB,gBAAgB,CACzBb,QAAS4C,MAAQ,OAASD,MAAQ,mCAClC7B,KAAO8B,QAAUD,MAAS,UAAY"} \ No newline at end of file diff --git a/amd/build/existing_activity.min.js b/amd/build/existing_activity.min.js new file mode 100644 index 0000000..67a584a --- /dev/null +++ b/amd/build/existing_activity.min.js @@ -0,0 +1,3 @@ +define("local_sitsgradepush/existing_activity",["exports","./table_helper","core/notification","core/modal_factory","core/modal_events","./sitsgradepush_helper"],(function(_exports,_table_helper,_notification,_modal_factory,_modal_events,_sitsgradepush_helper){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_modal_factory=_interopRequireDefault(_modal_factory),_modal_events=_interopRequireDefault(_modal_events);function getModalBody(type,name,endDate,mapcode,mabseq){return'\n ")}_exports.init=()=>{(0,_table_helper.tableHelperInit)("existing-activity-table",2,"filterInput");let selectAssessmentButtons=document.querySelectorAll(".select-assessment-button");selectAssessmentButtons&&selectAssessmentButtons.forEach((function(button){button.addEventListener("click",(function(){(async function(button){let mapcode=button.getAttribute("data-mapcode"),mabseq=button.getAttribute("data-mabseq"),mabid=button.getAttribute("data-mabid"),courseid=button.getAttribute("data-courseid"),coursemoduleid=button.getAttribute("data-coursemoduleid"),partid=button.getAttribute("data-partid"),currentrow=button.closest("tr"),type=currentrow.getElementsByTagName("td")[0].innerHTML,name=currentrow.getElementsByTagName("td")[1].innerHTML,endDate=currentrow.getElementsByTagName("td")[3].innerHTML,modal=await _modal_factory.default.create({type:_modal_factory.default.types.SAVE_CANCEL,title:"Map Assessment",body:getModalBody(type,name,endDate,mapcode,mabseq),large:!0,buttons:{save:"Confirm",cancel:"Cancel"}});await modal.show(),modal.getRoot().on(_modal_events.default.save,(()=>{(0,_sitsgradepush_helper.mapAssessment)(courseid,coursemoduleid,mabid,partid).then((result=>(result.success?(localStorage.setItem("successMessage",result.message),window.location.href="/local/sitsgradepush/dashboard.php?id="+courseid):_notification.default.addNotification({message:result.message,type:"error"}),result))).catch((error=>{window.console.error(error)}))}))})(button).then((result=>result)).catch((error=>{_notification.default.addNotification({message:error.message,type:"error"}),window.console.error(error)}))}))}))}})); + +//# sourceMappingURL=existing_activity.min.js.map \ No newline at end of file diff --git a/amd/build/existing_activity.min.js.map b/amd/build/existing_activity.min.js.map new file mode 100644 index 0000000..3bb1851 --- /dev/null +++ b/amd/build/existing_activity.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"existing_activity.min.js","sources":["../src/existing_activity.js"],"sourcesContent":["import {tableHelperInit} from './table_helper';\nimport notification from \"core/notification\";\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport {mapAssessment} from './sitsgradepush_helper';\n\nexport const init = () => {\n // Initialise the table helper.\n tableHelperInit('existing-activity-table', 2, 'filterInput');\n\n // Get all the select assessment buttons.\n let selectAssessmentButtons = document.querySelectorAll('.select-assessment-button');\n\n if (selectAssessmentButtons) {\n // Add an event listener to each select assessment button.\n // When the user clicks on each button, map the assessment to the selected component grade.\n selectAssessmentButtons.forEach(function(button) {\n button.addEventListener('click', function() {\n selectAssessment(button).then(\n (result) => {\n return result;\n }\n ).catch((error) => {\n notification.addNotification({\n message: error.message,\n type: 'error'\n });\n window.console.error(error);\n });\n });\n });\n }\n};\n\n/**\n * Show a modal to confirm the user wants to map the assessment to the selected component grade.\n *\n * @param {HTMLElement} button The select assessment button element.\n * @return {Promise}\n */\nasync function selectAssessment(button) {\n // Get assessment component data from the button.\n let mapcode = button.getAttribute('data-mapcode');\n let mabseq = button.getAttribute('data-mabseq');\n let mabid = button.getAttribute('data-mabid');\n let courseid = button.getAttribute('data-courseid');\n let coursemoduleid = button.getAttribute('data-coursemoduleid');\n let partid = button.getAttribute('data-partid');\n\n // Get assessment data from the button.\n // Find the closest row to the button.\n let currentrow = button.closest('tr');\n let type = currentrow.getElementsByTagName('td')[0].innerHTML;\n let name = currentrow.getElementsByTagName('td')[1].innerHTML;\n let endDate = currentrow.getElementsByTagName('td')[3].innerHTML;\n\n let modal = await ModalFactory.create({\n type: ModalFactory.types.SAVE_CANCEL,\n title: 'Map Assessment',\n body: getModalBody(type, name, endDate, mapcode, mabseq),\n large: true,\n buttons: {'save': 'Confirm', 'cancel': 'Cancel'}\n });\n\n await modal.show();\n modal.getRoot().on(ModalEvents.save, () => {\n mapAssessment(courseid, coursemoduleid, mabid, partid).then(\n (result) => {\n if (result.success) {\n // Store the success message in localStorage for display on the dashboard page.\n localStorage.setItem('successMessage', result.message);\n\n // Redirect to the dashboard page.\n window.location.href = '/local/sitsgradepush/dashboard.php?id=' + courseid;\n } else {\n notification.addNotification({\n message: result.message,\n type: 'error'\n });\n }\n return result;\n }\n ).catch((error) => {\n window.console.error(error);\n });\n });\n}\n\n/**\n * Get the modal body.\n *\n * @param {string} type\n * @param {string} name\n * @param {string} endDate\n * @param {string} mapcode\n * @param {string} mabseq\n * @return {string}\n */\nfunction getModalBody(type, name, endDate, mapcode, mabseq) {\n return `\n
\n

Confirm you want to return marks from:

\n \n \n \n \n \n \n \n \n \n \n \n
TypeNameEnd Date
${type}${name}${endDate}
\n

to

\n \n \n \n \n \n \n \n \n \n
MABSEQ
${mapcode}${mabseq}
\n
`;\n}\n"],"names":["getModalBody","type","name","endDate","mapcode","mabseq","selectAssessmentButtons","document","querySelectorAll","forEach","button","addEventListener","getAttribute","mabid","courseid","coursemoduleid","partid","currentrow","closest","getElementsByTagName","innerHTML","modal","ModalFactory","create","types","SAVE_CANCEL","title","body","large","buttons","show","getRoot","on","ModalEvents","save","then","result","success","localStorage","setItem","message","window","location","href","addNotification","catch","error","console","selectAssessment"],"mappings":"2kBAkGSA,aAAaC,KAAMC,KAAMC,QAASC,QAASC,uVAWpCJ,qCACAC,qCACAC,qRAUAC,wCACAC,yEApHI,uCAEA,0BAA2B,EAAG,mBAG1CC,wBAA0BC,SAASC,iBAAiB,6BAEpDF,yBAGAA,wBAAwBG,SAAQ,SAASC,QACrCA,OAAOC,iBAAiB,SAAS,2BAuBbD,YAExBN,QAAUM,OAAOE,aAAa,gBAC9BP,OAASK,OAAOE,aAAa,eAC7BC,MAAQH,OAAOE,aAAa,cAC5BE,SAAWJ,OAAOE,aAAa,iBAC/BG,eAAiBL,OAAOE,aAAa,uBACrCI,OAASN,OAAOE,aAAa,eAI7BK,WAAaP,OAAOQ,QAAQ,MAC5BjB,KAAOgB,WAAWE,qBAAqB,MAAM,GAAGC,UAChDlB,KAAOe,WAAWE,qBAAqB,MAAM,GAAGC,UAChDjB,QAAUc,WAAWE,qBAAqB,MAAM,GAAGC,UAEnDC,YAAcC,uBAAaC,OAAO,CAClCtB,KAAMqB,uBAAaE,MAAMC,YACzBC,MAAO,iBACPC,KAAM3B,aAAaC,KAAMC,KAAMC,QAASC,QAASC,QACjDuB,OAAO,EACPC,QAAS,MAAS,iBAAqB,kBAGrCR,MAAMS,OACZT,MAAMU,UAAUC,GAAGC,sBAAYC,MAAM,6CACnBpB,SAAUC,eAAgBF,MAAOG,QAAQmB,MAClDC,SACOA,OAAOC,SAEPC,aAAaC,QAAQ,iBAAkBH,OAAOI,SAG9CC,OAAOC,SAASC,KAAO,yCAA2C7B,gCAErD8B,gBAAgB,CACzBJ,QAASJ,OAAOI,QAChBvC,KAAM,UAGPmC,UAEbS,OAAOC,QACLL,OAAOM,QAAQD,MAAMA,cAjEjBE,CAAiBtC,QAAQyB,MACpBC,QACUA,SAEbS,OAAOC,8BACQF,gBAAgB,CACzBJ,QAASM,MAAMN,QACfvC,KAAM,UAEVwC,OAAOM,QAAQD,MAAMA"} \ No newline at end of file diff --git a/amd/build/push_tasks.min.js b/amd/build/push_tasks.min.js deleted file mode 100644 index db83ca9..0000000 --- a/amd/build/push_tasks.min.js +++ /dev/null @@ -1,3 +0,0 @@ -define("local_sitsgradepush/push_tasks",["exports","core/ajax"],(function(_exports,_ajax){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.schedulePushTask=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.schedulePushTask=assessmentmappingid=>new Promise(((resolve,reject)=>{_ajax.default.call([{methodname:"local_sitsgradepush_schedule_push_task",args:{assessmentmappingid:assessmentmappingid}}])[0].done((function(response){resolve(response)})).fail((function(err){window.console.log(err),reject(err)}))}))})); - -//# sourceMappingURL=push_tasks.min.js.map \ No newline at end of file diff --git a/amd/build/push_tasks.min.js.map b/amd/build/push_tasks.min.js.map deleted file mode 100644 index 3a920c9..0000000 --- a/amd/build/push_tasks.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"push_tasks.min.js","sources":["../src/push_tasks.js"],"sourcesContent":["import Ajax from 'core/ajax';\n\n/**\n * Schedule a task to push grades to SITS.\n *\n * @param {string} assessmentmappingid The assessment mapping ID.\n * @return {Promise} Promise.\n */\nexport const schedulePushTask = (assessmentmappingid) => {\n return new Promise((resolve, reject) => {\n Ajax.call([{\n methodname: 'local_sitsgradepush_schedule_push_task',\n args: {\n 'assessmentmappingid': assessmentmappingid,\n },\n }])[0].done(function(response) {\n resolve(response);\n }).fail(function(err) {\n window.console.log(err);\n reject(err);\n });\n });\n};\n"],"names":["assessmentmappingid","Promise","resolve","reject","call","methodname","args","done","response","fail","err","window","console","log"],"mappings":"yQAQiCA,qBACtB,IAAIC,SAAQ,CAACC,QAASC,wBACpBC,KAAK,CAAC,CACPC,WAAY,yCACZC,KAAM,qBACqBN,wBAE3B,GAAGO,MAAK,SAASC,UACjBN,QAAQM,aACTC,MAAK,SAASC,KACbC,OAAOC,QAAQC,IAAIH,KACnBP,OAAOO"} \ No newline at end of file diff --git a/amd/build/select_source.min.js b/amd/build/select_source.min.js new file mode 100644 index 0000000..0e9fb47 --- /dev/null +++ b/amd/build/select_source.min.js @@ -0,0 +1,3 @@ +define("local_sitsgradepush/select_source",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;_exports.init=(courseid,mabid)=>{document.getElementById("existing-activity").addEventListener("click",(event=>{event.preventDefault(),window.location.href="/local/sitsgradepush/select_source.php?courseid=".concat(courseid,"&mabid=").concat(mabid,"&source=existing")}))}})); + +//# sourceMappingURL=select_source.min.js.map \ No newline at end of file diff --git a/amd/build/select_source.min.js.map b/amd/build/select_source.min.js.map new file mode 100644 index 0000000..ae21252 --- /dev/null +++ b/amd/build/select_source.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"select_source.min.js","sources":["../src/select_source.js"],"sourcesContent":["export const init = (courseid, mabid) => {\n // Add an event listener to the select existing activity card.\n // When the user clicks on the card, redirect to the existing activity page.\n document.getElementById('existing-activity').addEventListener('click', (event) => {\n event.preventDefault();\n window.location.href =\n `/local/sitsgradepush/select_source.php?courseid=${courseid}&mabid=${mabid}&source=existing`;\n });\n};\n"],"names":["courseid","mabid","document","getElementById","addEventListener","event","preventDefault","window","location","href"],"mappings":"sKAAoB,CAACA,SAAUC,SAG3BC,SAASC,eAAe,qBAAqBC,iBAAiB,SAAUC,QACpEA,MAAMC,iBACNC,OAAOC,SAASC,+DACuCT,2BAAkBC"} \ No newline at end of file diff --git a/amd/build/sitsgradepush.min.js b/amd/build/sitsgradepush.min.js index d55a125..8d3163a 100644 --- a/amd/build/sitsgradepush.min.js +++ b/amd/build/sitsgradepush.min.js @@ -1,3 +1,3 @@ -define("local_sitsgradepush/sitsgradepush",["exports","./push_tasks","core/notification"],(function(_exports,_push_tasks,_notification){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};_exports.init=(coursemoduleid,mappingids)=>{let pushbuton=document.getElementById("local_sitsgradepush_pushbutton_async");if(null===pushbuton)return;let promises=[];pushbuton.addEventListener("click",(async e=>{e.preventDefault();let total=mappingids.length,count=0;mappingids.forEach((function(mappingid){let promise=(0,_push_tasks.schedulePushTask)(mappingid).then((function(result){let taskstatus=document.getElementById("taskstatus-"+mappingid);if(result.success)count+=1,taskstatus.innerHTML=' '+result.status;else{let errormessagerow=document.createElement("tr");errormessagerow.setAttribute("class","error-message-row"),errormessagerow.innerHTML='";let currentrow=taskstatus.closest("tr");null!==currentrow.nextElementSibling&¤trow.nextElementSibling.classList.contains("error-message-row")&¤trow.nextElementSibling.remove(),currentrow.insertAdjacentElement("afterend",errormessagerow)}return result.success})).catch((function(error){window.console.error(error)}));promises.push(promise)})),await Promise.all(promises),_notification.default.addNotification({message:count+" of "+total+" push tasks have been scheduled.",type:count===total?"success":"warning"})}))}})); +define("local_sitsgradepush/sitsgradepush",["exports","./sitsgradepush_helper","core/notification"],(function(_exports,_sitsgradepush_helper,_notification){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};_exports.init=(coursemoduleid,mappingids)=>{let pushbuton=document.getElementById("local_sitsgradepush_pushbutton_async");if(null===pushbuton)return;let promises=[];pushbuton.addEventListener("click",(async e=>{e.preventDefault();let total=mappingids.length,count=0;mappingids.forEach((function(mappingid){let promise=(0,_sitsgradepush_helper.schedulePushTask)(mappingid).then((function(result){let taskstatus=document.getElementById("taskstatus-"+mappingid);if(result.success)count+=1,taskstatus.innerHTML=' '+result.status;else{let errormessagerow=document.createElement("tr");errormessagerow.setAttribute("class","error-message-row"),errormessagerow.innerHTML='";let currentrow=taskstatus.closest("tr");null!==currentrow.nextElementSibling&¤trow.nextElementSibling.classList.contains("error-message-row")&¤trow.nextElementSibling.remove(),currentrow.insertAdjacentElement("afterend",errormessagerow)}return result.success})).catch((function(error){window.console.error(error)}));promises.push(promise)})),await Promise.all(promises),_notification.default.addNotification({message:count+" of "+total+" push tasks have been scheduled.",type:count===total?"success":"warning"})}))}})); //# sourceMappingURL=sitsgradepush.min.js.map \ No newline at end of file diff --git a/amd/build/sitsgradepush.min.js.map b/amd/build/sitsgradepush.min.js.map index bc89336..cb1f2b9 100644 --- a/amd/build/sitsgradepush.min.js.map +++ b/amd/build/sitsgradepush.min.js.map @@ -1 +1 @@ -{"version":3,"file":"sitsgradepush.min.js","sources":["../src/sitsgradepush.js"],"sourcesContent":["import {schedulePushTask} from \"./push_tasks\";\nimport notification from 'core/notification';\n\nexport const init = (coursemoduleid, mappingids) => {\n // Get the push button.\n let pushbuton = document.getElementById('local_sitsgradepush_pushbutton_async');\n\n // Exit if the push button is not found.\n if (pushbuton === null) {\n return;\n }\n\n let promises = [];\n\n // Schedule a push task for each assessment mapping.\n pushbuton.addEventListener('click', async(e) => {\n e.preventDefault();\n\n // Number of assessment mappings.\n let total = mappingids.length;\n let count = 0;\n\n // Schedule a task to push grades to SITS for each assessment mapping.\n mappingids.forEach(function(mappingid) {\n let promise = schedulePushTask(mappingid)\n .then(function(result) {\n let taskstatus = document.getElementById('taskstatus-' + mappingid);\n if (result.success) {\n count = count + 1;\n taskstatus.innerHTML = ' ' + result.status;\n } else {\n // Create an error message row.\n let errormessagerow = document.createElement(\"tr\");\n errormessagerow.setAttribute(\"class\", \"error-message-row\");\n errormessagerow.innerHTML =\n '\">' +\n '
' + result.message + '
' +\n '';\n\n // Find the closest row to the assessment mapping.\n let currentrow = taskstatus.closest(\"tr\");\n\n // Remove the existing error message row if it exists.\n if (currentrow.nextElementSibling !== null &&\n currentrow.nextElementSibling.classList.contains(\"error-message-row\")) {\n currentrow.nextElementSibling.remove();\n }\n\n // Insert the error message row for the assessment mapping.\n currentrow.insertAdjacentElement(\"afterend\", errormessagerow);\n }\n return result.success;\n })\n .catch(function(error) {\n window.console.error(error);\n });\n\n promises.push(promise);\n });\n\n // Wait for all the push tasks to be scheduled.\n await Promise.all(promises);\n\n // Display a notification.\n notification.addNotification({\n message: count + ' of ' + total + ' push tasks have been scheduled.',\n type: (count === total) ? 'success' : 'warning'\n });\n });\n};\n"],"names":["coursemoduleid","mappingids","pushbuton","document","getElementById","promises","addEventListener","async","e","preventDefault","total","length","count","forEach","mappingid","promise","then","result","taskstatus","success","innerHTML","status","errormessagerow","createElement","setAttribute","message","currentrow","closest","nextElementSibling","classList","contains","remove","insertAdjacentElement","catch","error","window","console","push","Promise","all","addNotification","type"],"mappings":"+SAGoB,CAACA,eAAgBC,kBAE7BC,UAAYC,SAASC,eAAe,2CAGtB,OAAdF,qBAIAG,SAAW,GAGfH,UAAUI,iBAAiB,SAASC,MAAAA,IAChCC,EAAEC,qBAGEC,MAAQT,WAAWU,OACnBC,MAAQ,EAGZX,WAAWY,SAAQ,SAASC,eACpBC,SAAU,gCAAiBD,WAC1BE,MAAK,SAASC,YACPC,WAAaf,SAASC,eAAe,cAAgBU,cACrDG,OAAOE,QACPP,OAAgB,EAChBM,WAAWE,UAAY,+CAAiDH,OAAOI,WAC5E,KAECC,gBAAkBnB,SAASoB,cAAc,MAC7CD,gBAAgBE,aAAa,QAAS,qBACtCF,gBAAgBF,UACZ,iEACkDH,OAAOQ,QADzD,kBAKAC,WAAaR,WAAWS,QAAQ,MAGE,OAAlCD,WAAWE,oBACXF,WAAWE,mBAAmBC,UAAUC,SAAS,sBACjDJ,WAAWE,mBAAmBG,SAIlCL,WAAWM,sBAAsB,WAAYV,wBAE1CL,OAAOE,WAEjBc,OAAM,SAASC,OACZC,OAAOC,QAAQF,MAAMA,UAG7B7B,SAASgC,KAAKtB,kBAIZuB,QAAQC,IAAIlC,gCAGLmC,gBAAgB,CACzBf,QAASb,MAAQ,OAASF,MAAQ,mCAClC+B,KAAO7B,QAAUF,MAAS,UAAY"} \ No newline at end of file +{"version":3,"file":"sitsgradepush.min.js","sources":["../src/sitsgradepush.js"],"sourcesContent":["import {schedulePushTask} from \"./sitsgradepush_helper\";\nimport notification from 'core/notification';\n\nexport const init = (coursemoduleid, mappingids) => {\n // Get the push button.\n let pushbuton = document.getElementById('local_sitsgradepush_pushbutton_async');\n\n // Exit if the push button is not found.\n if (pushbuton === null) {\n return;\n }\n\n let promises = [];\n\n // Schedule a push task for each assessment mapping.\n pushbuton.addEventListener('click', async(e) => {\n e.preventDefault();\n\n // Number of assessment mappings.\n let total = mappingids.length;\n let count = 0;\n\n // Schedule a task to push grades to SITS for each assessment mapping.\n mappingids.forEach(function(mappingid) {\n let promise = schedulePushTask(mappingid)\n .then(function(result) {\n let taskstatus = document.getElementById('taskstatus-' + mappingid);\n if (result.success) {\n count = count + 1;\n taskstatus.innerHTML = ' ' + result.status;\n } else {\n // Create an error message row.\n let errormessagerow = document.createElement(\"tr\");\n errormessagerow.setAttribute(\"class\", \"error-message-row\");\n errormessagerow.innerHTML =\n '\">' +\n '
' + result.message + '
' +\n '';\n\n // Find the closest row to the assessment mapping.\n let currentrow = taskstatus.closest(\"tr\");\n\n // Remove the existing error message row if it exists.\n if (currentrow.nextElementSibling !== null &&\n currentrow.nextElementSibling.classList.contains(\"error-message-row\")) {\n currentrow.nextElementSibling.remove();\n }\n\n // Insert the error message row for the assessment mapping.\n currentrow.insertAdjacentElement(\"afterend\", errormessagerow);\n }\n return result.success;\n })\n .catch(function(error) {\n window.console.error(error);\n });\n\n promises.push(promise);\n });\n\n // Wait for all the push tasks to be scheduled.\n await Promise.all(promises);\n\n // Display a notification.\n notification.addNotification({\n message: count + ' of ' + total + ' push tasks have been scheduled.',\n type: (count === total) ? 'success' : 'warning'\n });\n });\n};\n"],"names":["coursemoduleid","mappingids","pushbuton","document","getElementById","promises","addEventListener","async","e","preventDefault","total","length","count","forEach","mappingid","promise","then","result","taskstatus","success","innerHTML","status","errormessagerow","createElement","setAttribute","message","currentrow","closest","nextElementSibling","classList","contains","remove","insertAdjacentElement","catch","error","window","console","push","Promise","all","addNotification","type"],"mappings":"mUAGoB,CAACA,eAAgBC,kBAE7BC,UAAYC,SAASC,eAAe,2CAGtB,OAAdF,qBAIAG,SAAW,GAGfH,UAAUI,iBAAiB,SAASC,MAAAA,IAChCC,EAAEC,qBAGEC,MAAQT,WAAWU,OACnBC,MAAQ,EAGZX,WAAWY,SAAQ,SAASC,eACpBC,SAAU,0CAAiBD,WAC1BE,MAAK,SAASC,YACPC,WAAaf,SAASC,eAAe,cAAgBU,cACrDG,OAAOE,QACPP,OAAgB,EAChBM,WAAWE,UAAY,+CAAiDH,OAAOI,WAC5E,KAECC,gBAAkBnB,SAASoB,cAAc,MAC7CD,gBAAgBE,aAAa,QAAS,qBACtCF,gBAAgBF,UACZ,iEACkDH,OAAOQ,QADzD,kBAKAC,WAAaR,WAAWS,QAAQ,MAGE,OAAlCD,WAAWE,oBACXF,WAAWE,mBAAmBC,UAAUC,SAAS,sBACjDJ,WAAWE,mBAAmBG,SAIlCL,WAAWM,sBAAsB,WAAYV,wBAE1CL,OAAOE,WAEjBc,OAAM,SAASC,OACZC,OAAOC,QAAQF,MAAMA,UAG7B7B,SAASgC,KAAKtB,kBAIZuB,QAAQC,IAAIlC,gCAGLmC,gBAAgB,CACzBf,QAASb,MAAQ,OAASF,MAAQ,mCAClC+B,KAAO7B,QAAUF,MAAS,UAAY"} \ No newline at end of file diff --git a/amd/build/sitsgradepush_helper.min.js b/amd/build/sitsgradepush_helper.min.js new file mode 100644 index 0000000..9f9ee9a --- /dev/null +++ b/amd/build/sitsgradepush_helper.min.js @@ -0,0 +1,3 @@ +define("local_sitsgradepush/sitsgradepush_helper",["exports","core/ajax"],(function(_exports,_ajax){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.schedulePushTask=_exports.mapAssessment=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.schedulePushTask=assessmentmappingid=>new Promise(((resolve,reject)=>{_ajax.default.call([{methodname:"local_sitsgradepush_schedule_push_task",args:{assessmentmappingid:assessmentmappingid}}])[0].done((function(response){resolve(response)})).fail((function(err){window.console.log(err),reject(err)}))}));_exports.mapAssessment=function(courseid,coursemoduleid,mabid){let partid=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;return new Promise(((resolve,reject)=>{_ajax.default.call([{methodname:"local_sitsgradepush_map_assessment",args:{courseid:courseid,coursemoduleid:coursemoduleid,mabid:mabid,partid:partid}}])[0].done((function(response){resolve(response)})).fail((function(err){window.console.log(err),reject(err)}))}))}})); + +//# sourceMappingURL=sitsgradepush_helper.min.js.map \ No newline at end of file diff --git a/amd/build/sitsgradepush_helper.min.js.map b/amd/build/sitsgradepush_helper.min.js.map new file mode 100644 index 0000000..eddb2e8 --- /dev/null +++ b/amd/build/sitsgradepush_helper.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sitsgradepush_helper.min.js","sources":["../src/sitsgradepush_helper.js"],"sourcesContent":["import Ajax from 'core/ajax';\n\n/**\n * Schedule a task to push grades to SITS.\n *\n * @param {string} assessmentmappingid The assessment mapping ID.\n * @return {Promise} Promise.\n */\nexport const schedulePushTask = (assessmentmappingid) => {\n return new Promise((resolve, reject) => {\n Ajax.call([{\n methodname: 'local_sitsgradepush_schedule_push_task',\n args: {\n 'assessmentmappingid': assessmentmappingid,\n },\n }])[0].done(function(response) {\n resolve(response);\n }).fail(function(err) {\n window.console.log(err);\n reject(err);\n });\n });\n};\n\n/**\n * Map an assessment to a component grade.\n *\n * @param {string} courseid\n * @param {string} coursemoduleid\n * @param {string} mabid\n * @param {string} partid\n * @return {Promise}\n */\nexport const mapAssessment = (courseid, coursemoduleid, mabid, partid = null) => {\n return new Promise((resolve, reject) => {\n Ajax.call([{\n methodname: 'local_sitsgradepush_map_assessment',\n args: {\n 'courseid': courseid,\n 'coursemoduleid': coursemoduleid,\n 'mabid': mabid,\n 'partid': partid,\n },\n }])[0].done(function(response) {\n resolve(response);\n }).fail(function(err) {\n window.console.log(err);\n reject(err);\n });\n });\n};\n"],"names":["assessmentmappingid","Promise","resolve","reject","call","methodname","args","done","response","fail","err","window","console","log","courseid","coursemoduleid","mabid","partid"],"mappings":"0SAQiCA,qBACtB,IAAIC,SAAQ,CAACC,QAASC,wBACpBC,KAAK,CAAC,CACPC,WAAY,yCACZC,KAAM,qBACqBN,wBAE3B,GAAGO,MAAK,SAASC,UACjBN,QAAQM,aACTC,MAAK,SAASC,KACbC,OAAOC,QAAQC,IAAIH,KACnBP,OAAOO,kCAcU,SAACI,SAAUC,eAAgBC,WAAOC,8DAAS,YAC7D,IAAIhB,SAAQ,CAACC,QAASC,wBACpBC,KAAK,CAAC,CACPC,WAAY,qCACZC,KAAM,UACUQ,wBACMC,qBACTC,aACCC,WAEd,GAAGV,MAAK,SAASC,UACjBN,QAAQM,aACTC,MAAK,SAASC,KACbC,OAAOC,QAAQC,IAAIH,KACnBP,OAAOO"} \ No newline at end of file diff --git a/amd/build/table_helper.min.js b/amd/build/table_helper.min.js new file mode 100644 index 0000000..4c0dbda --- /dev/null +++ b/amd/build/table_helper.min.js @@ -0,0 +1,3 @@ +define("local_sitsgradepush/table_helper",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.tableHelperInit=void 0;let sortingDirection=[],iconElements=[];function sortTable(targetTable,columnIndex){let rows,switching,i,x,y,shouldSwitch;switching=!0;let iconElement=targetTable.rows[0].cells[columnIndex].firstElementChild;for(let i=0;iyText.toLowerCase()||"desc"===sortingDirection[columnIndex]&&xText.toLowerCase()-1){found=!0;break}found?(tr[i].style.display="",hasMatchingRow=!0):tr[i].style.display="none"}let noMatchingRowsMessage=document.getElementById("noMatchingRowsMessage");noMatchingRowsMessage.style.display=hasMatchingRow?"none":"block"}_exports.tableHelperInit=function(tableId,sortColumnNumber){let filterInputId=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,targetTable=document.getElementById(tableId);if(filterInputId){let filterInput=document.getElementById(filterInputId);filterInput.addEventListener("keyup",(function(){filterTable(filterInput,targetTable)}))}for(let i=0;i {\n // Get the target table.\n let targetTable = document.getElementById(tableId);\n\n // Add event listeners to filter input.\n if (filterInputId) {\n let filterInput = document.getElementById(filterInputId);\n filterInput.addEventListener(\"keyup\", function() {\n filterTable(filterInput, targetTable);\n });\n }\n\n // Add event listeners to table headers.\n for (let i = 0; i < sortColumnNumber; i++) {\n let header = targetTable.rows[0].cells[i];\n iconElements.push(header.firstElementChild);\n header.addEventListener(\"click\", function() {\n sortTable(targetTable, i);\n });\n }\n};\n\n/**\n * Sort a table.\n *\n * @param {HTMLElement} targetTable\n * @param {int} columnIndex\n */\nfunction sortTable(targetTable, columnIndex) {\n let rows, switching, i, x, y, shouldSwitch;\n switching = true;\n\n // Get the icon element for the current column.\n let iconElement = targetTable.rows[0].cells[columnIndex].firstElementChild;\n\n // Reset sorting direction and icons for all columns.\n for (let i = 0; i < sortingDirection.length; i++) {\n if (i !== columnIndex) {\n sortingDirection[i] = undefined;\n iconElements[i].className = \"fas fa-sort\";\n }\n }\n\n // Toggle sorting direction and update icon.\n if (sortingDirection[columnIndex] === undefined || sortingDirection[columnIndex] === \"desc\") {\n sortingDirection[columnIndex] = \"asc\";\n iconElement.className = \"fas fa-sort-up\";\n } else {\n sortingDirection[columnIndex] = \"desc\";\n iconElement.className = \"fas fa-sort-down\";\n }\n\n while (switching) {\n switching = false;\n rows = targetTable.rows;\n\n for (i = 1; i < rows.length - 1; i++) {\n shouldSwitch = false;\n x = rows[i].getElementsByTagName(\"td\")[columnIndex];\n y = rows[i + 1].getElementsByTagName(\"td\")[columnIndex];\n\n let xText = x.textContent || x.innerText;\n let yText = y.textContent || y.innerText;\n\n // Compare based on sorting direction.\n if ((sortingDirection[columnIndex] === \"asc\" && xText.toLowerCase() > yText.toLowerCase()) ||\n (sortingDirection[columnIndex] === \"desc\" && xText.toLowerCase() < yText.toLowerCase())) {\n shouldSwitch = true;\n break;\n }\n }\n\n if (shouldSwitch) {\n rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);\n switching = true;\n }\n }\n}\n\n/**\n * Filter a table.\n *\n * @param {HTMLElement} filterInput\n * @param {HTMLElement} targetTable\n */\nfunction filterTable(filterInput, targetTable) {\n let filter, tr, td, i, j, txtValue;\n filter = filterInput.value.toUpperCase();\n\n tr = targetTable.getElementsByTagName(\"tr\");\n let hasMatchingRow = false;\n\n // Loop through all rows in the table.\n for (i = 1; i < tr.length; i++) {\n let found = false;\n td = tr[i].getElementsByTagName(\"td\");\n\n // Loop through all columns in a row.\n for (j = 0; j < td.length; j++) {\n txtValue = td[j].textContent || td[j].innerText;\n if (txtValue.toUpperCase().indexOf(filter) > -1) {\n found = true;\n break; // If a match is found in any column, no need to check other columns.\n }\n }\n\n // Display the row if a match is found, otherwise hide it.\n if (found) {\n tr[i].style.display = \"\";\n hasMatchingRow = true;\n } else {\n tr[i].style.display = \"none\";\n }\n }\n\n // Show or hide the \"No matching rows found\" message.\n let noMatchingRowsMessage = document.getElementById(\"noMatchingRowsMessage\");\n if (!hasMatchingRow) {\n noMatchingRowsMessage.style.display = \"block\";\n } else {\n noMatchingRowsMessage.style.display = \"none\";\n }\n}\n"],"names":["sortingDirection","iconElements","sortTable","targetTable","columnIndex","rows","switching","i","x","y","shouldSwitch","iconElement","cells","firstElementChild","length","undefined","className","getElementsByTagName","xText","textContent","innerText","yText","toLowerCase","parentNode","insertBefore","filterTable","filterInput","filter","tr","td","j","txtValue","value","toUpperCase","hasMatchingRow","found","indexOf","style","display","noMatchingRowsMessage","document","getElementById","tableId","sortColumnNumber","filterInputId","addEventListener","header","push"],"mappings":"sKACIA,iBAAmB,GACnBC,aAAe,YA8BVC,UAAUC,YAAaC,iBACxBC,KAAMC,UAAWC,EAAGC,EAAGC,EAAGC,aAC9BJ,WAAY,MAGRK,YAAcR,YAAYE,KAAK,GAAGO,MAAMR,aAAaS,sBAGpD,IAAIN,EAAI,EAAGA,EAAIP,iBAAiBc,OAAQP,IACrCA,IAAMH,cACNJ,iBAAiBO,QAAKQ,EACtBd,aAAaM,GAAGS,UAAY,wBAKED,IAAlCf,iBAAiBI,cAAgE,SAAlCJ,iBAAiBI,cAChEJ,iBAAiBI,aAAe,MAChCO,YAAYK,UAAY,mBAExBhB,iBAAiBI,aAAe,OAChCO,YAAYK,UAAY,oBAGrBV,WAAW,KACdA,WAAY,EACZD,KAAOF,YAAYE,KAEdE,EAAI,EAAGA,EAAIF,KAAKS,OAAS,EAAGP,IAAK,CAClCG,cAAe,EACfF,EAAIH,KAAKE,GAAGU,qBAAqB,MAAMb,aACvCK,EAAIJ,KAAKE,EAAI,GAAGU,qBAAqB,MAAMb,iBAEvCc,MAAQV,EAAEW,aAAeX,EAAEY,UAC3BC,MAAQZ,EAAEU,aAAeV,EAAEW,aAGQ,QAAlCpB,iBAAiBI,cAA0Bc,MAAMI,cAAgBD,MAAMC,eACrC,SAAlCtB,iBAAiBI,cAA2Bc,MAAMI,cAAgBD,MAAMC,cAAgB,CACzFZ,cAAe,SAKnBA,eACAL,KAAKE,GAAGgB,WAAWC,aAAanB,KAAKE,EAAI,GAAIF,KAAKE,IAClDD,WAAY,aAWfmB,YAAYC,YAAavB,iBAC1BwB,OAAQC,GAAIC,GAAItB,EAAGuB,EAAGC,SAC1BJ,OAASD,YAAYM,MAAMC,cAE3BL,GAAKzB,YAAYc,qBAAqB,UAClCiB,gBAAiB,MAGhB3B,EAAI,EAAGA,EAAIqB,GAAGd,OAAQP,IAAK,KACxB4B,OAAQ,MACZN,GAAKD,GAAGrB,GAAGU,qBAAqB,MAG3Ba,EAAI,EAAGA,EAAID,GAAGf,OAAQgB,OACvBC,SAAWF,GAAGC,GAAGX,aAAeU,GAAGC,GAAGV,UAClCW,SAASE,cAAcG,QAAQT,SAAW,EAAG,CAC7CQ,OAAQ,QAMZA,OACAP,GAAGrB,GAAG8B,MAAMC,QAAU,GACtBJ,gBAAiB,GAEjBN,GAAGrB,GAAG8B,MAAMC,QAAU,WAK1BC,sBAAwBC,SAASC,eAAe,yBAIhDF,sBAAsBF,MAAMC,QAH3BJ,eAGqC,OAFA,iCAtHf,SAACQ,QAASC,sBAAkBC,qEAAgB,KAEnEzC,YAAcqC,SAASC,eAAeC,YAGtCE,cAAe,KACXlB,YAAcc,SAASC,eAAeG,eAC1ClB,YAAYmB,iBAAiB,SAAS,WAClCpB,YAAYC,YAAavB,oBAK5B,IAAII,EAAI,EAAGA,EAAIoC,iBAAkBpC,IAAK,KACnCuC,OAAS3C,YAAYE,KAAK,GAAGO,MAAML,GACvCN,aAAa8C,KAAKD,OAAOjC,mBACzBiC,OAAOD,iBAAiB,SAAS,WAC7B3C,UAAUC,YAAaI"} \ No newline at end of file diff --git a/amd/src/dashboard.js b/amd/src/dashboard.js index 3b96837..8acf58f 100644 --- a/amd/src/dashboard.js +++ b/amd/src/dashboard.js @@ -1,7 +1,10 @@ -import {schedulePushTask} from "./push_tasks"; +import {schedulePushTask} from "./sitsgradepush_helper"; import notification from "core/notification"; export const init = () => { + // If there is a saved message by successfully mapped an assessment in localStorage, display it. + displayNotification(); + // Find the page element. let page = document.getElementById("page"); @@ -45,15 +48,31 @@ export const init = () => { tableSelector.selectedIndex = 0; }); + // Get all change source buttons. + let changesourcebuttons = document.querySelectorAll(".change-source-button:not([disabled])"); + + // Add event listener to each change source button. + // When the user clicks on each change source button, redirect to the select source page. + if (changesourcebuttons.length > 0) { + changesourcebuttons.forEach(function(button) { + button.addEventListener("click", function() { + // Redirect to the change source page. + window.location.href = button.getAttribute("data-url"); + }); + }); + } + // Get all the push buttons that are not disabled. let mabpushbuttons = document.querySelectorAll(".push-mark-button:not([disabled])"); - // Push grades when the user clicks on each enabled push button. - mabpushbuttons.forEach(function(button) { - button.addEventListener("click", function() { - pushgrade(this); + if (mabpushbuttons.length > 0) { + // Push grades when the user clicks on each enabled push button. + mabpushbuttons.forEach(function(button) { + button.addEventListener("click", function() { + pushMarks(this); + }); }); - }); + } // Get the push all button. let pushallbutton = document.getElementById("push-all-button"); @@ -70,10 +89,10 @@ export const init = () => { // Create an array to hold all the Promises. let promises = []; - // Push grades to SITS for each MAB. + // Push grades to SITS for each component grade. mabpushbuttons.forEach(function(button) { // Create a Promise for each button and push it into the array. - let promise = pushgrade(button) + let promise = pushMarks(button) .then(function(result) { if (result) { count = count + 1; @@ -106,7 +125,7 @@ export const init = () => { * @param {HTMLElement} button The button element. * @return {Promise} Promise. */ -async function pushgrade(button) { +async function pushMarks(button) { try { // Get the assessment mapping ID from the button. let assessmentmappingid = button.getAttribute("data-assessmentmappingid"); @@ -159,3 +178,23 @@ async function pushgrade(button) { return false; } } + +/** + * Display a notification if a success message is available in localStorage. + */ +function displayNotification() { + // Retrieve the success message from localStorage. + let successMessage = localStorage.getItem('successMessage'); + + // Check if a success message is available. + if (successMessage) { + // Display the success message using a notification library or other means. + notification.addNotification({ + message: successMessage, + type: 'success' + }); + + // Remove the success message from localStorage to avoid showing it again. + localStorage.removeItem('successMessage'); + } +} diff --git a/amd/src/existing_activity.js b/amd/src/existing_activity.js new file mode 100644 index 0000000..99643d0 --- /dev/null +++ b/amd/src/existing_activity.js @@ -0,0 +1,127 @@ +import {tableHelperInit} from './table_helper'; +import notification from "core/notification"; +import ModalFactory from 'core/modal_factory'; +import ModalEvents from 'core/modal_events'; +import {mapAssessment} from './sitsgradepush_helper'; + +export const init = () => { + // Initialise the table helper. + tableHelperInit('existing-activity-table', 2, 'filterInput'); + + // Get all the select assessment buttons. + let selectAssessmentButtons = document.querySelectorAll('.select-assessment-button'); + + if (selectAssessmentButtons) { + // Add an event listener to each select assessment button. + // When the user clicks on each button, map the assessment to the selected component grade. + selectAssessmentButtons.forEach(function(button) { + button.addEventListener('click', function() { + selectAssessment(button).then( + (result) => { + return result; + } + ).catch((error) => { + notification.addNotification({ + message: error.message, + type: 'error' + }); + window.console.error(error); + }); + }); + }); + } +}; + +/** + * Show a modal to confirm the user wants to map the assessment to the selected component grade. + * + * @param {HTMLElement} button The select assessment button element. + * @return {Promise} + */ +async function selectAssessment(button) { + // Get assessment component data from the button. + let mapcode = button.getAttribute('data-mapcode'); + let mabseq = button.getAttribute('data-mabseq'); + let mabid = button.getAttribute('data-mabid'); + let courseid = button.getAttribute('data-courseid'); + let coursemoduleid = button.getAttribute('data-coursemoduleid'); + let partid = button.getAttribute('data-partid'); + + // Get assessment data from the button. + // Find the closest row to the button. + let currentrow = button.closest('tr'); + let type = currentrow.getElementsByTagName('td')[0].innerHTML; + let name = currentrow.getElementsByTagName('td')[1].innerHTML; + let endDate = currentrow.getElementsByTagName('td')[3].innerHTML; + + let modal = await ModalFactory.create({ + type: ModalFactory.types.SAVE_CANCEL, + title: 'Map Assessment', + body: getModalBody(type, name, endDate, mapcode, mabseq), + large: true, + buttons: {'save': 'Confirm', 'cancel': 'Cancel'} + }); + + await modal.show(); + modal.getRoot().on(ModalEvents.save, () => { + mapAssessment(courseid, coursemoduleid, mabid, partid).then( + (result) => { + if (result.success) { + // Store the success message in localStorage for display on the dashboard page. + localStorage.setItem('successMessage', result.message); + + // Redirect to the dashboard page. + window.location.href = '/local/sitsgradepush/dashboard.php?id=' + courseid; + } else { + notification.addNotification({ + message: result.message, + type: 'error' + }); + } + return result; + } + ).catch((error) => { + window.console.error(error); + }); + }); +} + +/** + * Get the modal body. + * + * @param {string} type + * @param {string} name + * @param {string} endDate + * @param {string} mapcode + * @param {string} mabseq + * @return {string} + */ +function getModalBody(type, name, endDate, mapcode, mabseq) { + return ` + `; +} diff --git a/amd/src/push_tasks.js b/amd/src/push_tasks.js deleted file mode 100644 index cc336b9..0000000 --- a/amd/src/push_tasks.js +++ /dev/null @@ -1,23 +0,0 @@ -import Ajax from 'core/ajax'; - -/** - * Schedule a task to push grades to SITS. - * - * @param {string} assessmentmappingid The assessment mapping ID. - * @return {Promise} Promise. - */ -export const schedulePushTask = (assessmentmappingid) => { - return new Promise((resolve, reject) => { - Ajax.call([{ - methodname: 'local_sitsgradepush_schedule_push_task', - args: { - 'assessmentmappingid': assessmentmappingid, - }, - }])[0].done(function(response) { - resolve(response); - }).fail(function(err) { - window.console.log(err); - reject(err); - }); - }); -}; diff --git a/amd/src/select_source.js b/amd/src/select_source.js new file mode 100644 index 0000000..081b5a2 --- /dev/null +++ b/amd/src/select_source.js @@ -0,0 +1,9 @@ +export const init = (courseid, mabid) => { + // Add an event listener to the select existing activity card. + // When the user clicks on the card, redirect to the existing activity page. + document.getElementById('existing-activity').addEventListener('click', (event) => { + event.preventDefault(); + window.location.href = + `/local/sitsgradepush/select_source.php?courseid=${courseid}&mabid=${mabid}&source=existing`; + }); +}; diff --git a/amd/src/sitsgradepush.js b/amd/src/sitsgradepush.js index 08e666e..6918cde 100644 --- a/amd/src/sitsgradepush.js +++ b/amd/src/sitsgradepush.js @@ -1,4 +1,4 @@ -import {schedulePushTask} from "./push_tasks"; +import {schedulePushTask} from "./sitsgradepush_helper"; import notification from 'core/notification'; export const init = (coursemoduleid, mappingids) => { diff --git a/amd/src/sitsgradepush_helper.js b/amd/src/sitsgradepush_helper.js new file mode 100644 index 0000000..347ae53 --- /dev/null +++ b/amd/src/sitsgradepush_helper.js @@ -0,0 +1,51 @@ +import Ajax from 'core/ajax'; + +/** + * Schedule a task to push grades to SITS. + * + * @param {string} assessmentmappingid The assessment mapping ID. + * @return {Promise} Promise. + */ +export const schedulePushTask = (assessmentmappingid) => { + return new Promise((resolve, reject) => { + Ajax.call([{ + methodname: 'local_sitsgradepush_schedule_push_task', + args: { + 'assessmentmappingid': assessmentmappingid, + }, + }])[0].done(function(response) { + resolve(response); + }).fail(function(err) { + window.console.log(err); + reject(err); + }); + }); +}; + +/** + * Map an assessment to a component grade. + * + * @param {string} courseid + * @param {string} coursemoduleid + * @param {string} mabid + * @param {string} partid + * @return {Promise} + */ +export const mapAssessment = (courseid, coursemoduleid, mabid, partid = null) => { + return new Promise((resolve, reject) => { + Ajax.call([{ + methodname: 'local_sitsgradepush_map_assessment', + args: { + 'courseid': courseid, + 'coursemoduleid': coursemoduleid, + 'mabid': mabid, + 'partid': partid, + }, + }])[0].done(function(response) { + resolve(response); + }).fail(function(err) { + window.console.log(err); + reject(err); + }); + }); +}; diff --git a/amd/src/table_helper.js b/amd/src/table_helper.js new file mode 100644 index 0000000..84e1e0e --- /dev/null +++ b/amd/src/table_helper.js @@ -0,0 +1,127 @@ +// Define variables to track sorting direction and icons. +let sortingDirection = []; +let iconElements = []; + +export const tableHelperInit = (tableId, sortColumnNumber, filterInputId = null) => { + // Get the target table. + let targetTable = document.getElementById(tableId); + + // Add event listeners to filter input. + if (filterInputId) { + let filterInput = document.getElementById(filterInputId); + filterInput.addEventListener("keyup", function() { + filterTable(filterInput, targetTable); + }); + } + + // Add event listeners to table headers. + for (let i = 0; i < sortColumnNumber; i++) { + let header = targetTable.rows[0].cells[i]; + iconElements.push(header.firstElementChild); + header.addEventListener("click", function() { + sortTable(targetTable, i); + }); + } +}; + +/** + * Sort a table. + * + * @param {HTMLElement} targetTable + * @param {int} columnIndex + */ +function sortTable(targetTable, columnIndex) { + let rows, switching, i, x, y, shouldSwitch; + switching = true; + + // Get the icon element for the current column. + let iconElement = targetTable.rows[0].cells[columnIndex].firstElementChild; + + // Reset sorting direction and icons for all columns. + for (let i = 0; i < sortingDirection.length; i++) { + if (i !== columnIndex) { + sortingDirection[i] = undefined; + iconElements[i].className = "fas fa-sort"; + } + } + + // Toggle sorting direction and update icon. + if (sortingDirection[columnIndex] === undefined || sortingDirection[columnIndex] === "desc") { + sortingDirection[columnIndex] = "asc"; + iconElement.className = "fas fa-sort-up"; + } else { + sortingDirection[columnIndex] = "desc"; + iconElement.className = "fas fa-sort-down"; + } + + while (switching) { + switching = false; + rows = targetTable.rows; + + for (i = 1; i < rows.length - 1; i++) { + shouldSwitch = false; + x = rows[i].getElementsByTagName("td")[columnIndex]; + y = rows[i + 1].getElementsByTagName("td")[columnIndex]; + + let xText = x.textContent || x.innerText; + let yText = y.textContent || y.innerText; + + // Compare based on sorting direction. + if ((sortingDirection[columnIndex] === "asc" && xText.toLowerCase() > yText.toLowerCase()) || + (sortingDirection[columnIndex] === "desc" && xText.toLowerCase() < yText.toLowerCase())) { + shouldSwitch = true; + break; + } + } + + if (shouldSwitch) { + rows[i].parentNode.insertBefore(rows[i + 1], rows[i]); + switching = true; + } + } +} + +/** + * Filter a table. + * + * @param {HTMLElement} filterInput + * @param {HTMLElement} targetTable + */ +function filterTable(filterInput, targetTable) { + let filter, tr, td, i, j, txtValue; + filter = filterInput.value.toUpperCase(); + + tr = targetTable.getElementsByTagName("tr"); + let hasMatchingRow = false; + + // Loop through all rows in the table. + for (i = 1; i < tr.length; i++) { + let found = false; + td = tr[i].getElementsByTagName("td"); + + // Loop through all columns in a row. + for (j = 0; j < td.length; j++) { + txtValue = td[j].textContent || td[j].innerText; + if (txtValue.toUpperCase().indexOf(filter) > -1) { + found = true; + break; // If a match is found in any column, no need to check other columns. + } + } + + // Display the row if a match is found, otherwise hide it. + if (found) { + tr[i].style.display = ""; + hasMatchingRow = true; + } else { + tr[i].style.display = "none"; + } + } + + // Show or hide the "No matching rows found" message. + let noMatchingRowsMessage = document.getElementById("noMatchingRowsMessage"); + if (!hasMatchingRow) { + noMatchingRowsMessage.style.display = "block"; + } else { + noMatchingRowsMessage.style.display = "none"; + } +} diff --git a/classes/assessment/assessment.php b/classes/assessment/assessment.php index 03fb7b0..012376f 100644 --- a/classes/assessment/assessment.php +++ b/classes/assessment/assessment.php @@ -25,27 +25,22 @@ * @author Alex Yeung */ abstract class assessment implements iassessment { - /** @var string Assessment name */ - public $assessmentname; /** @var \stdClass Course module object */ public $coursemodule; - /** - * Set assessment name. - * - * @return void - */ - abstract protected function set_assessment_name(); + /** @var \stdClass Module instance object */ + public $moduleinstance; /** * Constructor. * * @param \stdClass $coursemodule + * @throws \dml_exception */ public function __construct(\stdClass $coursemodule) { $this->coursemodule = $coursemodule; - $this->set_assessment_name(); + $this->set_module_instance(); } /** @@ -54,7 +49,7 @@ public function __construct(\stdClass $coursemodule) { * @return string */ public function get_assessment_name(): string { - return $this->assessmentname; + return $this->moduleinstance->name; } /** @@ -65,4 +60,55 @@ public function get_assessment_name(): string { public function get_course_module(): \stdClass { return $this->coursemodule; } + + /** + * Get the course module id. + * + * @return int + */ + public function get_coursemodule_id(): int { + return $this->coursemodule->id; + } + + /** + * Get the course id. + * + * @return string + */ + public function get_module_type(): string { + return get_module_types_names()[$this->coursemodule->modname]; + } + + /** + * Get the grade of a user. + * + * @param int $userid + * @param int|null $partid + * @return string|null + */ + public function get_user_grade(int $userid, int $partid = null): ?string { + $result = null; + if ($grade = grade_get_grades( + $this->coursemodule->course, 'mod', $this->coursemodule->modname, $this->coursemodule->instance, $userid)) { + foreach ($grade->items as $item) { + foreach ($item->grades as $grade) { + if (isset($grade->grade)) { + $result = $grade->grade; + } + } + } + } + + return $result; + } + + /** + * Set the module instance. + * @return void + * @throws \dml_exception + */ + protected function set_module_instance(): void { + global $DB; + $this->moduleinstance = $DB->get_record($this->coursemodule->modname, ['id' => $this->coursemodule->instance]); + } } diff --git a/classes/assessment/assign.php b/classes/assessment/assign.php index f03120b..b8bd5de 100644 --- a/classes/assessment/assign.php +++ b/classes/assessment/assign.php @@ -36,15 +36,20 @@ public function get_all_participants(): array { } /** - * Set assessment name. + * Get the start date of this assessment. * - * @return void - * @throws \dml_exception + * @return int|null */ - protected function set_assessment_name() { - global $DB; - if ($assign = $DB->get_record('assign', ['id' => $this->coursemodule->instance])) { - $this->assessmentname = $assign->name; - } + public function get_start_date() : ?int { + return $this->moduleinstance->allowsubmissionsfromdate; + } + + /** + * Get the end date of this assessment. + * + * @return int|null + */ + public function get_end_date() : ?int { + return $this->moduleinstance->duedate; } } diff --git a/classes/assessment/iassessment.php b/classes/assessment/iassessment.php index 14044c4..e0d271d 100644 --- a/classes/assessment/iassessment.php +++ b/classes/assessment/iassessment.php @@ -48,4 +48,46 @@ public function get_course_module(): \stdClass; * @return string */ public function get_assessment_name(): string; + + /** + * Get the grade of a user. + * + * @param int $userid + * @param int|null $partid + * @return string|null + * @package local_sitsgradepush + */ + public function get_user_grade(int $userid, int $partid = null): ?string; + + /** + * Get the start date of the assessment. + * + * @package local_sitsgradepush + * @return int|null + */ + public function get_start_date(): ?int; + + /** + * Get the end date of the assessment. + * + * @package local_sitsgradepush + * @return int|null + */ + public function get_end_date(): ?int; + + /** + * Get course module type. + * + * @package local_sitsgradepush + * @return string + */ + public function get_module_type(): string; + + /** + * Get the course module id. + * + * @package local_sitsgradepush + * @return int + */ + public function get_coursemodule_id(): int; } diff --git a/classes/assessment/quiz.php b/classes/assessment/quiz.php index 0b9ac3b..acaad7e 100644 --- a/classes/assessment/quiz.php +++ b/classes/assessment/quiz.php @@ -36,15 +36,20 @@ public function get_all_participants(): array { } /** - * Set assessment name. + * Get the start date of this assessment. * - * @return void - * @throws \dml_exception + * @return int|null */ - protected function set_assessment_name() { - global $DB; - if ($quiz = $DB->get_record('quiz', ['id' => $this->coursemodule->instance])) { - $this->assessmentname = $quiz->name; - } + public function get_start_date() : ?int { + return $this->moduleinstance->timeopen; + } + + /** + * Get the end date of this assessment. + * + * @return int|null + */ + public function get_end_date() : ?int { + return $this->moduleinstance->timeclose; } } diff --git a/classes/assessment/turnitintooltwo.php b/classes/assessment/turnitintooltwo.php index 2416477..50a3260 100644 --- a/classes/assessment/turnitintooltwo.php +++ b/classes/assessment/turnitintooltwo.php @@ -25,6 +25,31 @@ * @author Alex Yeung */ class turnitintooltwo extends assessment { + + /** @var array Parts of this turnitin assignment. */ + private $turnitinparts; + + /** @var int Current part id set. */ + private $partid; + + /** @var \stdClass First part of this turnitin assignment. */ + private $firstpart; + + /** + * Constructor. + * + * @param \stdClass $coursemodule + * @throws \dml_exception + */ + public function __construct(\stdClass $coursemodule) { + global $DB; + parent::__construct($coursemodule); + + // Get all parts for this assignment. + $this->turnitinparts = $DB->get_records('turnitintooltwo_parts', ['turnitintooltwoid' => $this->moduleinstance->id]); + $this->firstpart = reset($this->turnitinparts); + } + /** * Get all participants. * @@ -36,15 +61,43 @@ public function get_all_participants(): array { } /** - * Set assessment name. + * Get the start date of this assessment. * - * @return void - * @throws \dml_exception + * @return int|null */ - protected function set_assessment_name() { - global $DB; - if ($turnitinassign = $DB->get_record('turnitintooltwo', ['id' => $this->coursemodule->instance])) { - $this->assessmentname = $turnitinassign->name; + public function get_start_date() : ?int { + if ($this->partid) { + // Return the start date of this part. + return $this->turnitinparts[$this->partid]->dtstart; + } else { + // Return the start date of the first part. + return $this->firstpart->dtstart; + } + } + + /** + * Get the end date of this assessment. + * + * @return int|null + */ + public function get_end_date() : ?int { + if ($this->partid) { + // Return the end date of the part currently pointing to. + return $this->turnitinparts[$this->partid]->dtdue; + } else { + // Return the end date of the first part. + return $this->firstpart->dtdue; } } + + /** + * Set the part ID for this turnitin assignment. + * + * @param int $partid + * @return turnitintooltwo + */ + public function set_part_id(int $partid): turnitintooltwo { + $this->partid = $partid; + return $this; + } } diff --git a/classes/external/map_assessment.php b/classes/external/map_assessment.php new file mode 100644 index 0000000..bba13ea --- /dev/null +++ b/classes/external/map_assessment.php @@ -0,0 +1,103 @@ +. +namespace local_sitsgradepush\external; + +use context_course; +use core_external\external_api; +use core_external\external_function_parameters; +use core_external\external_single_structure; +use core_external\external_value; +use local_sitsgradepush\manager; + +/** + * External API for mapping an assessment to an SITS assessment component (MAB). + * + * @package local_sitsgradepush + * @copyright 2023 onwards University College London {@link https://www.ucl.ac.uk/} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Alex Yeung + */ +class map_assessment extends external_api { + /** + * Returns description of method parameters. + * + * @return external_function_parameters + */ + public static function execute_parameters() { + return new external_function_parameters([ + 'courseid' => new external_value(PARAM_INT, 'Coruse ID', VALUE_REQUIRED), + 'coursemoduleid' => new external_value(PARAM_INT, 'Course Module ID', VALUE_REQUIRED), + 'mabid' => new external_value(PARAM_INT, 'Assessment Component ID', VALUE_REQUIRED), + 'partid' => new external_value(PARAM_INT, 'Assessment Part ID', VALUE_OPTIONAL), + ]); + } + + /** + * Returns description of method result value. + * + * @return \external_single_structure + */ + public static function execute_returns() { + return new external_single_structure([ + 'success' => new external_value(PARAM_BOOL, 'Result of request', VALUE_REQUIRED), + 'message' => new external_value(PARAM_TEXT, 'Error message', VALUE_OPTIONAL), + ]); + } + + /** + * Map an assessment to an SITS assessment component (MAB). + * + * @param int $courseid + * @param int $coursemoduleid + * @param int $mabid + * @param int|null $partid + * @return array + */ + public static function execute(int $courseid, int $coursemoduleid, int $mabid, int $partid = null) { + try { + if (!has_capability('local/sitsgradepush:mapassessment', context_course::instance($courseid))) { + throw new \moodle_exception('error:mapassessment', 'local_sitsgradepush'); + } + + $params = self::validate_parameters( + self::execute_parameters(), + [ + 'courseid' => $courseid, + 'coursemoduleid' => $coursemoduleid, + 'mabid' => $mabid, + 'partid' => $partid, + ] + ); + + $manager = manager::get_manager(); + $data = new \stdClass(); + $data->componentgradeid = $params['mabid']; + $data->coursemoduleid = $params['coursemoduleid']; + $data->partid = $params['partid']; + $manager->save_assessment_mapping($data); + + return [ + 'success' => true, + 'message' => 'Assessment mapped successfully.', + ]; + } catch (\Exception $e) { + return [ + 'success' => false, + 'message' => $e->getMessage(), + ]; + } + } +} diff --git a/classes/manager.php b/classes/manager.php index d896e3e..87b64ea 100644 --- a/classes/manager.php +++ b/classes/manager.php @@ -21,6 +21,7 @@ use local_sitsgradepush\api\iclient; use local_sitsgradepush\api\irequest; use local_sitsgradepush\assessment\assessment; +use local_sitsgradepush\assessment\assessmentfactory; use local_sitsgradepush\output\pushrecord; use local_sitsgradepush\submission\submissionfactory; @@ -414,6 +415,7 @@ public function save_component_grades(array $componentgrades) { /** * Save assessment mappings to database. + * * @param \stdClass $data * @return void * @throws \coding_exception @@ -455,15 +457,59 @@ public function save_assessment_mappings(\stdClass $data): void { } } + /** + * Save component grade mapping to database. Used by the select existing activity page. + * + * @param \stdClass $data + * @return int|bool + * @throws \dml_exception + * @throws \moodle_exception + */ + public function save_assessment_mapping(\stdClass $data): int|bool { + global $DB; + + // Validate component grade. + $this->validate_component_grade($data->componentgradeid, $data->coursemoduleid); + + if ($mapping = $this->is_component_grade_mapped($data->componentgradeid)) { + // Checked in the above validation, the current mapping to this component grade + // can be deleted as it does not have push records nor mapped to the current activity. + $DB->delete_records(self::TABLE_ASSESSMENT_MAPPING, ['id' => $mapping->id]); + } + + // Get the course module. + $coursemodule = get_coursemodule_from_id('', $data->coursemoduleid); + + // Insert new mapping. + $record = new \stdClass(); + $record->courseid = $coursemodule->course; + $record->coursemoduleid = $coursemodule->id; + $record->moduletype = $coursemodule->modname; + $record->componentgradeid = $data->componentgradeid; + $record->timecreated = time(); + $record->timemodified = time(); + + return $DB->insert_record(self::TABLE_ASSESSMENT_MAPPING, $record); + } + /** * Lookup assessment mappings. * * @param int $cmid course module id - * @return array + * @param int|null $componentgradeid component grade id + * @return mixed * @throws \dml_exception */ - public function get_assessment_mappings(int $cmid) { + public function get_assessment_mappings(int $cmid, int $componentgradeid = null): mixed { global $DB; + + $params = ['cmid' => $cmid]; + $where = ''; + if (!empty($componentgradeid)) { + $params['componentgradeid'] = $componentgradeid; + $where = 'AND am.componentgradeid = :componentgradeid'; + } + $sql = "SELECT am.id, am.courseid, am.coursemoduleid, am.moduletype, am.componentgradeid, am.reassessment, am.reassessmentseq, cg.modcode, cg.modocc, cg.academicyear, cg.periodslotcode, cg.mapcode, cg.mabseq, cg.astcode, cg.mabname, @@ -471,8 +517,9 @@ public function get_assessment_mappings(int $cmid) { cg.mabseq, ' ', cg.mabname) AS 'formattedname' FROM {" . self::TABLE_ASSESSMENT_MAPPING . "} am JOIN {" . self::TABLE_COMPONENT_GRADE . "} cg ON am.componentgradeid = cg.id - WHERE am.coursemoduleid = :cmid"; - return $DB->get_records_sql($sql, ['cmid' => $cmid]); + WHERE am.coursemoduleid = :cmid $where"; + + return ($componentgradeid) ? $DB->get_record_sql($sql, $params) : $DB->get_records_sql($sql, $params); } /** @@ -658,8 +705,8 @@ public function push_grade_to_sits(\stdClass $assessmentmapping, int $userid): b $grade = $this->get_student_grade($assessmentmapping->coursemoduleid, $userid); // Push if grade is found. - if ($grade->grade) { - $data->marks = $grade->grade; + if (isset($grade)) { + $data->marks = $grade; $data->grade = ''; // TODO: Where to get the grade? $request = $this->apiclient->build_request(self::PUSH_GRADE, $data); @@ -990,7 +1037,7 @@ public function schedule_push_task(int $assessmentmappingid): bool|int { // Check course module exists. if (!$DB->record_exists('course_modules', ['id' => $mapping->coursemoduleid])) { - throw new \moodle_exception('error:coursemodulenotfound', 'local_sitsgradepush'); + throw new \moodle_exception('error:coursemodulenotfound', 'local_sitsgradepush', '', $mapping->coursemoduleid); } // Check if the assessment component exists. @@ -1285,30 +1332,102 @@ public function validate_component_grades(array $componentgrades, int $coursemod return $result; } + /** + * Validate assessment mapping request from select existing activity page. + * + * @param int $componentgradeid + * @param int $coursemoduleid + * @return bool + * @throws \dml_exception + * @throws \moodle_exception + */ + public function validate_component_grade(int $componentgradeid, int $coursemoduleid): bool { + // Check if the component grade exists. + if (!$componentgrade = $this->get_local_component_grade_by_id($componentgradeid)) { + throw new \moodle_exception('error:mab_not_found', 'local_sitsgradepush', '', $componentgradeid); + } + + // Check if the course module exists. + if (!$coursemodule = $this->get_course_module($coursemoduleid)) { + throw new \moodle_exception('error:coursemodulenotfound', 'local_sitsgradepush', '', $coursemoduleid); + } + + // A component grade can only be mapped to one activity, so there is only one mapping record for each component grade. + if (!empty($mapping = $this->is_component_grade_mapped($componentgradeid))) { + // Check if this mapping has grades pushed. + if ($this->has_grades_pushed($mapping->id)) { + throw new \moodle_exception( + 'error:mab_has_push_records', + 'local_sitsgradepush', + '', + $componentgrade->mapcode . '-' . $componentgrade->mabseq + ); + } + + // Nothing to update if the mapping is the same. + if ($mapping->coursemoduleid == $coursemoduleid) { + throw new \moodle_exception('error:no_update_for_same_mapping', 'local_sitsgradepush'); + } + } + + // Get existing mappings for this activity. + if ($existingmappings = $this->get_assessment_mappings($coursemoduleid)) { + // Make sure it does not map to another component grade with same map code. + foreach ($existingmappings as $existingmapping) { + if ($existingmapping->mapcode == $componentgrade->mapcode) { + throw new \moodle_exception('error:same_map_code_for_same_activity', 'local_sitsgradepush'); + } + } + } + + return true; + } + /** * Get grade of an assessment for a student. * * @param int $coursemoduleid * @param int $userid - * @return \stdClass|null + * @param int|null $partid + * @return string|null * @throws \coding_exception * @throws \moodle_exception */ - public function get_student_grade(int $coursemoduleid, int $userid): ?\stdClass { + public function get_student_grade(int $coursemoduleid, int $userid, int $partid = null): ?string { $coursemodule = get_coursemodule_from_id('', $coursemoduleid); if (empty($coursemodule)) { throw new \moodle_exception('error:coursemodulenotfound', 'local_sitsgradepush', '', $coursemoduleid); } - // Return grade of the first grade item. - if ($grade = grade_get_grades($coursemodule->course, 'mod', $coursemodule->modname, $coursemodule->instance, $userid)) { - foreach ($grade->items as $item) { - foreach ($item->grades as $grade) { - return $grade; + + // Get the assessment object. + $assessment = assessmentfactory::get_assessment($coursemodule); + if (empty($assessment)) { + throw new \moodle_exception('error:assessmentnotfound', 'local_sitsgradepush', '', $coursemoduleid); + } + + // Get the grade. + return $assessment->get_user_grade($userid, $partid); + } + + /** + * Get all course activities eligible for grade push. + * + * @param int $courseid Course id + * @return array + * @throws \moodle_exception + */ + public function get_all_course_activities(int $courseid): array { + $activities = []; + foreach (self::ALLOWED_ACTIVITIES as $modname) { + if (!empty($results = get_coursemodules_in_course($modname, $courseid))) { + foreach ($results as $result) { + $assessemnt = assessmentfactory::get_assessment($result); + $activities[] = $assessemnt; } } } - return null; + return $activities; } /** diff --git a/classes/output/pushrecord.php b/classes/output/pushrecord.php index 3d23c20..62bfa15 100644 --- a/classes/output/pushrecord.php +++ b/classes/output/pushrecord.php @@ -115,8 +115,8 @@ public function __construct(\stdClass $student, int $coursemoduleid, \stdClass $ */ protected function set_grade (int $coursemoduleid, int $studentid): void { $grade = $this->manager->get_student_grade($coursemoduleid, $studentid); - if (!empty($grade->grade)) { - $this->marks = $grade->grade; + if (isset($grade)) { + $this->marks = $grade; } } diff --git a/classes/output/renderer.php b/classes/output/renderer.php index a4c6411..5885ab1 100644 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -16,6 +16,7 @@ namespace local_sitsgradepush\output; +use local_sitsgradepush\assessment\assessment; use local_sitsgradepush\errormanager; use local_sitsgradepush\manager; use moodle_page; @@ -178,6 +179,13 @@ public function render_dashboard(array $moduledeliveries, int $courseid) : strin if (!empty($moduledelivery->componentgrades) && is_array($moduledelivery->componentgrades)) { $componentgrades = array_values($moduledelivery->componentgrades); foreach ($componentgrades as $componentgrade) { + // Add the select source url. + $selectsourceurl = new \moodle_url( + '/local/sitsgradepush/select_source.php', + ['courseid' => $courseid, 'mabid' => $componentgrade->id] + ); + $componentgrade->selectsourceurl = $selectsourceurl->out(false); + // No course module ID means the MAB is not mapped to any activity. if (empty($componentgrade->coursemoduleid)) { // Disable the change source button and push grade button if the MAB is not mapped to any activity. @@ -193,10 +201,11 @@ public function render_dashboard(array $moduledeliveries, int $courseid) : strin $assessmentmapping->id = $componentgrade->assessmentmappingid; $assessmentmapping->type = get_module_types_names()[$coursemodule->modname]; $assessmentmapping->name = $coursemodule->name; - $assessmentmapping->url = new \moodle_url( + $coursemoduleurl = new \moodle_url( '/mod/' . $coursemodule->modname . '/view.php', ['id' => $coursemodule->id] ); + $assessmentmapping->url = $coursemoduleurl->out(false); $assessmentmapping->status = $this->get_assessment_mapping_status_icon($componentgrade->assessmentmappingid); $assessmentmapping->statusicon = $assessmentmapping->status->statusicon; @@ -253,6 +262,73 @@ public function render_dashboard(array $moduledeliveries, int $courseid) : strin return $moduledeliveryselector . $moduledeliverytables . $pushallbutton . $backtotopbutton; } + /** + * Render the select source page. + * + * @return string Rendered HTML + * @throws \moodle_exception + */ + public function render_select_source_page() { + return $this->output->render_from_template('local_sitsgradepush/select_source_page', []); + } + + /** + * Render the select existing activity page. + * + * @param array $param Parameters containing course ID and MAB ID + * @return bool|string + * @throws \dml_exception + * @throws \moodle_exception + */ + public function render_existing_activity_page(array $param) { + // Make sure we have the required parameters. + if (empty($param['courseid']) || empty($param['mabid'])) { + throw new \moodle_exception('error:missingparams', 'local_sitsgradepush'); + } + + // Make sure the component grade exists. + if (empty($componentgrade = $this->manager->get_local_component_grade_by_id($param['mabid']))) { + throw new \moodle_exception('error:mab_not_found', 'local_sitsgradepush', '', $param['mabid']); + } + + $formattedactivities = []; + // Get all activities in the course. + $activities = $this->manager->get_all_course_activities($param['courseid']); + + // Remove the currently mapped activity from the list. + if ($assessmentmapping = $this->manager->is_component_grade_mapped($param['mabid'])) { + foreach ($activities as $key => $activity) { + if ($activity->get_coursemodule_id() == $assessmentmapping->coursemoduleid) { + unset($activities[$key]); + } + } + } + + foreach ($activities as $activity) { + // Skip activities mapped with the same map code. + if (!empty($mappings = $this->manager->get_assessment_mappings($activity->get_coursemodule_id()))) { + if (in_array($componentgrade->mapcode, array_column($mappings, 'mapcode'))) { + continue; + } + } + + $tempactivity = new \stdClass(); + $tempactivity->courseid = $param['courseid']; + $tempactivity->coursemoduleid = $activity->get_coursemodule_id(); + $tempactivity->mabid = $param['mabid']; + $tempactivity->mapcode = $componentgrade->mapcode; + $tempactivity->mabseq = $componentgrade->mabseq; + $tempactivity->type = $activity->get_module_type(); + $tempactivity->name = $activity->get_assessment_name(); + $tempactivity->startdate = !empty($activity->get_start_date()) ? date('d/m/Y H:i:s', $activity->get_start_date()) : '-'; + $tempactivity->enddate = !empty($activity->get_end_date()) ? date('d/m/Y H:i:s', $activity->get_end_date()) : '-'; + $formattedactivities[] = $tempactivity; + } + + return $this->output->render_from_template('local_sitsgradepush/select_source_existing', + ['activities' => $formattedactivities]); + } + /** * Get the last push result label. * @@ -374,6 +450,7 @@ private function disable_change_source_button(int $assessmentmappingid) : bool { * @param string $pushstatus Push task status * @param int $courseid Course ID * @return bool + * @throws \coding_exception */ private function disable_push_grade_button(string $pushstatus, int $courseid) : bool { // Disable the push grade button if the user does not have the capability. diff --git a/classes/task/adhoctask.php b/classes/task/adhoctask.php index d59fc6d..7e7a77a 100644 --- a/classes/task/adhoctask.php +++ b/classes/task/adhoctask.php @@ -65,7 +65,8 @@ public function execute() { // Get the course module. if (!$coursemodule = get_coursemodule_from_id(null, $assessmentmapping->coursemoduleid)) { - throw new \moodle_exception('error:coursemodulenotfound', 'local_sitsgradepush'); + throw new \moodle_exception( + 'error:coursemodulenotfound', 'local_sitsgradepush', '', $assessmentmapping->coursemoduleid); } // Check the MAB exists. diff --git a/db/services.php b/db/services.php index 777c8d6..dcfae10 100644 --- a/db/services.php +++ b/db/services.php @@ -34,6 +34,13 @@ 'type' => 'write', 'loginrequired' => true, ], + 'local_sitsgradepush_map_assessment' => [ + 'classname' => 'local_sitsgradepush\external\map_assessment', + 'description' => 'Map assessment', + 'ajax' => true, + 'type' => 'write', + 'loginrequired' => true, + ], ]; diff --git a/lang/en/local_sitsgradepush.php b/lang/en/local_sitsgradepush.php index f938dbe..4f59d4f 100644 --- a/lang/en/local_sitsgradepush.php +++ b/lang/en/local_sitsgradepush.php @@ -70,6 +70,19 @@ // Grade push index page. $string['index:header'] = 'Sits Grade Push History'; +// Select source page. +$string['selectsource:header'] = 'Select Source'; +$string['selectsource:existing'] = 'Select an Existing Activity'; +$string['selectsource:new'] = 'Create a New Activity'; +$string['selectsource:gradeitem'] = 'Select a Gradebook Item'; +$string['selectsource:mul_turnitin'] = 'Advanced Multiple Turnitin Activity Selector'; +$string['error:invalid_source_type'] = 'Invalid source type. {$a}'; + +// Existing activity page. +$string['existingactivity:header'] = 'Select Existing Activity'; +$string['existingactivity:no_match_row'] = 'No matching rows found.'; + + // Error strings. $string['error:assessmentmapping'] = 'Assessment mapping is not found. ID: {$a}'; $string['error:assessmentisnotmapped'] = 'This activity is not mapped to any assessment component.'; @@ -83,7 +96,6 @@ $string['error:emptyresponse'] = 'Empty response received when calling {$a}.'; $string['error:turnitin_numparts'] = 'Turnitin assignment with multiple parts is not supported by Grade Push.'; $string['error:duplicatedtask'] = 'There is already a push task in queue / processing for this assessment mapping.'; -$string['error:coursemodulenotfound'] = 'Course module not found.'; $string['error:tasknotfound'] = 'Push task not found.'; $string['error:multiplemappingsnotsupported'] = 'Multiple assessment component mappings is not supported by {$a}'; $string['error:studentnotfound'] = 'Student with idnumber {$a->idnumber} not found for component grade {$a->componentgrade}'; @@ -92,6 +104,11 @@ $string['error:nomoduledeliveryfound'] = 'No module delivery found.'; $string['error:no_mab_found'] = 'No assessment component found for this module delivery.'; $string['error:mab_not_found'] = 'Assessment component not found. ID: {$a}'; +$string['error:assessmentnotfound'] = 'Error getting assessment. ID: {$a}'; +$string['error:mab_has_push_records'] = 'Assessment component mapping cannot be updated as grades have been pushed for {$a}'; +$string['error:no_update_for_same_mapping'] = 'Nothing to update as the assessment component is already mapped to this activity.'; +$string['error:same_map_code_for_same_activity'] = 'An activity cannot be mapped to more than one assessment component with same map code'; +$string['error:missingparams'] = 'Missing parameters.'; $string['form:alert_no_mab_found'] = 'No assessment components found'; $string['form:info_turnitin_numparts'] = 'Please note Turnitin assignment with multiple parts is not supported by Grade Push.'; diff --git a/lib.php b/lib.php index 4c28e4a..1abbb08 100644 --- a/lib.php +++ b/lib.php @@ -283,7 +283,7 @@ function local_sitsgradepush_extend_navigation_course(navigation_node $parentnod navigation_node::NODETYPE_LEAF, 'local_sitsgradepush', 'local_sitsgradepush', - new pix_icon('repeat', get_string('pluginname', 'local_sitsgradepush'), 'local_sitsgradepush') + new pix_icon('i/grades', get_string('pluginname', 'local_sitsgradepush'), 'moodle') ); if ($PAGE->url->compare($url, URL_MATCH_BASE)) { diff --git a/select_source.php b/select_source.php new file mode 100644 index 0000000..80befce --- /dev/null +++ b/select_source.php @@ -0,0 +1,105 @@ +. + +/** + * Select source pages for local_sitsgradepush to select source for assessment component. + * + * @package local_sitsgradepush + * @copyright 2023 onwards University College London {@link https://www.ucl.ac.uk/} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Alex Yeung + */ + +namespace local_sitsgradepush; + +use context_course; +use moodle_exception; +use moodle_url; + +require_once('../../config.php'); + +// Course ID. +$courseid = required_param('courseid', PARAM_INT); +$mabid = required_param('mabid', PARAM_INT); +$source = optional_param('source', '', PARAM_TEXT); + +// Get course context. +$context = context_course::instance($courseid); + +// Get course instance. +if (!$course = get_course($courseid)) { + throw new moodle_exception('course not found.', 'local_sitsgradepush'); +} + +// Get the component grades. +$manager = manager::get_manager(); + +// Check MAB exists. +if ($manager->get_local_component_grade_by_id($mabid) === false) { + throw new moodle_exception('error:mab_not_found', 'local_sitsgradepush', '', $mabid); +} + +// Make sure user is authenticated. +require_login(); + +// Check user's capability. +require_capability('local/sitsgradepush:mapassessment', $context); + +// Set the required data into the PAGE object. +$param = ['courseid' => $courseid, 'mabid' => $mabid]; +$url = new moodle_url('/local/sitsgradepush/select_source.php', $param); +$PAGE->set_context($context); +$PAGE->set_url($url); +$PAGE->set_title('SITS Grade Push Select Source'); +$PAGE->set_secondary_navigation(false); + +// Set the breadcrumbs. +$PAGE->navbar->add(get_string('courses'), new moodle_url('/course/index.php')); +$PAGE->navbar->add($course->fullname, new moodle_url('/course/view.php', ['id' => $courseid])); +$PAGE->navbar->add('SITS Grade Push', + new moodle_url('/local/sitsgradepush/dashboard.php', ['id' => $courseid])); +$PAGE->navbar->add('Select Source', new moodle_url('/local/sitsgradepush/select_source.php', $param)); +if (!empty($source)) { + $param['source'] = $source; + $PAGE->navbar->add('Existing Activity', new moodle_url('/local/sitsgradepush/select_source.php', $param)); +} + +// Page header. +echo $OUTPUT->header(); + +// Get renderer. +$renderer = $PAGE->get_renderer('local_sitsgradepush'); + +if (empty($source)) { + // Render the page. + echo $renderer->render_select_source_page(); + // Initialise the javascript. + $PAGE->requires->js_call_amd('local_sitsgradepush/select_source', 'init', [$courseid, $mabid]); +} else { + switch ($source) { + case 'existing': + // Render the page. + echo $renderer->render_existing_activity_page($param); + // Initialise the javascript. + $PAGE->requires->js_call_amd('local_sitsgradepush/existing_activity', 'init', []); + break; + default: + throw new moodle_exception('error:invalid_source_type', 'local_sitsgradepush', '', $source); + } +} + +// Page footer. +echo $OUTPUT->footer(); diff --git a/styles.css b/styles.css index 6aa3ffd..90046c9 100644 --- a/styles.css +++ b/styles.css @@ -55,3 +55,54 @@ .sitsgradepush-dasboard .back-to-top:hover { background-color: #0056b3; /* Hover background color */ } + +/* Styles for select source page */ +/* Styles scoped to cards within a specific .sitsgradepush-select-source container */ +.sitsgradepush-select-source .card-custom { + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 30px; + height: 200px; + width: 100%; + transition: transform 0.3s, box-shadow 0.3s; + border-radius: 15px; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); + color: #333; +} + +/* Card background colors */ +.sitsgradepush-select-source .card-1 { + background-color: #e0bbe4; +} +.sitsgradepush-select-source .card-2 { + background-color: #ffdfd3; +} +.sitsgradepush-select-source .card-3 { + background-color: #bee7e9; +} +.sitsgradepush-select-source .card-4 { + background-color: #ffebbb; +} + +/* Hover effect for cards */ +.sitsgradepush-select-source .card-custom:hover { + transform: scale(1.05); + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); +} + +/* Centering the card container on the page */ +.sitsgradepush-select-source .centered-container { + min-height: 100vh; + display: flex; + flex-direction: column; + justify-content: flex-start; + margin-top: 100px; + align-items: center; +} + +/* Max width for the container */ +.sitsgradepush-select-source .container { + max-width: 90%; +} diff --git a/templates/module_delivery_table.mustache b/templates/module_delivery_table.mustache index c78271e..7555876 100644 --- a/templates/module_delivery_table.mustache +++ b/templates/module_delivery_table.mustache @@ -109,10 +109,8 @@ {{type}} {{/assessmentmapping}} {{^assessmentmapping}} - + {{/assessmentmapping}} @@ -123,6 +121,7 @@ data-toggle="tooltip" data-placement="top" title="Change Source" + data-url="{{selectsourceurl}}" {{#assessmentmapping}} data-assessmentmappingid="{{id}}" {{/assessmentmapping}} diff --git a/templates/select_source_existing.mustache b/templates/select_source_existing.mustache new file mode 100644 index 0000000..be1a021 --- /dev/null +++ b/templates/select_source_existing.mustache @@ -0,0 +1,85 @@ +{{! + This file is part the Local Analytics plugin for Moodle + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template local_sitsgradepush/select_source_existing + + Template for displaying existing activities. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * activities - Array, array of activity. + * type - String, acitivity type. + * name - String, activity name. + * startdate - String, activity start date. + * enddate - String, activity end date. + + Example context (json): + { + "activities": + [ + { + "type": "Assignment", + "name": "Test assignment", + "startdate": "13/07/2023 00:00:00", + "enddate": "20/07/2023 00:00:00" + } + ] + } +}} +
+

{{#str}} existingactivity:header, local_sitsgradepush {{/str}}

+ + + + + + + + + + + + + {{#activities}} + + + + + + + + {{/activities}} + +
Type Name Start DateEnd DateSelect
{{type}}{{name}}{{startdate}}{{enddate}} + +
+ +
diff --git a/templates/select_source_page.mustache b/templates/select_source_page.mustache new file mode 100644 index 0000000..5da3e44 --- /dev/null +++ b/templates/select_source_page.mustache @@ -0,0 +1,69 @@ +{{! + This file is part the Local Analytics plugin for Moodle + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template local_sitsgradepush/select_source_page + + Template for displaying source options. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * none + + Example context (json): {} +}} +

Select Source

+
+
+

{{#str}} selectsource:header, local_sitsgradepush {{/str}}

+
+
+
+
+
+ {{#str}} selectsource:existing, local_sitsgradepush {{/str}} +
+
+
+
+
+
+ {{#str}} selectsource:new, local_sitsgradepush {{/str}} +
+
+
+
+
+
+ {{#str}} selectsource:gradeitem, local_sitsgradepush {{/str}} +
+
+
+
+
+
+ {{#str}} selectsource:mul_turnitin, local_sitsgradepush {{/str}} +
+
+
+
+
+
+
diff --git a/version.php b/version.php index 8cc5bba..f6d9902 100644 --- a/version.php +++ b/version.php @@ -27,7 +27,7 @@ $plugin->component = 'local_sitsgradepush'; $plugin->release = '0.1.0'; -$plugin->version = 2023112400; +$plugin->version = 2023112901; $plugin->requires = 2021051708; $plugin->maturity = MATURITY_ALPHA; $plugin->dependencies = [