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,35 @@
import React, { FC, useState } from "react";
import { FaFilter } from "react-icons/fa";
import { Navigator } from "../../../components";
import { QuestionsFilter } from "./QuestionFilter";
import { QuestionsPainel } from "./QuestionsPainel";
import { FiltersProvider } from './QuestionFilter/QuestionsFilterProvider'
export const List: FC = () => {
const [filterOpen, setFilterOpen] = useState(false);
return (
<FiltersProvider>
<Navigator newQuestion={true}>
<li className={"hover:text-white ml-auto"}>
<button onClick={() => setFilterOpen(true)} className="flex">
<FaFilter className="my-auto" />
<span className="pl-3">Filtros</span>
</button>
</li>
</Navigator>
<QuestionsFilter
isOpen={filterOpen}
setIsOpen={setFilterOpen}
/>
<div className="bg-gray-100 w-full">
<main className="sm:px-8 rounded-t-xlg">
<div className="mx-2 sm:mx-0 sm:mr-4">
<QuestionsPainel />
</div>
</main>
</div>
</FiltersProvider>
);
};

View File

@@ -0,0 +1,49 @@
import React, { Dispatch, FC, SetStateAction } from "react";
type Props = {
setChanged: Dispatch<SetStateAction<boolean>>
register: any
}
export const AuthorshipFilter: FC<Props> = ({ setChanged, register }) => {
const options = [
{
label: "Qualquer",
value: 'null'
},
{
label: "Própria",
value: 'true'
},
{
label: "Terceiro",
value: 'false',
}
]
return (
<div className="mt-2 sm:mt-0 flex flex-col">
<h3 className="font-bold mb-1">Autoria</h3>
<div
className="grid grid-cols-2 sm:flex sm:flex-col"
key={`filter-group-authorship`}
>
{options.map(({ value, label }, index) => (
<span className="mr-1 mb-2 sm:mb-0 sm:mr-0" key={label}>
<input
ref={register}
type="radio"
name="authorship"
value={value}
id={value}
defaultChecked={!index}
/>
<label htmlFor={value} className="ml-2">
{label}
</label>
</span>
))}
</div>
</div>
)
}

View File

@@ -0,0 +1,39 @@
import React, { Dispatch, FC, SetStateAction } from 'react'
import { range } from '../../../../utils/math'
import { useFiltersProvider } from './QuestionsFilterProvider'
type Props = {
register: any
setChanged: Dispatch<SetStateAction<boolean>>
}
const CURRENT_YEAR = new Date().getFullYear()
const YEARS = range(1900, CURRENT_YEAR + 1).reverse()
export const QuestionsAuthorshipTypeFilter: FC<Props> = ({ register, setChanged }) => {
const { where } = useFiltersProvider()
return (
<div>
<select
ref={register}
className="w-full rounded p-1 border-gray-400 border shadow-sm"
name="authorshipYear"
defaultValue={where.authorshipYear ?? ""}
onClick={() => setChanged(true)}
>
<option value="" />
{YEARS.map((year) => (
<option
key={`questionYear-${year}`}
value={year}
>
{year}
</option>
))}
</select>
</div>
)
}

View File

