Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a0ee4da
Starting my own folder for the challenge + setting things up for usin…
luizarthuur Jun 22, 2026
e028b34
installing react-router-dom
luizarthuur Jun 22, 2026
4231ec0
installing new dependencies + creating project typing
luizarthuur Jun 22, 2026
4e85ef0
changing project folder names + feat: implement Redux store and Saga …
luizarthuur Jun 23, 2026
2594bef
feat: add dashboard page routing and UI layou Set up React Router wit…
luizarthuur Jun 23, 2026
261d334
feat: integrate Highcharts for sensor data visualization
luizarthuur Jun 23, 2026
f1eff3d
feat: implement synchronized crosshair across all charts
luizarthuur Jun 23, 2026
e5c1b90
feat: add dark/light theme support and UI refinements + Create Theme…
luizarthuur Jun 24, 2026
5780322
changes - changing the styles of the charts, and the charts order
luizarthuur Jun 24, 2026
677c2ec
fix - switching the header style to be more consistent based on the F…
luizarthuur Jun 25, 2026
ff993af
fix - ending the header editing, adding new styles and new icons for …
luizarthuur Jun 25, 2026
255be08
fix - style: overhaul UI design to match Dynamox reference
luizarthuur Jun 26, 2026
8c1e01d
fix: re-styling dark mode support with theme-aware chart styling
luizarthuur Jun 26, 2026
e5aa70d
feat - test: add unit tests for Redux, Saga, and API, test sensorSlic…
luizarthuur Jun 26, 2026
a29cb7a
chore: set up Storybook with autodocs and custom component stories
luizarthuur Jun 26, 2026
f58b800
test: Implementing Cypress E2E tests to use stable CSS selectors and …
luizarthuur Jun 26, 2026
f34ec91
fix - editing Readme content, removing unnecessary coments, and chang…
luizarthuur Jun 26, 2026
a036142
fix - fixing typing errors and unused imports for deploy
luizarthuur Jun 26, 2026
dd39c0a
fix: use static JSON file for production data fetching
luizarthuur Jun 26, 2026
474d981
docs: add disclaimer about static JSON data in README
luizarthuur Jun 26, 2026
dcdfa9c
docs: add disclaimer about static JSON and JSON Server alternatives
luizarthuur Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions front-end-challenge-v2-luiz-arthur/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

*storybook.log
storybook-static
.vercel
17 changes: 17 additions & 0 deletions front-end-challenge-v2-luiz-arthur/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
"stories": [
"../src/**/*.mdx",
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
],
"addons": [
"@chromatic-com/storybook",
"@storybook/addon-vitest",
"@storybook/addon-a11y",
"@storybook/addon-docs",
"@storybook/addon-mcp"
],
"framework": "@storybook/react-vite"
};
export default config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,400;14..32,500;14..32,600;14..32,700&display=swap" rel="stylesheet" />
<style>
body { font-family: 'Inter', sans-serif; }
</style>
21 changes: 21 additions & 0 deletions front-end-challenge-v2-luiz-arthur/.storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Preview } from '@storybook/react-vite'

const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},

a11y: {
// 'todo' - show a11y violations in the test UI only
// 'error' - fail CI on a11y violations
// 'off' - skip a11y checks entirely
test: 'todo'
}
},
};

export default preview;
192 changes: 192 additions & 0 deletions front-end-challenge-v2-luiz-arthur/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Dynamox Front-end Challenge

Dashboard para análise de dados de sensores, desenvolvido como parte do processo seletivo da Dynamox.

## 🚀 Tecnologias

- **React 19** + **TypeScript**
- **Vite** - Build tool
- **Redux Toolkit** + **Redux Saga** - Gerenciamento de estado e efeitos colaterais
- **Material UI 5** - Interface e componentes
- **Highcharts** - Visualização de dados
- **JSON Server** - API mock para desenvolvimento
- **Vitest** - Testes unitários
- **Storybook** - Documentação de componentes
- **Cypress** - Testes E2E

## 📋 Funcionalidades

- Rota `/data` com dashboard de análise
- Header com informações da máquina (Máquina 1023, Ponto 20192, RPM 200, 16g, 20 min)
- 3 gráficos de séries temporais:
- Aceleração RMS (3 eixos: Horizontal, Radial, Axial)
- Velocidade RMS (3 eixos: Horizontal, Radial, Axial)
- Temperatura (1 série)
- Crosshair vertical sincronizado entre todos os gráficos
- Modo claro/escuro com persistência no localStorage
- Layout responsivo
- Testes unitários (Redux, Saga, API)
- Storybook para documentação de componentes
- Testes E2E com Cypress

## 🛠️ Como rodar o projeto

