diff --git a/.gitignore b/.gitignore index ee359c3a..54446e25 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ api/node_modules api/env.local db attachments-db +nextjs/.next +nextjs/node_modules + diff --git a/api/controllers/ProposalsController.js b/api/controllers/ProposalsController.js index 2a5f92f4..b57f928e 100644 --- a/api/controllers/ProposalsController.js +++ b/api/controllers/ProposalsController.js @@ -5,7 +5,8 @@ const ModelController = require('./ModelController'); const Proposal = require('../models/Proposal'); const Curriculum = require('../models/Curriculum'); const Degree = require('../models/Degree'); -const { ForbiddenError, BadRequestError } = require('../exceptions/ApiException') +const Exam = require('../models/Exam'); +const { ForbiddenError, BadRequestError, ValidationError } = require('../exceptions/ApiException') const fields = { "state": { @@ -94,6 +95,8 @@ const ProposalsController = { const user = req.user const body = req.body const now = new Date() + const issues = {} + let has_issues = false // l'autenticazione viene controllata nel router // dunque l'utente ci deve essere @@ -114,6 +117,7 @@ const ProposalsController = { const degree = await Degree.findOne({_id: new ObjectId(curriculum.degree_id)}) if (!degree) throw new BadRequestError("Degree not found") const obj = { + state, curriculum_id: new ObjectId(body.curriculum_id), curriculum_name: curriculum.name, degree_academic_year: degree.academic_year, @@ -136,13 +140,14 @@ const ProposalsController = { if (body.exams.length !== curriculum.years.length) throw new BadRequestError(`Years count mismatch`) + issues.exams = [] for (let year=0; year < curriculum.years.length; ++year) { const year_exams = curriculum.years[year].exams if (body.exams[year].length acc + y.credits, 0) @@ -257,8 +285,8 @@ const ProposalsController = { } } } - - const proposal = new Proposal(body) + console.log(`creating proposal with body ${JSON.stringify(obj)}`) + const proposal = new Proposal(obj) return await proposal.save() }, diff --git a/api/models/ProposalSchema.js b/api/models/ProposalSchema.js index c1eab766..3cb150d7 100644 --- a/api/models/ProposalSchema.js +++ b/api/models/ProposalSchema.js @@ -53,6 +53,7 @@ ProposalSchema = new mongoose.Schema({ state: { type: String, enum: ['draft', 'submitted', 'approved', 'rejected'], + required: true, }, date_modified: { type: Date, diff --git a/frontend/src/modules/engine.tsx b/frontend/src/modules/engine.tsx index 577f72e6..c4bdc024 100644 --- a/frontend/src/modules/engine.tsx +++ b/frontend/src/modules/engine.tsx @@ -191,7 +191,7 @@ export function useIndex(path:string, query={}) { }) } -export function usePost(path: string) { +export function usePost(path: string, onError?: (err: any) => void) { // funziona anche per Multipart Post const queryClient = useQueryClient() return useMutation({ @@ -201,6 +201,7 @@ export function usePost(path: string) { onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: [path] }) }, + onError, }) } @@ -424,8 +425,8 @@ export function useGetProposal(id:string|undefined) { return useGet('proposals/', id) } -export function usePostProposal() { - return usePost('proposals/') +export function usePostProposal(onError?: (err: any) => void) { + return usePost('proposals/', onError) } export function useIndexProposal(query={}) { diff --git a/frontend/src/pages/ProposalPage.tsx b/frontend/src/pages/ProposalPage.tsx index c78c2dc5..25d62e36 100644 --- a/frontend/src/pages/ProposalPage.tsx +++ b/frontend/src/pages/ProposalPage.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, SetStateAction, useState } from 'react' +import React, { Dispatch, SetStateAction, useState, useEffect } from 'react' import { useParams } from "react-router-dom" import {Button, ButtonGroup} from 'react-bootstrap' import assert from 'assert' @@ -20,14 +20,16 @@ import {FlashCard} from '../components/Flash' export function EditProposalPage() { const { id } = useParams() - const proposalQuery = useGetProposal(id || '') + const curriculumQuery = useGetCurriculum(proposalQuery.data?.curriculum_id || '') if (proposalQuery.isError) return errore piano di studi... if (!proposalQuery.data) return caricamento piano di studi... + + if (curriculumQuery.isError) return errore curriculum... + if (!curriculumQuery.data) return caricamento curriculum... - - return + return } export default function ProposalPage() { @@ -132,27 +134,27 @@ export default function ProposalPage() { } function ExamRow({ exam }) { - const query = useGetExam(exam.exam_id || null) + const query = useGetExam(exam ? exam.exam_id : null) if (query.isLoading) return loading... if (query.isError) return error... const real_exam: any = query.data return - {exam.exam_code} - {exam.exam_name} + {exam?.exam_code || '---'} + {exam?.exam_name || '---'} {real_exam && real_exam.tags.map(tag =>
{tag}
)} - {exam.exam_sector} - {exam.exam_credits} - {{ + {exam?.exam_sector || '---'} + {exam?.exam_credits || '---'} + {exam ? { 'CompulsoryExam': 'Obbligatorio', 'CompulsoryGroup': exam.group, 'FreeChoiceGroup': 'A scelta libera (G)', 'FreeChoiceExam': 'A scelta libera', 'ExternalExam': 'Esame Esterno', - }[exam.__t]} + }[exam.__t] : '---'} } @@ -172,7 +174,7 @@ export default function ProposalPage() { - { exams .map(e => ) } + { exams.map((e,i) => ) } @@ -209,16 +211,20 @@ export default function ProposalPage() { } } -function ProposalForm({ proposal }:{ - proposal: ProposalGet +function ProposalForm({ proposal, curriculum }:{ + proposal: ProposalGet, + curriculum: CurriculumGet, }) { + // curriculum serve solo per conoscere il degreeId + const engine = useEngine() - const [degreeId, setDegreeId] = useState(proposal.degree_id) + const [issues, setIssues] = useState({}) + const [degreeId, setDegreeId] = useState(curriculum?.degree_id || null) const [curriculumId, setCurriculumId] = useState(proposal.curriculum_id) const degreesQuery = useIndexDegree() - const curriculaQuery = useIndexCurriculum(degreeId ? { degree_id: degreeId } : undefined ) + const curriculaQuery = useIndexCurriculum(degreeId ? { degree_id: degreeId } : undefined) const examsQuery = useIndexExam() if (degreesQuery.isError) return errore corsi di laurea... @@ -280,7 +286,10 @@ function ProposalForm({ proposal }:{ { degreeId && curriculumId && - + } @@ -302,11 +311,13 @@ function ProposalForm({ proposal }:{ } } -function ProposalFormYears({ proposal, curriculum, allExams, degree }:{ +function ProposalFormYears({ proposal, curriculum, allExams, degree, issues, setIssues }:{ proposal: ProposalGet, curriculum: CurriculumGet, degree: DegreeGet, allExams: {[key: string]: ExamGet}, + issues: any, + setIssues: Dispatch>, }) { const groups = Object.fromEntries( Object.entries(degree.groups).map(([group_id, group_exams]) => { @@ -319,8 +330,12 @@ function ProposalFormYears({ proposal, curriculum, allExams, degree }:{ // console.log(JSON.stringify({chosenExams,groups})) return <> - {curriculum.years.map((yearExams, number) => )} - + {curriculum.years.map((yearExams, number) => + )} + function initExams(curriculum: CurriculumGet): ProposalExamPost[][] { @@ -390,13 +405,14 @@ function ProposalFormYears({ proposal, curriculum, allExams, degree }:{ } } -function YearCard({ year, number, allExams, chosenExams, setChosenExams, groups }:{ +function YearCard({ year, number, allExams, chosenExams, setChosenExams, groups, issues }:{ year: CurriculumGet['years'][0], number: number, allExams: {[key: string]: ExamGet}, chosenExams: ProposalExamPost[][], setChosenExams: Dispatch>, groups: {[key: string]: ExamGet[]}, + issues: any, }) { const yearName = ["Primo", "Secondo", "Terzo"][number] || `#${number}` @@ -419,7 +435,11 @@ function YearCard({ year, number, allExams, chosenExams, setChosenExams, groups return - {year.exams.map((exam, examNumber) => )} + {year.exams.map((exam, examNumber) => + )}
@@ -435,12 +455,13 @@ function YearCard({ year, number, allExams, chosenExams, setChosenExams, groups } } -function ExamSelect({ exam, allExams, groups, chosenExam, setExam }:{ +function ExamSelect({ exam, allExams, groups, chosenExam, setExam, issues }:{ exam: CurriculumExamGet, allExams: {[key: string]: ExamGet}, groups: {[key: string]: ExamGet[]}, chosenExam: ProposalExamPost, setExam: (e:string) => void, + issues: any, }) { if (exam.__t === "CompulsoryExam") { const compulsoryExam = allExams[exam.exam_id] @@ -459,8 +480,9 @@ function ExamSelect({ exam, allExams, groups, chosenExam, setExam }:{ } else if (exam.__t === "CompulsoryGroup" || exam.__t === "FreeChoiceGroup") { const options = groups[exam.group] const exam_id = typeof(chosenExam) === 'string' ? chosenExam : '' + const style=issues?{background:"yellow",padding:"1ex"}:{} - return
  • + return
  • + {issues &&
    {issues}
    }
    @@ -510,19 +533,22 @@ function FreeChoiceExamSelect({allExams, chosenExam, setExam}:{
  • } -function Submit({proposal, curriculum, chosenExams}:{ +function Submit({proposal, curriculum, chosenExams, setIssues}:{ proposal: ProposalGet, curriculum: CurriculumGet, chosenExams: ProposalExamPost[][], + setIssues: Dispatch>, }) { - const poster = usePostProposal() + const poster = usePostProposal((err:any) => { + setIssues(err.response.data.issues) + }) if (poster.isLoading) return return <> {poster.isError && } diff --git a/frontend/src/pages/UserPage.tsx b/frontend/src/pages/UserPage.tsx index b7eeff29..39a486a0 100644 --- a/frontend/src/pages/UserPage.tsx +++ b/frontend/src/pages/UserPage.tsx @@ -93,6 +93,8 @@ function ProposalCard({proposal}) { }) } + console.log(`proposal ${JSON.stringify(proposal)}`) + const [state, stateClass] = { draft: ["Bozza", "secondary"], submitted: ["Inviato", "warning"], @@ -101,16 +103,17 @@ function ProposalCard({proposal}) { }[proposal.state] return
    - navigate(`/proposals/${proposal._id}`)}> +
    {state}
    + { proposal.state === "draft" ? <> - +