move move frontend to progress-test
This commit is contained in:
24
app/javascript/pages/dashboard/Dashboard.tsx
Normal file
24
app/javascript/pages/dashboard/Dashboard.tsx
Normal 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>
|
||||
)
|
||||
47
app/javascript/pages/dashboard/DashboardContext.tsx
Normal file
47
app/javascript/pages/dashboard/DashboardContext.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
155
app/javascript/pages/dashboard/Filters.tsx
Normal file
155
app/javascript/pages/dashboard/Filters.tsx
Normal 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>
|
||||
)
|
||||
@@ -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}/>
|
||||
)
|
||||
}
|
||||
150
app/javascript/pages/dashboard/charts/QuestionsByCheckType.tsx
Normal file
150
app/javascript/pages/dashboard/charts/QuestionsByCheckType.tsx
Normal 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}/>
|
||||
)
|
||||
}
|
||||
@@ -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}/>
|
||||
)
|
||||
}
|
||||
50
app/javascript/pages/dashboard/charts/QuestionsBySubject.tsx
Normal file
50
app/javascript/pages/dashboard/charts/QuestionsBySubject.tsx
Normal 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}/>
|
||||
)
|
||||
}
|
||||
4
app/javascript/pages/dashboard/charts/index.ts
Normal file
4
app/javascript/pages/dashboard/charts/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./QuestionsBySubject";
|
||||
export * from "./QuestionsByBloomTaxonomy";
|
||||
export * from "./QuestionsByDifficulty";
|
||||
export * from "./QuestionsByCheckType";
|
||||
38
app/javascript/pages/dashboard/components/charts/Pie.tsx
Normal file
38
app/javascript/pages/dashboard/components/charts/Pie.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./Pie";
|
||||
1
app/javascript/pages/dashboard/components/index.ts
Normal file
1
app/javascript/pages/dashboard/components/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./charts";
|
||||
1
app/javascript/pages/dashboard/index.ts
Normal file
1
app/javascript/pages/dashboard/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./Dashboard";
|
||||
Reference in New Issue
Block a user