Compare commits

..

13 Commits

Author SHA1 Message Date
João Geonizeli
65a3180599 Merge pull request #8 from teste-de-progresso/feat-new-assessment-manual-page
Feat new assessment manual page
2023-09-21 14:16:10 -03:00
João Geonizeli
45b401eda3 Merge pull request #7 from teste-de-progresso/update-question-list-collapse
update question list collapse
2023-09-21 14:10:40 -03:00
João Geonizeli
5899d2ad1f Merge pull request #9 from teste-de-progresso/update-new-assessement-page
add assessement endpoint
2023-09-21 14:05:17 -03:00
1edd26bf1b add assessement endpoint 2023-09-21 14:04:29 -03:00
WindowsCrashed
ce7d38e95b add filters query 2023-08-25 23:44:31 -03:00
WindowsCrashed
230a59e999 add questions query 2023-08-25 22:33:33 -03:00
WindowsCrashed
147fb997d0 update question card with question card field 2023-08-12 20:20:22 -03:00
WindowsCrashed
3d946d03ff add question area and clear selection button 2023-08-11 14:51:13 -03:00
WindowsCrashed
a28b21f44e add button to access new assessment manual 2023-08-09 12:50:21 -03:00
WindowsCrashed
8e0111b8ea add bottom bar 2023-08-09 12:30:49 -03:00
WindowsCrashed
155bbddbd7 remove package-lock.json 2023-07-08 17:06:37 -03:00
WindowsCrashed
f8ff95d188 remove yarn-error.log 2023-07-08 17:06:09 -03:00
WindowsCrashed
2a1883dd8e update useState to useLocalStorage 2023-07-08 17:03:00 -03:00
19 changed files with 657 additions and 465 deletions

View File

