diff --git a/package-lock.json b/package-lock.json index fbcc8a4..190f87d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,8 @@ "bootstrap": "^5.3.3", "bootstrap-icons": "^1.11.3", "file-saver": "^2.0.5", + "jspdf": "^2.5.2", + "jspdf-autotable": "^3.8.4", "jwt-decode": "^4.0.0", "mammoth": "^1.8.0", "react": "^18.3.1", @@ -2223,6 +2225,13 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/react": { "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", @@ -2492,6 +2501,18 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -2556,6 +2577,16 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -2661,6 +2692,18 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -2708,6 +2751,33 @@ } ] }, + "node_modules/canvg": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", + "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/canvg/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT", + "optional": true + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -2836,6 +2906,18 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/core-js": { + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz", + "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -2871,6 +2953,16 @@ "node": ">= 8" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -3025,6 +3117,13 @@ "csstype": "^3.0.2" } }, + "node_modules/dompurify": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.7.tgz", + "integrity": "sha512-2q4bEI+coQM8f5ez7kt2xclg1XsecaV9ASJk/54vwlfRRNQfDqJz2pzQ8t0Ix/ToBpXlVjrRIx7pFC/o8itG2Q==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optional": true + }, "node_modules/duck": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz", @@ -3592,6 +3691,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -3955,6 +4060,20 @@ "react-is": "^16.7.0" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "optional": true, + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -4465,6 +4584,33 @@ "node": ">=6" } }, + "node_modules/jspdf": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz", + "integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "fflate": "^0.8.1" + }, + "optionalDependencies": { + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^2.5.4", + "html2canvas": "^1.0.0-rc.5" + } + }, + "node_modules/jspdf-autotable": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-3.8.4.tgz", + "integrity": "sha512-rSffGoBsJYX83iTRv8Ft7FhqfgEL2nLpGAIiqruEQQ3e4r0qdLFbPUB7N9HAle0I3XgpisvyW751VHCqKUVOgQ==", + "license": "MIT", + "peerDependencies": { + "jspdf": "^2.5.1" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -4938,6 +5084,13 @@ "node": ">=8" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", + "optional": true + }, "node_modules/picocolors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", @@ -5050,6 +5203,16 @@ } ] }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -5316,6 +5479,16 @@ "node": ">=0.10.0" } }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -5554,6 +5727,16 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -5699,6 +5882,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -5928,6 +6131,16 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "optional": true, + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/vanilla-colorful": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/vanilla-colorful/-/vanilla-colorful-0.7.2.tgz", diff --git a/package.json b/package.json index e5c553c..537548d 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "bootstrap": "^5.3.3", "bootstrap-icons": "^1.11.3", "file-saver": "^2.0.5", + "jspdf": "^2.5.2", + "jspdf-autotable": "^3.8.4", "jwt-decode": "^4.0.0", "mammoth": "^1.8.0", "react": "^18.3.1", diff --git a/src/roles/admin/manage_progress/hooks/useProgressStudent.jsx b/src/roles/admin/manage_progress/hooks/useProgressStudent.jsx index a89b9f2..1c75ea4 100644 --- a/src/roles/admin/manage_progress/hooks/useProgressStudent.jsx +++ b/src/roles/admin/manage_progress/hooks/useProgressStudent.jsx @@ -1,5 +1,7 @@ import { useState, useEffect } from 'react'; import progressService from '../services/serviceProgress'; +import jsPDF from "jspdf"; +import "jspdf-autotable"; const useProgressStudent = (monitoringId) => { const [progress, setProgress] = useState([]); @@ -23,7 +25,6 @@ const useProgressStudent = (monitoringId) => { try { const data = await progressService.fetchDataStudentProgress(monitoringId, search, sort, page, limit); setProgress(data.payload.levels); - console.log(data.payload); setTotalPages(data.payload.totalPages); setTotalData(data.payload.totalItems); @@ -74,6 +75,35 @@ const useProgressStudent = (monitoringId) => { .replace(/\./g, ':') + ' WIB'; } + const downloadPDF = () => { + const doc = new jsPDF(); + const pageWidth = doc.internal.pageSize.getWidth(); + + const text = "Progress Report"; + const textWidth = doc.getTextWidth(text); + const titleCenter = (pageWidth - textWidth) / 2; + + doc.setFontSize(14); + doc.text(text, titleCenter, 12); + + doc.setFontSize(12); + doc.text(name, 14, 24); + doc.text(nisn.toString(), 14, 30); + doc.setFontSize(10); + doc.text(`${section} - ${topic}`, 14, 36); + + const columns = ["No", "Name", "Score", "Feedback", "Start Exercise", "Finish Exercise"]; + const rows = progress.map((item, index) => [(index+1), item.NAME_LEVEL, item.SCORE, item.FEEDBACK_STUDENT, formatLocalDate(item.STUDENT_START), formatLocalDate(item.STUDENT_FINISH)]); + + doc.autoTable({ + head: [columns], + body: rows, + startY: 44, + }); + + doc.save(`SEALS-report-${name}.pdf`); + }; + return { progress, section, @@ -90,7 +120,8 @@ const useProgressStudent = (monitoringId) => { handlePageChange, handleLimitsChange, handleSearchChange, - formatLocalDate + formatLocalDate, + downloadPDF }; }; diff --git a/src/roles/admin/manage_progress/views/StudentProgress.jsx b/src/roles/admin/manage_progress/views/StudentProgress.jsx index fbd8a07..6f6cca1 100644 --- a/src/roles/admin/manage_progress/views/StudentProgress.jsx +++ b/src/roles/admin/manage_progress/views/StudentProgress.jsx @@ -22,7 +22,8 @@ const StudentProgress = () => { handlePageChange, handleLimitsChange, handleSearchChange, - formatLocalDate + formatLocalDate, + downloadPDF } = useProgressStudent(progressId); return (
@@ -37,7 +38,7 @@ const StudentProgress = () => { - @@ -86,7 +87,7 @@ const StudentProgress = () => { ):( progress.length > 0?( progress.map((data, index) => ( - + {index + 1} {data.NAME_LEVEL} {data.SCORE} diff --git a/src/roles/teacher/monitoring/hooks/useProgressStudent.jsx b/src/roles/teacher/monitoring/hooks/useProgressStudent.jsx index a89b9f2..1c75ea4 100644 --- a/src/roles/teacher/monitoring/hooks/useProgressStudent.jsx +++ b/src/roles/teacher/monitoring/hooks/useProgressStudent.jsx @@ -1,5 +1,7 @@ import { useState, useEffect } from 'react'; import progressService from '../services/serviceProgress'; +import jsPDF from "jspdf"; +import "jspdf-autotable"; const useProgressStudent = (monitoringId) => { const [progress, setProgress] = useState([]); @@ -23,7 +25,6 @@ const useProgressStudent = (monitoringId) => { try { const data = await progressService.fetchDataStudentProgress(monitoringId, search, sort, page, limit); setProgress(data.payload.levels); - console.log(data.payload); setTotalPages(data.payload.totalPages); setTotalData(data.payload.totalItems); @@ -74,6 +75,35 @@ const useProgressStudent = (monitoringId) => { .replace(/\./g, ':') + ' WIB'; } + const downloadPDF = () => { + const doc = new jsPDF(); + const pageWidth = doc.internal.pageSize.getWidth(); + + const text = "Progress Report"; + const textWidth = doc.getTextWidth(text); + const titleCenter = (pageWidth - textWidth) / 2; + + doc.setFontSize(14); + doc.text(text, titleCenter, 12); + + doc.setFontSize(12); + doc.text(name, 14, 24); + doc.text(nisn.toString(), 14, 30); + doc.setFontSize(10); + doc.text(`${section} - ${topic}`, 14, 36); + + const columns = ["No", "Name", "Score", "Feedback", "Start Exercise", "Finish Exercise"]; + const rows = progress.map((item, index) => [(index+1), item.NAME_LEVEL, item.SCORE, item.FEEDBACK_STUDENT, formatLocalDate(item.STUDENT_START), formatLocalDate(item.STUDENT_FINISH)]); + + doc.autoTable({ + head: [columns], + body: rows, + startY: 44, + }); + + doc.save(`SEALS-report-${name}.pdf`); + }; + return { progress, section, @@ -90,7 +120,8 @@ const useProgressStudent = (monitoringId) => { handlePageChange, handleLimitsChange, handleSearchChange, - formatLocalDate + formatLocalDate, + downloadPDF }; }; diff --git a/src/roles/teacher/monitoring/views/StudentProgress.jsx b/src/roles/teacher/monitoring/views/StudentProgress.jsx index 6fcb899..971098f 100644 --- a/src/roles/teacher/monitoring/views/StudentProgress.jsx +++ b/src/roles/teacher/monitoring/views/StudentProgress.jsx @@ -22,7 +22,8 @@ const StudentProgress = () => { handlePageChange, handleLimitsChange, handleSearchChange, - formatLocalDate + formatLocalDate, + downloadPDF } = useProgressStudent(progressId); return (
@@ -37,7 +38,7 @@ const StudentProgress = () => { - @@ -86,7 +87,7 @@ const StudentProgress = () => { ):( progress.length > 0?( progress.map((data, index) => ( - + {index + 1} {data.NAME_LEVEL} {data.SCORE} diff --git a/src/utils/Constant.jsx b/src/utils/Constant.jsx index b31c660..23a8a39 100644 --- a/src/utils/Constant.jsx +++ b/src/utils/Constant.jsx @@ -1,7 +1,4 @@ -// export const API_URL = 'https://8x7r3mdp-3001.asse.devtunnels.ms'; export const API_URL = 'http://localhost:3001'; -// export const API_URL = 'https://0f4c-114-6-25-184.ngrok-free.app'; - export const slugify = (text) => { if (!text) {