Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions dynamox-dashboard/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_API_URL=http://localhost:3001
31 changes: 31 additions & 0 deletions dynamox-dashboard/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
storybook-static
.cache
cypress/screenshots
cypress/videos
*.local
.env
.env.*
!.env.example

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
9 changes: 9 additions & 0 deletions dynamox-dashboard/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { StorybookConfig } from '@storybook/react-vite'

const config: StorybookConfig = {
addons: ['@storybook/addon-docs'],
framework: '@storybook/react-vite',
stories: ['../src/**/*.stories.@(ts|tsx)'],
}

export default config
25 changes: 25 additions & 0 deletions dynamox-dashboard/.storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { CssBaseline, ThemeProvider } from '@mui/material'
import type { Preview } from '@storybook/react-vite'
import type { ReactNode } from 'react'
import { theme } from '../src/theme'

const preview: Preview = {
decorators: [
(Story: () => ReactNode) => (
<ThemeProvider theme={theme}>
<CssBaseline />
<Story />
</ThemeProvider>
),
],
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
}

export default preview
153 changes: 153 additions & 0 deletions dynamox-dashboard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Dynamox Dashboard

Dashboard desenvolvido para o desafio front-end da Dynamox. A aplicação exibe a rota `/data` com informações da máquina monitorada e gráficos de séries temporais para aceleração, temperatura e velocidade.

## Funcionalidades

- Tela `/data` com cabeçalho e metadados da máquina.
- Consumo de dados por uma API REST mockada com `json-server`.
- Três gráficos de séries temporais usando Highcharts:
- Aceleração RMS
- Temperatura
- Velocidade RMS
- Tooltip e crosshair nos gráficos ao passar o mouse sobre os pontos.
- Gerenciamento de estado com Redux Toolkit.
- Efeitos assíncronos com Redux Saga.
- Testes unitários, testes de componentes e testes e2e com Cypress.
- Storybook para documentação visual dos componentes.

## Tecnologias

- React
- TypeScript
- Vite
- Material UI 5
- Redux Toolkit
- Redux Saga
- Highcharts
- Vitest
- Cypress
- Storybook
- json-server

## Requisitos

- Node.js
- npm

## Como executar

Instale as dependências:

```bash
npm install
```

Crie o arquivo de ambiente a partir do exemplo:

```bash
cp .env.example .env.development
```

Em um terminal, inicie a API mockada:

```bash
npm run api
```

Em outro terminal, inicie a aplicação:

```bash
npm run dev
```

A aplicação ficará disponível em:

```text
http://localhost:5173/data
```

A API mockada roda em:

```text
http://localhost:3001
```

## Hospedagem

A aplicação foi hospedada usando AWS Amplify:

```text
https://maria-betman.dredu640vdttu.amplifyapp.com/data
```

A API mockada foi disponibilizada com AWS API Gateway e AWS Lambda para servir os dados consumidos pelo dashboard.

## Endpoints mockados

- `GET /machine`: retorna os dados da máquina.
- `GET /measurements`: retorna as séries temporais usadas nos gráficos.

## Scripts

```bash
npm run dev
```

Inicia o servidor de desenvolvimento.

```bash
npm run api
```

Inicia a API mockada com `json-server`.

```bash
npm run build
```

Gera a versão de produção.

```bash
npm run preview
```

Executa o preview do build.

```bash
npm run lint
```

Executa a validação com ESLint.

```bash
npm run test
```

Executa os testes com Vitest.

```bash
npm run e2e
```

Executa os testes end-to-end com Cypress.

```bash
npm run storybook
```

Inicia o Storybook em `http://localhost:6006`.

## Estrutura principal

```text
src/
modules/
machine/ # estado, saga e serviço dos dados da máquina
measurements/ # estado, saga e serviço das medições
pages/
data/ # tela principal e componentes do dashboard
store/ # configuração do Redux e sagas
mock/
db.json # base usada pelo json-server
```
8 changes: 8 additions & 0 deletions dynamox-dashboard/cypress.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { defineConfig } = require('cypress')