@@ -0,0 +1,185 @@
import React, { Dispatch, FC, SetStateAction, useRef, useState } from "react";
import { useForm } from "react-hook-form";
import {
QuestionBloomTaxonomy,
QuestionCheckType,
QuestionDifficulty,
} from "../../../../__generated__/graphql-schema";
import { CHECK_TYPE, BLOOM_TAXONOMY, DIFFICULTY } from "../../../../utils/types";
import { Button, Modal } from "../../../../components";
import { useFiltersProvider } from "./QuestionsFilterProvider";
import { QuestionsSubjectFilter } from './QuestionsSubjectFilter'
import { QuestionsAuthorshipTypeFilter } from "./QuestionsAuthorshipTypeFilter";
import { AuthorshipFilter } from "./AuthorshipFilter";
type FilterGroupProps = {
title: string;
register: any;
options: {
value: string;
label: string;
}[];
selecteds: any[];
setChanged: Dispatch<SetStateAction<boolean>>;
};
const FilterGroup: FC<FilterGroupProps> = ({
title,
options,
register,
selecteds,
setChanged,
}) => (
<>
<div className="mt-2 sm:mt-0 flex flex-col">
<h3 className="font-bold mb-1">{title}</h3>
<div
className="grid grid-cols-2 sm:flex sm:flex-col"
key={`filter-group-${title}`}
>
{options.map(({ value, label }) => (
<span className="mr-1 mb-2 sm:mb-0 sm:mr-0" key={value}>
<input
type="checkbox"
name={value}
ref={register}
id={value}
defaultChecked={selecteds.includes(value)}
onClick={() => setChanged(true)}
/>
<label htmlFor={value} className="ml-2">
{label}
</label>
</span>
))}
</div>
</div>
</>
);
type Props = {
isOpen: boolean;
setIsOpen: (state: boolean) => void;
};
export const QuestionsFilter: FC<Props> = ({ isOpen, setIsOpen }) => {
const { handleSubmit, register, reset } = useForm();
const { where, setWhere } = useFiltersProvider();
const { difficulty, checkType, bloomTaxonomy } = where;
const [changed, setChanged] = useState(false);
const submitRef = useRef<HTMLInputElement>()
const onSubmit = (inputs: any) => {
const valuesFromCheckType = CHECK_TYPE.filter(
({ value }) => inputs[value]
).map(({ value }) => value) as QuestionCheckType[];
const valuesFromBloomTaxonomy = BLOOM_TAXONOMY.filter(
({ value }) => inputs[value]
).map(({ value }) => value) as QuestionBloomTaxonomy[];
const valuesFromDifficulty = DIFFICULTY.filter(
({ value }) => inputs[value]
).map(({ value }) => value) as QuestionDifficulty[];
const removeKeysWithUndefiend = (obj: any) => {
for (var propName in obj) {
if (obj[propName] === null || obj[propName] === undefined) {
delete obj[propName];
}
}
return obj;
};
setWhere({
unifesoAuthorship: inputs.authorship === 'null' ? null : inputs.authorship === 'true',
...removeKeysWithUndefiend({
checkType: valuesFromCheckType.length ? valuesFromCheckType : undefined,
bloomTaxonomy: valuesFromBloomTaxonomy.length
? valuesFromBloomTaxonomy
: undefined,
difficulty: valuesFromDifficulty.length
? valuesFromDifficulty
: undefined,
subjectId: inputs.subjectId === "" ? undefined : inputs.subjectId,
authorshipYear: inputs.authorshipYear === "" ? undefined : [inputs.authorshipYear],
}),
});
setChanged(false);
setIsOpen(false);
};
const handleClean = () => {
setChanged(false);
setWhere({});
reset();
};
return (
<Modal
title="Filtros"
setIsOpen={setIsOpen}
isOpen={isOpen}
buttons={
<>
<Button
onClick={() => handleClean()}
disabled={!changed}
className={`${changed ? 'opacity-1' : 'opacity-0'}`}
>
Limpar
</Button>
<Button onClick={() => setIsOpen(false)}>
Cancelar
</Button>
<Button type="primary" onClick={() => submitRef.current?.click()}>
Aplicar
</Button>
</>
}
>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="grid grid-cols-1 gap-4 sm:gap-8 lg:grid-cols-2">
<div className="mt-2 sm:mt-0 flex flex-col">
<h3 className="font-bold mb-1">Assunto</h3>
<div className="grid grid-cols-2 sm:flex sm:flex-col">
<QuestionsSubjectFilter register={register} setChanged={setChanged} />
</div>
</div>
<div className="mt-2 sm:mt-0 flex flex-col">
<h3 className="font-bold mb-1">Ano</h3>
<div className="grid grid-cols-2 sm:flex sm:flex-col">
<QuestionsAuthorshipTypeFilter register={register} setChanged={setChanged} />
</div>
</div>
<FilterGroup
title="Tipo"
register={register}
options={CHECK_TYPE}
selecteds={(checkType ?? []) as QuestionCheckType[]}
setChanged={setChanged}
/>
<FilterGroup
title="Habilidade Cognitiva"
register={register}
options={BLOOM_TAXONOMY}
selecteds={(bloomTaxonomy ?? []) as QuestionBloomTaxonomy[]}
setChanged={setChanged}
/>
<FilterGroup
title="Grau de Dificuldade"
register={register}
options={DIFFICULTY}
selecteds={(difficulty ?? []) as QuestionDifficulty[]}
setChanged={setChanged}
/>
<AuthorshipFilter register={register} setChanged={setChanged} />
<input hidden type="submit" ref={submitRef as any} />
</div>
</form>
</Modal >
);
};

View File