### Pré-requisitos

- Node.js 18+
- npm ou yarn

### Instalação

```bash
# Clone o repositório
git clone https://github.com/seu-usuario/js-ts-full-stack-test.git
cd js-ts-full-stack-test

# Instale as dependências
npm install
Rodando o projeto
O projeto requer dois servidores rodando simultaneamente:

Terminal 1 - JSON Server (API mock):

bash
npm run server
O servidor estará disponível em http://localhost:3000

Terminal 2 - Aplicação React:

bash
npm run dev
A aplicação estará disponível em http://localhost:5173

Acesse http://localhost:5173/data para visualizar o dashboard.

Rodando os testes
bash
# Testes unitários
npm run test

# Testes E2E (Cypress)
npm run cypress:open # Modo interativo
npm run cypress:run # Modo headless
Rodando o Storybook
bash
npm run storybook
Acesse http://localhost:6006 para visualizar a documentação.

📁 Estrutura do Projeto
text
src/
├── components/
│ ├── Header/ # Cabeçalho com informações da máquina
│ └── SensorChart/ # Componente de gráfico com Highcharts
├── pages/
│ └── DashboardPage/ # Página principal com os 3 gráficos
├── store/
│ ├── modules/
│ │ ├── sensorSlice.ts # Estado global do Redux
│ │ └── sensorSaga.ts # Efeitos colaterais (API calls)
│ ├── rootSaga.ts # Combinação de sagas
│ └── index.ts # Configuração da store
├── services/
│ └── api.ts # Comunicação com JSON Server
├── types/
│ └── sensor.types.ts # Tipos TypeScript
├── context/
│ └── ThemeContext.tsx # Tema claro/escuro
├── stories/ # Documentação Storybook
├── test/ # Configuração de testes
└── ...
🎯 Decisões técnicas
Redux + Saga
Escolhidos para gerenciar o estado global e efeitos colaterais, conforme exigido pelo desafio. O Saga lida com a chamada assíncrona à API mock.

Highcharts
Escolhido pela facilidade de implementação de crosshair sincronizado entre múltiplos gráficos e suporte a séries temporais.

Material UI 5
Proporciona uma interface consistente e responsiva com componentes prontos para uso, além de suporte nativo a temas claro/escuro.

JSON Server
Permite mockar a API REST de forma rápida e eficiente durante o desenvolvimento.

📊 Funcionalidades em detalhe
Crosshair Sincronizado
Ao passar o mouse sobre um ponto em qualquer gráfico, uma linha vertical aparece em todos os gráficos no mesmo timestamp, com tooltips mostrando os valores de cada série.

Modo Claro/Escuro
O tema é persistido no localStorage e detecta automaticamente a preferência do sistema operacional.

Gráficos com múltiplos eixos
Aceleração RMS: 3 linhas (Horizontal, Radial, Axial)

Velocidade RMS: 3 linhas (Horizontal, Radial, Axial)

Temperatura: 1 linha

🧪 Testes
Unitários: Redux reducers, sagas e API service (8 testes passando)

E2E: Cypress testando header, cards, containers e dark mode

📚 Documentação
O Storybook está configurado com autodocs para documentar automaticamente os componentes. Stories disponíveis:

SensorChart (com variantes: Aceleração, Velocidade, Temperatura)

Header

DashboardPage

📄 Licença
Este projeto foi desenvolvido como parte do processo seletivo da Dynamox.

text

---

## 📝 Commit do README

```bash
git add README.md
git commit -m "docs: add comprehensive README with project overview and sesetup instructions"

##Você pode achar o deploy deste app com os dados fixos (sem execução de servidor JSON) no link abaixo

https://front-end-challenge-v2-luiz-arthur-y8cni4d6f.vercel.app

##📦 Dados mockados: arquivo estático vs JSON Server
Para este desafio, optei por utilizar um arquivo JSON estático na pasta public/ em vez de manter o JSON Server em produção. Essa decisão foi tomada por dois motivos principais:

Simplicidade no deploy: O JSON Server é uma ferramenta excelente para desenvolvimento local, mas não é prático (nem recomendado) para produção. Em ambientes como Vercel, Netlify ou outros provedores de hospedagem estática, não há um servidor Node.js contínuo para manter o JSON Server rodando. Usar um arquivo estático elimina essa dependência.

Foco no front-end: O desafio é sobre desenvolvimento front-end, não sobre infraestrutura de back-end. Optar por um arquivo estático mantém o foco no que é avaliado: a qualidade do código React, a integração com Redux/Saga, a UI/UX e a documentação.

