move move frontend to progress-test

This commit is contained in:
João Geonizeli
2022-07-21 21:16:59 -03:00
parent f8d5d08447
commit 386050d4ad
129 changed files with 159374 additions and 39 deletions

View File

@@ -0,0 +1,24 @@
import React, {FC,} from 'react'
import {DashboardProvider} from './DashboardContext'
import {
QuestionsBySubject,
QuestionByBloomTaxonomy,
QuestionsByDifficulty,
QuestionByCheckType,
} from './charts'
import {Filters} from './Filters'
export const Dashboard: FC = () => (
<DashboardProvider>
<main className="max-h-screen sm:px-8 gap-2 pt-2 sm:pt-4">
<Filters/>
<div className="pt-3 grid gap-2 grid-cols-1 md:grid-cols-2 2xl:grid-cols-3 3xl:grid-cols-4">
<QuestionsBySubject/>
<QuestionByBloomTaxonomy/>
<QuestionsByDifficulty/>
<QuestionByCheckType/>
</div>
</main>
</DashboardProvider>
)

View File

@@ -0,0 +1,47 @@
import React, {
createContext,
useState,
useMemo,
FC,
useContext,
Dispatch,
SetStateAction,
} from 'react'
import {QuestionWhereInput} from '../../__generated__/graphql-schema'
import {UserContext, useCurrentUser} from "../../contexts";
type ProviderValue = {
where: QuestionWhereInput
setWhere: Dispatch<SetStateAction<QuestionWhereInput>>
}
const DashboardContext = createContext<ProviderValue | null>(null)
export const useDashboardContext = () => {
const context = useContext(DashboardContext)
if (context === null) {
throw new Error('You probably forgot to put <DashboardProvider>.')
}
return context
}
export const whereDefaultState = (userContext: UserContext) => (
userContext.isOnlyTeacher ? {userId: userContext.user?.id} : {}
)
export const DashboardProvider: FC = ({children}) => {
const userContext = useCurrentUser()
const [where, setWhere] = useState<QuestionWhereInput>(whereDefaultState(userContext))
const providerValue = useMemo(() => ({where, setWhere}), [
where,
setWhere,
])
return (
<DashboardContext.Provider value={providerValue}>
{children}
</DashboardContext.Provider>
)
}

View File

@@ -0,0 +1,155 @@
import React, {FC, Fragment} from 'react'
import {Disclosure, Transition} from "@headlessui/react"
import {ChevronDownIcon, XIcon} from "@heroicons/react/outline"
import {useForm} from "react-hook-form"
import {QuestionWhereInput} from "../../__generated__/graphql-schema"
import {useDashboardContext, whereDefaultState} from "./DashboardContext"
import {useCurrentUser} from "../../contexts"
import {Button, Input} from "../../components"
type FilterBarForm = {
fromOtherUsers?: boolean
createDate: {
startAt: string
endAt: string
}
}
const startDateISO8601Date = '2021-01-01'
const currentISO8601Date = new Date().toISOString().split('T')[0]
const formDefaultValues: FilterBarForm = {
fromOtherUsers: false,
createDate: {
startAt: startDateISO8601Date,
endAt: currentISO8601Date
}
}
const mapFilter = (values: FilterBarForm, userId?: string): QuestionWhereInput => ({
userId: values.fromOtherUsers ? undefined : userId,
createDate: {
startAt: values.createDate.startAt.length ? values.createDate.startAt : startDateISO8601Date,
endAt: values.createDate.endAt.length ? values.createDate.endAt : currentISO8601Date,
}
})
const FiltersForm: FC = () => {
const {register, handleSubmit, reset, getValues, formState} = useForm({
defaultValues: formDefaultValues,
})
const {setWhere} = useDashboardContext()
const userContext = useCurrentUser()
const {user, isOnlyTeacher} = userContext
const onSubmit = (values: FilterBarForm) => {
reset(getValues(), {
isDirty: false
})
setWhere(mapFilter(values, user?.id))
}
const handleClean = () => {
reset(formDefaultValues)
setWhere(whereDefaultState(userContext))
}
return (
<form
onSubmit={handleSubmit(onSubmit)}
className={"flex justify-between"}
>
<span>
<label className={"pl-2 pt-2"}>Data de Criação</label>
<div className={"grid grid-cols-2 gap-2 border p-2 m-2 rounded-md border-gray-300"}>
<Input
type="date"
placeholder="createDate.startAt"
ref={register({
maxLength: 10,
minLength: 10,
})}
name={"createDate.startAt"}
label={"A Partir De"}
/>
<Input
type="date"
placeholder="createDate.endAt"
ref={register({
maxLength: 10,
minLength: 10,
})}
name={"createDate.endAt"}
label={"Até"}
/>
</div>
</span>
{!isOnlyTeacher && (
<span className={"flex items-center"}>
<label
htmlFor={"fromOtherUsers"}
children={"Apenas questões próprias?"}
className={"mr-3"}
/>
<input
id={"fromOtherUsers"}
type="checkbox"
placeholder="fromOtherUsers"
ref={register}
name={"fromOtherUsers"}
/>
</span>
)}
<div className={"grid grid-cols-2 gap-2 place-items-center"}>
<div>
<Button type={'tertiary'} onClick={handleClean}>
<span className={"flex"}>
<XIcon className={"w-5 h-5 text-gray-800"}/>
Limpar filtro
</span>
</Button>
</div>
<div>
<Button disabled={!formState.isDirty} type={'primary'} htmlType={"submit"} className={"w-full"}>
Filtar
</Button>
</div>
</div>
</form>
);
}
export const Filters: FC = () => (
<Disclosure>
{({open}) => (
<div className="m-auto bg-white rounded-md shadow-sm hover:shadow transition-shadow duration-300">
<Disclosure.Button as={Fragment}>
<button className="flex p-2 w-full justify-between">
<div className="grid place-items-center pl-4">
Filtros
</div>
<div className={"pr-4"}>
<ChevronDownIcon
className={`${open ? 'transform rotate-180' : ''} w-5 h-5 text-gray-800`}
/>
</div>
</button>
</Disclosure.Button>
<Transition
show={open}
enter="transition duration-100 ease-out"
enterFrom="transform scale-95 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0"
>
<Disclosure.Panel className={"p-4"}>
<FiltersForm/>
</Disclosure.Panel>
</Transition>
</div>
)}
</Disclosure>
)