@@ -0,0 +1,43 @@
import React, {
createContext,
useState,
useMemo,
FC,
useContext,
Dispatch,
SetStateAction,
} from 'react'
import { QuestionWhereInput } from '../../../../__generated__/graphql-schema'
type ProviderValue = {
where: QuestionWhereInput
setWhere: Dispatch<SetStateAction<QuestionWhereInput>>
}
const FiltersContext = createContext<ProviderValue | null>(null)
export const useFiltersProvider = () => {
const context = useContext(FiltersContext)
if (context === null) {
throw new Error('You probably forgot to put <FiltersProvider>.')
}
return context
}
export const FiltersProvider: FC = ({ children }) => {
const [where, setWhere] = useState<QuestionWhereInput>({})
const providerValue = useMemo(() => ({ where, setWhere }), [
where,
setWhere,
])
return (
<FiltersContext.Provider value={providerValue}>
{children}
</FiltersContext.Provider>
)
}

View File

@@ -0,0 +1,52 @@
import React, { Dispatch, FC, SetStateAction } from 'react'
import { gql, useQuery } from '@apollo/client'
import { Query } from '../../../../__generated__/graphql-schema'
import { useFiltersProvider } from './QuestionsFilterProvider'
const SUBJECTS_QUERY = gql`
query {
subjects {
nodes {
id
name
}
}
}
`
type Props = {
register: any
setChanged: Dispatch<SetStateAction<boolean>>
}
export const QuestionsSubjectFilter: FC<Props> = ({ register, setChanged }) => {
const { where } = useFiltersProvider();
const { loading, data } = useQuery<Query>(SUBJECTS_QUERY)
if (loading) return null
const subjects = data?.subjects.nodes
return (
<div>
<select
ref={register}
className="w-full rounded p-1 border-gray-400 border shadow-sm"
name="subjectId"
defaultValue={where.subjectId ?? ""}
onClick={() => setChanged(true)}
>
<option value="" />
{subjects?.map((subject) => (
<option
key={`${subject?.name}-${subject?.id}`}
value={subject?.id}
>
{subject?.name}
</option>
))}
</select>
</div>
)
}

View File

@@ -0,0 +1,2 @@
export * from "./QuestionsFilter";
export * from "./QuestionsFilterProvider";

View File

@@ -0,0 +1,131 @@
import React, { FC, useState } from 'react'
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa';
import { MdModeEdit } from 'react-icons/md';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { Question, QuestionStatus } from '../../../__generated__/graphql-schema'
import { useCurrentUser } from '../../../contexts';
import { NodeId } from '../../../utils/graphql';
import { gql } from '@apollo/client';
const EditIcon = styled(MdModeEdit)`
margin: auto;
font-size: 1.5rem;
`;
type Props = {
questions: Question[]
title: string
pagination?: {
onNextPageClick: () => void
hasNextPage: boolean
hasPreviousPage: boolean
onPreviousPageClick: () => void
}
}
export const QuestionsListFragments = gql`
fragment QuestionFields on Question {
id
status
user {
id
}
updatedAt
}
`
export const QuestionsList: FC<Props> = ({ questions, title, pagination }) => {
const { user } = useCurrentUser()
const [pageCount, setPageCount] = useState(1)
const formatDate = (stringDate: string) => new Date(stringDate).toLocaleDateString()
const handleOnNextPageClick = () => {
if (pagination?.hasNextPage) {
pagination.onNextPageClick()
setPageCount(pageCount + 1)
}
}
const handleOnPreviousPageClick = () => {
if (pagination?.hasPreviousPage) {
pagination.onPreviousPageClick()
setPageCount(pageCount - 1)
}
}
return (
<div className="bg-gray-200 p-4 rounded my-2">
<div className="flex">
<h2 className="text-gray-500 font-medium text-xl">{title}</h2>
<div className="ml-auto text-sm sm:text-base text-gray-700">
<button
className="p-2"
onClick={handleOnPreviousPageClick}
style={{ visibility: (pagination?.hasPreviousPage ? 'visible' : 'hidden') }}
>
<FaArrowLeft />
</button>
Página: {pageCount}
<button
className="p-2"
onClick={handleOnNextPageClick}
style={{ visibility: (pagination?.hasNextPage ? 'visible' : 'hidden') }}
>
<FaArrowRight />
</button>
</div>
</div>
<hr className="border-t border-gray-400 m-px" />
<div className="p-2 text-sm">
{questions.length
? <div className="flex-col w-full sm:grid gap-4 sm:col-gap-8 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
{questions.map((question) => (
<div
key={`question-${question.id}`}
className="mx-1 sm:mx-0 mb-4 sm:mb-0 border-l-8 border-primary-light flex bg-white hover:bg-unifeso-50 rounded shadow hover:shadow-md cursor-pointer group transition-all duration-500"
>
<Link
className="flex flex-col w-full px-3 py-2"
to={`/questions/${question.id}/${(question.user.id === user?.id ? '' : 'review')}`}
>
<h2>
{`# ${NodeId.decode(question.id).id}`}
</h2>
<div className="text-sm text-gray-700 flex flex-col flex-wrap justify-between">
<span>
Atualizada em:
{" "}
{formatDate(question.updatedAt)}
</span>
</div>
</Link>
{(question.user.id === user?.id && question.status !== QuestionStatus.Registered) &&
<div
className="flex flex-col relative flex-grow justify-center"
>
<Link
className="group-hover:block absolute bg-gray-300 hover:bg-primary-normal text-gray-500 hover:text-gray-100 hover:shadow-lg rounded-full p-2 cursor-pointer shadow-inner transition-all duration-500"
style={{ left: '-1.5rem' }}
to={`/questions/${question.id}/edit`}
>
<EditIcon />
</Link>
</div>
}
</div>
))}
</div>
: <div className="grid text-gray-800" style={{ placeItems: 'center' }}>
<div className="text-center">
<span className="text-sm sm:text-base">
Nenhuma questão.
</span>
</div>
</div>
}
</div>
</div>
)
}