@@ -7,12 +7,14 @@ import { AssessmentRoutePaths, DashboardRoutePaths, QuestionRoutePaths } from ".
import { RootState } from "../../services/store"; import { RootState } from "../../services/store";
import { turnOff } from "../../services/store/unsavedChanges"; import { turnOff } from "../../services/store/unsavedChanges";
import { Dialog } from '../Dialog'; import { Dialog } from '../Dialog';
import { useCurrentUser } from "../../contexts";
export const AppbarTabs = () => { export const AppbarTabs = () => {
const unsavedChanges = useSelector((state: RootState) => state.unsavedChanges) const unsavedChanges = useSelector((state: RootState) => state.unsavedChanges)
const dispatch = useDispatch() const dispatch = useDispatch()
const location = useLocation() const location = useLocation()
const history = useHistory() const history = useHistory()
const { isOnlyTeacher } = useCurrentUser()
const [newPath, setNewPath] = useState<string>() const [newPath, setNewPath] = useState<string>()
@@ -45,14 +47,16 @@ export const AppbarTabs = () => {
tabel: 'Questões', tabel: 'Questões',
pathname: QuestionRoutePaths.index, pathname: QuestionRoutePaths.index,
isCurrent: location.pathname.includes('question'), isCurrent: location.pathname.includes('question'),
}, }]
{
icon: <DocumentIcon className="w-6" />, if (!isOnlyTeacher) {
tabel: 'Avaliações', links.push({
pathname: AssessmentRoutePaths.index, icon: <DocumentIcon className="w-6" />,
isCurrent: false, tabel: 'Avaliações',
pathname: AssessmentRoutePaths.index,
isCurrent: false,
})
} }
]
return ( return (
<> <>

View File

@@ -5,6 +5,7 @@ import { Controller, useForm } from "react-hook-form";
import { Axis, Query } from "../../__generated__/graphql-schema"; import { Axis, Query } from "../../__generated__/graphql-schema";
import { Button, Card, Input, Navigator } from '../../components'; import { Button, Card, Input, Navigator } from '../../components';
import { useHistory } from "react-router";
type NewAssessementForm = { type NewAssessementForm = {
axisWeights: Record<string, any> axisWeights: Record<string, any>
@@ -47,6 +48,8 @@ export const NewAssessement = () => {
const notSelectedAxis: Axis[] = axes.filter((axis) => !subjectsIds.includes(axis.id)) const notSelectedAxis: Axis[] = axes.filter((axis) => !subjectsIds.includes(axis.id))
const selectedAxis: Axis[] = axes.filter((axis) => subjectsIds.includes(axis.id)) const selectedAxis: Axis[] = axes.filter((axis) => subjectsIds.includes(axis.id))
const navigate = useHistory()
return ( return (
<> <>
<Navigator home /> <Navigator home />
@@ -179,9 +182,14 @@ export const NewAssessement = () => {
</div> </div>
</Card> </Card>
</div> </div>
<Button type="primary" className="ml-auto mr-6 mt-6"> <div className="flex justify-end mr-6 gap-4">
Gerar <Button type="primary" className="mt-6">
</Button> Gerar Automaticamente
</Button>
<Button type="primary" className="mt-6" onClick={() => navigate.push('new-manual')}>
Gerar Manualmente
</Button>
</div>
</> </>
) )
} }

View File

@@ -1,98 +1,84 @@
import { gql, useQuery } from "@apollo/client";
import React, { useState } from "react"; import React, { useState } from "react";
import { Controller, useForm } from "react-hook-form"; import { Navigator } from '../../components';
import { Axis, Query } from "../../__generated__/graphql-schema";
import { Button, Card, Input, Navigator } from '../../components';
import { SideBar } from "./components/SideBar";
import { QuestionCard } from "./components/QuestionCard";
import { Link } from "react-router-dom";
import { SelectedQuestionsSideBar } from "./components/SelectedQuestionsSideBar"; import { SelectedQuestionsSideBar } from "./components/SelectedQuestionsSideBar";
import { FiltersSideBar } from "./components/FiltersSideBar"; import { FiltersSideBar } from "./components/FiltersSideBar";
import { BottomBar } from "./components/BottomBar";
import { QuestionArea } from "./components/QuestionArea";
import { gql, useQuery } from "@apollo/client";
import { Query, Question } from "../../__generated__/graphql-schema";
type NewAssessementManualForm = { const QuestionFragments = gql`
axisWeights: Record<string, any> fragment QuestionFields on Question {
} id
authorship
const NEW_ASSESSEMENT_DATA_QUERY = gql` authorshipYear
query NewAssessementDataQuery { bloomTaxonomy
axes { body
nodes { checkType
id difficulty
status
subject {
axis {
name name
} }
category {
name
}
name
}
}
`
const QUESTIONS_QUERY = gql`
${QuestionFragments}
query {
questions {
nodes {
...QuestionFields
}
} }
} }
` `
export const NewAssessementManual = () => { export const NewAssessementManual = () => {
const { data } = useQuery<Query>(NEW_ASSESSEMENT_DATA_QUERY) const [questions, setQuestions] = useState<Question[]>([])
const axes = data?.axes.nodes const [selectedQuestions, setSelectedQuestions] = useState<{id: string, label: string, removeHandler: Function}[]>([])
const [questions, setQuestions] = useState<{id: string, label: string, removeHandler: Function}[]>([]) useQuery<Query>(QUESTIONS_QUERY, {
onCompleted: (response) => {
const [subjectsIds, setSubjectsIds] = useState<string[]>([]) const { questions: questionConnection } = response
const { register, control, watch } = useForm<NewAssessementManualForm>({ setQuestions(questionConnection.nodes as Question[])
mode: 'onBlur' },
fetchPolicy: "network-only"
}) })
const addAxisForm = useForm<{ axisId: string }>()
const handleAddAxis = (formData: { axisId: string }) => {
setSubjectsIds(prev => [...prev, formData.axisId])
addAxisForm.reset();
}
const handleRemoveAxis = (axisId: string) => {
setSubjectsIds(prev => prev.filter((axis => axis !== axisId)))
}
if (!axes?.length) return null
const notSelectedAxis: Axis[] = axes.filter((axis) => !subjectsIds.includes(axis.id))
const selectedAxis: Axis[] = axes.filter((axis) => subjectsIds.includes(axis.id))
const addQuestion = (label: string, removeHandler: Function) => { const addQuestion = (label: string, removeHandler: Function) => {
const id: string = label.replace(/\s+/g, '') const id: string = label.replace(/\s+/g, '_')
if (!questions.find(q => q.id === id)) { if (!selectedQuestions.find(q => q.id === id)) {
setQuestions(q => [...q, { id, label, removeHandler }]) setSelectedQuestions(q => [...q, { id, label, removeHandler }])
} }
} }
const removeQuestion = (id: string) => { const removeQuestion = (id: string) => {
setQuestions(q => q.filter(i => i.id !== id)) setSelectedQuestions(q => q.filter(i => i.id !== id))
}
const clearSelectedQuestions = () => {
setSelectedQuestions([])
} }
return ( return (
<> <>
<Navigator home /> <Navigator home />
<div className="grid grid-cols-5 gap-4 mt-4 mx-4"> <BottomBar/>
<div className="grid grid-cols-5 gap-4 mt-4 mx-4 pb-20">
<FiltersSideBar/> <FiltersSideBar/>
<div className="col-span-3 border-l-2 border-r-2 border-gray-300 px-6 pb-20"> {/*bg-blue-500*/} <QuestionArea questions={questions}
<QuestionCard title="Question 1" onAddQuestion={addQuestion} onRemoveQuestion={removeQuestion}/>
onAddQuestion={addQuestion} <SelectedQuestionsSideBar
onRemoveQuestion={removeQuestion}/> questions={selectedQuestions}
<QuestionCard title="Question 2" onClearSelectedQuestions={clearSelectedQuestions}
onAddQuestion={addQuestion} />
onRemoveQuestion={removeQuestion}/>
<QuestionCard title="Question 3"
onAddQuestion={addQuestion}
onRemoveQuestion={removeQuestion}/>
<QuestionCard title="Question 4"
onAddQuestion={addQuestion}
onRemoveQuestion={removeQuestion}/>
<QuestionCard title="Question 5"
onAddQuestion={addQuestion}
onRemoveQuestion={removeQuestion}/>
<QuestionCard title="Question 6"
onAddQuestion={addQuestion}
onRemoveQuestion={removeQuestion}/>
<QuestionCard title="Question 7"
onAddQuestion={addQuestion}
onRemoveQuestion={removeQuestion}/>
</div>
<SelectedQuestionsSideBar questions={questions}/>
</div> </div>
</> </>
) )

View File

@@ -0,0 +1,21 @@
import React, { FC, } from "react";
import { Button } from "../../../components";
type Props = {
}
export const BottomBar: FC<Props> = () => {
return (
<div className="fixed bottom-0 bg-white w-full h-16 flex items-center justify-end shadow-lg">
<div className="flex gap-6 mx-16">
<Button className="w-32">
Cancelar
</Button>
<Button type="primary" className="w-32">
Salvar
</Button>
</div>
</div>
)
}

View File

@@ -1,57 +1,83 @@
import React, { FC, } from "react"; import React, { FC, useState, } from "react";
import { SideBar } from "./SideBar"; import { SideBar } from "./SideBar";
import { SelectedQuestionCard } from "./SelectedQuestionCard";
import { Button } from "../../../components"; import { Button } from "../../../components";
import { SelectFilterField } from "./SelectFilterField"; import { SelectFilterField } from "./SelectFilterField";
import { RangeFilterField } from "./RangeFilterField"; import { RangeFilterField } from "./RangeFilterField";
import { BLOOM_TAXONOMY, CHECK_TYPE, DIFFICULTY } from "../../../utils/types";
import { gql, useQuery } from "@apollo/client";
import { Axis, Category, Query, Subject } from "../../../__generated__/graphql-schema";
const FILTERS_QUERY = gql`
query {
categories {
nodes {
id
name
}
}
axes {
nodes {
id
name
}
}
subjects {
nodes {
id
name
}
}
}
`
type Props = { type Props = {
questions?: {
id: string, label: string, removeHandler: Function
}[]
} }
export const FiltersSideBar: FC<Props> = () => { export const FiltersSideBar: FC<Props> = () => {
const [categories, setCategories] = useState<Category[]>([])
const [axis, setAxis] = useState<Axis[]>([])
const [subjects, setSubjects] = useState<Subject[]>([])
const difficulties = DIFFICULTY.map(item => ({id: item.value, label: item.label}))
const bloomTaxonomyTypes = BLOOM_TAXONOMY.map(item => ({id: item.value, label: item.label}))
const checkTypes = CHECK_TYPE.map(item => ({id: item.value, label: item.label}))
const authorshipTypes = [
{id: 1, label: 'Própria'},
{id: 2, label: 'Outro'},
]
useQuery<Query>(FILTERS_QUERY, {
onCompleted: (response) => {
const {
categories: categoriesConnection,
axes: axisConnection,
subjects: subjectConnection
} = response
setCategories(categoriesConnection.nodes as Category[])
setAxis(axisConnection.nodes as Axis[])
setSubjects(subjectConnection.nodes as Subject[])
},
fetchPolicy: "network-only"
})
return ( return (
<SideBar header="Filtros"> <SideBar header="Filtros">
<div className="mt-3"> <div className="mt-3">
<form className="flex flex-col gap-4" <form className="flex flex-col gap-4">
// onSubmit={addAxisForm.handleSubmit(handleAddAxis)} <SelectFilterField label="Grau de Dificuldade:" options={difficulties}/>
> <SelectFilterField label="Categoria:" options={
<SelectFilterField label="Grau de Dificuldade:" options={[ categories.map(item => ({id: item.id, label: item.name}))
{id: 1, label: 'Fácil'}, }/>
{id: 2, label: 'Média'}, <SelectFilterField label="Eixo de Formação:" options={
{id: 3, label: 'Difícil'} axis.map(item => ({id: item.id, label: item.name}))
]}/> }/>
<SelectFilterField label="Categoria:" options={[ <SelectFilterField label="Assunto:" options={
{id: 1, label: 'Conhecimentos Básicos'}, subjects.map(item => ({id: item.id, label: item.name}))
{id: 2, label: 'Redes e Sistemas Computacionais'}, }/>
{id: 3, label: 'Modelagem e Simulacao'} <SelectFilterField label="Habilidade Cognitiva:" options={bloomTaxonomyTypes}/>
]}/> <SelectFilterField label="Tipo:" options={checkTypes}/>
<SelectFilterField label="Eixo de Formação:" options={[ <SelectFilterField label="Autoria:" options={authorshipTypes}/>
{id: 1, label: 'Infraestrutura de Sistemas Computacionais'},
{id: 2, label: 'Sistemas de Software'},
{id: 3, label: 'Algoritmos de Alto Desempenho'}
]}/>
<SelectFilterField label="Assunto:" options={[
{id: 1, label: 'Cálculo'},
{id: 2, label: 'Pesquisa Operacional'},
{id: 3, label: 'Sistemas Digitais'}
]}/>
<SelectFilterField label="Habilidade Cognitiva:" options={[
{id: 1, label: 'Recordar'},
{id: 2, label: 'Compreender'},
{id: 3, label: 'Criar'}
]}/>
<SelectFilterField label="Tipo:" options={[
{id: 1, label: 'Resposta Multipla'},
{id: 2, label: 'Lacuna'},
{id: 3, label: 'Foco Negativo'}
]}/>
<SelectFilterField label="Autoria:" options={[
{id: 1, label: 'Própria'},
{id: 2, label: 'Outro'},
]}/>
<RangeFilterField label="Ano:"/> <RangeFilterField label="Ano:"/>
<div className="w-full flex flex-col mt-2 gap-3"> <div className="w-full flex flex-col mt-2 gap-3">
<Button type="primary" htmlType="submit"> <Button type="primary" htmlType="submit">
@@ -61,7 +87,6 @@ export const FiltersSideBar: FC<Props> = () => {
Limpar Filtro Limpar Filtro
</Button> </Button>
</div> </div>
</form> </form>
</div> </div>
</SideBar> </SideBar>

View File

@@ -0,0 +1,20 @@
import React, { FC } from "react";
import { QuestionCard } from "./QuestionCard";
import { Question } from "../../../__generated__/graphql-schema";
interface Props {
questions: Question[]
onAddQuestion: Function,
onRemoveQuestion: Function
}
export const QuestionArea: FC<Props> = ({ questions, onAddQuestion, onRemoveQuestion }) => {
return (
<div className="col-span-3 border-l-2 border-r-2 border-gray-300 px-6">
{questions.map(question =>
<QuestionCard key={question.id} question={question}
onAddQuestion={onAddQuestion}
onRemoveQuestion={onRemoveQuestion}/>)}
</div>
)
}

View File

@@ -1,17 +1,25 @@
import React, { FC, useState } from "react"; import React, { FC, useState } from "react";
import { Button, Card } from "../../../components"; import { Button, Card } from "../../../components";
import { QuestionCardField } from "./QuestionCardField";
import { Question } from "../../../__generated__/graphql-schema";
import { NodeId } from "../../../utils/graphql";
import { BLOOM_TAXONOMY, CHECK_TYPE, DIFFICULTY } from "../../../utils/types";
interface Props { interface Props {
title: string question: Question
onAddQuestion: Function, onAddQuestion: Function,
onRemoveQuestion: Function onRemoveQuestion: Function
} }
export const QuestionCard: FC<Props> = ({ title, onAddQuestion, onRemoveQuestion }) => { export const QuestionCard: FC<Props> = ({ question, onAddQuestion, onRemoveQuestion }) => {
const [collapsed, setCollapsed] = useState(false) const [collapsed, setCollapsed] = useState(false)
const questionId = title.replace(/\s+/g, '') const title = `Questão ${NodeId.decode(question.id).id}`
const htmlId = title.replace(/\s+/g, '_')
const difficulty = DIFFICULTY.find(item => item.value === question.difficulty)?.label
const bloomTaxonomy = BLOOM_TAXONOMY.find(item => item.value === question.bloomTaxonomy)?.label
const checkType = CHECK_TYPE.find(item => item.value === question.checkType)?.label
const handleAddQuestion = () => { const handleAddQuestion = () => {
setButtonState({ setButtonState({
bg: 'bg-red-700', label: 'Remover', method: handleRemoveQuestion bg: 'bg-red-700', label: 'Remover', method: handleRemoveQuestion
@@ -23,7 +31,7 @@ export const QuestionCard: FC<Props> = ({ title, onAddQuestion, onRemoveQuestion
setButtonState({ setButtonState({
bg: '', label: 'Adicionar', method: handleAddQuestion bg: '', label: 'Adicionar', method: handleAddQuestion
}) })
onRemoveQuestion(questionId) onRemoveQuestion(htmlId)
} }
const [buttonState, setButtonState] = useState({ const [buttonState, setButtonState] = useState({
@@ -31,47 +39,21 @@ export const QuestionCard: FC<Props> = ({ title, onAddQuestion, onRemoveQuestion
}) })
return ( return (
<div id={questionId}> <div id={htmlId}>
<Card title={title} className="mb-5"> <Card title={title} className="mb-5">
<div> <div>
{!collapsed && <div className="grid grid-cols-2 gap-2"> {!collapsed && <div className="grid grid-cols-2 gap-2">
<div> <QuestionCardField label="Grau de Dificuldade" value={difficulty}/>
<span className="text-gray-700">Grau de Dificuldade: </span> <QuestionCardField label="Categoria" value={question.subject?.category.name}/>
<span>Média</span> <QuestionCardField label="Eixo de Formação" value={question.subject?.axis.name}/>
</div> <QuestionCardField label="Assunto" value={question.subject?.name}/>
<div> <QuestionCardField label="Habilidade Cognitiva" value={bloomTaxonomy}/>
<span className="text-gray-700">Categoria: </span> <QuestionCardField label="Tipo" value={checkType}/>
<span>Modelagem</span> <QuestionCardField label="Autoria" value={question.authorship} />
</div> <QuestionCardField label="Ano" value={question.authorshipYear}/>
<div>
<span className="text-gray-700">Eixo de Formação: </span>
<span>Infra Sistemas</span>
</div>
<div>
<span className="text-gray-700">Assunto: </span>
<span>Fisica</span>
</div>
<div>
<span className="text-gray-700">Habilidade Cognitiva: </span>
<span>Compreender</span>
</div>
<div>
<span className="text-gray-700">Tipo: </span>
<span>Resposta Multipla</span>
</div>
<div>
<span className="text-gray-700">Autoria: </span>
<span>UNIFESO</span>
</div>
<div>
<span className="text-gray-700">Ano: </span>
<span>2023</span>
</div>
<div className="col-span-2"> <div className="col-span-2">
<span className="text-gray-700">Enunciado:</span> <span className="text-gray-700">Enunciado:</span>
<div> <div dangerouslySetInnerHTML={{__html: question.body ?? ''}}></div>
ijodsjidsoifidfsiojsdfiojdsfiodfs ijdf iodsf iosd iojdf sijodsf iojdsf ioj sdfiojdf sioj dfsiojsdf iojdfs ijodsfijoidfsijodfsijdfsijo dsiofd ijosdfjiofdsidsfio
</div>
</div> </div>
</div>} </div>}
<div className="mt-6"> <div className="mt-6">

View File

@@ -0,0 +1,16 @@
import React, { FC } from "react";
import { Maybe } from "../../../__generated__/graphql-schema";
interface Props {
label: string,
value?: Maybe<string>
}
export const QuestionCardField: FC<Props> = ({ label, value }) => {
return (
<div>
<span className="text-gray-700">{`${label}: `}</span>
<span>{value ?? ''}</span>
</div>
)
}

View File

@@ -1,7 +1,7 @@
import React, { FC, } from "react"; import React, { FC, } from "react";
type Props = { type Props = {
options: {id: number, label: string,}[] options: {id: number | string, label: string,}[]
label: string label: string
} }

View File

@@ -1,24 +1,35 @@
import React, { FC, } from "react"; import React, { FC, } from "react";
import { SideBar } from "./SideBar"; import { SideBar } from "./SideBar";
import { SelectedQuestionCard } from "./SelectedQuestionCard"; import { SelectedQuestionCard } from "./SelectedQuestionCard";
import { Button } from "../../../components";
type Props = { type Props = {
questions: { questions: {
id: string, label: string, removeHandler: Function id: string, label: string, removeHandler: Function
}[] }[]
onClearSelectedQuestions: Function
} }
export const SelectedQuestionsSideBar: FC<Props> = ({ questions }) => { export const SelectedQuestionsSideBar: FC<Props> = ({ questions, onClearSelectedQuestions }) => {
return ( return (
<SideBar header="Questões Selecionadas"> <SideBar header="Questões Selecionadas">
<div> <div>
{questions.length ? {questions.length ?
questions.map(q => <SelectedQuestionCard <>
key={q.id} id={q.id} label={q.label} <div>
onRemoveQuestion={q.removeHandler}/>) : {questions.map(q => <SelectedQuestionCard
<h2 className="text-gray-700 mt-3"> key={q.id} id={q.id} label={q.label}
Nenhuma questão selecionada onRemoveQuestion={q.removeHandler}/>)}
</h2> </div>
<div className="flex justify-center mt-6">
<Button type="primary" onClick={() => onClearSelectedQuestions()}>
Limpar Seleção
</Button>
</div>
</> :
<h2 className="text-gray-700 mt-3">
Nenhuma questão selecionada
</h2>
} }
</div> </div>
</SideBar> </SideBar>

View File

@@ -3,6 +3,7 @@ import { FaArrowLeft, FaArrowRight, FaAngleDown, FaAngleUp } from 'react-icons/f
import { MdModeEdit } from 'react-icons/md'; import { MdModeEdit } from 'react-icons/md';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';
import { useLocalStorage } from 'usehooks-ts';
import { Question, QuestionStatus } from '../../../__generated__/graphql-schema' import { Question, QuestionStatus } from '../../../__generated__/graphql-schema'
import { useCurrentUser } from '../../../contexts'; import { useCurrentUser } from '../../../contexts';
@@ -39,7 +40,7 @@ export const QuestionsListFragments = gql`
export const QuestionsList: FC<Props> = ({ questions, title, pagination }) => { export const QuestionsList: FC<Props> = ({ questions, title, pagination }) => {
const { user } = useCurrentUser() const { user } = useCurrentUser()
const [pageCount, setPageCount] = useState(1) const [pageCount, setPageCount] = useState(1)
const [collapsed, setCollapsed] = useState(false) const [collapsed, setCollapsed] = useLocalStorage<boolean>('collapsed', false)
const formatDate = (stringDate: string) => new Date(stringDate).toLocaleDateString() const formatDate = (stringDate: string) => new Date(stringDate).toLocaleDateString()

23
app/models/assessment.rb Normal file
View File

@@ -0,0 +1,23 @@
# == Schema Information
#
# Table name: assessments
#
# id :bigint not null, primary key
# observations :text
# params :jsonb
# title :string
# created_at :datetime not null
# updated_at :datetime not null
# user_id :bigint not null
#
# Indexes
#
# index_assessments_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (user_id => users.id)
#
class Assessment < ApplicationRecord
belongs_to :user
end

View File

@@ -0,0 +1,13 @@
class AssessmentPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all
end
def index?
@roles.find do |role|
admin nde coordinator center_director pro_rector teacher
end
end
end
end

View File

@@ -0,0 +1,12 @@
class CreateAssessments < ActiveRecord::Migration[7.0]
def change
create_table :assessments do |t|
t.string :title
t.text :observations
t.jsonb :params
t.references :user, null: false, foreign_key: true
t.timestamps
end
end
end

13
db/schema.rb generated
View File

@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2022_08_05_233401) do ActiveRecord::Schema[7.0].define(version: 2023_07_05_145916) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@@ -56,6 +56,16 @@ ActiveRecord::Schema[7.0].define(version: 2022_08_05_233401) do
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
end end
create_table "assessments", force: :cascade do |t|
t.string "title"
t.text "observations"
t.jsonb "params"
t.bigint "user_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_assessments_on_user_id"
end
create_table "axes", force: :cascade do |t| create_table "axes", force: :cascade do |t|
t.string "name" t.string "name"
t.datetime "created_at", null: false t.datetime "created_at", null: false
@@ -155,6 +165,7 @@ ActiveRecord::Schema[7.0].define(version: 2022_08_05_233401) do
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "assessments", "users"
add_foreign_key "questions", "subjects" add_foreign_key "questions", "subjects"
add_foreign_key "questions", "users" add_foreign_key "questions", "users"
add_foreign_key "review_messages", "questions" add_foreign_key "review_messages", "questions"

View File

@@ -38,6 +38,7 @@
"postcss": "^7.0.32", "postcss": "^7.0.32",
"postcss-cli": "^7.1.1", "postcss-cli": "^7.1.1",
"postcss-import": "^12.0.1", "postcss-import": "^12.0.1",
"tailwindcss": "^1.5.1" "tailwindcss": "^1.5.1",
"usehooks-ts": "2.6.0"
} }
} }

View File

@@ -0,0 +1,28 @@
# == Schema Information
#
# Table name: assessments
#
# id :bigint not null, primary key
# observations :text
# params :jsonb
# title :string
# created_at :datetime not null
# updated_at :datetime not null
# user_id :bigint not null
#
# Indexes
#
# index_assessments_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (user_id => users.id)
#
FactoryBot.define do
factory :assessment do
title { "MyString" }
observations { "MyText" }
params { "" }
user { nil }
end
end

View File

@@ -0,0 +1,25 @@
# == Schema Information
#
# Table name: assessments
#
# id :bigint not null, primary key
# observations :text
# params :jsonb
# title :string
# created_at :datetime not null
# updated_at :datetime not null
# user_id :bigint not null
#
# Indexes
#
# index_assessments_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (user_id => users.id)
#
require 'rails_helper'
RSpec.describe Assessment, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

585
yarn.lock

File diff suppressed because it is too large Load Diff