View File

@@ -0,0 +1,106 @@
import React, {FC} from 'react'
import {gql, useQuery} from '@apollo/client'
import {Query} from '../../../__generated__/graphql-schema'
import {Pie} from '../components/charts'
import {useDashboardContext} from "../DashboardContext";
type ResponsivePieData = {
id: string
label: string
value: number
}[]
type QuestionsByBloomTaxonomyCountQuery = {
remember: Query['questions']
understand: Query['questions']
apply: Query['questions']
analyze: Query['questions']
evaluate: Query['questions']
create: Query['questions']
}
const QuestionsByBloomTaxonomyCount = gql`
query QuestionsByBloomTaxonomyCount (
$rememberWhere: QuestionWhereInput!,
$understandWhere: QuestionWhereInput!,
$applyWhere: QuestionWhereInput!,
$analyzeWhere: QuestionWhereInput!,
$evaluateWhere: QuestionWhereInput!,
$createWhere: QuestionWhereInput!,
) {
remember: questions(where: $rememberWhere) {
totalCount
}
understand: questions(where: $understandWhere) {
totalCount
}
apply: questions(where: $applyWhere) {
totalCount
}
analyze: questions(where: $analyzeWhere) {
totalCount
}
evaluate: questions(where: $evaluateWhere) {
totalCount
}
create: questions(where: $createWhere) {
totalCount
}
}
`
export const QuestionByBloomTaxonomy: FC = () => {
const {where} = useDashboardContext()
const {loading, data} = useQuery<QuestionsByBloomTaxonomyCountQuery>(
QuestionsByBloomTaxonomyCount, {
variables: {
rememberWhere: {bloomTaxonomy: ['remember'], ...where},
understandWhere: {bloomTaxonomy: ['understand'], ...where},
applyWhere: {bloomTaxonomy: ['apply'], ...where},
analyzeWhere: {bloomTaxonomy: ['analyze'], ...where},
evaluateWhere: {bloomTaxonomy: ['evaluate'], ...where},
createWhere: {bloomTaxonomy: ['create'], ...where},
}
})
if (loading || !data) return null
const mappedData: ResponsivePieData = [
{
id: "Recordar",
label: "Recordar",
value: data.remember.totalCount ?? 0
},
{
id: "Compreender",
label: "Compreender",
value: data.understand.totalCount ?? 0
},
{
id: "Aplicar",
label: "Aplicar",
value: data.apply.totalCount ?? 0
},
{
id: "Analisar",
label: "Analisar",
value: data.analyze.totalCount ?? 0
},
{
id: "Avaliar",
label: "Avaliar",
value: data.evaluate.totalCount ?? 0
},
{
id: "Criar",
label: "Criar",
value: data.create.totalCount ?? 0
},
]
const filteredData = mappedData.filter(item => item.value)
return (
<Pie title="Questões por Habilidade Cognitiva" data={filteredData}/>
)
}