View File

@@ -0,0 +1,22 @@
import React, { FC } from 'react'
import { QuestionStatus } from '../../../__generated__/graphql-schema'
import { useFiltersProvider } from './QuestionFilter/QuestionsFilterProvider'
import { QuestionsQuery } from './QuestionsQuery'
import { QuestionsRevisedQuery } from './QuestionsRevisedQuery'
import { QuestionsWaitingReviewQuery } from './QuestionsWaitingReviewQuery'
export const QuestionsPainel: FC = () => {
const { where } = useFiltersProvider()
return (
<>
<QuestionsWaitingReviewQuery title="Aguardando seu Parecer" />
<QuestionsQuery title="Aguardando Parecer do Revisor" where={where} status={QuestionStatus.WaitingReview} />
<QuestionsQuery title="Pendentes de Alterações" where={where} status={QuestionStatus.WithRequestedChanges} />
<QuestionsQuery title="Rascunhos" where={where} status={QuestionStatus.Draft} />
<QuestionsQuery title="Aprovadas" where={where} status={QuestionStatus.Approved} />
<QuestionsQuery title="Registradas" where={where} status={QuestionStatus.Registered} />
<QuestionsRevisedQuery title="Revisadas por Você" />
</>
)
}

View File

@@ -0,0 +1,95 @@
import React, { FC, useState } from 'react'
import { PageInfo, Query, Question, QuestionWhereInput, QuestionStatus } from '../../../__generated__/graphql-schema';
import { gql, useQuery } from '@apollo/client';
import { QuestionsList, QuestionsListFragments } from './QuestionsList'
import { useCurrentUser } from '../../../contexts';
const QUESTIONS_QUERY = gql`
${QuestionsListFragments}
query QuestionsQuery($first: Int!, $after: String, $before: String, $where: QuestionWhereInput) {
questions (
first: $first,
after: $after,
before: $before,
where: $where
) {
nodes {
... QuestionFields
}
}
}
`
const PAGE_SIZE = 4
type Props = {
title: string
where?: QuestionWhereInput
status?: QuestionStatus
}
export const QuestionsQuery: FC<Props> = ({ title, where, status }) => {
const { user } = useCurrentUser()
const [questions, setQuestions] = useState<Question[]>([])
const [pageInfo, setPageInfo] = useState<PageInfo | undefined>()
const updateQuestions = (queryResult: Query) => {
const { questions: questionConnection } = queryResult
setQuestions(questionConnection.nodes as Question[])
setPageInfo(questionConnection.pageInfo)
}
const whereInput = {
status,
userId: user?.id,
...where
}
const { fetchMore } = useQuery<Query>(QUESTIONS_QUERY, {
onCompleted: (response) => {
updateQuestions(response)
},
variables: {
first: PAGE_SIZE,
where: whereInput,
},
fetchPolicy: "network-only",
})
const onNextPageClick = () => {
fetchMore({
variables: {
first: PAGE_SIZE,
after: pageInfo?.endCursor,
},
}).then(({ data }) => {
updateQuestions(data)
})
}
const onPreviousPageClick = () => {
fetchMore({
variables: {
first: PAGE_SIZE,
before: pageInfo?.startCursor,
},
}).then(({ data }) => {
updateQuestions(data)
})
}
return (
<QuestionsList
title={title}
questions={questions}
pagination={{
onNextPageClick: onNextPageClick,
hasNextPage: pageInfo?.hasNextPage ?? false,
hasPreviousPage: pageInfo?.hasPreviousPage ?? false,
onPreviousPageClick: onPreviousPageClick
}}
/>
)
};

