diff --git a/next/components/atoms/Button.js b/next/components/atoms/Button.js new file mode 100644 index 00000000..cef1264c --- /dev/null +++ b/next/components/atoms/Button.js @@ -0,0 +1,33 @@ +import { + Box, +} from "@chakra-ui/react"; + +export default function Button({ children, onClick, ...props }) { + return ( + + {children} + + ) +} \ No newline at end of file diff --git a/next/components/atoms/Checkbox.js b/next/components/atoms/Checkbox.js new file mode 100644 index 00000000..0029ac25 --- /dev/null +++ b/next/components/atoms/Checkbox.js @@ -0,0 +1,59 @@ +import { + Checkbox, + Icon +} from '@chakra-ui/react'; + +function CustomCheckbockIcon({ variant, isIndeterminate, isChecked, ...props }) { + function variantIcon() { + if(isIndeterminate) return "M5.06135 11.0002C4.76599 11.0002 4.51496 10.9029 4.30826 10.7084C4.10157 10.514 3.99823 10.2778 3.99823 9.99984C3.99823 9.72193 4.10157 9.48573 4.30826 9.29124C4.51496 9.09676 4.76599 8.99951 5.06135 8.99951H14.9391C15.2344 8.99951 15.4855 9.09676 15.6922 9.29124C15.8988 9.48573 16.0022 9.72193 16.0022 9.99984C16.0022 10.2778 15.8988 10.514 15.6922 10.7084C15.4855 10.9029 15.2344 11.0002 14.9391 11.0002H5.06135Z" + return "M7.17894 10.9259L13.306 4.7988C13.5043 4.60057 13.7378 4.50146 14.0068 4.50146C14.2758 4.50146 14.5094 4.60057 14.7076 4.7988C14.9058 4.99702 15.0049 5.23196 15.0049 5.50364C15.0049 5.7753 14.9058 6.01024 14.7076 6.20846L7.87972 13.0444C7.6815 13.2427 7.44791 13.3418 7.17894 13.3418C6.90998 13.3418 6.67639 13.2427 6.47817 13.0444L3.2943 9.86058C3.09609 9.66236 2.99834 9.42742 3.00103 9.15576C3.00373 8.88408 3.10418 8.64914 3.30241 8.45091C3.50063 8.25269 3.73557 8.15358 4.00725 8.15358C4.27891 8.15358 4.51385 8.25269 4.71207 8.45091L7.17894 10.9259Z" + } + + return ( + + + + ) +} + +export default function CustomCheckbox({ children, hasIndeterminate = false , icon, ...props}) { + return ( + + } + {...props} + > + {children} + + ) +} \ No newline at end of file diff --git a/next/components/atoms/ControlledInput.js b/next/components/atoms/ControlledInput.js index 6718f979..f6e508cf 100644 --- a/next/components/atoms/ControlledInput.js +++ b/next/components/atoms/ControlledInput.js @@ -3,12 +3,12 @@ import { InputGroup, InputRightAddon, InputRightElement, + InputLeftElement } from "@chakra-ui/react"; import { useEffect, useState } from "react"; export default function ControlledInput({ placeholder, - variant, value, onChange, onEnterPress, @@ -117,3 +117,76 @@ export function DebouncedControlledInput({ ); } + +export function ControlledInputSimple({ + refInput = null, + placeholder, + value, + onChange, + onEnterPress, + inputFocus, + changeInputFocus, + icon = null, + inputStyle, + inputElementStyle, + fill, + ...props +}) { + async function checkForEnter(e) { + if (e.key === "Enter" && onEnterPress) { + onEnterPress(); + } + } + + return ( + + + + onChange(e.target.value)} + onKeyDown={checkForEnter} + onFocus={() => changeInputFocus(true)} + onBlur={() => changeInputFocus(false)} + autoComplete="off" + variant="outline" + border="2px solid transparent !important" + color="#464A51" + _hover={{ + border:"2px solid transparent !important", + backgroundColor:"#DEDFE0", + }} + _focus={{ + border:"2px solid #0068C5 !important", + backgroundColor: "#FFF", + }} + paddingLeft="52px !important" + backgroundColor="#EEEEEE" + height="40px" + fontSize="14px" + lineHeight="20px" + width="100%" + fontFamily="Roboto" + fontWeight="400" + borderRadius="14px" + _placeholder={{color: "#464A51", opacity: 1}} + {...inputStyle} + /> + + ) +} \ No newline at end of file diff --git a/next/components/atoms/FilterAccordion.js b/next/components/atoms/FilterAccordion.js index 8a09c85c..413643df 100644 --- a/next/components/atoms/FilterAccordion.js +++ b/next/components/atoms/FilterAccordion.js @@ -4,20 +4,17 @@ import { AccordionIcon, AccordionItem, Box, - Checkbox, CheckboxGroup, VStack, Text, - Image, HStack, + Skeleton } from "@chakra-ui/react"; import { useEffect, useState } from "react"; -import ControlledInput from "./ControlledInput"; +import Checkbox from "../atoms/Checkbox"; +import { ControlledInput, ControlledInputSimple} from "./ControlledInput"; import SectionText from "./SectionText"; import SearchIcon from "../../public/img/icons/searchIcon" -import BDLogoPlusImage from "../../public/img/logos/bd_logo_plus"; -import BDLogoProImage from "../../public/img/logos/bd_logo_pro"; - export function BaseFilterAccordion({ fieldName, @@ -26,8 +23,6 @@ export function BaseFilterAccordion({ isOpen = null, isActive = false, onChange = () => { }, - bdPlus = null, - bdPro = false, alwaysOpen = false, isHovering = true }) { @@ -39,9 +34,8 @@ export function BaseFilterAccordion({ {fieldName} - {bdPlus && - - } - {bdPro && - - } - {!alwaysOpen ? : <>} + {!alwaysOpen ? : <>} {(isOpen && isOpen === true) || (isOpen == null && isExpanded) ? ( - + <> {children} - + ) : ( <> )} @@ -109,11 +83,14 @@ export function CheckboxFilterAccordion({ isActive = false, isOpen = null, canSearch = false, + isLoading }) { const [options , setOptions] = useState([]) const [search, setSearch] = useState(""); + const [inputFocus, setInputFocus] = useState(false) useEffect(() => { + if (choices.length === 0) return setOptions(choices) }, [choices]) @@ -135,48 +112,91 @@ export function CheckboxFilterAccordion({ overflowX="hidden" alwaysOpen={alwaysOpen} > - - {canSearch && - - - } - /> + + + {canSearch && + + + } + /> + + } + + {options.length > 0 && options.map((c) => ( + + { onChange(e.target.value)}} + minWidth="18px" + minHeight="18px" + maxWidth="18px" + maxHeight="18px" + marginRight="14px" + flexShrink={0} + /> + + {c[displayField]} + + + {c["count"] ? `(${c["count"]})` : `(0)`} + + + ))} - } - - {options.length > 0 && options.map((c) => ( - { onChange(e.target.value)}} - > - {c[displayField]} {c["count"] ? `(${c["count"]})` : `(0)`} - - ))} - - + + ); } @@ -265,73 +285,6 @@ export function RangeFilterAccordion({ ); } -export function FilterAccordion({ - fieldName, - choices, - onChange, - onToggle, - value, - bdPlus = null, - bdPro = false, - valueField = "id", - displayField = "display_name", - isOpen = null, - alwaysOpen = false, - isActive = false, - isHovering, -}) { - if(choices.length < 1) return null - - return ( - - - {choices.map((c) => ( - - onChange(c[valueField])} - > - {c[displayField]} - - - ))} - - - ); -} - export function SimpleFilterAccordion({ fieldName, children, @@ -349,7 +302,7 @@ export function SimpleFilterAccordion({ diff --git a/next/components/atoms/GreenTab.js b/next/components/atoms/GreenTab.js index 7d4916a2..1c5bf8d3 100644 --- a/next/components/atoms/GreenTab.js +++ b/next/components/atoms/GreenTab.js @@ -4,20 +4,24 @@ import { Tab } from "@chakra-ui/react"; export default function GreenTab({ children, ...style }) { return ( diff --git a/next/components/atoms/HelpWidget.js b/next/components/atoms/HelpWidget.js index 0df5adf0..2481a353 100644 --- a/next/components/atoms/HelpWidget.js +++ b/next/components/atoms/HelpWidget.js @@ -6,40 +6,29 @@ import { MenuDivider, Tooltip } from "@chakra-ui/react"; -import { useDisclosure } from "@chakra-ui/hooks"; -import { useState, useEffect } from 'react'; -import { useCheckMobile } from "../../hooks/useCheckMobile.hook"; import HelpIcon from "../../public/img/icons/helpIcon" export default function HelpWidget({options, tooltip}) { - const isMobile = useCheckMobile(); - const [isMobileMode, setIsMobileMode] = useState(false) - - useEffect(() => { - setIsMobileMode(isMobile) - },[isMobile]) - - const { isOpen } = useDisclosure() - const optionsRender = (options) => { - return options.map((option) => { + return options.map((option, i) => { if(option.name){ return ( window.open(option.url, "_blank")} > {option.name} ) - } else { return } + } else { return } }) } @@ -50,12 +39,12 @@ export default function HelpWidget({options, tooltip}) { - + { - const settingsImage = { - alt: title || "", - borderRadius:"32px", - boxShadow:"0px 4px 8px rgba(100, 96, 103, 0.16)", - width:{ base: "25%", lg: "100%" }, - minWidth:"250px", - maxWidth:"250px", - minHeight:"250px", - maxHeight:"250px", - height:{ base: "25%", lg: "100%" }, - objectFit:"contain", - widthImage:"100%", - heightImage:"100%" - } - - if(!image) return - - return ( - - ) -} - diff --git a/next/components/atoms/LinkDash.js b/next/components/atoms/LinkDash.js deleted file mode 100644 index 960515ae..00000000 --- a/next/components/atoms/LinkDash.js +++ /dev/null @@ -1,21 +0,0 @@ -import { Link } from "@chakra-ui/react"; - -export default function LinkDash({ children, href, dash = true, ...style }) { - return ( - - {children} {dash ? ">>" : null} - - ) -} diff --git a/next/components/atoms/ObservationLevelTable.js b/next/components/atoms/ObservationLevelTable.js new file mode 100644 index 00000000..595f6c8f --- /dev/null +++ b/next/components/atoms/ObservationLevelTable.js @@ -0,0 +1,108 @@ +import { + TableContainer, + Table, + Thead, + Tbody, + Tr, + Th, + Td, +} from "@chakra-ui/react"; + +export default function ObservationLevel({ resource }) { + const headers = ["Entidade","Colunas Correspondentes"] + + let array = [] + const keys = Object.keys(resource?.observationLevels) + + keys.forEach((elm) => { + const value = resource?.observationLevels[elm] + + const newValue = [value?.entity?.name || "Não informado", value?.columns[0]?.name || "Não informado"] + array.push(newValue) + }) + + return ( + + + + + {headers.map((elm, i) => ( + + ))} + + + + + {array.map((elm, i) => ( + + + + + ))} + +
+ {elm} +
+ {elm[0]} + + {elm[1]} +
+
+ ) +} \ No newline at end of file diff --git a/next/components/atoms/ReadMore.js b/next/components/atoms/ReadMore.js index b06a14b0..bff6393f 100644 --- a/next/components/atoms/ReadMore.js +++ b/next/components/atoms/ReadMore.js @@ -1,41 +1,93 @@ import { - VStack, + Flex, Text, } from "@chakra-ui/react"; -import { useState, useEffect } from "react"; -import LinkDash from "./LinkDash"; +import { useState, useEffect, useRef } from "react"; -export default function ReadMore({ children, textSize = 240, isMobileMod, ...props}) { - const [isReadMore, setIsReadMore] = useState(true) - const text = children +export default function ReadMore({ children, id, ...props}) { + const [isReadMore, setIsReadMore] = useState(false) + const [isOverflowing, setIsOverflowing] = useState(false) + const textRef = useRef(null) + + const modifiedChildren = ` + ${children.trim()} + + Ler menos` useEffect(() => { - if(text.length < textSize) setIsReadMore(false) + if (textRef.current) { + const { clientHeight, scrollHeight } = textRef.current + setIsOverflowing(scrollHeight > clientHeight) + } }, [children]) - + + useEffect(() => { + const readLess = document.getElementById(id) + if (readLess) readLess.addEventListener('click', toggleReadMore) + return () => { if (readLess) readLess.removeEventListener('click', toggleReadMore)} + }, [isReadMore]) + const toggleReadMore = () => { setIsReadMore(!isReadMore) } return ( - + - {isReadMore ? text.slice(0, textSize)+"..." : text} + {isOverflowing ? + + : + children + } - - {text.length > textSize && - - {isReadMore ? "Ler mais" : " Ler menos"} - + {isOverflowing && + + ...Ler mais + } - + ) -} \ No newline at end of file +} diff --git a/next/components/atoms/Tag.js b/next/components/atoms/Tag.js index 54b652ed..614a6b94 100644 --- a/next/components/atoms/Tag.js +++ b/next/components/atoms/Tag.js @@ -25,3 +25,32 @@ export default function TagBase({ ) } + +export function TagFilter({text, handleClick}) { + return ( + + {text} + handleClick()} + /> + + ) +} \ No newline at end of file diff --git a/next/components/atoms/Toggle.js b/next/components/atoms/Toggle.js index 4807ebff..cd2e1815 100644 --- a/next/components/atoms/Toggle.js +++ b/next/components/atoms/Toggle.js @@ -1,12 +1,13 @@ import { Switch } from "@chakra-ui/react"; import styles from "../../styles/toggle.module.css"; -export default function Toggle({ value, onChange }) { +export default function Toggle({ value, onChange, ...props }) { return ( ) } \ No newline at end of file diff --git a/next/components/molecules/ColumnDatasets.js b/next/components/molecules/ColumnDatasets.js deleted file mode 100644 index e95e2720..00000000 --- a/next/components/molecules/ColumnDatasets.js +++ /dev/null @@ -1,416 +0,0 @@ -import { - TableContainer, - Table, - Thead, - Tbody, - Tr, - Th, - Td, - Tooltip, - HStack, - Stack, - Box, - Text, - Input, - InputGroup, - InputRightElement, - InputLeftAddon, - Select, -} from '@chakra-ui/react'; -import { useState, useEffect } from 'react'; -import FuzzySearch from 'fuzzy-search'; -import Latex from 'react-latex-next'; -import { useCheckMobile } from "../../hooks/useCheckMobile.hook"; -import SectionText from '../atoms/SectionText'; -import LoadingSpin from '../atoms/Loading' -import Tag from "../atoms/Tag"; -import { TemporalCoverage } from "./TemporalCoverageDisplay"; - -import { - getColumnsBdmTable -} from "../../pages/api/datasets/index"; - -import InfoIcon from '../../public/img/icons/infoIcon'; -import RedirectIcon from '../../public/img/icons/redirectIcon'; -import FilterIcon from '../../public/img/icons/filterIcon'; -import SearchIcon from '../../public/img/icons/searchIcon'; -import CrossIcon from '../../public/img/icons/crossIcon'; -import 'katex/dist/katex.min.css'; - -function TableDatasets({ headers, values }) { - const [columnsHeaders, setColumnsHeaders] = useState([]) - const [columnsValues, setColumnsValues] = useState([]) - - function sortElements(a, b) { - if (a.node.order < b.node.order) { - return -1 - } - if (a.node.order > b.node.order) { - return 1 - } - return 0 - } - - useEffect(() => { - const newValues = values?.map((elm) => { - delete elm.node._id - return elm - }) - - setColumnsHeaders(Object.keys(headers)) - setColumnsValues(newValues.sort(sortElements)) - },[values, headers]) - - - const empty = () => { - return ( -

- Não listado -

- ) - } - - function valueVerification (value) { - if(value === null || value === undefined) return empty() - - if(typeof value === "function") return value() - - if(value === true) return "Sim" - if(value === false) return "Não" - - if(value) { - if(value === "Não listado"){ - return empty() - } else { - return value - } - } else { - return empty() - } - } - - const directoryColumnValue = (value) => { - const dataset = value?.table?.dataset - const table = value?.table - const newDirectoryColumn = `${dataset?.slug}.${table?.slug}:${value?.name}` - - if (newDirectoryColumn === "undefined.undefined:undefined") return empty() - - return ( -
- {newDirectoryColumn} - - - -
- ) - } - - const measurementUnit = (value) => { - if(!value) return null - - const measurementUnitLatex = () => { - const splitValue = value.split(/([^a-z])/) - const translated = (value) => value.map((elm) => elm) - return ( - {`$${translated(splitValue).join("")}$`} - ) - } - return measurementUnitLatex - } - - const TableHeader = ({ header, ...props }) => { - if(header === undefined) return null - - return ( - - - {headers[header].pt} - - - - - - ) - } - - function TreatmentValues({ value }) { - const objectValue = value?.node - let data = [] - - data.push({ value: objectValue.name, style:{position:"sticky", left:"0", zIndex:2, background:"linear-gradient(to left,#EAEAEA, #EAEAEA 2px, #FFF 2px, #FFF 100%)"}}); - data.push({ value: objectValue.bigqueryType.name, style:{textTransform: "uppercase"}}) - data.push({ value: objectValue.description}) - data.push({ value: objectValue.coverage}) - data.push({ value: objectValue.coveredByDictionary}) - data.push({ value: directoryColumnValue(objectValue.directoryPrimaryKey)}) - data.push({ value: measurementUnit(objectValue.measurementUnit)}) - data.push({ value: objectValue.containsSensitiveData}) - data.push({ value: objectValue.observations}) - - return data.map((elm, i) => - - {i===3 ? : valueVerification(elm.value)} - - ) - } - - function TableValue({children, ...props}) { - return ( - - {children} - - ) - } - - return ( - - - - - - {columnsHeaders.map((elm, i) => ( - i != 0 && - ))} - - - {columnsValues?.length > 0 && columnsValues.map((elm) => ( - - - - ))} - -
-
-
- ) -} - -export default function ColumnsDatasets({ tableId }) { - const [resource, setResource] = useState({}) - const [isError, setIsError] = useState({}) - const [isLoading, setIsLoading] = useState(false) - - const featchColumns = async () => { - setIsLoading(true) - try { - const result = await getColumnsBdmTable(tableId) - setResource(result) - } catch (error) { - setIsError(error) - console.error(error) - } - setIsLoading(false) - } - - useEffect(() => { - featchColumns() - },[tableId]) - - const headers = { - name: { - pt: "Nome", - tooltip:"Nome da coluna." - }, - bigqueryType: { - pt: "Tipo No BigQuery", - tooltip:"Tipo de dado no BigQuery — categorias: INTEGER (Inteiro), STRING (Texto), DATE (Data), FLOAT64 (Decimal), GEOGRAPHY (Geográfico)." - }, - description: { - pt: "Descrição", - tooltip:"Descrição dos dados da coluna." - }, - datetimeRanges: { - pt: "Cobertura Temporal", - tooltip:"Data inicial e final de cobertura dos dados. Pode variar entre colunas, de acordo com a disponibilidade nos dados originais." - }, - coveredByDictionary: { - pt: "Coberta Por Um Dicionário", - tooltip:"Indica se a coluna possui categorias descritas na tabela 'dicionario', explicando o significado das suas chaves e valores — ex: 'sexo' possui os valores 0 e 1 na coluna, e, no dicionario, você irá encontrar 'sexo' com as categorias (chave: 1 - valor: Feminino), (chave: 0 - valor: Masculino)." - }, - directoryPrimaryKey: { - pt: "Coluna Correspondente Nos Diretórios", - tooltip:"Caso preenchida, indica que a coluna é chave primária de uma entidade — ex: id_municipio = chave primária de municípios. Isso significa que a coluna é igual em todos os conjuntos do datalake. Informações centralizadas da entidade se encontram no diretório conforme: [diretorio].[tabela]:[coluna]." - }, - measurementUnit: { - pt: "Unidade De Medida", - tooltip:"Unidade de medida da coluna — ex: km, m2, kg." - }, - containsSensitiveData: { - pt: "Contém Dados Sensíveis (LGPD)", - tooltip:"Indica se a coluna possui dados sensíveis — ex: CPF identificado, dados de conta bancária, etc." - }, - observations: { - pt: "Observações", - tooltip:"Descreve processos de tratamentos realizados na coluna que precisam ser evidenciados." - } - } - - if(isLoading) return - - if(isError?.message?.length > 0) return Nenhuma informação foi encontrada. - if(resource === undefined || Object.keys(resource).length === 0) return Nenhuma informação de coluna fornecida. - - return ( - - {/* - - - - Filtrar - - - - - - - {tagFilter.length > 0 && ( - - {tagFilter.map((elm) => ( - - {elm.header} - removeTagFilter(elm, null)} - /> - - ))} - - } - /> - )} - - setFilter(e.target.value)} - variant="outline" - letterSpacing="0.5px" - fontWeight="300" - border="none" - borderRadius="16px" - fontFamily="lato" - fontSize="16px" - color="#252A32" - width="100%" - minWidth="200px" - height="40px" - placeholder="Insira o nome ou o valor da propriedade" - _placeholder={{color:"#6F6F6F"}} - /> - appliedFilter()} - /> - : - { - setTagFilter([]) - setHeaderSelection("") - setColumnValues(defaultValues) - setFilter("") - }} - /> - }/> - - - */} - - - - ) -} diff --git a/next/components/molecules/ColumnsTable.js b/next/components/molecules/ColumnsTable.js new file mode 100644 index 00000000..a5e93a4d --- /dev/null +++ b/next/components/molecules/ColumnsTable.js @@ -0,0 +1,685 @@ +import { + TableContainer, + Table, + Thead, + Tbody, + Tr, + Th, + Td, + Tooltip, + Stack, + Box, + Text, + Skeleton, +} from '@chakra-ui/react'; +import { useState, useEffect } from 'react'; +import { useRouter } from 'next/router'; +import FuzzySearch from 'fuzzy-search'; +import Latex from 'react-latex-next'; +import cookies from 'js-cookie'; +import { ControlledInputSimple } from '../atoms/ControlledInput'; +import Checkbox from '../atoms/Checkbox'; +import { triggerGAEvent, formatBytes } from '../../utils'; + +import { + getColumnsBdmTable +} from "../../pages/api/tables/index"; + +import InternalError from '../../public/img/internalError' +import InfoIcon from '../../public/img/icons/infoIcon'; +import DownloadIcon from '../../public/img/icons/downloadIcon'; +import SearchIcon from '../../public/img/icons/searchIcon'; +import RedirectIcon from '../../public/img/icons/redirectIcon'; +import 'katex/dist/katex.min.css'; + +function SearchColumn({ isLoaded, resource, columns }) { + const [inputFocus, setInputFocus] = useState(false) + const [search, setSearch] = useState("") + const [value, setValue] = useState("") + const [_timeout, _setTimeout] = useState(null) + + useEffect(() => { + clearTimeout(_timeout) + isLoaded(true) + if(value.trim() === "") { + isLoaded(false) + return columns(resource) + } + + _setTimeout(setTimeout(() => { + const result = searcherColumn.search(search.trim()) + if(result.length > 0) { + columns(result) + } else { + columns(resource) + } + isLoaded(false) + }, 500)) + }, [value]) + + useEffect(() => { + setValue(search) + }, [search]) + + const searcherColumn = new FuzzySearch ( + resource, ["node.name", "node.description"], {sort: true} + ) + + return ( + + } + /> + ) +} + +export default function ColumnsTable({ + tableId, + checkedColumns, + onChangeCheckedColumns, + hasLoading, + setHasLoading, + numberColumns, + template, + columnsPro +}) { + const router = useRouter() + const { query } = router + const [resource, setResource] = useState({}) + const [columns, setColumns] = useState({}) + const [isError, setIsError] = useState(false) + const [isLoading, setIsLoading] = useState(true) + const [isSearchLoading, setIsSearchLoading] = useState(true) + + const isChecked = (columnSlug) => checkedColumns.includes(columnSlug) + + const isUserPro = () => { + let user + if(cookies.get("userBD")) user = JSON.parse(cookies.get("userBD")) + + if(user?.proSubscriptionStatus === "active") return true + return false + } + + const handleCheckboxChange = (columnSlug) => { + if (isChecked(columnSlug)) { + onChangeCheckedColumns(checkedColumns.filter(slug => slug !== columnSlug)) + } else { + onChangeCheckedColumns([...checkedColumns, columnSlug]) + } + } + + const handleMasterCheckboxChange = () => { + if(checkedColumns.length > 0) return onChangeCheckedColumns([]) + const allColumnSlugs = resource.map(column => column.node.name) + + if (checkedColumns.length === allColumnSlugs.length) { + onChangeCheckedColumns([]) + } else { + onChangeCheckedColumns(allColumnSlugs) + } + } + + useEffect(() => { + if(tableId === undefined) return + + const filterClosedTables = (data) => { + return data.filter(elm => { + const table = elm?.node?.directoryPrimaryKey?.table + return table && table.isClosed === true + }) + } + + const featchColumns = async () => { + setHasLoading(true) + + try { + const result = await getColumnsBdmTable(tableId) + + if(result) { + setResource(result.sort(sortElements)) + numberColumns(result.length) + columnsPro(filterClosedTables(result)) + setColumns(result.sort(sortElements)) + setHasLoading(false) + setIsSearchLoading(false) + } + + } catch (error) { + console.error(error) + setIsError(true) + } + } + + featchColumns() + },[tableId]) + + useEffect(() => { + setIsLoading(hasLoading) + }, [hasLoading]) + + const headers = [ + { + pt: "Nome", + tooltip:"Nome da coluna." + }, + { + pt: + template === "download" ? + "Tabela de tradução" + : + "Precisa de tradução" + , + tooltip: + template === "download" ? + "Para traduzir os códigos institucionais da tabela você precisa utilizar as tabelas de dicionário e diretórios, dependendo de qual coluna você quiser usar." + : + "A coluna possui códigos institucionais a serem traduzidos." + }, + { + pt: "Descrição", + tooltip:"Descrição dos dados da coluna." + }, + { + pt: "Tipo No BigQuery", + tooltip:"Tipo de dado no BigQuery — categorias: INTEGER (Inteiro), STRING (Texto), DATE (Data), FLOAT64 (Decimal), GEOGRAPHY (Geográfico)." + }, + { + pt: "Cobertura Temporal", + tooltip:"Data inicial e final de cobertura dos dados. Pode variar entre colunas, de acordo com a disponibilidade nos dados originais." + }, + { + pt: "Unidade De Medida", + tooltip:"Unidade de medida da coluna — ex: km, m2, kg." + }, + { + pt: "Contém Dados Sensíveis (LGPD)", + tooltip:"Indica se a coluna possui dados sensíveis — ex: CPF identificado, dados de conta bancária, etc." + }, + { + pt: "Observações", + tooltip:"Descreve processos de tratamentos realizados na coluna que precisam ser evidenciados." + } + ] + + function sortElements(a, b) { + if (a.node.order < b.node.order) { + return -1 + } + if (a.node.order > b.node.order) { + return 1 + } + return 0 + } + + function HasDownloadPermitted(value) { + let downloadPermitted = false + let downloadWarning = "" + + if (value) { + const limit100MB = 100 * 1024 * 1024; + const limit1GB = 1 * 1024 * 1024 * 1024; + + if (value < limit100MB) { + downloadPermitted = true + downloadWarning = "free" + } else if (value < limit1GB) { + downloadPermitted = isUserPro() + downloadWarning = "100mbBetween1gb" + } else { + downloadPermitted = false + downloadWarning = "biggest1gb" + } + } + + return { + downloadPermitted : downloadPermitted, + downloadWarning : downloadWarning + } + } + + function DictionaryDownload() { + async function downloadTable() { + const result = await fetch(`/api/tables/getDictionaryTable?p=${btoa(query.dataset)}`, {method: "GET"}) + .then(res => res.json()) + + if(result?.error) return + + let cloudTables = result?.cloudTables?.edges[0]?.node + + triggerGAEvent("download_da_tabela",`{ + gcp: ${cloudTables?.gcpProjectId+"."+cloudTables?.gcpDatasetId+"."+cloudTables?.gcpTableId}, + tamanho: ${formatBytes(result.uncompressedFileSize) || ""}, + dataset: ${query.dataset}, + table: ${resource?._id}, + columnDownload: true + }`) + window.open(`https://storage.googleapis.com/basedosdados-public/one-click-download/${cloudTables?.gcpDatasetId}/${cloudTables?.gcpTableId}/${cloudTables?.gcpTableId}.csv.gz`) + } + + return ( + + downloadTable()} + display="flex" + flexDirection="row" + alignItems="center" + gap="4px" + color="#0068C5" + fill="#0068C5" + _hover={{ + color:"#0057A4", + fill:"#0057A4" + }} + > + Baixar tabela que faz a tradução desta coluna + + + Dicionário + + ) + } + + function TranslationTable({ value, dictionary }) { + const downloadInfo = HasDownloadPermitted(value?.table?.uncompressedFileSize) + const cloudValues = value?.table?.cloudTables?.edges?.[0]?.node + + const gcpProjectID = cloudValues?.gcpProjectId || "" + const gcpDatasetID = cloudValues?.gcpDatasetId || "" + const gcpTableId = cloudValues?.gcpTableId || "" + + const datasetName = value?.table?.dataset?.name || "" + const tableName = value?.table?.name || "" + + if(gcpDatasetID === "br_bd_diretorios_data_tempo") return "Não precisa de tradução" + if(gcpDatasetID === "br_bd_diretorios_brasil") { + if(gcpTableId === "empresa" || gcpTableId === "cep") return "Não precisa de tradução" + } + if(value?.name === "ddd") return "Não precisa de tradução" + + const downloadUrl = `https://storage.googleapis.com/basedosdados-public/one-click-download/${gcpDatasetID}/${gcpTableId}/${gcpTableId}.csv.gz` + + return ( + + {value === null ? + + Não precisa de tradução + + : + + { + if(!downloadInfo.downloadPermitted) return + triggerGAEvent("download_da_tabela",`{ + gcp: ${gcpProjectID+"."+gcpDatasetID+"."+gcpTableId}, + tamanho: ${formatBytes(value?.table?.uncompressedFileSize) || ""}, + dataset: ${value?.table?.dataset?._id}, + table: ${value?.table?._id}, + columnDownload: true + }`) + }} + flexDirection="row" + alignItems="center" + gap="4px" + color="#0068C5" + fill="#0068C5" + _hover={{ + color:"#0057A4", + fill:"#0057A4" + }} + > + {value?.table?.isClosed || !downloadInfo.downloadPermitted + ? + <> + Acessar tabela que faz a tradução desta coluna + + + : + <> + Baixar tabela que faz a tradução desta coluna + + + } + + {datasetName} - {tableName} + + } + + {dictionary === true && + + } + + ) + } + + const measurementUnit = (value) => { + if(!value) return "Não informado" + + const splitValue = value.split(/([^a-z])/) + const translated = (value) => value.map((elm) => elm) + + return ( + {`$${translated(splitValue).join("")}$`} + ) + } + + function TableHeader({ header, ...props }) { + return ( + + + {header.pt} + + + + + + + + ) + } + + function TableValue({children, ...props}) { + return ( + + {children} + + ) + } + + function TranslationColumnException({ value }) { + const cloudValues = value?.node?.directoryPrimaryKey?.table?.cloudTables?.edges?.[0]?.node + const gcpDatasetID = cloudValues?.gcpDatasetId || "" + const gcpTableId = cloudValues?.gcpTableId || "" + + if(gcpDatasetID === "br_bd_diretorios_data_tempo") return "Não" + if(gcpDatasetID === "br_bd_diretorios_brasil") { + if(gcpTableId === "empresa" || gcpTableId === "cep") return "Não" + } + if(value?.node?.name === "ddd") return "Não" + if(value?.node?.coveredByDictionary === true) return "Sim" + if(value?.node?.directoryPrimaryKey?._id) return "Sim" + if(value?.node?.coveredByDictionary === false) return "Não" + return "Não informado" + } + + if(isError) return ( + + + + ) + + return ( + + + + + + + + + + + {template === "checks" && + + } + + + + {headers.map((elm, i) => ( + i != 0 && + ))} + + + + + {columns.length > 0 && columns.map((elm,i) => ( + + {template === "checks" && + + } + + + {elm?.node?.name ? elm.node.name : "Não informado"} + + + + + {template === "download" ? + + : + + } + + + + {elm?.node?.description ? elm.node.description : "Não informado"} + + + + {elm?.node?.bigqueryType?.name ? elm.node.bigqueryType.name : "Não informado"} + + + + {elm?.node?.coverage?.start && elm?.node?.coverage?.end ? + elm.node.coverage.start +" - "+ elm.node.coverage.end + : + "Não informado" + } + + + + {elm?.node?.measurementUnit ? + measurementUnit(elm.node.measurementUnit) + : + "Não informado" + } + + + + { + elm?.node?.containsSensitiveData === true ? "Sim" + : + elm?.node?.containsSensitiveData === false ? "Não" + : + "Não informado" + } + + + + {elm?.node?.observations ? elm.node.observations : "Não informado"} + + + ))} + +
+ + 0} + hasIndeterminate={checkedColumns.length !== resource.length && checkedColumns.length > 0} + /> + + +
+ + handleCheckboxChange(elm.node.name)} + /> + +
+
+
+
+ ) +} diff --git a/next/components/molecules/DataInformationQuery.js b/next/components/molecules/DataInformationQuery.js index f44bc089..73aaec93 100644 --- a/next/components/molecules/DataInformationQuery.js +++ b/next/components/molecules/DataInformationQuery.js @@ -1,359 +1,883 @@ import { VStack, - Stack, Tabs, TabList, + Tab, TabPanel, TabPanels, Text, - Image, Box, useClipboard, - Button, - HStack, - Menu, - MenuItem, - MenuList, - MenuButton, - IconButton, + Tooltip, + Skeleton, + Stack } from "@chakra-ui/react"; -import { useState, useEffect } from "react"; -import { isMobileMod } from "../../hooks/useCheckMobile.hook"; +import { useState, useEffect, useRef } from "react"; +import hljs from "highlight.js/lib/core"; +import sqlHighlight from "highlight.js/lib/languages/sql"; +import pythonHighlight from "highlight.js/lib/languages/python"; +import rHighlight from "highlight.js/lib/languages/r"; +import cookies from "js-cookie"; +import 'highlight.js/styles/obsidian.css' -import Link from "../atoms/Link"; import GreenTab from "../atoms/GreenTab"; -import SectionText from "../atoms/SectionText"; -import Subtitle from "../atoms/Subtitle"; -import RoundedButton from "../atoms/RoundedButton"; +import Toggle from "../atoms/Toggle"; +import ColumnsTable from "./ColumnsTable"; +import { AlertDiscalimerBox} from "./DisclaimerBox"; +import { triggerGAEvent, formatBytes } from "../../utils"; + +import { + getBigTableQuery +} from "../../pages/api/tables" -import DisclaimerBox from "./DisclaimerBox"; -import { triggerGAEvent } from "../../utils"; import { CopyIcon } from "../../public/img/icons/copyIcon"; import DownloadIcon from "../../public/img/icons/downloadIcon"; -import ExclamationIcon from "../../public/img/icons/exclamationIcon"; -import MenuVerticalIcon from "../../public/img/icons/menuVerticalIcon"; +import InfoIcon from "../../public/img/icons/infoIcon"; +import ChevronIcon from "../../public/img/icons/chevronIcon"; +import CheckIcon from "../../public/img/icons/checkIcon"; -export function BoxBigQueryGoogle({ href }) { - return ( - - - - - Para usar o BigQuery basta ter uma conta Google. Primeira vez? - Siga o passo a passo. - - - - - ) -} +export function CodeHighlight({ language, children }) { + const textRef = useRef(null) + const [isOverflowing, setIsOverflowing] = useState(false) + const [isExpanded, setIsExpanded] = useState(true) -export function PrismCodeHighlight({ language, children }) { + const [highlightedCode, setHighlightedCode] = useState("") const { hasCopied, onCopy } = useClipboard(children) + useEffect(() => { + if(language === "sql") hljs.registerLanguage("sql", sqlHighlight) + if(language === "python") hljs.registerLanguage("python", pythonHighlight) + if(language === "r") hljs.registerLanguage("r", rHighlight) + + const highlighted = hljs.highlight(children, { language:language }).value + setHighlightedCode(highlighted) + }, [children, language]) + + useEffect(() => { + if (textRef.current) { + setIsOverflowing(false) + setIsExpanded(true) + const { clientHeight } = textRef.current + setIsOverflowing(clientHeight > 190) + if(clientHeight > 190) setIsExpanded(false) + } + }, [highlightedCode]) + return ( -
-      
-        {children}
-      
-
-      
-    
+ + + + {hasCopied ? "Copiado" : "Copiar"} + {hasCopied ? + + : + + } + +
+ + {isOverflowing && ( + setIsExpanded(!isExpanded)} + fill="#878A8E" + color="#878A8E" + _hover={{ + fill:"#9D9FA3", + color:"#9D9FA3" + }} + > + + + {isExpanded ? "Recolher" : "Expandir"} + + + )} +
) } export default function DataInformationQuery({ resource }) { - const gcpDatasetID = resource?.cloudTables[0]?.gcpDatasetId - const gcpTableId = resource?.cloudTables[0]?.gcpTableId - const downloadUrl = `https://storage.googleapis.com/basedosdados-public/one-click-download/${gcpDatasetID}/${gcpTableId}/${gcpTableId}.csv.gz` - const queryBQ = `${gcpDatasetID}.${gcpTableId}` + const [tabAccessIndex, setTabAccessIndex] = useState(0) const [tabIndex, setTabIndex] = useState(0) const [downloadNotAllowed, setDownloadNotAllowed] = useState(false) + const [checkedColumns, setCheckedColumns] = useState([]) + const [numberColumns, setNumberColumns] = useState(0) + const [columnsTranslationPro, setColumnsTranslationPro] = useState([]) + const [sqlCode, setSqlCode] = useState("") + const [insufficientChecks, setInsufficientChecks] = useState(false) + const [includeTranslation, setIncludeTranslation] = useState(true) + const [hasLoadingColumns, setHasLoadingColumns] = useState(true) + const [isLoadingCode, setIsLoadingCode] = useState(false) + const [hasLoadingResponse, setHasLoadingResponse] = useState(false) + + const [gcpProjectID, setGcpProjectID] = useState("") + const [gcpDatasetID, setGcpDatasetID] = useState("") + const [gcpTableId, setGcpTableId] = useState("") + const [downloadUrl, setDownloadUrl] = useState("") + + const isUserPro = () => { + let user + if(cookies.get("userBD")) user = JSON.parse(cookies.get("userBD")) + + if(user?.proSubscriptionStatus === "active") return true + return false + } useEffect(() => { - if (window) window?.Prism?.highlightAll() + if(resource?.dataset?._id === "e083c9a2-1cee-4342-bedc-535cbad6f3cd") setIncludeTranslation(false) + }, [resource.dataset]) - if (resource?.numberRows === 0) return setDownloadNotAllowed(false) - if (resource?.numberRows) return resource?.numberRows > 200000 ? setDownloadNotAllowed(false) : setDownloadNotAllowed(true) - }, [resource]) + useEffect(() => { + if (resource?.numberRows === 0) setDownloadNotAllowed(false) + if (resource?.numberRows) resource?.numberRows > 200000 ? setDownloadNotAllowed(false) : setDownloadNotAllowed(true) + + if (resource?.cloudTables?.[0]) { + setGcpProjectID(resource.cloudTables[0]?.gcpProjectId || "") + setGcpDatasetID(resource.cloudTables[0]?.gcpDatasetId || "") + setGcpTableId(resource.cloudTables[0]?.gcpTableId || "") + } + + if (gcpDatasetID) { + if (gcpTableId) { + setDownloadUrl(`https://storage.googleapis.com/basedosdados-public/one-click-download/${gcpDatasetID}/${gcpTableId}/${gcpTableId}.csv.gz`) + } + } + }, [resource.numberRows, resource.cloudTables]) + + useEffect(() => { + if(resource._id === undefined) return + setIsLoadingCode(true) + setHasLoadingResponse(false) + setSqlCode("") + setInsufficientChecks(false) + }, [resource._id, checkedColumns, includeTranslation]) + + useEffect(() => { + if(hasLoadingResponse === true) { + SqlCodeString() + } + }, [hasLoadingResponse]) + + useEffect(() => { + if(sqlCode !== "") scrollFocus() + }, [sqlCode]) + + function scrollFocus() { + let idTab + + if (tabIndex === 0) idTab = "SQL_section" + else if (tabIndex === 1) idTab = "python_section" + else if (tabIndex === 2) idTab = "r_section" + else return + + const targetElement = document.getElementById(idTab) + + if (targetElement) { + const { top } = targetElement.getBoundingClientRect() + const heightScreen = window.innerHeight + const positionTarget = top + window.pageYOffset + + window.scrollTo({ + top: positionTarget - (heightScreen / 2), + behavior: "smooth", + }) + } + } - const handlerDownload = () => { - if (downloadNotAllowed === false) return null + async function SqlCodeString() { + const result = await getBigTableQuery(resource._id, checkedColumns, includeTranslation) + setSqlCode(result.trim()) + setIsLoadingCode(false) + } + + const handleAccessIndexes = (index) => { + const categoryValues = ["BigQuery e Pacotes", "Download"] + setTabAccessIndex(index) + triggerGAEvent("category_click", categoryValues[index]) + } - return window.open(downloadUrl) + const handleCategoryIndexes = (index) => { + const categoryValues = ["SQL", "Python", "R"] + setTabIndex(index) + triggerGAEvent("category_click", categoryValues[index]) } - const handleIndexes = (index) => { - const categoryValues = ["SQL", "Python", "R", "Stata", "Download"]; - setTabIndex(index); - triggerGAEvent("category_click", categoryValues[index]); + const queryLanguage = () => { + const language = { + 0 : "SQL", + 1 : "Python", + 2 : "R" + } + return language[tabIndex] ? language[tabIndex] : "" } return ( - Consulta aos dados handleIndexes(index)} - index={tabIndex} + width="100%" + variant="unstyled" + isLazy + onChange={(index) => handleAccessIndexes(index)} + index={tabAccessIndex} + overflow="hidden" > - - SQL - - - Python - - - R - - - Stata - - {resource?.isClosed ? <> : - isMobileMod() ? - - - } - borderRadius="none" - borderBottom={tabIndex === 4 && "3px solid #2B8C4D"} - /> - - handleIndexes(4)}>Download - - - : - - Download - } + BigQuery e Pacotes + Download - - - {resource?.isClosed ? - - Com uma assinatura BD Pro válida, copie o código abaixo e cole no Editor de Consultas no BigQuery: - - : - + + + - Copie o código abaixo, - clique aqui - para ir ao datalake no BigQuery e cole no Editor de Consultas: - - } - - {`SELECT * FROM \`basedosdados.${queryBQ}\` LIMIT 100`} - + {tabAccessIndex === 0 ? "Selecione as colunas que você deseja acessar:" : "Confira as colunas da tabela:"} +
+ - - - - - - Criamos um pacote em Python para você acessar o datalake. Basta rodar o código: - + + Essa tabela possui códigos institucionais que variam entre anos. Por isso, ainda estamos trabalhando para automatizar o processo de tradução. + Por enquanto, recomendamos acessar o dicionário e os diretórios para entender como traduzir os códigos presentes na tabela. + + - - {`import basedosdados as bd + + + + setIncludeTranslation(!includeTranslation)} + />Traduzir códigos institucionais + + + + + + -# Para carregar o dado direto no pandas -df = bd.read_table(dataset_id='${gcpDatasetID}', -table_id='${gcpTableId}', -billing_project_id="")`} - + {checkedColumns.length > 0 && resource.uncompressedFileSize && resource.uncompressedFileSize/(1024 * 1024 * 1024) > 5 && + + + Essa tabela completa, com todas as colunas, tem {formatBytes(resource.uncompressedFileSize)}. Cuidado para não ultrapassar o limite de processamento gratuito do BigQuery. + {numberColumns === checkedColumns.length && "Para otimizar a consulta, você pode selecionar menos colunas ou adicionar filtros no BigQuery."} + + + } - - - + {columnsTranslationPro.length > 0 && tabAccessIndex !== 1 && + + A tabela de tradução da{columnsTranslationPro.length > 1 && "s"} coluna{columnsTranslationPro.length > 1 && "s"} {columnsTranslationPro.map((elm) => elm?.node?.name).join(", ")} é exclusiva para assinantes. Todos os demais códigos institucionais são de acesso aberto. + + } - - + + + } + + - Criamos um pacote em R para você acessar o datalake. Basta rodar o código: - + { + if(checkedColumns.length === 0) return setInsufficientChecks(true) + triggerGAEvent("gerar_consulta_click", queryLanguage()) + setHasLoadingResponse(true) + }} + display="flex" + alignItems="center" + height="40px" + width="fit-content" + borderRadius="8px" + backgroundColor="#2B8C4D" + padding="8px 16px" + cursor="pointer" + color="#FFF" + fill="#FFF" + fontFamily="Roboto" + fontWeight="500" + fontSize="14px" + gap="8px" + lineHeight="20px" + _hover={{ + backgroundColor:"#22703E" + }} + > + Gerar consulta + + + - - {`install.packages("basedosdados") -library("basedosdados") + + + {downloadNotAllowed ? + isUserPro() ? "" : + + Estes dados estão disponíveis porque diversas pessoas colaboram para a sua manutenção. + Antes de baixar os dados, apoie você também com uma doação financeira ou + + saiba como contribuir com seu tempo. + + + : + + } -# Defina o seu projeto no Google Cloud -set_billing_id("") + { triggerGAEvent("download_da_tabela",` + { + gcp: ${gcpProjectID+"."+gcpDatasetID+"."+gcpTableId}, + tamanho: ${formatBytes(resource.uncompressedFileSize) || ""}, + dataset: ${resource?.dataset?._id}, + table: ${resource?._id}, + }` + ) }} + target="_blank" + display="flex" + alignItems="center" + height="40px" + width="fit-content" + borderRadius="8px" + backgroundColor={downloadNotAllowed ? "#2B8C4D" : "#ACAEB1"} + padding="8px 16px" + cursor={downloadNotAllowed ? "pointer" : "default"} + color="#FFF" + fill="#FFF" + fontFamily="Roboto" + fontWeight="500" + fontSize="14px" + gap="8px" + lineHeight="20px" + pointerEvents={downloadNotAllowed ? "default" : "none"} + _hover={{ + backgroundColor: "#22703E" + }} + > + + Download da tabela {downloadNotAllowed && `(${formatBytes(resource.uncompressedFileSize)})`} + + + -# Para carregar o dado direto no R -query <- bdplyr("${queryBQ}") -df <- bd_collect(query)`} - + + handleCategoryIndexes(index)} + index={tabIndex} + overflow="hidden" + > + + SQL + Python + R + - - - + + + + + + No editor de consultas do BigQuery, digite a seguinte instrução: + + - - - Criamos um pacote em Stata para você acessar o datalake. Basta rodar o código: - + + + Primeira vez usando o BigQuery? + + Siga o passo a passo. + + + - - {`net install basedosdados, from("https://raw.githubusercontent.com/basedosdados/mais/master/stata-package") + + + + + Acessar o BigQuery + + + + -bd_read_table, /// - path("") /// - dataset_id("${gcpDatasetID}") /// - table_id("${gcpTableId}") /// - billing_project_id("")`} - + + + {sqlCode} + + + - - - - - - - Estes dados estão disponíveis porque diversas pessoas colaboram para a sua manutenção. - - - Antes de baixar os dados, apoie você também com uma doação financeira ou saiba como contribuir com seu tempo. - - - {!downloadNotAllowed && - - - - - ATENÇÃO: O tamanho da tabela ultrapassou o limite permitido para download, de 200.000 linhas. - Para acessar os dados, utilize nosso datalake no BigQuery ou nossos pacotes em Python, R e Stata. - - - - } + + + + No terminal do Python, digite a seguinte instrução: + + - handlerDownload()} - > - - Download dos dados - - - + + + Primeira vez usando o pacote Python? + + Siga o passo a passo. + + + + + + {`import basedosdados as bd + +billing_id = + +query = """ + ${sqlCode} +""" + +bd.read_sql(query = query, billing_project_id = billing_id)`} + + + + + + + + No terminal do R, digite a seguinte instrução: + + + + + + Primeira vez usando o pacote R? + + Siga o passo a passo. + + + + + + {` +# Defina o seu projeto no Google Cloud +set_billing_id("") + +# Para carregar o dado direto no R +query <- " +${sqlCode} +" + +read_sql(query, billing_project_id = get_billing_id()) +`} + + + + + + + + ) diff --git a/next/components/molecules/DisclaimerBox.js b/next/components/molecules/DisclaimerBox.js index a741c10e..031794e1 100644 --- a/next/components/molecules/DisclaimerBox.js +++ b/next/components/molecules/DisclaimerBox.js @@ -1,7 +1,15 @@ -import { Stack } from "@chakra-ui/react"; +import { + Stack, + Box, + Text, +} from "@chakra-ui/react"; import SectionText from "../atoms/SectionText"; +import InfoIcon from "../../public/img/icons/infoIcon"; +import WarningIcon from "../../public/img/icons/warningIcon"; +import ExclamationIcon from "../../public/img/icons/exclamationIcon"; +import { SolidSuccessIcon } from "../../public/img/icons/successIcon"; -export default function DisclaimerBox({ title, text, children, ...style }) { +export function DisclaimerBox({ title, text, children, ...style }) { return ( - ); + ) } + +export function AlertDiscalimerBox({ type = "info", text, children, ...props }) { + const backgroundColor = { + info: "#E4F2FF", + warning: "#FFF8DF", + error: "#F6E3E3", + success: "#D5E8DB" + } + const borderColor = { + info: "#0068C5", + warning: "#F9C50B", + error: "#BF3434", + success: "#2B8C4D" + } + + return ( + + + + {type === "info" && + + } + + {type === "warning" && + + } + + {type === "error" && + + } + + {type === "success" && + + } + + + {text} + {children} + + + + ) +} \ No newline at end of file diff --git a/next/components/molecules/Footer.js b/next/components/molecules/Footer.js index 44d08a6e..ca4600cc 100644 --- a/next/components/molecules/Footer.js +++ b/next/components/molecules/Footer.js @@ -2,11 +2,11 @@ import { HStack, Stack, VStack, + Text } from "@chakra-ui/react"; -import { useState, useEffect } from "react"; import Link from "../atoms/Link"; import BodyText from "../atoms/BodyText" -import { useCheckMobile } from "../../hooks/useCheckMobile.hook" +import { isMobileMod } from "../../hooks/useCheckMobile.hook" import YoutubeIcon from "../../public/img/icons/youtubeIcon"; import TwitterIcon from "../../public/img/icons/twitterIcon"; @@ -62,13 +62,61 @@ function FooterLink(props) { ) } -export default function Footer({ ocult = false }) { - const mobileCheck = useCheckMobile() - const [isMobileMod, setIsMobileMod] = useState(false) - - useEffect(() => { - setIsMobileMod(mobileCheck) - },[]) +function TextFooterSimple({children, ...props}) { + return ( + + {children} + + ) +} + +export default function Footer({ template, ocult = false }) { + if(template === "simple") return ( + + + + + ® 2024 Base dos Dados + + + Termos de Uso + + + Políticas de Privacidade + + + Contato + + + + + ) if(ocult === true) return null @@ -105,17 +153,17 @@ export default function Footer({ ocult = false }) { - + Mecanismo de busca @@ -133,7 +181,7 @@ export default function Footer({ ocult = false }) { - + Captura de dados @@ -148,7 +196,7 @@ export default function Footer({ ocult = false }) { - + Documentação @@ -157,7 +205,7 @@ export default function Footer({ ocult = false }) { - + Quem somos @@ -189,7 +237,7 @@ export default function Footer({ ocult = false }) { @@ -198,17 +246,17 @@ export default function Footer({ ocult = false }) { maxWidth="1264px" margin="0 auto" justifyContent="space-between" - flexDirection={isMobileMod && "column-reverse"} + flexDirection={isMobileMod() && "column-reverse"} alignItems="flex-start" spacing={0} > ® 2024 Base dos Dados diff --git a/next/components/molecules/FormTable.js b/next/components/molecules/FormTable.js index c33bf77a..ab8a7b8d 100644 --- a/next/components/molecules/FormTable.js +++ b/next/components/molecules/FormTable.js @@ -6,7 +6,6 @@ import { Input, Textarea, Select, - Checkbox, Alert, AlertIcon, AlertTitle, @@ -21,6 +20,7 @@ import { } from "@chakra-ui/react"; import { useState, useEffect } from "react" import { useRouter } from "next/router"; +import Checkbox from "../atoms/Checkbox"; import RoundedButton from "../atoms/RoundedButton"; import SelectSearch from "../atoms/SelectSearch"; import LoadingSpin from "../atoms/Loading"; diff --git a/next/components/molecules/Menu.js b/next/components/molecules/Menu.js index 9859681b..ae77a85b 100644 --- a/next/components/molecules/Menu.js +++ b/next/components/molecules/Menu.js @@ -26,9 +26,10 @@ import { useRouter } from "next/router" import cookies from "js-cookie"; import MenuDropdown from "./MenuDropdown"; import { isMobileMod, useCheckMobile } from "../../hooks/useCheckMobile.hook" -import ControlledInput from "../atoms/ControlledInput"; +import { ControlledInputSimple } from "../atoms/ControlledInput"; import Link from "../atoms/Link"; import RoundedButton from "../atoms/RoundedButton"; +import HelpWidget from "../atoms/HelpWidget"; import { triggerGAEvent } from "../../utils"; import BDLogoProImage from "../../public/img/logos/bd_logo_pro"; @@ -37,7 +38,6 @@ import BDLogoLabImage from "../../public/img/logos/bd_logo_lab"; import BDLogoImage from "../../public/img/logos/bd_logo"; import FarBarsIcon from "../../public/img/icons/farBarsIcon"; import SearchIcon from "../../public/img/icons/searchIcon"; -import CrossIcon from "../../public/img/icons/crossIcon"; import RedirectIcon from "../../public/img/icons/redirectIcon"; import SettingsIcon from "../../public/img/icons/settingsIcon"; import SignOutIcon from "../../public/img/icons/signOutIcon"; @@ -62,6 +62,7 @@ function MenuDrawer({ userData, isOpen, onClose, links }) { backgroundColor={b.color} minWidth="100px" height="38px" + fontFamily="Roboto" fontSize="20px" borderRadius="30px" onClick={() => window.open(b.href, "_blank")} @@ -81,7 +82,8 @@ function MenuDrawer({ userData, isOpen, onClose, links }) { > @@ -104,8 +106,9 @@ function MenuDrawer({ userData, isOpen, onClose, links }) { display="flex" gap="16px" fontSize="16px" - fontFamily="Ubuntu" - fontWeight="300" + fontFamily="Roboto" + letterSpacing="0.1px" + fontWeight="400" href={c.href} >{c.icon && c.icon} {c.name} ) @@ -119,7 +122,8 @@ function MenuDrawer({ userData, isOpen, onClose, links }) { {key} @@ -133,26 +137,56 @@ function MenuDrawer({ userData, isOpen, onClose, links }) { <> : - window.open("/user/login", "_self")} + Entrar - - window.open("/user/register", "_self")} + + + Cadastrar - + } @@ -200,19 +234,19 @@ function MenuDrawerUser({ userData, isOpen, onClose}) { {userData?.username || ""} {userData?.email || ""} @@ -227,10 +261,10 @@ function MenuDrawerUser({ userData, isOpen, onClose}) { Configurações @@ -250,10 +284,10 @@ function MenuDrawerUser({ userData, isOpen, onClose}) { key={index} color="#575757" fontSize="14px" - fontFamily="Ubuntu" + fontFamily="Roboto" + letterSpacing="0.1px" fontWeight="400" lineHeight="27px" - letterSpacing="0.3px" onClick={() => { onClose() router.push({pathname: `/user/${userData.username}`, query: elm.value})} @@ -268,24 +302,30 @@ function MenuDrawerUser({ userData, isOpen, onClose}) { { cookies.remove('userBD', { path: '/' }) cookies.remove('token', { path: '/' }) window.open("/", "_self") }} > - + Sair @@ -322,7 +362,7 @@ function MenuUser ({ userData, onOpen, onClose }) { {userData?.username ? userData?.username : ""} {userData?.email ? userData?.email : ""} @@ -420,20 +460,20 @@ function MenuUser ({ userData, onOpen, onClose }) { window.open(`/user/${userData.username}`, "_self")} > - + Configurações @@ -442,10 +482,10 @@ function MenuUser ({ userData, onOpen, onClose }) { { cookies.remove('userBD', { path: '/' }) cookies.remove('token', { path: '/' }) @@ -455,11 +495,11 @@ function MenuUser ({ userData, onOpen, onClose }) { Sair @@ -471,169 +511,105 @@ function MenuUser ({ userData, onOpen, onClose }) { } -function SearchInput ({ status }) { - const router = useRouter() - const { query } = router - const [showSearchInput, setShowSearchInput] = useState(false) - - const [showSearch, setShowSearch] = useState(false) +function SearchInputUser ({ user }) { + const inputMobileRef = useRef(null) const [search, setSearch] = useState("") + const [showInput, setShowInput] = useState(false) + const [inputFocus, setInputFocus] = useState(false) - const searchStatus = () => { - const newStatus = !showSearch - setShowSearch(newStatus) - status({ - status: newStatus - }) + function openSearchLink() { + if(search.trim() === "") return + triggerGAEvent("search_menu", search.trim()) + window.open(`/dataset?q=${search.trim()}`, "_self") } - useEffect(() => { - if(query.dataset) return setShowSearchInput(true) - },[query]) - - function openSearchLink() { - triggerGAEvent("search_menu", search) - window.open(`/dataset?q=${search}`, "_self") + const handleClickOutside = (event) => { + if (inputMobileRef.current && !inputMobileRef.current.contains(event.target)) { + setShowInput(false); + } } - if(!showSearchInput) return null - - return ( - <> - {!showSearch ? - - : - { + if (showInput) { + document.addEventListener('click', handleClickOutside) + } else { + document.removeEventListener('click', handleClickOutside) + } + + return () => { + document.removeEventListener('click', handleClickOutside) + } + }, [showInput]) + + if (isMobileMod()) return ( + + + openSearchLink()} /> } /> - } - + + + { + setShowInput(true) + setTimeout(() => { + inputMobileRef.current.focus() + }, 0) + }} + /> + ) -} - -function SearchInputUser () { - const [search, setSearch] = useState("") - const [showSearch, setShowSearch] = useState(false) - - function openSearchLink() { - triggerGAEvent("search_menu", search) - window.open(`/dataset?q=${search}`, "_self") - } - - // if(isMobileMod()) return ( - // - // setShowSearch(true)} - // /> - // - // openSearchLink()} - // /> - // } - // /> - // - // - // ) - - if(isMobileMod()) return null return ( - openSearchLink()} /> } @@ -643,12 +619,6 @@ function SearchInputUser () { } function DesktopLinks({ userData, links, position = false, path, userTemplate = false }) { - const [statusSearch, setStatusSearch] = useState(false) - - const searchStatus = (elm) => { - setStatusSearch(elm.status) - } - function LinkMenuDropDown ({ url, text, icon }) { const [flag, setFlag] = useBoolean() @@ -658,13 +628,14 @@ function DesktopLinks({ userData, links, position = false, path, userTemplate = @@ -703,7 +674,11 @@ function DesktopLinks({ userData, links, position = false, path, userTemplate = backgroundColor={b.color} minWidth="80px" height="35px" - fontSize="15px" + fontSize="14px" + fontFamily="Roboto" + letterSpacing="0.1px" + fontWeight="400" + lineHeight="20px" borderRadius="30px" > {b.name} @@ -721,9 +696,11 @@ function DesktopLinks({ userData, links, position = false, path, userTemplate = marginTop="10px" minWidth="202px" borderColor="#FFF" - fontFamily="Ubuntu" + fontSize="14px" + fontFamily="Roboto" + letterSpacing="0.1px" fontWeight="400" - letterSpacing="0.3px" + lineHeight="20px" borderRadius="10px" padding="32px" _first={{ paddingTop: "10px"}} @@ -745,11 +722,12 @@ function DesktopLinks({ userData, links, position = false, path, userTemplate = return ( @@ -759,31 +737,93 @@ function DesktopLinks({ userData, links, position = false, path, userTemplate = })} - - {userTemplate && !isMobileMod() && } - - {!statusSearch && - - {userData ? ( - - - - ) : ( - <> - - Entrar - - - - Cadastrar - - - - )} - + {userTemplate && !isMobileMod() && + } + + + {(path === "/dataset" || path === "/dataset/[dataset]") && + + } + + {userData ? ( + + + + ) : ( + <> + + Entrar + + + + Cadastrar + + + )} + - ); + ) } export default function MenuNav({ simpleTemplate = false, userTemplate = false }) { @@ -797,6 +837,32 @@ export default function MenuNav({ simpleTemplate = false, userTemplate = false } const [isScrollDown, setIsScrollDown] = useState(false) const [userData, setUserData] = useState(null) + const [lastScrollY, setLastScrollY] = useState(0) + const [menuVisible, setMenuVisible] = useState(true) + + const handleScroll = () => { + const currentScrollY = window.scrollY + if (currentScrollY > lastScrollY) { + setMenuVisible(false) + } else { + setMenuVisible(true) + } + setLastScrollY(currentScrollY) + } + + useEffect(() => { + if(route !== "/dataset/[dataset]") return + window.addEventListener('scroll', handleScroll) + return () => { + window.removeEventListener('scroll', handleScroll) + } + }, [lastScrollY]) + + function maxWidthDataset() { + if (route === "/dataset" || route === "/dataset/[dataset]") return "1440px" + return "1264px" + } + useEffect(() => { let userInfo = userBD if(userInfo !== null && userInfo !== "undefined") { @@ -833,7 +899,7 @@ export default function MenuNav({ simpleTemplate = false, userTemplate = false } ], Contato: "/contato", Button: [] - }; + } useEffect(() => { document.addEventListener("scroll", () => { @@ -863,16 +929,17 @@ export default function MenuNav({ simpleTemplate = false, userTemplate = false } width="100%" left="0px" backgroundColor="#FFFFFF" - padding="16px 28px" + padding={isMobileMod() ? "15px 20px" : "15px 24px"} zIndex="99" transition="0.5s" as="nav" + transform={menuVisible ? 'translateY(0)' : 'translateY(-100%)'} > @@ -883,6 +950,7 @@ export default function MenuNav({ simpleTemplate = false, userTemplate = false } top="0" left="0" margin="20px 0 0 20px" + marginRight="auto" width="30px" height="30px" onClick={menuDisclosure.onOpen} @@ -895,8 +963,13 @@ export default function MenuNav({ simpleTemplate = false, userTemplate = false } aria-label="Home" width={ route === "/" ? - isScrollDown ? "88px" : "0" - : "88px" + isScrollDown ? "80px" : "0" + : "80px" + } + minWidth={ + route === "/" ? + isScrollDown ? "80px" : "0" + : "80px" } _hover={{opacity:"none"}} href={route === "/" ? "/#home" : "/"} @@ -921,7 +994,11 @@ export default function MenuNav({ simpleTemplate = false, userTemplate = false } /> } - {userTemplate && isMobileMod() && } + {userTemplate && isMobileMod() && + + } {useCheckMobile() && userData && { return ( {string} @@ -146,285 +142,233 @@ export function TemporalCoverageBar ({ value }) { ) } - value = !!value ? Object.values(value) : null + useEffect(() => { + if (value === null || value === undefined) return setValues(null) - if(!value) return - if(!value.length) return + let newValue = {} - let dateStart = "" - let dateMid = "" - let dateEnd = "" + if(value["2"]?.type === "closed") newValue["3"] = value["2"].date - if (value.length === 2) { - dateStart = value[0] - dateEnd = value[1] - } + if(value["0"]?.type === "open") newValue["0"] = value["0"].date + if(value["0"]?.type === "closed") newValue["2"] = value["0"].date - if (value.length === 3) { - dateStart = value[0] - dateMid = value[1] - dateEnd = value[2] - } + if(value["1"]?.type === "open") newValue["1"] = value["1"].date + if(value["1"]?.type === "closed") newValue["3"] = value["1"].date - const checkoutBdpro = (value) => { - if(value === "open") return - window.open("/precos", "_blank") - } + setValues(newValue) + }, [value]) - const BadgeContainer = ({ - value, - bool = false, - mouseOn = null, - mouseOff = null, - ...props - }) => { - const toggleTag = value === "open" + if(values === null) return - return ( - checkoutBdpro(value)} - _hover={toggleTag ? "" : {opacity: 0.7}} - opacity={bool && 0.7} - onMouseEnter={mouseOn} - onMouseLeave={mouseOff} - cursor="pointer" - >{toggleTag ? - "GRÁTIS" : - PAGO - - } - - ) - } - - const TooltipContent = ({children, text, firstValue, lastValue, ...props}) => { - return ( + return ( + + + GRÁTIS + + - {text} - -
+ - - -
─ -
- - -
+
-
- } - hasArrow - bg="#2A2F38" - fontSize="16px" - fontWeight="400" - padding="5px 16px 6px" - marginTop="10px" - color="#FFF" - borderRadius="6px" - placement="top" - top="-4px" - fontFamily="lato" - {...props} - > - {children} -
- ) - } - return ( - - - - - -
- - -
-
- - {dateMid !== "" && - - - + - - + position="absolute" + display="flex" + alignItems="center" + flexDirection="column" + right={0} + top="-3px" + > + + + - -
- - -
+
- } + - - + - checkoutBdpro(dateEnd?.type)} - onMouseEnter={setFlag.on} - onMouseLeave={setFlag.off} - /> - - - -
- - -
-
-
+ width="100%" + borderBottom="solid 3px #0068C5" + marginBottom="10px" + > + + + + + + + + + + + + + + + + +
) } \ No newline at end of file diff --git a/next/components/organisms/BdmTablePage.js b/next/components/organisms/BdmTablePage.js index 4c303173..d83bd530 100644 --- a/next/components/organisms/BdmTablePage.js +++ b/next/components/organisms/BdmTablePage.js @@ -1,99 +1,100 @@ import { - VStack, + HStack, Stack, Box, Text, - Grid, - GridItem, + Skeleton, + SkeletonText, + Divider, Tooltip, } from "@chakra-ui/react"; import { useState, useEffect } from "react"; -import { useCheckMobile } from "../../hooks/useCheckMobile.hook"; -import Link from "../atoms/Link"; -import SectionText from "../atoms/SectionText"; -import { SimpleTable } from "../atoms/SimpleTable"; -import LoadingSpin from "../atoms/Loading"; -import Subtitle from "../atoms/Subtitle"; +import ReadMore from "../atoms/ReadMore"; +import { formatBytes } from "../../utils"; +import ObservationLevel from "../atoms/ObservationLevelTable"; import { TemporalCoverageBar } from "../molecules/TemporalCoverageDisplay"; -import ColumnDatasets from "../molecules/ColumnDatasets"; -import BaseResourcePage from "../molecules/BaseResourcePage"; import DataInformationQuery from "../molecules/DataInformationQuery"; import FourOFour from "../templates/404"; -import StarIcon from "../../public/img/icons/starIcon"; -import FrequencyIcon from "../../public/img/icons/frequencyIcon"; -import PartitionIcon from "../../public/img/icons/partitionIcon"; -import UserIcon from "../../public/img/icons/userIcon"; -import VersionIcon from "../../public/img/icons/versionIcon"; import EmailIcon from "../../public/img/icons/emailIcon"; import GithubIcon from "../../public/img/icons/githubIcon"; import WebIcon from "../../public/img/icons/webIcon"; import TwitterIcon from "../../public/img/icons/twitterIcon"; -import FileIcon from "../../public/img/icons/fileIcon"; import InfoIcon from "../../public/img/icons/infoIcon"; import DownloadIcon from "../../public/img/icons/downloadIcon"; +import RedirectIcon from "../../public/img/icons/redirectIcon"; export default function BdmTablePage({ id }) { const [isLoading, setIsLoading] = useState(true) const [resource, setResource] = useState({}) - const [isError, setIsError] = useState({}) - - const fetchBdmTable = async () => { - try { - const result = await fetch(`/api/tables/getBdmTable?p=${id}`, { method: "GET" }) - .then(res => res.json()) - setResource(result) - } catch (error) { - setIsError(error) - console.error(error) - } - setIsLoading(false) - } + const [isError, setIsError] = useState(false) useEffect(() => { - fetchBdmTable() + const fetchData = async () => { + setIsLoading(true) + try { + const response = await fetch(`/api/tables/getBdmTable?p=${id}`, { method: "GET" }) + const result = await response.json() + + if (result.success) { + setResource(result.resource) + setIsError(false) + } else { + console.error(result.error) + setIsError(true) + } + } catch (error) { + console.error("Fetch error: ", error) + setIsError(true) + } finally { + setIsLoading(false) + } + } + + fetchData() }, [id]) - const AddInfoTextBase = ({title, text, info, children, ...style}) => { + const TooltipText = ({ text, info, ...props }) => { return ( - + {title} - {info && - - - - } - - + > {text} - - {children} + + + +
) } @@ -125,225 +126,621 @@ export default function BdmTablePage({ id }) { return { alt: alt, cursor: "pointer", - width:"18px", - height:"18px", - fill: "#42B0FF", + width:"20px", + height:"20px", + fill: "#0068C5", + _hover: { + fill: "#0057A4" + }, onClick: () => {window.open(href)} } } const PublishedOrDataCleanedBy = ({ resource }) => { - return ( - <> - {resource?.firstName && resource?.lastName ? {`${resource.firstName} ${resource.lastName}`} : Não listado} + + {resource?.firstName && resource?.lastName ? + + {`${resource.firstName} ${resource.lastName}`} + + : + + Não informado + + } {resource?.email && } {resource?.github && } {resource?.website && } {resource?.twitter && } - + ) } - const UpdateFrequency = () => { - const value = resource?.updates?.[0] - if(value === undefined || Object.keys(value).length === 0) return "Não listado" - - if(value?.frequency >= 0 && value?.entity?.name) return `${value.frequency} ${value.entity.name}` - if(value?.entity?.name) return `${value.entity.name}` - - return "Não listado" - } - - const Empty = () => { + const StackSkeleton = ({ children, ...props }) => { return ( -

- Não listado -

+ + {children} + ) } - const ObservationLevel = () => { - const notFound = Nenhum nível da observação fornecido. - if(resource?.observationLevels === undefined || Object.keys(resource?.observationLevels).length === 0) return notFound - - let array = [] - const keys = Object.keys(resource?.observationLevels) - - keys.forEach((elm) => { - const value = resource?.observationLevels[elm] + const formatDate = (value) => { + const date = new Date(value); + const formattedDate = date.getFullYear()+"-"+String(date.getMonth() + 1).padStart(2, "0")+"-"+String(date.getDate()).padStart(2, "0") + return formattedDate + } - const newValue = [value?.entity?.name || , value?.columns[0]?.name || ] - array.push(newValue) - }) + const getUpdateFormat = (value) => { + const formats = { + "second":"Atualização por segundo", + "minute":"Atualização por minuto", + "hour":"Atualização por hora", + "day":"Atualização diária", + "week":"Atualização semanal", + "month":"Atualização mensal", + "bimester":"Atualização bimestral", + "quarter":"Atualização trimestral", + "semester":"Atualização semestral", + "year":"Atualização anual", + } - return ( - - ) + return formats[value] ? formats[value] : "Atualização não definida" } - if(isError?.message?.length > 0 || resource === null || Object.keys(resource).length < 0) return - - if(isLoading) return + if(isError) return return ( - - - - - Descrição - - {resource.description || "Nenhuma descrição fornecida."} - - - - + + {resource?.name} + + {resource?.uncompressedFileSize && + + {`(${formatBytes(resource.uncompressedFileSize)})`} + + } + + + - Cobertura temporal - - - - - - Colunas - - - - - - - Nível da observação - - - - - - - Informações adicionais - - - - - - - - - - - - - - - - - - - - - - - - - - + + {resource?.description || "Não informado"} + + + + + + + Cobertura temporal da tabela + + + + + + + + + + + + Acesso aos dados + + + + + + + + + + Frequência de atualização dos dados + + + + + + {resource?.updates?.[0]?.latest ? + formatDate(resource.updates[0].latest) + : + "Não informado" + }: Última vez que atualizamos na BD + {resource?.updates?.[0]?.entity?.slug && Publicação por - - - - - - - - - + > + {getUpdateFormat(resource.updates[0].entity.slug)} +
+ } + {!resource?.updates?.[0] && Tratamento por - - - - - - - - - + Sem previsão de atualização +
+ } + + + {resource?.rawDataSource?.[0]?.updates?.[0]?.latest ? + formatDate(resource.rawDataSource[0].updates[0].latest) + : + "Não informado" + }: Última vez que atualizaram na fonte original + {resource?.rawDataSource?.[0]?.updates?.[0]?.entity?.slug ? + + {getUpdateFormat(resource?.rawDataSource?.[0]?.updates?.[0]?.entity?.slug)} + + : + !resource?.rawDataSource?.[0]?.updates?.[0] || !resource?.updates?.[0]?.frequency ? + + Sem previsão de atualização + + : + <> + } + + + {resource?.rawDataSource?.[0]?.polls?.[0]?.latest ? + formatDate(resource.rawDataSource[0].polls[0].latest) + : + "Não informado" + }: Última vez que verificamos a fonte original + + + + + + + + ID do BigQuery + + + + + + {!resource?.cloudTables ? + "Não informado" + : + resource?.cloudTables?.[0]?.gcpProjectId+"."+resource?.cloudTables?.[0]?.gcpDatasetId+"."+resource?.cloudTables?.[0]?.gcpTableId + } + - - - - - + + + + + + + + + + + {resource?.partitions ? resource.partitions :"Não informado"} + + + + + + + + + + + {resource?.observationLevels && Object.keys(resource?.observationLevels).length > 0 ? + + : + - {resource?.auxiliaryFilesUrl && - - Download dos arquivos - - - } - - - - - + Não informado + + } + + + + + + + + + + {resource?.auxiliaryFilesUrl ? + + Download dos arquivos + + + : + "Não informado" + } + + + + + + + + + + + + {resource?.rawDataSource?.[0]?._id && resource?.rawDataSource?.[0]?.dataset?._id ? + + {resource.rawDataSource[0].name} + + : + "Não informado" + } + + + + + + + + + Informações adicionais + + + + + Publicação por + + + + + Tratamento por + + + + + Versão + {resource?.version || "Não informado"} + + ) } diff --git a/next/components/organisms/Database.js b/next/components/organisms/Database.js index 5529952f..a661d5dc 100644 --- a/next/components/organisms/Database.js +++ b/next/components/organisms/Database.js @@ -1,21 +1,12 @@ import { - Heading, HStack, Image, Stack, VStack, - Flex, Text, - Center, - Tooltip, + Box } from "@chakra-ui/react"; import { useCheckMobile } from "../../hooks/useCheckMobile.hook"; -import { CategoryIcon } from "../atoms/CategoryIcon"; -import Title from "../atoms/Title"; -import Link from "../atoms/Link"; -import SectionText from "../atoms/SectionText"; -import { ImageOrganization } from "../atoms/ImageOrganization"; -import { TemporalCoverageString } from "../molecules/TemporalCoverageDisplay"; import LinkIcon from "../../public/img/icons/linkIcon"; import InfoArrowIcon from "../../public/img/icons/infoArrowIcon"; @@ -30,7 +21,6 @@ export default function Database({ rawDataSources, informationRequests, contains, - themes = [], }) { const Tables = () => { @@ -38,32 +28,38 @@ export default function Database({ if(tables.number === undefined) tablesNumber = 0 return ( - 0 && `/dataset/${id}?table=${tables.id}`}> - 0 ? "pointer" : "normal"} - _hover={tablesNumber > 0 && {opacity: "0.7"}} + 0 ? "pointer" : "normal"} + color={tablesNumber === 0 ? "#ACAEB1" : "#0068C5"} + fill={tablesNumber === 0 ? "#ACAEB1" : "#0068C5"} + pointerEvents={tablesNumber === 0 && "none"} + fontFamily="Roboto" + fontWeight="400" + fontSize="14px" + lineHeight="20px" + _hover={{ + color: "#0057A4", + fill: "#0057A4" + }} + href={tablesNumber > 0 ? `/dataset/${id}?table=${tables.id}` : ""} + > + + - - - {tablesNumber}{" "} - {tablesNumber === 1 ? "tabela tratada" : "tabelas tratadas"} - - - + {tablesNumber}{" "} + {tablesNumber === 1 ? "tabela tratada" : "tabelas tratadas"} + + ) } @@ -72,28 +68,38 @@ export default function Database({ if(rawDataSources.number === undefined) rawDataSourcesNumber = 0 return ( - 0 && `/dataset/${id}?raw_data_source=${rawDataSources.id}`}> - 0 ? "pointer" : "normal"} - _hover={rawDataSourcesNumber > 0 && {opacity: "0.7"}} + 0 ? "pointer" : "normal"} + color={rawDataSourcesNumber === 0 ? "#ACAEB1" : "#0068C5"} + fill={rawDataSourcesNumber === 0 ? "#ACAEB1" : "#0068C5"} + pointerEvents={rawDataSourcesNumber === 0 && "none"} + fontFamily="Roboto" + fontWeight="400" + fontSize="14px" + lineHeight="20px" + _hover={{ + color: "#0057A4", + fill: "#0057A4" + }} + href={rawDataSourcesNumber > 0 ? `/dataset/${id}?raw_data_source=${rawDataSources.id}` : ""} + > + + - - - {rawDataSourcesNumber}{" "} - {rawDataSourcesNumber === 1 ? "fonte original" : "fontes originais"} - - - + {rawDataSourcesNumber}{" "} + {rawDataSourcesNumber === 1 ? "fonte original" : "fontes originais"} + + ) } @@ -102,29 +108,38 @@ export default function Database({ if(informationRequests.number === undefined) informationRequestsNumber = 0 return ( - 0 && `/dataset/${id}?information_request=${informationRequests.id}`}> - 0 ? "pointer" : "normal"} - _hover={informationRequestsNumber > 0 && {opacity: "0.7"}} + 0 ? "pointer" : "normal"} + color={informationRequestsNumber === 0 ? "#ACAEB1" : "#0068C5"} + fill={informationRequestsNumber === 0 ? "#ACAEB1" : "#0068C5"} + pointerEvents={informationRequestsNumber === 0 && "none"} + fontFamily="Roboto" + fontWeight="400" + fontSize="14px" + lineHeight="20px" + _hover={{ + color: "#0057A4", + fill: "#0057A4" + }} + href={informationRequestsNumber > 0 ? `/dataset/${id}?information_request=${informationRequests.id}` : ""} + > + + - - - {informationRequestsNumber}{" "} - {informationRequestsNumber === 1 ? "pedido LAI" : "pedidos LAI"} - - - + {informationRequestsNumber}{" "} + {informationRequestsNumber === 1 ? "pedido LAI" : "pedidos LAI"} + + ) } @@ -134,7 +149,6 @@ export default function Database({ alignItems="flex-start" width="100%" spacing={{ base: 4, md: 0 }} - padding="16px 0px" > - - + {organization.name - + + - - - - - {name} - - - - {themes.slice(0,6).map((elm, i) => ( - -
- - - -
-
- ))} -
-
-
- + {name} + +
+ + - - Organização: - - - {organization?.name} - - - + + Organização: + + + {organization?.name} + + - + Cobertura temporal: + + - Cobertura temporal: - - + {temporalCoverageText ? temporalCoverageText : "Não informado"} + + - + Recursos: + + - Recursos: - - {contains.free && "Grátis"} {contains.free && contains.pro && "e"} {contains.pro && "Pro"} - {!contains.free && !contains.pro && "Não listado"} - - + {contains.free && "Grátis"} {contains.free && contains.pro && "e"} {contains.pro && "Pagos"} + {!contains.free && !contains.pro && "Não informado"} + - - - - - - - + + + + + + diff --git a/next/components/organisms/DatasetResource.js b/next/components/organisms/DatasetResource.js index 6c98aba3..6d1f4c90 100644 --- a/next/components/organisms/DatasetResource.js +++ b/next/components/organisms/DatasetResource.js @@ -1,12 +1,13 @@ import { Stack, VStack, + Box, + HStack, + Divider, + Text } from "@chakra-ui/react"; import { useEffect, useState } from "react"; import { useRouter } from "next/router"; -import { isMobileMod } from "../../hooks/useCheckMobile.hook"; - -import { FilterAccordion } from "../atoms/FilterAccordion"; import BdmTablePage from "./BdmTablePage"; import RawDataSourcesPage from "./RawDataSourcesPage"; @@ -41,7 +42,18 @@ export default function DatasetResource({ } useEffect(() => { - const dataset_tables = dataset?.tables?.edges.map((elm) => elm.node).filter((elm) => elm?.status?.slug !== "under_review").sort(sortElements) || [] + // Id do dataset do SAEB + let dataset_tables = [] + if(dataset?._id === "e083c9a2-1cee-4342-bedc-535cbad6f3cd") { + dataset_tables = dataset?.tables?.edges.map((elm) => elm.node) + .filter((elm) => elm?.status?.slug !== "under_review").sort(sortElements) || [] + } else { + dataset_tables = dataset?.tables?.edges.map((elm) => elm.node) + .filter((elm) => elm?.status?.slug !== "under_review") + .filter((elm) => elm?.slug !== "dicionario") + .filter((elm) => elm?.slug !== "dictionary").sort(sortElements) || [] + } + const raw_data_sources = dataset?.rawDataSources?.edges.map((elm) => elm.node).filter((elm) => elm?.status?.slug !== "under_review").sort(sortElements) || [] const information_request = dataset?.informationRequests?.edges.map((elm) => elm.node).filter((elm) => elm?.status?.slug !== "under_review").sort(sortElements) || [] @@ -56,7 +68,7 @@ export default function DatasetResource({ if(raw_data_sources.length > 0) return pushQuery("raw_data_source", raw_data_sources[0]?._id) if(information_request.length > 0) return pushQuery("information_request", information_request[0]?._id) } - },[]) + }, [dataset]) function SwitchResource ({route}) { if (route.hasOwnProperty("table")) return @@ -65,70 +77,121 @@ export default function DatasetResource({ return null } + function ContentFilter({ + fieldName, + choices, + onChange, + value, + hasDivider = true + }) { + if(choices.length < 1) return null + + return ( + + + + + {fieldName} + + + + {choices.map((elm, i) => ( + + + onChange(elm._id)} + > + {elm.name || elm.number} + + + ))} + + + ) + } + return ( - - { pushQuery("table", id) }} + hasDivider={false} /> - { pushQuery("raw_data_source", id) }} + hasDivider={tables.length > 0 ? true : false} /> - { pushQuery("information_request", id) }} + hasDivider={tables.length > 0 || rawDataSources.length > 0 ? true : false} /> - - - - - + + + ) } \ No newline at end of file diff --git a/next/components/organisms/InformationRequestPage.js b/next/components/organisms/InformationRequestPage.js index b1b39d07..ad04a34e 100644 --- a/next/components/organisms/InformationRequestPage.js +++ b/next/components/organisms/InformationRequestPage.js @@ -1,201 +1,259 @@ import { - VStack, - HStack, Stack, - Center, - Image, Box, Text, - Grid, - GridItem, + Skeleton, + SkeletonText, + Divider } from "@chakra-ui/react"; import { useEffect, useState } from "react"; -import { useCheckMobile } from "../../hooks/useCheckMobile.hook"; -import LoadingSpin from "../atoms/Loading"; -import Subtitle from "../atoms/Subtitle"; -import SectionText from "../atoms/SectionText"; -import RoundedButton from "../atoms/RoundedButton"; +import ReadMore from "../atoms/ReadMore"; import { TemporalCoverage } from "../molecules/TemporalCoverageDisplay"; -import BaseResourcePage from "../molecules/BaseResourcePage"; -import DisclaimerBox from "../molecules/DisclaimerBox"; +import { AlertDiscalimerBox } from "../molecules/DisclaimerBox"; import FourOFour from "../templates/404"; -import { - getInformationRequest -} from "../../pages/api/datasets/index"; - -import StatusIcon from "../../public/img/icons/statusIcon"; -import UserIcon from "../../public/img/icons/userIcon"; -import ExclamationIcon from "../../public/img/icons/exclamationIcon"; import RedirectIcon from "../../public/img/icons/redirectIcon"; export default function InformationRequestPage({ id }) { const [isLoading, setIsLoading] = useState(true) const [resource, setResource] = useState({}) - const [isError, setIsError] = useState({}) - - const featchInformationRequest = async () => { - try { - const result = await getInformationRequest(id) - setResource(result) - } catch (error) { - setIsError(error) - console.error(error) - } - setIsLoading(false) - } + const [isError, setIsError] = useState(false) useEffect(() => { + const featchInformationRequest = async () => { + setIsLoading(true) + + try { + const response = await fetch(`/api/datasets/getInformationRequest?p=${id}`, { method: "GET" }) + const result = await response.json() + + if (result.success) { + setResource(result.resource) + setIsError(false) + } else { + console.error(result.error) + setIsError(true) + } + } catch (error) { + console.error("Fetch error: ", error) + setIsError(true) + } finally { + setIsLoading(false) + } + } + featchInformationRequest() },[id]) - const AddInfoTextBase = ({title, text, children, ...style}) => { + const AddInfoTextBase = ({ title, text, ...props }) => { return ( - + {title} - - {text} - - {children} - + {text || "Não informado"} + ) } + const StackSkeleton = ({ children, ...props }) => { + return ( + + {children} + + ) + } - if(isError?.message?.length > 0 || resource === null || Object.keys(resource).length < 0) return - - if(isLoading) return + if(isError) return return ( - - + + Número do pedido: {resource?.number} + + + + + + Estes dados não passaram pela metodologia de tratamento da Base dos Dados. + + + + - - Consulta aos dados - - -
- - ATENÇÃO: -
- - Estes dados não passaram pela metodologia de tratamento da Base dos Dados. - -
-
- - + Acessar dados + + + + + Acessar pedido + + +
+ + + + - - - - Acessar dados - - - - - - - Acessar pedido - - - -
- - + + + - Descrição - - {resource?.observations || "Nenhuma descrição fornecida."} - - - - - Cobertura temporal - - - - - - - - Informações adicionais - - - - - - - - - - - - - - - -
+ + {resource?.observations || "Não informado"} + + + + + + + + + Informações adicionais + + + + + ) } diff --git a/next/components/organisms/PaymentSystem.js b/next/components/organisms/PaymentSystem.js index 870618d2..28633997 100644 --- a/next/components/organisms/PaymentSystem.js +++ b/next/components/organisms/PaymentSystem.js @@ -21,20 +21,35 @@ import { const stripePromise = loadStripe(process.env.NEXT_PUBLIC_KEY_STRIPE) -const PaymentForm = ({ onSucess, onErro }) => { +const PaymentForm = ({ onSucess, onErro, clientSecret}) => { const stripe = useStripe() const elements = useElements() const handlerSubmit = async (e) => { e.preventDefault() - const data = await stripe.confirmPayment({ - elements, - redirect: 'if_required', - }) - - if(data?.error?.code === "card_declined") return onErro() - if(data?.paymentIntent?.status === "succeeded") return onSucess() + const isSetupIntent = clientSecret.startsWith('seti_'); + if (isSetupIntent) { + await elements.submit(); + + const data = await stripe.confirmSetup({ + elements, + clientSecret: clientSecret, + redirect: 'if_required', + }); + + if (data?.error?.code === "card_declined") return onErro(); + if (data?.setupIntent?.status === "succeeded") return onSucess(); + + } else { + const data = await stripe.confirmPayment({ + elements, + redirect: 'if_required', + }) + + if(data?.error?.code === "card_declined") return onErro() + if(data?.paymentIntent?.status === "succeeded") return onSucess() + } } return ( @@ -89,26 +104,12 @@ export default function PaymentSystem({ userData, plan, onSucess, onErro }) { } const customerCreatPost = async (id) => { - let secret = "" - - const subscriptionCreate = await fetch(`/api/stripe/createSubscription?p=${btoa(id)}`, {method: "GET"}) + const clientSecret = await fetch(`/api/stripe/createSubscription?p=${btoa(id)}`, {method: "GET"}) .then(res => res.json()) - if(subscriptionCreate?.clientSecret) { - secret = subscriptionCreate?.clientSecret + if (clientSecret) { + setClientSecret(clientSecret) } - if(secret !== "") return setClientSecret(secret) - - const result = await fetch(`/api/stripe/createCustomer`, {method: "GET"}) - .then(res => res.json()) - - if(result?.id) { - const subscriptionCreate = await fetch(`/api/stripe/createSubscription?p=${btoa(id)}`, {method: "GET"}) - .then(res => res.json()) - secret = subscriptionCreate?.clientSecret - } - - return setClientSecret(secret) } async function customerCreat(plan) { @@ -169,7 +170,12 @@ export default function PaymentSystem({ userData, plan, onSucess, onErro }) { return ( - + ) } \ No newline at end of file diff --git a/next/components/organisms/PostDatasetForm.js b/next/components/organisms/PostDatasetForm.js index 8aa842f8..83485ec6 100644 --- a/next/components/organisms/PostDatasetForm.js +++ b/next/components/organisms/PostDatasetForm.js @@ -6,7 +6,6 @@ import { Input, Textarea, Select, - Checkbox, Text, Alert, AlertIcon, @@ -22,10 +21,11 @@ import { } from "@chakra-ui/react"; import { useState, useEffect } from "react" import { useRouter } from "next/router"; -import SelectList from "../molecules/SelectList"; +import Checkbox from "../atoms/Checkbox"; import RoundedButton from "../atoms/RoundedButton"; import SelectSearch from "../atoms/SelectSearch"; import LoadingSpin from "../atoms/Loading"; +import SelectList from "../molecules/SelectList"; import { postDataset, diff --git a/next/components/organisms/RawDataSourcesPage.js b/next/components/organisms/RawDataSourcesPage.js index e4e0d091..aa8545c4 100644 --- a/next/components/organisms/RawDataSourcesPage.js +++ b/next/components/organisms/RawDataSourcesPage.js @@ -1,64 +1,55 @@ import { - VStack, - HStack, Stack, - Center, Box, Text, - Grid, - GridItem + Skeleton, + SkeletonText, + Tooltip, + Divider } from "@chakra-ui/react"; import { useState, useEffect } from "react"; -import { useCheckMobile } from "../../hooks/useCheckMobile.hook"; -import LoadingSpin from "../atoms/Loading"; -import { SimpleTable } from "../atoms/SimpleTable"; -import SectionText from "../atoms/SectionText"; -import Subtitle from "../atoms/Subtitle"; -import RoundedButton from "../atoms/RoundedButton"; +import ReadMore from "../atoms/ReadMore"; +import ObservationLevel from "../atoms/ObservationLevelTable"; import { TemporalCoverage } from "../molecules/TemporalCoverageDisplay"; -import BaseResourcePage from "../molecules/BaseResourcePage"; -import DisclaimerBox from "../molecules/DisclaimerBox"; +import { AlertDiscalimerBox } from "../molecules/DisclaimerBox"; import FourOFour from "../templates/404"; -import { - getRawDataSources -} from "../../pages/api/datasets/index"; - import RedirectIcon from "../../public/img/icons/redirectIcon" -import LanguageIcon from "../../public/img/icons/languageIcon"; -import DisplayIcon from "../../public/img/icons/displayIcon"; -import DataStructureIcon from "../../public/img/icons/dataStructureIcon"; -import ApiIcon from "../../public/img/icons/apiIcon"; -import FrequencyIcon from "../../public/img/icons/frequencyIcon"; -import ObservationLevelIcon from "../../public/img/icons/observationLevelIcon"; -import RegisterIcon from "../../public/img/icons/registerIcon"; -import IpIcon from "../../public/img/icons/ipIcon"; -import CoinIcon from "../../public/img/icons/coinIcon"; -import ExclamationIcon from "../../public/img/icons/exclamationIcon"; +import InfoIcon from "../../public/img/icons/infoIcon"; export default function RawDataSourcesPage({ id }) { const [isLoading, setIsLoading] = useState(true) const [resource, setResource] = useState({}) - const [isError, setIsError] = useState({}) - - const featchRawDataSources = async () => { - try { - const result = await getRawDataSources(id) - setResource(result) - } catch (error) { - setIsError(error) - console.error(error) - } - setIsLoading(false) - } + const [isError, setIsError] = useState(false) useEffect(() => { + const featchRawDataSources = async () => { + setIsLoading(true) + try { + const response = await fetch(`/api/datasets/getRawDataSources?p=${id}`, { method: "GET" }) + const result = await response.json() + + if (result.success) { + setResource(result.resource) + setIsError(false) + } else { + console.error(result.error) + setIsError(true) + } + } catch (error) { + console.error("Fetch error: ", error) + setIsError(true) + } finally { + setIsLoading(false) + } + } + featchRawDataSources() },[id]) const ObjectValues = (value) => { - if(value === undefined || Object.keys(value).length === 0) return "Não listado" + if(value === undefined || Object.keys(value).length === 0) return "Não informado" const array = [] @@ -66,7 +57,7 @@ export default function RawDataSourcesPage({ id }) { array.push(elm.name) }) - if(array.length === 0) return "Não listado" + if(array.length === 0) return "Não informado" return array.join(", ").toString() } @@ -79,213 +70,309 @@ export default function RawDataSourcesPage({ id }) { return "Não" break; default: - return "Não listado" + return "Não informado" break; } } const UpdateFrequency = () => { const value = resource?.updates?.[0] - if(value === undefined || Object.keys(value).length === 0) return "Não listado" + if(value === undefined || Object.keys(value).length === 0) return "Não informado" if(value?.frequency >= 0 && value?.entity?.name) return `${value.frequency} ${value.entity.name}` if(value?.entity?.name) return `${value.entity.name}` - return "Não listado" + return "Não informado" } - const Empty = () => { + const TooltipText = ({ text, info, ...props }) => { return ( -

- Não listado -

+ + + {text} + + + + + ) } - const ObservationLevel = () => { - const notFound = Nenhum nível da observação fornecido. - if(resource?.observationLevels === undefined || Object.keys(resource?.observationLevels).length === 0) return notFound - - let array = [] - const keys = Object.keys(resource?.observationLevels) - - keys.forEach((elm) => { - const value = resource?.observationLevels[elm] - - const newValue = [value?.entity?.name || , value?.columns[0]?.name || ] - array.push(newValue) - }) - + const StackSkeleton = ({ children, ...props }) => { return ( - + + {children} + ) } - const AddInfoTextBase = ({title, text, children, ...style}) => { + const AddInfoTextBase = ({ title, text, ...props }) => { return ( - + {title} - - {text} - - {children} - + {text || "Não informado"} + ) } - if(isError?.message?.length > 0 || resource === null || Object.keys(resource).length < 0) return - - if(isLoading) return + if(isError) return return ( - - - Consulta aos dados - - -
- - ATENÇÃO: -
- - Estes dados não passaram pela metodologia de tratamento da Base dos Dados. - -
-
- - + + {resource?.name} + + + + + + Estes dados não passaram pela metodologia de tratamento da Base dos Dados. + + + + + window.open(resource?.url)} + fill="#FFF" + fontFamily="Roboto" + fontWeight="500" + fontSize="14px" + gap="8px" + lineHeight="20px" + _hover={{ + backgroundColor: resource?.url ? "#22703E" : "#ACAEB1" + }} > Acessar fonte original - - -
- - - Descrição - - {resource?.description || "Nenhuma descrição fornecida."} - - - - - Cobertura temporal - - - - - - - - Informações adicionais - - - - - - - + + + + + + + Descrição + + + + + + {resource?.description || "Não informado"} + + + - - - - + - - - - + + + Informações adicionais + + - - - - + - - - - - - - - - Nível da observação - - - - - - - - + - - - - + - - - - - - -
+ + + + + + + + + + + {resource?.observationLevels && Object.keys(resource?.observationLevels).length > 0 ? + + : + + Não informado + + } + + + + + + + + + ) } diff --git a/next/components/templates/main.js b/next/components/templates/main.js index 4c83a971..b0ce3a54 100644 --- a/next/components/templates/main.js +++ b/next/components/templates/main.js @@ -8,21 +8,28 @@ export function MainPageTemplate({ backgroundColor = "#FFFFFF", cleanTemplate = false, userTemplate = false, + footerTemplate = "default", ...style }) { return ( - + {children} -