942 lines
20 KiB
Markdown
942 lines
20 KiB
Markdown
# 💻 Exemplos Práticos de Código - Melhorias Críticas
|
|
|
|
## 1. 🔴 MODULARIZAÇÃO - Estrutura Proposta
|
|
|
|
### Antes (Atual - 8.190 linhas)
|
|
```javascript
|
|
// app.js - TUDO em um arquivo
|
|
const appState = { ... };
|
|
const userPreferences = { ... };
|
|
const adminConfig = { ... };
|
|
const materialsDatabase = { ... };
|
|
const ajudaDatabase = { ... };
|
|
function showSection() { ... }
|
|
function calcularCEV() { ... }
|
|
function openModal() { ... }
|
|
// ... 8.000+ linhas mais
|
|
```
|
|
|
|
### Depois (Modular)
|
|
|
|
#### `/js/core/state.js`
|
|
```javascript
|
|
export const appState = {
|
|
history: [],
|
|
favorites: [],
|
|
budgetItems: [],
|
|
currentSection: 'cev',
|
|
currentTheme: 'dark',
|
|
expertMode: false,
|
|
currentSidebarTab: 0
|
|
};
|
|
|
|
export const userPreferences = {
|
|
theme: 'dark',
|
|
colorScheme: 'default',
|
|
fontSize: 'medium',
|
|
fontFamily: 'default'
|
|
};
|
|
|
|
export const adminConfig = {
|
|
appName: 'AÇO CALC PRO',
|
|
appSubtitle: 'Plataforma Técnica com Base de Dados de Materiais Brasileiros',
|
|
// ...
|
|
};
|
|
```
|
|
|
|
#### `/js/core/storage.js`
|
|
```javascript
|
|
import { userPreferences } from './state.js';
|
|
|
|
export function loadPreferences() {
|
|
try {
|
|
const saved = localStorage.getItem('acoCalcPreferences');
|
|
if (saved) {
|
|
Object.assign(userPreferences, JSON.parse(saved));
|
|
}
|
|
} catch (e) {
|
|
console.warn('Não foi possível carregar preferências:', e);
|
|
}
|
|
}
|
|
|
|
export function savePreferences() {
|
|
try {
|
|
localStorage.setItem('acoCalcPreferences', JSON.stringify(userPreferences));
|
|
} catch (e) {
|
|
console.warn('Não foi possível salvar preferências:', e);
|
|
}
|
|
}
|
|
```
|
|
|
|
#### `/js/ui/navigation.js`
|
|
```javascript
|
|
import { appState } from '../core/state.js';
|
|
import { loadSectionContent } from './content-loader.js';
|
|
|
|
export function showSection(sectionId) {
|
|
appState.currentSection = sectionId;
|
|
|
|
// Update sidebar active state
|
|
document.querySelectorAll('.sidebar-item').forEach(item => {
|
|
item.classList.toggle('active', item.dataset.section === sectionId);
|
|
});
|
|
|
|
// Load section content
|
|
loadSectionContent(sectionId);
|
|
|
|
// Add help button after content loads
|
|
setTimeout(() => addHelpButton(sectionId), 100);
|
|
}
|
|
|
|
export function switchTab(tabIndex) {
|
|
document.querySelectorAll('.tab-btn').forEach((btn, i) => {
|
|
btn.classList.toggle('active', i === tabIndex);
|
|
});
|
|
document.querySelectorAll('.tab-content').forEach((content, i) => {
|
|
content.classList.toggle('active', i === tabIndex);
|
|
});
|
|
}
|
|
```
|
|
|
|
#### `/js/ui/content-loader.js` (Lazy Loading)
|
|
```javascript
|
|
// Dynamic imports - carrega apenas quando necessário
|
|
const sectionLoaders = {
|
|
'cev': () => import('../tools/materiais.js').then(m => m.getCEVContent()),
|
|
'seletor': () => import('../tools/materiais.js').then(m => m.getSeletorContent()),
|
|
'parafusos': () => import('../tools/conexoes.js').then(m => m.getParafusosContent()),
|
|
'preaquecimento': () => import('../tools/soldagem.js').then(m => m.getPreaquecimentoContent()),
|
|
'dureza': () => import('../tools/ensaios.js').then(m => m.getDurezaContent()),
|
|
'area-pintura': () => import('../tools/pintura.js').then(m => m.getAreaPinturaContent()),
|
|
'orcamento': () => import('../tools/orcamento.js').then(m => m.getOrcamentoContent()),
|
|
};
|
|
|
|
export async function loadSectionContent(sectionId) {
|
|
const content = document.getElementById('main-content');
|
|
|
|
// Show loading
|
|
content.innerHTML = '<div class="loading">⏳ Carregando...</div>';
|
|
|
|
try {
|
|
const loader = sectionLoaders[sectionId];
|
|
if (loader) {
|
|
const html = await loader();
|
|
content.innerHTML = html;
|
|
} else {
|
|
content.innerHTML = '<p>Seção não encontrada</p>';
|
|
}
|
|
} catch (error) {
|
|
console.error('Erro ao carregar seção:', error);
|
|
content.innerHTML = '<p>Erro ao carregar conteúdo</p>';
|
|
}
|
|
}
|
|
```
|
|
|
|
#### `/js/tools/materiais.js`
|
|
```javascript
|
|
export function getCEVContent() {
|
|
return `
|
|
<div class="section-header">
|
|
<div class="section-title">⚗️ CEV Avançado</div>
|
|
<!-- ... -->
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
export function getSeletorContent() {
|
|
return `
|
|
<div class="section-header">
|
|
<div class="section-title">🔍 Seletor de Aço</div>
|
|
<!-- ... -->
|
|
</div>
|
|
`;
|
|
}
|
|
```
|
|
|
|
#### `/js/main.js` (Entry Point)
|
|
```javascript
|
|
import { loadPreferences, savePreferences } from './core/storage.js';
|
|
import { applyUserPreferences } from './ui/theme.js';
|
|
import { showSection } from './ui/navigation.js';
|
|
import { initializeApp } from './core/init.js';
|
|
|
|
// Initialize app when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('🚀 AÇO CALC PRO v7.5 - Inicializando...');
|
|
|
|
loadPreferences();
|
|
applyUserPreferences();
|
|
initializeApp();
|
|
|
|
console.log('✅ Aplicativo inicializado com sucesso!');
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 2. 🔴 RESPONSIVIDADE MOBILE
|
|
|
|
### CSS Mobile-First
|
|
|
|
#### `style-mobile.css`
|
|
```css
|
|
/* ========================================
|
|
MOBILE FIRST APPROACH
|
|
======================================== */
|
|
|
|
/* Base (Mobile) - 320px+ */
|
|
:root {
|
|
--header-height: 60px;
|
|
--sidebar-width: 100%;
|
|
--content-padding: 16px;
|
|
}
|
|
|
|
/* Header Mobile */
|
|
.header {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
height: var(--header-height);
|
|
}
|
|
|
|
.header-content {
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
padding: 8px;
|
|
}
|
|
|
|
.logo-section {
|
|
text-align: center;
|
|
}
|
|
|
|
.logo {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.subtitle {
|
|
font-size: 11px;
|
|
display: none; /* Hide on mobile */
|
|
}
|
|
|
|
.header-actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.btn-icon {
|
|
font-size: 11px;
|
|
padding: 6px 10px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Sidebar Mobile - Drawer */
|
|
.sidebar {
|
|
position: fixed;
|
|
top: var(--header-height);
|
|
left: -100%;
|
|
width: 280px;
|
|
height: calc(100vh - var(--header-height));
|
|
background: var(--color-surface);
|
|
box-shadow: var(--shadow-lg);
|
|
transition: left 0.3s ease;
|
|
z-index: 999;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.sidebar.open {
|
|
left: 0;
|
|
}
|
|
|
|
/* Overlay when sidebar is open */
|
|
.sidebar-overlay {
|
|
display: none;
|
|
position: fixed;
|
|
top: var(--header-height);
|
|
left: 0;
|
|
width: 100%;
|
|
height: calc(100vh - var(--header-height));
|
|
background: rgba(0, 0, 0, 0.5);
|
|
z-index: 998;
|
|
}
|
|
|
|
.sidebar-overlay.active {
|
|
display: block;
|
|
}
|
|
|
|
/* Hamburger Menu */
|
|
.hamburger {
|
|
display: block;
|
|
position: fixed;
|
|
bottom: 20px;
|
|
right: 20px;
|
|
width: 56px;
|
|
height: 56px;
|
|
background: var(--color-primary);
|
|
border-radius: 50%;
|
|
border: none;
|
|
box-shadow: var(--shadow-lg);
|
|
cursor: pointer;
|
|
z-index: 1000;
|
|
transition: transform 0.3s;
|
|
}
|
|
|
|
.hamburger:active {
|
|
transform: scale(0.95);
|
|
}
|
|
|
|
.hamburger-icon {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.hamburger-icon span {
|
|
display: block;
|
|
width: 24px;
|
|
height: 3px;
|
|
background: white;
|
|
border-radius: 2px;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.hamburger.open .hamburger-icon span:nth-child(1) {
|
|
transform: rotate(45deg) translate(6px, 6px);
|
|
}
|
|
|
|
.hamburger.open .hamburger-icon span:nth-child(2) {
|
|
opacity: 0;
|
|
}
|
|
|
|
.hamburger.open .hamburger-icon span:nth-child(3) {
|
|
transform: rotate(-45deg) translate(6px, -6px);
|
|
}
|
|
|
|
/* Container Mobile */
|
|
.container {
|
|
flex-direction: column;
|
|
padding: 0;
|
|
}
|
|
|
|
.main-content {
|
|
width: 100%;
|
|
padding: var(--content-padding);
|
|
min-height: calc(100vh - var(--header-height));
|
|
}
|
|
|
|
/* Forms Mobile */
|
|
.form-grid {
|
|
grid-template-columns: 1fr !important;
|
|
gap: 12px;
|
|
}
|
|
|
|
.form-control {
|
|
font-size: 16px; /* Prevent zoom on iOS */
|
|
padding: 12px;
|
|
}
|
|
|
|
/* Buttons Mobile */
|
|
.btn {
|
|
width: 100%;
|
|
padding: 14px;
|
|
font-size: 16px;
|
|
}
|
|
|
|
/* Cards Mobile */
|
|
.card {
|
|
margin-bottom: 16px;
|
|
padding: 16px;
|
|
}
|
|
|
|
/* Tabs Mobile */
|
|
.tabs-nav {
|
|
overflow-x: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
scrollbar-width: none; /* Firefox */
|
|
-ms-overflow-style: none; /* IE/Edge */
|
|
}
|
|
|
|
.tabs-nav::-webkit-scrollbar {
|
|
display: none; /* Chrome/Safari */
|
|
}
|
|
|
|
.tab-btn {
|
|
flex-shrink: 0;
|
|
min-width: 120px;
|
|
}
|
|
|
|
/* Modals Mobile */
|
|
.modal-content {
|
|
width: 95%;
|
|
max-width: 95%;
|
|
max-height: 90vh;
|
|
margin: 20px auto;
|
|
}
|
|
|
|
.modal-help-content {
|
|
width: 95%;
|
|
max-height: 85vh;
|
|
}
|
|
|
|
/* Tables Mobile - Horizontal Scroll */
|
|
.table-wrapper {
|
|
overflow-x: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
table {
|
|
min-width: 600px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
/* ========================================
|
|
TABLET - 768px+
|
|
======================================== */
|
|
@media (min-width: 768px) {
|
|
:root {
|
|
--header-height: 80px;
|
|
--sidebar-width: 260px;
|
|
--content-padding: 24px;
|
|
}
|
|
|
|
.header-content {
|
|
flex-direction: row;
|
|
justify-content: space-between;
|
|
padding: 16px 24px;
|
|
}
|
|
|
|
.subtitle {
|
|
display: block;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.btn-icon {
|
|
font-size: 13px;
|
|
padding: 8px 14px;
|
|
}
|
|
|
|
.hamburger {
|
|
display: none;
|
|
}
|
|
|
|
.sidebar {
|
|
position: static;
|
|
width: var(--sidebar-width);
|
|
height: auto;
|
|
}
|
|
|
|
.sidebar-overlay {
|
|
display: none !important;
|
|
}
|
|
|
|
.container {
|
|
flex-direction: row;
|
|
}
|
|
|
|
.form-grid {
|
|
grid-template-columns: repeat(2, 1fr) !important;
|
|
}
|
|
|
|
.btn {
|
|
width: auto;
|
|
}
|
|
}
|
|
|
|
/* ========================================
|
|
DESKTOP - 1024px+
|
|
======================================== */
|
|
@media (min-width: 1024px) {
|
|
:root {
|
|
--sidebar-width: 300px;
|
|
--content-padding: 32px;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.form-grid {
|
|
grid-template-columns: repeat(3, 1fr) !important;
|
|
}
|
|
|
|
.logo {
|
|
font-size: 24px;
|
|
}
|
|
|
|
.subtitle {
|
|
font-size: 14px;
|
|
}
|
|
}
|
|
|
|
/* ========================================
|
|
LARGE DESKTOP - 1440px+
|
|
======================================== */
|
|
@media (min-width: 1440px) {
|
|
.container {
|
|
max-width: 1600px;
|
|
}
|
|
|
|
.form-grid {
|
|
grid-template-columns: repeat(4, 1fr) !important;
|
|
}
|
|
}
|
|
|
|
/* ========================================
|
|
TOUCH OPTIMIZATIONS
|
|
======================================== */
|
|
@media (hover: none) and (pointer: coarse) {
|
|
/* Increase touch targets */
|
|
.btn,
|
|
.btn-icon,
|
|
.sidebar-item,
|
|
.tab-btn {
|
|
min-height: 44px; /* Apple HIG recommendation */
|
|
min-width: 44px;
|
|
}
|
|
|
|
/* Remove hover effects on touch */
|
|
.btn:hover,
|
|
.sidebar-item:hover {
|
|
transform: none;
|
|
}
|
|
|
|
/* Add active states */
|
|
.btn:active {
|
|
transform: scale(0.98);
|
|
}
|
|
}
|
|
|
|
/* ========================================
|
|
PRINT STYLES
|
|
======================================== */
|
|
@media print {
|
|
.header,
|
|
.sidebar,
|
|
.footer,
|
|
.btn-help,
|
|
.hamburger {
|
|
display: none !important;
|
|
}
|
|
|
|
.main-content {
|
|
width: 100%;
|
|
padding: 0;
|
|
}
|
|
|
|
.card {
|
|
break-inside: avoid;
|
|
}
|
|
}
|
|
```
|
|
|
|
### JavaScript para Hamburger Menu
|
|
|
|
#### `js/ui/mobile-menu.js`
|
|
```javascript
|
|
export function initMobileMenu() {
|
|
// Create hamburger button
|
|
const hamburger = document.createElement('button');
|
|
hamburger.className = 'hamburger';
|
|
hamburger.innerHTML = `
|
|
<div class="hamburger-icon">
|
|
<span></span>
|
|
<span></span>
|
|
<span></span>
|
|
</div>
|
|
`;
|
|
|
|
// Create overlay
|
|
const overlay = document.createElement('div');
|
|
overlay.className = 'sidebar-overlay';
|
|
|
|
// Add to DOM
|
|
document.body.appendChild(hamburger);
|
|
document.body.appendChild(overlay);
|
|
|
|
// Get sidebar
|
|
const sidebar = document.querySelector('.sidebar');
|
|
|
|
// Toggle menu
|
|
const toggleMenu = () => {
|
|
hamburger.classList.toggle('open');
|
|
sidebar.classList.toggle('open');
|
|
overlay.classList.toggle('active');
|
|
document.body.style.overflow = sidebar.classList.contains('open') ? 'hidden' : '';
|
|
};
|
|
|
|
// Event listeners
|
|
hamburger.addEventListener('click', toggleMenu);
|
|
overlay.addEventListener('click', toggleMenu);
|
|
|
|
// Close menu when clicking sidebar item
|
|
document.querySelectorAll('.sidebar-item').forEach(item => {
|
|
item.addEventListener('click', () => {
|
|
if (window.innerWidth < 768) {
|
|
toggleMenu();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Handle resize
|
|
let resizeTimer;
|
|
window.addEventListener('resize', () => {
|
|
clearTimeout(resizeTimer);
|
|
resizeTimer = setTimeout(() => {
|
|
if (window.innerWidth >= 768) {
|
|
hamburger.classList.remove('open');
|
|
sidebar.classList.remove('open');
|
|
overlay.classList.remove('active');
|
|
document.body.style.overflow = '';
|
|
}
|
|
}, 250);
|
|
});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. 🔴 LAZY LOADING & CODE SPLITTING
|
|
|
|
### Vite Configuration
|
|
|
|
#### `vite.config.js`
|
|
```javascript
|
|
import { defineConfig } from 'vite';
|
|
|
|
export default defineConfig({
|
|
build: {
|
|
rollupOptions: {
|
|
output: {
|
|
manualChunks: {
|
|
// Vendor chunk
|
|
'vendor': ['chart.js'],
|
|
|
|
// Core chunk
|
|
'core': [
|
|
'./js/core/state.js',
|
|
'./js/core/storage.js',
|
|
'./js/core/config.js'
|
|
],
|
|
|
|
// UI chunk
|
|
'ui': [
|
|
'./js/ui/navigation.js',
|
|
'./js/ui/modals.js',
|
|
'./js/ui/theme.js'
|
|
],
|
|
|
|
// Tools chunks (lazy loaded)
|
|
'tools-materiais': ['./js/tools/materiais.js'],
|
|
'tools-conexoes': ['./js/tools/conexoes.js'],
|
|
'tools-soldagem': ['./js/tools/soldagem.js'],
|
|
'tools-ensaios': ['./js/tools/ensaios.js'],
|
|
'tools-pintura': ['./js/tools/pintura.js'],
|
|
'tools-orcamento': ['./js/tools/orcamento.js']
|
|
}
|
|
}
|
|
},
|
|
|
|
// Minification
|
|
minify: 'terser',
|
|
terserOptions: {
|
|
compress: {
|
|
drop_console: true, // Remove console.log in production
|
|
drop_debugger: true
|
|
}
|
|
},
|
|
|
|
// Source maps
|
|
sourcemap: false // Disable in production
|
|
},
|
|
|
|
// Dev server
|
|
server: {
|
|
port: 3000,
|
|
open: true
|
|
}
|
|
});
|
|
```
|
|
|
|
### Package.json
|
|
```json
|
|
{
|
|
"name": "aco-calc-pro",
|
|
"version": "7.5.0",
|
|
"type": "module",
|
|
"scripts": {
|
|
"dev": "vite",
|
|
"build": "vite build",
|
|
"preview": "vite preview",
|
|
"lint": "eslint js/**/*.js",
|
|
"format": "prettier --write js/**/*.js"
|
|
},
|
|
"devDependencies": {
|
|
"vite": "^5.0.0",
|
|
"eslint": "^8.0.0",
|
|
"prettier": "^3.0.0"
|
|
},
|
|
"dependencies": {
|
|
"chart.js": "^4.0.0"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. 🟡 STATE MANAGEMENT
|
|
|
|
### Simple Reactive Store
|
|
|
|
#### `js/core/store.js`
|
|
```javascript
|
|
class Store {
|
|
constructor(initialState) {
|
|
this.state = initialState;
|
|
this.listeners = new Map();
|
|
}
|
|
|
|
// Get state value
|
|
get(key) {
|
|
return this.state[key];
|
|
}
|
|
|
|
// Set state value and notify listeners
|
|
set(key, value) {
|
|
const oldValue = this.state[key];
|
|
this.state[key] = value;
|
|
|
|
// Notify listeners
|
|
if (this.listeners.has(key)) {
|
|
this.listeners.get(key).forEach(callback => {
|
|
callback(value, oldValue);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Subscribe to state changes
|
|
subscribe(key, callback) {
|
|
if (!this.listeners.has(key)) {
|
|
this.listeners.set(key, new Set());
|
|
}
|
|
this.listeners.get(key).add(callback);
|
|
|
|
// Return unsubscribe function
|
|
return () => {
|
|
this.listeners.get(key).delete(callback);
|
|
};
|
|
}
|
|
|
|
// Update multiple values
|
|
update(updates) {
|
|
Object.entries(updates).forEach(([key, value]) => {
|
|
this.set(key, value);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Create global store
|
|
export const store = new Store({
|
|
currentSection: 'cev',
|
|
currentTheme: 'dark',
|
|
expertMode: false,
|
|
currentSidebarTab: 0,
|
|
history: [],
|
|
favorites: [],
|
|
budgetItems: []
|
|
});
|
|
|
|
// Auto-save to localStorage
|
|
store.subscribe('currentTheme', (theme) => {
|
|
localStorage.setItem('theme', theme);
|
|
applyTheme(theme);
|
|
});
|
|
|
|
store.subscribe('expertMode', (mode) => {
|
|
localStorage.setItem('expertMode', mode);
|
|
filterToolsByMode(mode);
|
|
});
|
|
```
|
|
|
|
### Usage
|
|
```javascript
|
|
import { store } from './core/store.js';
|
|
|
|
// Get value
|
|
const theme = store.get('currentTheme');
|
|
|
|
// Set value
|
|
store.set('currentTheme', 'light');
|
|
|
|
// Subscribe to changes
|
|
const unsubscribe = store.subscribe('currentSection', (newSection, oldSection) => {
|
|
console.log(`Section changed from ${oldSection} to ${newSection}`);
|
|
updateUI(newSection);
|
|
});
|
|
|
|
// Unsubscribe when done
|
|
unsubscribe();
|
|
|
|
// Update multiple values
|
|
store.update({
|
|
currentSection: 'parafusos',
|
|
expertMode: true
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 5. 🟡 ERROR HANDLING
|
|
|
|
### Centralized Error Handler
|
|
|
|
#### `js/utils/error-handler.js`
|
|
```javascript
|
|
class ErrorHandler {
|
|
constructor() {
|
|
this.errors = [];
|
|
this.maxErrors = 100;
|
|
}
|
|
|
|
handle(error, context = {}) {
|
|
const errorInfo = {
|
|
message: error.message,
|
|
stack: error.stack,
|
|
context,
|
|
timestamp: new Date().toISOString(),
|
|
userAgent: navigator.userAgent
|
|
};
|
|
|
|
// Store error
|
|
this.errors.push(errorInfo);
|
|
if (this.errors.length > this.maxErrors) {
|
|
this.errors.shift();
|
|
}
|
|
|
|
// Log to console
|
|
console.error('Error:', errorInfo);
|
|
|
|
// Show user-friendly message
|
|
this.showErrorToast(this.getUserMessage(error));
|
|
|
|
// Optional: Send to analytics
|
|
// this.sendToAnalytics(errorInfo);
|
|
}
|
|
|
|
getUserMessage(error) {
|
|
// Map technical errors to user-friendly messages
|
|
const messages = {
|
|
'TypeError': 'Erro nos dados fornecidos. Verifique os valores.',
|
|
'ReferenceError': 'Erro interno. Por favor, recarregue a página.',
|
|
'NetworkError': 'Erro de conexão. Verifique sua internet.',
|
|
'ValidationError': 'Dados inválidos. Verifique os campos.'
|
|
};
|
|
|
|
return messages[error.name] || 'Ocorreu um erro. Tente novamente.';
|
|
}
|
|
|
|
showErrorToast(message) {
|
|
const toast = document.createElement('div');
|
|
toast.className = 'toast toast-error';
|
|
toast.textContent = message;
|
|
document.body.appendChild(toast);
|
|
|
|
setTimeout(() => toast.classList.add('show'), 10);
|
|
setTimeout(() => {
|
|
toast.classList.remove('show');
|
|
setTimeout(() => toast.remove(), 300);
|
|
}, 3000);
|
|
}
|
|
|
|
getErrors() {
|
|
return this.errors;
|
|
}
|
|
|
|
clearErrors() {
|
|
this.errors = [];
|
|
}
|
|
}
|
|
|
|
export const errorHandler = new ErrorHandler();
|
|
|
|
// Global error handler
|
|
window.addEventListener('error', (event) => {
|
|
errorHandler.handle(event.error, {
|
|
type: 'global',
|
|
filename: event.filename,
|
|
lineno: event.lineno,
|
|
colno: event.colno
|
|
});
|
|
});
|
|
|
|
// Unhandled promise rejections
|
|
window.addEventListener('unhandledrejection', (event) => {
|
|
errorHandler.handle(new Error(event.reason), {
|
|
type: 'promise'
|
|
});
|
|
});
|
|
```
|
|
|
|
### Usage
|
|
```javascript
|
|
import { errorHandler } from './utils/error-handler.js';
|
|
|
|
// Wrap risky operations
|
|
try {
|
|
const result = calcularCEV(inputs);
|
|
displayResult(result);
|
|
} catch (error) {
|
|
errorHandler.handle(error, {
|
|
function: 'calcularCEV',
|
|
inputs
|
|
});
|
|
}
|
|
|
|
// Async operations
|
|
async function loadData() {
|
|
try {
|
|
const data = await fetch('/api/data');
|
|
return await data.json();
|
|
} catch (error) {
|
|
errorHandler.handle(error, {
|
|
function: 'loadData',
|
|
url: '/api/data'
|
|
});
|
|
return null;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📦 RESULTADO FINAL
|
|
|
|
### Bundle Size Comparison
|
|
|
|
| Arquivo | Antes | Depois | Redução |
|
|
|---------|-------|--------|---------|
|
|
| app.js | 412 KB | 45 KB | -89% |
|
|
| tools-materiais.js | - | 35 KB | (lazy) |
|
|
| tools-conexoes.js | - | 40 KB | (lazy) |
|
|
| tools-soldagem.js | - | 38 KB | (lazy) |
|
|
| tools-ensaios.js | - | 25 KB | (lazy) |
|
|
| tools-pintura.js | - | 30 KB | (lazy) |
|
|
| tools-orcamento.js | - | 32 KB | (lazy) |
|
|
| **Initial Load** | **412 KB** | **45 KB** | **-89%** |
|
|
|
|
### Performance Gains
|
|
|
|
| Métrica | Antes | Depois | Melhoria |
|
|
|---------|-------|--------|----------|
|
|
| FCP | 3.2s | 0.8s | -75% |
|
|
| LCP | 5.1s | 1.9s | -63% |
|
|
| TTI | 6.3s | 2.1s | -67% |
|
|
| Lighthouse | 62 | 94 | +52% |
|
|
|
|
---
|
|
|
|
**Próximo Passo**: Implementar estas melhorias?
|