View File

@@ -0,0 +1,89 @@
import React, { FC, useState } from 'react'
import { PageInfo, Query, Question, ReviewRequest, User } from '../../../__generated__/graphql-schema';
import { gql, useQuery } from '@apollo/client';
import { QuestionsList, QuestionsListFragments } from './QuestionsList'
const QUESTIONS_QUERY = gql`
${QuestionsListFragments}
query QuestionsRevisedQuery($first: Int!, $after: String) {
currentUser {
id
inactiveReviewRequests(
first: $first,
after: $after
) {
nodes {
id
question {
... QuestionFields
}
}
}
}
}
`
const PAGE_SIZE = 4
type Props = {
title: string
}
export const QuestionsRevisedQuery: FC<Props> = ({ title }) => {
const [questions, setQuestions] = useState<Question[]>([])
const [pageInfo, setPageInfo] = useState<PageInfo | undefined>()
const updateQuestions = (queryResult: Query) => {
const { currentUser } = queryResult
const { inactiveReviewRequests } = currentUser as User
const reviewRequests = inactiveReviewRequests.nodes as ReviewRequest[]
setQuestions(reviewRequests.map(item => item.question))
setPageInfo(inactiveReviewRequests.pageInfo)
}
const { fetchMore } = useQuery<Query>(QUESTIONS_QUERY, {
onCompleted: (response) => {
updateQuestions(response)
},
variables: {
first: PAGE_SIZE,
},
fetchPolicy: "network-only"
})
const onNextPageClick = () => {
fetchMore({
variables: {
first: PAGE_SIZE,
after: pageInfo?.endCursor,
},
}).then(({ data }) => {
updateQuestions(data)
})
}
const onPreviousPageClick = () => {
fetchMore({
variables: {
first: PAGE_SIZE,
before: pageInfo?.startCursor,
},
}).then(({ data }) => {
updateQuestions(data)
})
}
return (
<QuestionsList
title={title}
questions={questions}
pagination={{
onNextPageClick: onNextPageClick,
hasNextPage: pageInfo?.hasNextPage ?? false,
hasPreviousPage: pageInfo?.hasPreviousPage ?? false,
onPreviousPageClick: onPreviousPageClick
}}
/>
)
};

View File

@@ -0,0 +1,89 @@
import React, { FC, useState } from 'react'
import { PageInfo, Query, Question, ReviewRequest, User } from '../../../__generated__/graphql-schema';
import { gql, useQuery } from '@apollo/client';
import { QuestionsList, QuestionsListFragments } from './QuestionsList'
const QUESTIONS_QUERY = gql`
${QuestionsListFragments}
query QuestionsWaitingReviewQuery($first: Int!, $after: String) {
currentUser {
id
activeReviewRequests(
first: $first,
after: $after
) {
nodes {
id
question {
... QuestionFields
}
}
}
}
}
`
const PAGE_SIZE = 4
type Props = {
title: string
}
export const QuestionsWaitingReviewQuery: FC<Props> = ({ title }) => {
const [questions, setQuestions] = useState<Question[]>([])
const [pageInfo, setPageInfo] = useState<PageInfo | undefined>()
const updateQuestions = (queryResult: Query) => {
const { currentUser } = queryResult
const { activeReviewRequests } = currentUser as User
const reviewRequests = activeReviewRequests.nodes as ReviewRequest[]
setQuestions(reviewRequests.map(item => item.question))
setPageInfo(activeReviewRequests.pageInfo)
}
const { fetchMore } = useQuery<Query>(QUESTIONS_QUERY, {
onCompleted: (response) => {
updateQuestions(response)
},
variables: {
first: PAGE_SIZE,
},
fetchPolicy: "network-only"
})
const onNextPageClick = () => {
fetchMore({
variables: {
first: PAGE_SIZE,
after: pageInfo?.endCursor,
},
}).then(({ data }) => {
updateQuestions(data)
})
}
const onPreviousPageClick = () => {
fetchMore({
variables: {
first: PAGE_SIZE,
before: pageInfo?.startCursor,
},
}).then(({ data }) => {
updateQuestions(data)
})
}
return (
<QuestionsList
title={title}
questions={questions}
pagination={{
onNextPageClick: onNextPageClick,
hasNextPage: pageInfo?.hasNextPage ?? false,
hasPreviousPage: pageInfo?.hasPreviousPage ?? false,
onPreviousPageClick: onPreviousPageClick
}}
/>
)
};

View File

@@ -0,0 +1 @@
export { List } from "./List";