This is a solution to the Time tracking dashboard challenge on Frontend Mentor. Frontend Mentor challenges help you improve your coding skills by building realistic projects.
This is the front-end mentor's seventeen challenge. The challenge is to build the "Time tracking dashboard" and make it as close to the design as possible. Building the desing with whatever you want to finish, any language, framework or tools.
Users should be able to:
- View the optimal layout for the component depending on their device's screen size
- See a hover state on desktop for the Sign Up call-to-action
-
Solution URL: My solution for this challenge
-
Live Site URL: check the result
-
My figma design: Figma
- Mobile-first workflow
- Grid
- Grid templete areas
- React - JS library
- Styled components - CSS in js with stiled components
- JSON server - Fake API with JSON server
- Axio - Requests with axios
O arquivo de context sustenta os estados globais da aplicação, como os dados que server para preencher os cards. O useEffect com array vazio faz o papel de atualizar uma vez após a renderização da página, pegando os dados da fake API. E então os cards são renderizados com os dados adequados.
import React, { createContext, useContext, useEffect, useState } from 'react';
import P from 'prop-types';
import { api } from '../services/api';
const ActivitysContexts = createContext();
const ActivitysProvider = ({ children }) => {
const [name, setName] = useState(null);
const [activitys, setActivitys] = useState([]);
const [period, setPeriod] = useState([]);
const [searchPeriod, setSearchPeriod] = useState('weekly');
useEffect(() => {
function findInfoPeriod(obj, searchPeriod, store = []) {
const myStore = store;
return Object.keys(obj).map((key) => {
if (key === searchPeriod) {
myStore.push(obj[key]);
}
if (obj[key]) {
if (typeof obj[key] == 'object' || Array.isArray(obj[key])) {
findInfoPeriod(obj[key], searchPeriod, myStore);
}
}
return myStore[myStore.length - 1];
});
}
const recursiveResult = findInfoPeriod(activitys, searchPeriod);
setPeriod(recursiveResult);
}, [activitys, searchPeriod]);
useEffect(() => {
api.get('/profiles').then((response) => {
setActivitys(response.data[0].activitys);
setName(response.data[0].name);
});
}, []);
return (
<ActivitysContexts.Provider
value={{
name,
setName,
activitys,
setActivitys,
period,
setPeriod,
searchPeriod,
setSearchPeriod,
}}
>
{children}
</ActivitysContexts.Provider>
);
};
const ActivitysConsumer = () => useContext(ActivitysContexts);
export { ActivitysContexts, ActivitysProvider, ActivitysConsumer };
ActivitysProvider.propTypes = {
children: P.object.isRequired,
};
O segundo useEffect atualiza de acordo com quando as dependências mudam, e está associado a função de busca abaixo. Essa função é uma algoritmo de Backtracking, que faz uma busca recursiva. Está sendo usada para aplicar, mesmo que no caso em questão não seria necessário, pois se sabe exatamente o formato e a dimensão do JSON que chega pela requisição, e este não se altera. A função recebe o obj que será feita a busca, a palavra que vai buscar dentro das chaves e um parâmetro store, que serve para orientar a pesquisa, salvando os resultados válidos. Este recebe o valor formal de um array vazio, portanto se não houver um valor real passado na chamada da função ele adotará array vazio como valor. O objeto é recebido por uma constante, e se a chave for válida será adicionado o valor ao array, e sempre que houver um novo nível e for chamado a recursividade o myStore será passado como parâmetro para a função, mantendo a integridade de seu valor. Ao final essa busca é retornada, contendo as horas do estado atual e anterior, que será salvo em um estado global para ser usado.
function findInfoPeriod(obj, searchPeriod, store = []) {
const myStore = store;
return Object.keys(obj).map((key) => {
if (key === searchPeriod) {
myStore.push(obj[key]);
}
if (obj[key]) {
if (typeof obj[key] == 'object' || Array.isArray(obj[key])) {
findInfoPeriod(obj[key], searchPeriod, myStore);
}
}
return myStore[myStore.length - 1];
});
}
Se o name for null quer dizer que a requisição não voltou ainda, então um "loading spinner" íra aparecer na tela, caso não os cards são renderizados. o name e period contém um short circuit para evitar erros, caso o nome seja null ou algum outro valor considerado falsy, para ambos os casos. O map em activitys retorna os card de atividade, sendo que o título é atribuido via desestruturação do JSON e o period é pego acessando o estado global já criado, que segue a mesma ordem dos cards a serem renderizados.
import React from 'react';
import { ActivityCard } from './components/ActivityCard';
import { AvatarCard } from './components/AvatarCard';
import { Content } from './components/Content';
import { Conteiner, GlobalStyle } from './Global';
import { ActivitysConsumer } from './Contexts/ActivitysContext';
function App() {
const { name, activitys, period } = ActivitysConsumer();
return (
<>
<GlobalStyle />
<Conteiner>
<Content>
{name === null ? (
<div className="c-loader"></div>
) : (
<>
<AvatarCard name={name || ''} />
{activitys?.map(({ title }, index) => (
<ActivityCard
key={index}
tagCard={index}
title={title}
period={period[index] || {}}
/>
))}
</>
)}
</Content>
</Conteiner>
</>
);
}
export default App;
- react tutorial - This helped me structure the components and build the proposed page.
- my figma design - My figma design for help anyone who wants to build this challenge.
- CSS units conversor - px to VH/VW/REM - CSS units conversor .
- Converting Colors - HSL for all color systems.
- Personal Page - Jean Carlos De Meira
- Frontend Mentor - @JCDMeira
- Instagram - @jean.meira10
- GitHub - JCDMeira