module.exports = defineConfig({
e2e: {
baseUrl: 'http://127.0.0.1:5173',
supportFile: 'cypress/support/e2e.ts',
},
})
102 changes: 102 additions & 0 deletions dynamox-dashboard/cypress/e2e/data.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/// <reference types="cypress-real-events" />

function interceptDashboardRequests() {
cy.intercept('GET', 'http://localhost:3001/machine').as('getMachine')
cy.intercept('GET', 'http://localhost:3001/measurements').as('getMeasurements')
}

function waitForDashboardData() {
cy.wait('@getMachine')
cy.wait('@getMeasurements')
}

function getChartPanel(title: string | RegExp) {
return cy.contains('h2', title).parents('.MuiPaper-root').first()
}

function hoverChartByIndex(index: number) {
cy.get('.highcharts-series-group')
.eq(index)
.should('be.visible')
.scrollIntoView()
.find('.highcharts-series-0')
.first()
.click({ force: true })
.realHover()
}

function validateTooltipContent(contents: string[]) {
cy.get('.highcharts-tooltip').should('be.visible')

contents.forEach((content) => {
cy.get('.highcharts-tooltip').should('contain.text', content)
})
}

describe('Data dashboard', () => {
beforeEach(() => {
interceptDashboardRequests()
})

it('redirects unknown routes to the data page', () => {
cy.visit('/unknown-route')

cy.location('pathname').should('eq', '/data')
waitForDashboardData()
cy.contains('h1', /Dados/).should('be.visible')
})

it('loads machine metadata from the API', () => {
cy.visit('/data')
waitForDashboardData()

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('renders the required metric charts with their legends', () => {
cy.visit('/data')
waitForDashboardData()

getChartPanel('Aceleração RMS').within(() => {
cy.contains('h2', 'Aceleração RMS').should('be.visible')
cy.contains('.highcharts-legend-item', 'Axial').should('be.visible')
cy.contains('.highcharts-legend-item', 'Horizontal').should('be.visible')
cy.contains('.highcharts-legend-item', 'Radial').should('be.visible')
})

getChartPanel('Temperatura').within(() => {
cy.contains('h2', 'Temperatura').should('be.visible')
cy.contains('.highcharts-legend-item', 'Temperatura').should('be.visible')
})

getChartPanel('Velocidade RMS').within(() => {
cy.contains('h2', 'Velocidade RMS').should('be.visible')
cy.contains('.highcharts-legend-item', 'Axial').should('be.visible')
cy.contains('.highcharts-legend-item', 'Horizontal').should('be.visible')
cy.contains('.highcharts-legend-item', 'Radial').should('be.visible')
})

cy.get('.highcharts-container').should('have.length', 3)
cy.get('.highcharts-series').should('have.length.at.least', 7)
})

it('shows tooltip on hover for each chart', () => {
cy.visit('/data')
waitForDashboardData()

cy.get('.highcharts-container').should('have.length', 3)

hoverChartByIndex(0)
validateTooltipContent(['Axial', 'Horizontal', 'Radial'])

hoverChartByIndex(1)
validateTooltipContent(['Temperatura'])

hoverChartByIndex(2)
validateTooltipContent(['Axial', 'Horizontal', 'Radial'])
})
})
1 change: 1 addition & 0 deletions dynamox-dashboard/cypress/support/e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import 'cypress-real-events'
45 changes: 45 additions & 0 deletions dynamox-dashboard/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
globalIgnores([
'.cache',
'cypress/screenshots',
'cypress/videos',
'dist',
'storybook-static',
]),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
globals: globals.browser,
},
},
{
files: ['cypress/**/*.ts'],
languageOptions: {
globals: {
...globals.browser,
...globals.mocha,
cy: 'readonly',
Cypress: 'readonly',
},
},
},
{
files: ['cypress.config.cjs'],
languageOptions: {
globals: globals.node,
},
},
])
Loading