View File

@@ -0,0 +1,150 @@
import React, {FC} from 'react'
import {gql, useQuery} from '@apollo/client'
import {Query} from '../../../__generated__/graphql-schema'
import {Pie} from '../components/charts'
import {useDashboardContext} from "../DashboardContext";
type ResponsivePieData = {
id: string
label: string
value: number
}[]
type QuestionsByCheckTypeCountQuery = {
uniqueAnswer: Query['questions']
incompleteAffirmation: Query['questions']
multipleAnswer: Query['questions']
negativeFocus: Query['questions']
assertionAndReason: Query['questions']
gap: Query['questions']
interpretation: Query['questions']
association: Query['questions']
orderingOrRanking: Query['questions']
constantAlternatives: Query['questions']
}
const QuestionsByCheckTypeCount = gql`
query QuestionsByCheckTypeCount(
$uniqueAnswer: QuestionWhereInput!,
$incompleteAffirmation: QuestionWhereInput!,
$multipleAnswer: QuestionWhereInput!,
$negativeFocus: QuestionWhereInput!,
$assertionAndReason: QuestionWhereInput!,
$gap: QuestionWhereInput!,
$interpretation: QuestionWhereInput!,
$association: QuestionWhereInput!,
$orderingOrRanking: QuestionWhereInput!,
$constantAlternatives: QuestionWhereInput!,
) {
uniqueAnswer: questions(where: $uniqueAnswer) {
totalCount
}
incompleteAffirmation: questions(where: $incompleteAffirmation) {
totalCount
}
multipleAnswer: questions(where: $multipleAnswer) {
totalCount
}
negativeFocus: questions(where: $negativeFocus) {
totalCount
}
assertionAndReason: questions(where: $assertionAndReason) {
totalCount
}
gap: questions(where: $gap) {
totalCount
}
interpretation: questions(where: $interpretation) {
totalCount
}
association: questions(where: $association) {
totalCount
}
orderingOrRanking: questions(where: $orderingOrRanking) {
totalCount
}
constantAlternatives: questions(where: $constantAlternatives) {
totalCount
}
}
`
export const QuestionByCheckType: FC = () => {
const {where} = useDashboardContext()
const {loading, data} = useQuery<QuestionsByCheckTypeCountQuery>(
QuestionsByCheckTypeCount, {
variables: {
uniqueAnswer: {checkType: ['unique_answer'], ...where},
incompleteAffirmation: {checkType: ['incomplete_affirmation'], ...where},
multipleAnswer: {checkType: ['multiple_answer'], ...where},
negativeFocus: {checkType: ['negative_focus'], ...where},
assertionAndReason: {checkType: ['assertion_and_reason'], ...where},
gap: {checkType: ['gap'], ...where},
interpretation: {checkType: ['interpretation'], ...where},
association: {checkType: ['association'], ...where},
orderingOrRanking: {checkType: ['ordering_or_ranking'], ...where},
constantAlternatives: {checkType: ['constant_alternatives'], ...where},
}
})
if (loading || !data) return null
const mappedData: ResponsivePieData = [
{
id: "Asserção e Razão",
label: "Asserção e Razão",
value: data.assertionAndReason.totalCount ?? 0
},
{
id: "Associação",
label: "Associação",
value: data.association.totalCount ?? 0
},
{
id: "Alternativas Constantes",
label: "Alternativas Constantes",
value: data.constantAlternatives.totalCount ?? 0
},
{
id: "Lacuna",
label: "Lacuna",
value: data.gap.totalCount ?? 0
},
{
id: "Afirmação Incompleta",
label: "Afirmação Incompleta",
value: data.incompleteAffirmation.totalCount ?? 0
},
{
id: "Interpretação",
label: "Interpretação",
value: data.interpretation.totalCount ?? 0
},
{
id: "Resposta Múltipla",
label: "Resposta Múltipla",
value: data.multipleAnswer.totalCount ?? 0
},
{
id: "Foco Negativo",
label: "Foco Negativo",
value: data.negativeFocus.totalCount ?? 0
},
{
id: "Ordenação ou Seriação",
label: "Ordenação ou Seriação",
value: data.orderingOrRanking.totalCount ?? 0
},
{
id: "Resposta Única",
label: "Resposta Única",
value: data.uniqueAnswer.totalCount ?? 0
},
]
const filteredData = mappedData.filter(item => item.value)
return (
<Pie title="Questões por Tipo" data={filteredData}/>
)
}

View File