Durante o desenvolvimento local, o JSON Server ainda pode ser usado (via npm run server) para simular uma API REST com endpoints reais. Mas, para a versão final (deploy), substituí a chamada à API por um fetch direto ao arquivo JSON estático, garantindo que o aplicativo funcione em qualquer ambiente sem necessidade de configuração adicional.

Essa abordagem é comum em desafios técnicos e projetos de demonstração, pois mantém o código enxuto e o deploy simples, sem prejudicar a experiência do usuário.

(Sugestão) - Como adaptar para um back-end real
Se no futuro este projeto evoluísse para um ambiente de produção com uma API real, bastaria substituir a URL do fetch no arquivo src/services/api.ts pela URL da API real e ajustar os tipos de dados conforme necessário. O restante da arquitetura (Redux, Saga, componentes) já está preparado para consumir dados de qualquer fonte.

Alternando entre arquivo estático e JSON Server
Por padrão, a aplicação em produção utiliza o arquivo response-challenge-v2.json localizado na pasta public/. Isso garante que o app funcione em qualquer ambiente sem a necessidade de um servidor adicional.

Se você deseja usar o JSON Server (ex: para desenvolvimento ou para testar chamadas REST), siga os passos abaixo:

Remova o arquivo estático (ou mantenha, mas altere o código para priorizar o servidor).

No arquivo src/services/api.ts, substitua a função fetchAllMetrics pela versão que faz requisições ao JSON Server (há um código comentado no arquivo que faz exatamente isso).

Rode o JSON Server em um terminal separado:

bash
npm run server
O servidor estará disponível em http://localhost:3000.

Rode a aplicação normalmente:

bash
npm run dev
Nota: O código atual está preparado para ambas as abordagens. A versão com fetch para o JSON Server está comentada no api.ts para referência.
18 changes: 18 additions & 0 deletions front-end-challenge-v2-luiz-arthur/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineConfig } from "cypress";

export default defineConfig({
allowCypressEnv: false,

component: {
devServer: {
framework: "react",
bundler: "vite",
},
},

e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
});
9 changes: 9 additions & 0 deletions front-end-challenge-v2-luiz-arthur/cypress/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from 'cypress';

export default defineConfig({
e2e: {
baseUrl: 'http://localhost:5173',
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'cypress/support/e2e.ts',
},
});
3 changes: 3 additions & 0 deletions front-end-challenge-v2-luiz-arthur/cypress/e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// cypress/support/e2e.ts
// Aqui você pode importar comandos customizados do Cypress
// import './commands';
68 changes: 68 additions & 0 deletions front-end-challenge-v2-luiz-arthur/cypress/e2e/dashboard.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
describe('Dashboard E2E', () => {
beforeEach(() => {
cy.visit('http://localhost:5173/data');
cy.get('.app-container', { timeout: 10000 }).should('be.visible');
});

it('should load the page successfully', () => {
cy.get('.app-container').should('be.visible');
});

it('should display the header', () => {
cy.get('.header-paper').should('be.visible');
});

it('should display chart cards', () => {
cy.get('.chart-card').should('have.length.at.least', 3);
});

it('should render Highcharts containers', () => {
cy.get('.highcharts-container').should('have.length.at.least', 3);
});

it.skip('should toggle dark mode', () => {
cy.get('button[aria-label="toggle theme"]').click();
cy.get('body').should('have.attr', 'data-theme', 'dark');
});
});

//Teste mais completo
/*
describe('Dashboard E2E', () => {
beforeEach(() => {
cy.visit('http://localhost:5173/data')
cy.contains('Análise de Dados', { timeout: 10000 }).should('be.visible');
});

it('should display the header with machine info', () => {
cy.contains('Máquina 1023').should('be.visible');
cy.contains('Ponto 20192').should('be.visible');
cy.contains('200').should('be.visible');
cy.contains('16g').should('be.visible');
cy.contains('20 min').should('be.visible');
});

it('should display the three chart cards', () => {
cy.contains('Aceleração RMS').should('be.visible');
cy.contains('Velocidade RMS').should('be.visible');
cy.contains('Temperatura').should('be.visible');
});

it('should render Highcharts containers', () => {
cy.get('.highcharts-container').should('have.length.at.least', 3);
});

it('should toggle dark mode', () => {
cy.get('button[aria-label="toggle theme"]').click();
cy.get('body').should('have.attr', 'data-theme', 'dark');
cy.get('button[aria-label="toggle theme"]').click();
cy.get('body').should('have.attr', 'data-theme', 'light');
});

it('should sync crosshair on hover', () => {
cy.get('.highcharts-container').first().should('be.visible');
cy.get('.highcharts-series-group').first().trigger('mouseover');
cy.get('.highcharts-crosshair').should('have.length.at.least', 3);
});
});
*/
Loading