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
55 changes: 0 additions & 55 deletions README.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# 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?
128 changes: 128 additions & 0 deletions dev-sec-fin-ops-challenge-v1/dynamox-front-end-challenge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Dynamox Full-Stack Developer Challenge (Front-end)

This project was implemented as part of the **Dynamox Full-Stack Developer Challenge** using:

- **Vite + React + TypeScript**
- **Material UI 5**
- **Redux Toolkit + Thunk**
- Persistence using **json-server** (mock REST API)
- LocalStorage used for authentication state
- **Unit tests** with **Vitest**

---

## ✅ Features

### Authentication
- Login with fixed credentials (fake auth)
- Protected routes (`/machines`, `/monitoring-points`)
- Logout

### Machine Management
- Create / Edit / Delete machines
- Machine type: `Pump` or `Fan`

### Monitoring Points & Sensors
- Create / Edit / Delete monitoring points linked to a machine
- Associate a sensor to a monitoring point:
- Sensor has a **unique ID**
- Model is one of: `TcAg`, `TcAs`, `HF+`
- Business rule:
- Machines of type **Pump** cannot use `TcAg` or `TcAs`

### List UX
- Monitoring points table supports:
- Pagination: **5 items per page**
- Sorting by any column (asc/desc)

### Automated tests
- Business rules: sensor vs machine type
- Sorting utilities
- Pagination utilities

---

## 🧠 Assumptions (ambiguities handled)

- **Persistence**: Implemented using a mock REST API with **json-server**, simulating a real backend.
- This approach was chosen to better reflect a real-world frontend + API integration.
- The architecture allows an easy migration to a real backend (Node.js / NestJS) with minimal changes.
- **Sensor per monitoring point**: each monitoring point can have **at most 1 sensor** (`sensor: Sensor | null`).
- **Deleting a machine**: monitoring points referencing a deleted machine may appear as `(machine removed)` in the list.
- In a production system, this could be handled by cascade delete or by blocking deletion if dependencies exist.

---

## 🔐 Fixed credentials

- Email: `admin@dynamox.com`
- Password: `123456`

---

## ▶️ How to run

### Requirements
- Node.js 18+ recommended

### Install
```bash
npm install
```
### Run (dev)
```bash
npm run dev
```
### 🧪 Tests
```bash
npm run test:run
```
### Watch mode:
```bash
npm test
```

### Mock API (json-server)

This project uses **json-server** to simulate a REST API.

Run the API:
```bash
npm run server

```
The API will be available at:

http://localhost:3001

Make sure the API is running before using the application.

```json
"scripts": {
"server": "json-server --watch db.json --port 3001"
}
```


## Project structure (high-level)

- src/routes – routing + private route

- src/features – Redux slices/thunks per domain (auth, machines, monitoring)

- src/services – localStorage persistence adapters

- src/utils – pure functions (validation/sorting/pagination)

- src/pages – screens

- src/components – reusable UI components

- src/test – test setup

## Notes
### This implementation prioritizes:

- Clean separation between UI / state / business rules
- Readability and maintainability
- Correct behavior according to the challenge requirements
65 changes: 65 additions & 0 deletions dev-sec-fin-ops-challenge-v1/dynamox-front-end-challenge/db.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"machines": [
{
"id": "ae161551-1856-4696-b2ea-e7b218c903e9",
"name": "Maquina 01",
"type": "Fan"
}
],
"monitoringPoints": [
{
"id": "29ee1d02-9bac-4f21-8961-ec59d030911a",
"machineId": "ae161551-1856-4696-b2ea-e7b218c903e9",
"name": "Ponto 01",
"sensor": {
"id": "123456",
"model": "TcAg"
}
},
{
"id": "032bc334-8507-4239-9310-d719184ac66c",
"machineId": "ae161551-1856-4696-b2ea-e7b218c903e9",
"name": "Ponto 01",
"sensor": {
"id": "123456",
"model": "TcAg"
}
},
{
"id": "7a6be175-976a-4267-aa5d-695a5f375ddb",
"machineId": "ae161551-1856-4696-b2ea-e7b218c903e9",
"name": "revwrvwv",
"sensor": {
"id": "vfd\\ f b\\fv",
"model": "TcAg"
}
},
{
"id": "4af8e633-1e74-47a3-b827-ab501ee1b67d",
"machineId": "ae161551-1856-4696-b2ea-e7b218c903e9",
"name": "evcwqecvqwec",
"sensor": {
"id": "qecqCAqee",
"model": "TcAs"
}
},
{
"id": "29286fc3-3300-4d71-8a1d-393958a24e16",
"machineId": "ae161551-1856-4696-b2ea-e7b218c903e9",
"name": "revwrvw",
"sensor": {
"id": "ervwevw",
"model": "TcAs"
}
},
{
"id": "ed688f0e-c02e-46e2-a119-137733bdec75",
"machineId": "ae161551-1856-4696-b2ea-e7b218c903e9",
"name": "ewvcqweceqwc",
"sensor": {
"id": "efvwecqec",
"model": "HF+"
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
},
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>dynamox-challenge</title>
<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:wght@400;500;600;700;800;900&display=swap"
rel="stylesheet" />

</head>

<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>

</html>
Loading