@@ -0,0 +1,73 @@
import React, {FC} from 'react'
import {gql, useQuery} from '@apollo/client'
import {Query} from '../../../__generated__/graphql-schema'
import {Pie} from '../components/charts'
import {useDashboardContext} from "../DashboardContext";
type ResponsivePieData = {
id: string
label: string
value: number
}[]
type QuestionsByDifficultyCountQuery = {
easy: Query['questions']
medium: Query['questions']
hard: Query['questions']
}
const QuestionsByDifficultyCount = gql`
query QuestionsByDifficultyCount(
$easyWhere: QuestionWhereInput!,
$mediumWhere: QuestionWhereInput!,
$hardWhere: QuestionWhereInput!,
) {
easy: questions(where: $easyWhere) {
totalCount
}
medium: questions(where: $mediumWhere) {
totalCount
}
hard: questions(where: $hardWhere) {
totalCount
}
}
`
export const QuestionsByDifficulty: FC = () => {
const {where} = useDashboardContext()
const {loading, data} = useQuery<QuestionsByDifficultyCountQuery>(
QuestionsByDifficultyCount, {
variables: {
easyWhere: {difficulty: ['easy'], ...where},
mediumWhere: {difficulty: ['medium'], ...where},
hardWhere: {difficulty: ['hard'], ...where},
},
})
if (loading || !data) return null
const mappedData: ResponsivePieData = [
{
id: "Fácil",
label: "Fácil",
value: data.easy.totalCount ?? 0
},
{
id: "Difícil",
label: "Difícil",
value: data.hard.totalCount ?? 0
},
{
id: "Média",
label: "Média",
value: data.medium.totalCount ?? 0
},
]
const filteredData = mappedData.filter(item => item.value)
return (
<Pie title="Questões por Dificuldade" data={filteredData}/>
)
}

View File

@@ -0,0 +1,50 @@
import React, {FC} from 'react'
import {gql, useQuery} from '@apollo/client'
import {Query} from '../../../__generated__/graphql-schema'
import {Pie} from '../components/charts'
import {useDashboardContext} from "../DashboardContext";
type ResponsivePieData = {
id: string
label: string
value: number
}[]
const QuestionsBySubjectCount = gql`
query QuestionsBySubjectCount($where: QuestionWhereInput!) {
subjects {
nodes {
id
name
questions(where: $where) {
totalCount
}
}
}
}
`
export const QuestionsBySubject: FC = () => {
const {where} = useDashboardContext()
const {loading, data} = useQuery<Query>(QuestionsBySubjectCount, {
variables: {
where,
},
})
if (loading) return null
const subjects = data?.subjects.nodes ?? []
const subjectWithQuestions = subjects.filter(subject => !!subject?.questions.totalCount)
const mappedData: ResponsivePieData = subjectWithQuestions.map(subject => ({
id: subject.name,
label: subject.name,
value: subject.questions.totalCount,
}))
const filteredData = mappedData.filter(item => item.value)
return (
<Pie title="Questões por Assunto" data={filteredData}/>
)
}

View File

@@ -0,0 +1,4 @@
export * from "./QuestionsBySubject";
export * from "./QuestionsByBloomTaxonomy";
export * from "./QuestionsByDifficulty";
export * from "./QuestionsByCheckType";

View File

@@ -0,0 +1,38 @@
import {ResponsivePie} from '@nivo/pie'
import React, {FC} from 'react'
type Props = {
title: string
data: {
id: string
label: string
value: number
}[]
}
export const Pie: FC<Props> = ({title, data}) => {
return (
<div
className="m-auto bg-white rounded-md p-4 shadow-sm hover:shadow transition-shadow duration-300"
style={{ height: '36rem', width: '36rem' }}
>
<h3 className="text-lg leading-6 font-medium text-gray-900">{title}</h3>
<ResponsivePie
data={data}
margin={{top: 40, right: 80, bottom: 80, left: 80}}
innerRadius={0.5}
padAngle={0.7}
cornerRadius={3}
activeOuterRadiusOffset={8}
borderWidth={1}
borderColor={{from: 'color', modifiers: [['darker', 0.2]]}}
arcLinkLabelsSkipAngle={10}
arcLinkLabelsTextColor="#333333"
arcLinkLabelsThickness={2}
arcLinkLabelsColor={{from: 'color'}}
arcLabelsSkipAngle={10}
arcLabelsTextColor={{from: 'color', modifiers: [['darker', 2]]}}
/>
</div>
)
}

View File

@@ -0,0 +1 @@
export * from "./Pie";

View File

@@ -0,0 +1 @@
export * from "./charts";

View File

@@ -0,0 +1 @@
export * from "./Dashboard";