diff --git a/backend/src/models/index.js b/backend/src/models/index.js index 7d366d6bc356a800a81dfce11183858cdb72056d..93bd054e43ac788970c2ca485b9c745c7bab470a 100644 --- a/backend/src/models/index.js +++ b/backend/src/models/index.js @@ -5,7 +5,7 @@ const path = require('path'); const Sequelize = require('sequelize'); const basename = path.basename(__filename); const env = process.env.NODE_ENV || 'development'; -const config = require(__dirname + '/../config/config.json')[env]; +const config = require(__dirname + '/../config/config.json')[env.trim()]; const db = {}; let sequelize; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 581e5830457992857b9f096531b0687e31934c38..2b61b803b256a42b09ed4ecff30c0634a35c3fba 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1748,6 +1748,16 @@ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "optional": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "cacache": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", @@ -1774,6 +1784,34 @@ "unique-filename": "^1.1.1" } }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "optional": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "optional": true + }, "fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -1785,6 +1823,25 @@ "universalify": "^0.1.0" } }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "optional": true + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "optional": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, "ssri": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", @@ -1795,6 +1852,16 @@ "minipass": "^3.1.1" } }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "optional": true, + "requires": { + "has-flag": "^4.0.0" + } + }, "terser-webpack-plugin": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz", @@ -1811,6 +1878,18 @@ "terser": "^4.6.12", "webpack-sources": "^1.4.3" } + }, + "vue-loader-v16": { + "version": "npm:vue-loader@16.2.0", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.2.0.tgz", + "integrity": "sha512-TitGhqSQ61RJljMmhIGvfWzJ2zk9m1Qug049Ugml6QP3t0e95o0XJjk29roNEiPKJQBEi8Ord5hFuSuELzSp8Q==", + "dev": true, + "optional": true, + "requires": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "loader-utils": "^2.0.0" + } } } }, @@ -12757,87 +12836,6 @@ } } }, - "vue-loader-v16": { - "version": "npm:vue-loader@16.1.2", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz", - "integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==", - "dev": true, - "optional": true, - "requires": { - "chalk": "^4.1.0", - "hash-sum": "^2.0.0", - "loader-utils": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "optional": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "optional": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "optional": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "optional": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "optional": true - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "optional": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "optional": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "vue-router": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 39f634cc4d94909d6e70f511e1d0d11d1dcce272..6cfd88ca8a5f68cc845cd2f3d27aa50078d5dc8a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -57,11 +57,7 @@ "parser": "babel-eslint" }, "rules": { - "prettier/prettier": "error", - "indent": [ - "error", - 2 - ] + "prettier/prettier": "error" } }, "browserslist": [ diff --git a/frontend/src/components/LecturerCourseEnrollment/LecturerCourseEnrollment.vue b/frontend/src/components/LecturerCourseEnrollment/LecturerCourseEnrollment.vue index 082c489bfbeea59214a19720442aec04e8f25302..1aeb434fd8705140096b0f0c847cae804aeac33c 100644 --- a/frontend/src/components/LecturerCourseEnrollment/LecturerCourseEnrollment.vue +++ b/frontend/src/components/LecturerCourseEnrollment/LecturerCourseEnrollment.vue @@ -202,25 +202,101 @@ tidak dapat membatalkan FRS hingga periode PRS dimulai. </p> </div> - <div class="row"> - <div class="form-group col-6"> - <button - type="button" - class="btn btn-secondary-six col-12" - data-dismiss="modal" - > - Batal - </button> + <div class="modal-body overflow-auto"> + <div class="row" v-if="selectedStudent"> + <div class="col"> + {{ selectedStudent.name }} - {{ selectedStudent.nim }} / Total + SKS : {{ selectedStudent.studyPlan.creditsTotal }} + </div> + <div class="col-auto"> + Status + <span + class="badge" + :class=" + selectedStudent.studyPlan.status === 'APPROVED' + ? 'badge-success' + : 'badge-danger' + " + >{{ + selectedStudent.studyPlan.status === "APPROVED" + ? "Disetujui" + : "Tidak Disetujui" + }}</span + > + </div> </div> - <div class="form-group col-6"> - <button - type="button" - class="btn btn-primary-six col-12" - data-dismiss="modal" - @click="submitPRSButton" - > - Simpan - </button> + + <div class="row"> + <!-- Field Table --> + <table class="table table-hover my-4"> + <thead> + <tr> + <th scope="col">#</th> + <th scope="col">Kode</th> + <th scope="col">Mata Kuliah</th> + <th scope="col" class="text-center">Kelas</th> + <th scope="col" class="text-center">SKS</th> + <th scope="col" class="text-center">Jenis Mata Kuliah</th> + <th scope="col" class="text-center">Penyetujuan</th> + </tr> + </thead> + <tbody v-if="selectedStudent && selectedStudent.studyPlan"> + <tr + v-for="(item, i) of selectedStudent.studyPlan + .studyPlanCourses" + :key="'student' + i" + > + <th scope="row">{{ i + 1 }}</th> + <td>{{ item.code }}</td> + <td>{{ item.name }}</td> + <td class="text-center">01</td> + <td class="text-center">{{ item.credits }}</td> + <td class="text-center">{{ item.type }}</td> + <!-- @TODO: modify code in here --> + <td class="text-center"> + <div class="col-auto"> + <div class="form-group"> + <div class="custom-control custom-switch"> + <input + type="checkbox" + class="custom-control-input p-0" + :id="'customSwitch' + i" + :checked="item.status === 'APPROVED'" + @click="updateStudyPlan(i)" + /> + <label + class="custom-control-label" + :for="'customSwitch' + i" + > + </label> + </div> + </div> + </div> + </td> + </tr> + </tbody> + </table> + </div> + <div> + <div class="form-group col-6"> + <button + type="button" + class="btn btn-secondary-six col-12" + data-dismiss="modal" + > + Batal + </button> + </div> + <div class="form-group col-6"> + <button + type="button" + class="btn btn-primary-six col-12" + data-dismiss="modal" + @click="submitPRSButton" + > + Simpan + </button> + </div> </div> </div> </div> @@ -451,13 +527,13 @@ <input type="checkbox" class="custom-control-input p-0" - id="customSwitch1" - v-model="selectedStudentapproved" - @click="updateStudyPlan" + :id="'custom1Switch' + i" + :checked="item.status === 'APPROVED'" + @click="updateStudyPlan(i)" /> <label class="custom-control-label" - for="customSwitch1" + :for="'custom1Switch' + i" > </label> </div> @@ -638,11 +714,6 @@ export default { openEditStudentCourseEnrollmentModal(student) { this.selectedStudent = student; this.getCoursesSelectedStudent(); - if (student.studyPlan.status === "APPROVED") { - this.selectedStudentapproved = true; - } else { - this.selectedStudentapproved = false; - } this.editModal = new Modal(document.getElementById("editButton")); this.editModal.show(); }, @@ -651,10 +722,36 @@ export default { this.editModal.hide(); }, submitStudentCourseEnrollmentModal() { - this.selectedStudent = null; + let notApproved = 0; + for (const sp of this.selectedStudent.studyPlan.studyPlanCourses) { + if (sp.status !== "APPROVED") { + notApproved++; + } + } this.editModal.hide(); - // @TODO: Add if statement student course enrollment didn't approved - this.openNotesApprovalModal(); + if (notApproved > 0) { + this.openNotesApprovalModal(); + } else { + const requestBody = { + status: "APPROVED" + }; + axios + .put( + process.env.VUE_APP_API_ENDPOINT + + "/study-plan/" + + this.selectedStudent.studyPlan.id, + requestBody + ) + .then(async (response) => { + if (response.status == 200) { + await this.getAdvisedStudents(); + this.selectedStudent = null; + } + }) + .catch((error) => { + console.error(error); + }); + } }, openNotesApprovalModal() { this.notesModal = new Modal(document.getElementById("notesApproval")); @@ -664,7 +761,27 @@ export default { this.notesModal.hide(); }, submitNotesApproval() { - console.log(this.reasonApproval); + const requestBody = { + status: "SUBMITTED", + notes: this.reasonApproval + }; + axios + .put( + process.env.VUE_APP_API_ENDPOINT + + "/study-plan/" + + this.selectedStudent.studyPlan.id, + requestBody + ) + .then(async (response) => { + if (response.status == 200) { + await this.getAdvisedStudents(); + } + }) + .catch((error) => { + console.error(error); + }); + this.notesModal.hide(); + this.selectedStudent = null; }, // Get methods async getAllStudents() { @@ -738,6 +855,8 @@ export default { }, async getAdvisedStudents() { const lecturerId = localStorage.uid; + this.studentsData = []; + this.studentsDataAdvised = []; await axios .get(process.env.VUE_APP_API_ENDPOINT + "/lecturer/" + lecturerId) .then((response) => { @@ -829,14 +948,23 @@ export default { }); } }, - // Update Study Plan - updateStudyPlan() { + updateStudyPlan(index) { let newStatus = "APPROVED"; - if (this.selectedStudentapproved) { - newStatus = "SUBMITTED"; + if ( + this.selectedStudent.studyPlan.studyPlanCourses[index].status === + newStatus + ) { + newStatus = "UNAPPROVED"; } + const updateStudent = Object.assign({}, this.selectedStudent); + updateStudent.studyPlan.studyPlanCourses[index].status = newStatus; const requestBody = { - status: newStatus + studyPlanCourses: updateStudent.studyPlan.studyPlanCourses.map((sp) => { + return { + courseClassId: sp.courseClassId, + status: sp.status + }; + }) }; axios .put( @@ -845,15 +973,9 @@ export default { this.selectedStudent.studyPlan.id, requestBody ) - .then((response) => { + .then(async (response) => { if (response.status == 200) { - if (newStatus === "APPROVED") { - this.selectedStudentapproved = true; - } else { - this.selectedStudentapproved = false; - } - this.selectedStudent.studyPlan.status = newStatus; - console.log(response); + await this.getCoursesSelectedStudent(); } }) .catch((error) => {