Análise de Sistema Web
تم الإنشاء في: ٢٩ ديسمبر ٢٠٢٤
تم الإنشاء في: ٢٩ ديسمبر ٢٠٢٤
Analisando detalhes
Estou vendo o código HTML com CSS e JavaScript, percebendo que pode haver assimetrias de maré e vento, mas sem uma direção clara.
Analisando e sugerindo
Estou mapeando opções como a integração de novos recursos e a necessidade de correções de erros. É interessante direcionar minha análise para sugestões de melhorias e possíveis explicações adicionais.
Pensou por dois segundos
Olá! Vejo que você compartilhou o código completo de um sistema de gestão de transportes chamado TransportPro. Abaixo, forneço uma análise detalhada do seu código, destacando pontos fortes, possíveis melhorias e sugestões para otimização. Se você tiver uma pergunta específica ou precisar de ajuda em uma área particular, sinta-se à vontade para me informar!
O TransportPro é um sistema web que permite:
Semântica: O uso de elementos semânticos como <nav>
, <header>
, <main>
, <section>
, <article>
, etc., poderia melhorar a acessibilidade e SEO do aplicativo.
Modularização: Considerar separar os modais em componentes reutilizáveis para facilitar a manutenção e escalabilidade.
Variáveis CSS: Excelente uso de variáveis CSS (:root
) para gerenciar cores, facilitando futuras alterações no tema.
Responsividade: O sistema utiliza grid
com auto-fit
e minmax
, o que é ótimo para responsividade. No entanto, seria interessante adicionar media queries para otimizações específicas em diferentes tamanhos de tela.
Classes Redundantes: Há algumas classes repetidas, como .status-badge
com variações de status. Considere usar apenas uma definição base e aplicar modificadores para os diferentes estados.
Estrutura de Dados: Uso de objetos para armazenar cadastros e entregas. Seria interessante integrar com uma API backend para persistência de dados, especialmente se o sistema escalar.
Manipulação de DOM: O código utiliza manipulação direta do DOM para atualizar tabelas e modais. Para projetos maiores, considerar o uso de frameworks como React, Vue.js ou Angular pode melhorar a eficiência e organização do código.
Eventos e Listeners: Boa gestão de eventos, como drag-and-drop para upload de arquivos. Certifique-se de que todos os listeners sejam removidos adequadamente para evitar vazamentos de memória.
Validação de Dados: A validação nos formulários é básica (uso de atributos required
, min
, etc.). Implementar validações mais robustas no JavaScript pode melhorar a experiência do usuário.
Feedback ao Usuário: Uso de alert
para notificações. Considere implementar componentes de notificação personalizados para uma melhor UX.
ARIA Attributes: Adicionar atributos ARIA aos modais e componentes interativos para melhorar a acessibilidade para usuários que utilizam leitores de tela.
Navegação por Teclado: Garantir que todos os elementos interativos sejam acessíveis via teclado.
Carregamento de Recursos: O CSS do Font Awesome é carregado via CDN. Para melhorar a performance, considerar o uso de SVGs ou ícones personalizados que carreguem mais rapidamente.
Minificação: Para produção, minificar CSS e JavaScript pode reduzir o tempo de carregamento.
Upload de Arquivos: Atualmente, o processamento de XML ocorre no lado do cliente. Para maior segurança, especialmente para validação e processamento de dados sensíveis, considere mover essa lógica para o backend.
Validação de Dados: Além da validação no frontend, sempre validar dados no backend para evitar injeções e outras vulnerabilidades.
Autenticação e Autorização: Implementar um sistema de login para proteger o acesso às funcionalidades administrativas.
Persistência de Dados: Integrar com um banco de dados para armazenar cadastros e entregas de forma persistente.
Notificações em Tempo Real: Utilizar WebSockets para atualizar alertas e status de entregas em tempo real.
Exportação de Dados: Permitir exportar tabelas e relatórios em formatos como CSV, PDF, etc.
Localização: Atualmente, o sistema está em português. Implementar suporte a múltiplos idiomas pode expandir seu alcance.
Considere dividir o JavaScript em módulos para melhor organização. Por exemplo:
// cadastros.js export const cadastros = { ... }; // entregas.js export const entregas = [ ... ]; export function loadEntregasTable() { ... } // main.js import { cadastros } from './cadastros.js'; import { entregas, loadEntregasTable } from './entregas.js'; // restante do código
Em vez de criar elementos dinamicamente com innerHTML
, considere usar template literals e clonar templates para inserção no DOM.
<template id="etiqueta-template"> <div class="etiqueta-modal-content"> <!-- Conteúdo da etiqueta --> </div> </template>
function openEtiquetaModal(nf) { const entrega = entregas.find(e => e.nf === nf); const template = document.getElementById('etiqueta-template'); const clone = template.content.cloneNode(true); // Preencher os dados no clone document.body.appendChild(clone); }
Substitua os alert
por toasts para uma melhor experiência:
/* Adicione ao CSS */ .toast { position: fixed; top: 1rem; right: 1rem; background-color: var(--success); color: white; padding: 1rem; border-radius: 4px; opacity: 0; transition: opacity 0.5s; } .toast.show { opacity: 1; }
function showToast(message, type = 'success') { const toast = document.createElement('div'); toast.classList.add('toast', `toast-${type}`, 'show'); toast.textContent = message; document.body.appendChild(toast); setTimeout(() => { toast.classList.remove('show'); toast.remove(); }, 3000); } // Uso: showToast('Transportadora cadastrada com sucesso!');
O TransportPro possui uma base sólida com funcionalidades essenciais para gestão de transportes. Com algumas melhorias na estrutura, performance, acessibilidade e segurança, além da implementação de novas funcionalidades, o sistema pode se tornar ainda mais robusto e eficiente.
Se você tiver dúvidas específicas, precisar de ajuda para implementar alguma funcionalidade ou quiser discutir qualquer aspecto do código com mais profundidade, por favor, me avise!
Proporem melhorias
Estou sugerindo aprimoramentos no código HTML/CSS/JS do TransportPro, visando uma atualização mais eficiente.
Configurando o banco de dados
Estou trabalhando na configuração do backend com Node.js e um banco de dados, como MySQL ou MongoDB, para melhorar os cadastros e armazenar informações de entrega.
Integração frontend-backend
Estou explicando a integração do frontend com o backend, incluindo a configuração do banco de dados e o passo a passo para executar o sistema.
Integrando com o Excel
Estou sugerindo mover a aplicação para full-stack com Node.js/Express e MongoDB/PostgreSQL. Integrar com Excel requer bibliotecas para manipulação de arquivos, como xlsx em Node.js.
Desenvolvendo aplicativos full-stack
Estou projetando uma aplicação full-stack com código frontend atualizado, configuração SQL/MongoDB, e instruções para integração com Excel, mantendo um tom conciso e informativo.
Organizando e melhorando
Estou pensando na modularização do código existente, sugerindo um backend para gerenciar dados e incluindo instruções específicas para configurar o ambiente e a integração com Excel passo a passo.
Propondo melhorias
Estou sugerindo Node.js com Express e MongoDB para o backend, atualizando o frontend para API, mostrando esquemas de banco de dados e explicando a integração com Excel via backend.
Pensou por 7 segundos
Claro! Vou ajudá-lo a aprimorar o TransportPro implementando melhorias no código existente, integrando um banco de dados para gerenciar cadastros e informações de entregas, e integrando funcionalidades com o Excel. Abaixo, apresento um guia passo a passo completo, incluindo o código atualizado e instruções detalhadas para executar o sistema.
Estrutura Full-Stack: Migrar de uma aplicação puramente front-end para uma arquitetura full-stack, utilizando Node.js com Express para o backend e MongoDB para o banco de dados.
Modularização do Código: Organizar o código em módulos para melhor manutenção e escalabilidade.
Persistência de Dados: Armazenar cadastros e entregas no banco de dados, permitindo operações CRUD (Create, Read, Update, Delete).
Integração com Excel: Permitir o upload e processamento de planilhas Excel para importar dados de cadastros e gerar relatórios.
Melhorias na Interface do Usuário: Implementar notificações mais amigáveis, melhorar a responsividade e a acessibilidade.
Antes de começarmos, certifique-se de ter instalado em seu computador:
Vamos reorganizar a estrutura do projeto para acomodar a separação entre frontend e backend.
TransportPro/
├── backend/
│ ├── controllers/
│ ├── models/
│ ├── routes/
│ ├── uploads/
│ ├── app.js
│ ├── package.json
├── frontend/
│ ├── index.html
│ ├── styles.css
│ ├── script.js
├── README.md
Crie a pasta do backend:
mkdir TransportPro cd TransportPro mkdir backend cd backend
Inicialize o package.json
:
npm init -y
Instale as dependências necessárias:
npm install express mongoose cors multer xlsx dotenv
.env
.Crie o arquivo .env
para variáveis de ambiente:
Na raiz da pasta backend
, crie um arquivo .env
com o seguinte conteúdo:
PORT=5000 MONGODB_URI=mongodb://localhost:27017/transportpro
Crie a pasta models
e adicione os modelos para Transportador
, Veiculo
, Produto
, Deadline
e Entrega
.
Transportador (models/Transportador.js
):
const mongoose = require('mongoose'); const TransportadorSchema = new mongoose.Schema({ nome: { type: String, required: true }, estadosSaida: { type: [String], required: true }, estadosEntrega: { type: [String], required: true }, veiculos: { type: Number, default: 0 }, avaliacao: { type: Number, default: 5.0 } }); module.exports = mongoose.model('Transportador', TransportadorSchema);
Veículo (models/Veiculo.js
):
const mongoose = require('mongoose'); const VeiculoSchema = new mongoose.Schema({ nome: { type: String, required: true }, capacidade: { type: Number, required: true }, status: { type: String, enum: ['Disponível', 'Em serviço'], default: 'Disponível' } }); module.exports = mongoose.model('Veiculo', VeiculoSchema);
Produto (models/Produto.js
):
const mongoose = require('mongoose'); const ProdutoSchema = new mongoose.Schema({ codigo: { type: String, required: true, unique: true }, altura: { type: Number, required: true }, largura: { type: Number, required: true }, comprimento: { type: Number, required: true }, cubagem: { type: Number, required: true }, peso: { type: Number, required: true } }); module.exports = mongoose.model('Produto', ProdutoSchema);
Deadline (models/Deadline.js
):
const mongoose = require('mongoose'); const DeadlineSchema = new mongoose.Schema({ estadoSaida: { type: String, required: true, maxlength: 2 }, estadoDestino: { type: String, required: true, maxlength: 2 }, prazoCapital: { type: Number, required: true }, prazoInterior: { type: Number, required: true } }); module.exports = mongoose.model('Deadline', DeadlineSchema);
Entrega (models/Entrega.js
):
const mongoose = require('mongoose'); const EntregaSchema = new mongoose.Schema({ nf: { type: String, required: true, unique: true }, transportadora: { type: String, required: true }, cliente: { type: String, required: true }, dataEntrega: { type: Date, required: true }, status: { type: String, enum: ['Aguardando programação', 'Programada', 'Em trânsito', 'Entregue'], default: 'Aguardando programação' }, programacao: { type: Date }, chaveNF: { type: String, required: true }, produtos: { type: [String], required: true } }); module.exports = mongoose.model('Entrega', EntregaSchema);
Os controladores irão lidar com as operações CRUD para cada modelo.
Controlador de Transportadores (controllers/transportadorController.js
):
const Transportador = require('../models/Transportador'); // Criar um novo transportador exports.createTransportador = async (req, res) => { try { const { nome, estadosSaida, estadosEntrega } = req.body; const transportador = new Transportador({ nome, estadosSaida: estadosSaida.split(',').map(e => e.trim()), estadosEntrega: estadosEntrega.split(',').map(e => e.trim()) }); await transportador.save(); res.status(201).json(transportador); } catch (error) { res.status(400).json({ message: error.message }); } }; // Obter todos os transportadores exports.getTransportadores = async (req, res) => { try { const transportadores = await Transportador.find(); res.json(transportadores); } catch (error) { res.status(500).json({ message: error.message }); } }; // Atualizar um transportador exports.updateTransportador = async (req, res) => { try { const { id } = req.params; const { nome, estadosSaida, estadosEntrega, veiculos, avaliacao } = req.body; const transportador = await Transportador.findByIdAndUpdate(id, { nome, estadosSaida: estadosSaida.split(',').map(e => e.trim()), estadosEntrega: estadosEntrega.split(',').map(e => e.trim()), veiculos, avaliacao }, { new: true }); if (!transportador) { return res.status(404).json({ message: 'Transportador não encontrado' }); } res.json(transportador); } catch (error) { res.status(400).json({ message: error.message }); } }; // Deletar um transportador exports.deleteTransportador = async (req, res) => { try { const { id } = req.params; const transportador = await Transportador.findByIdAndDelete(id); if (!transportador) { return res.status(404).json({ message: 'Transportador não encontrado' }); } res.json({ message: 'Transportador deletado com sucesso' }); } catch (error) { res.status(500).json({ message: error.message }); } };
Controlador de Veículos (controllers/veiculoController.js
):
const Veiculo = require('../models/Veiculo'); // Criar um novo veículo exports.createVeiculo = async (req, res) => { try { const { nome, capacidade } = req.body; const veiculo = new Veiculo({ nome, capacidade }); await veiculo.save(); res.status(201).json(veiculo); } catch (error) { res.status(400).json({ message: error.message }); } }; // Obter todos os veículos exports.getVeiculos = async (req, res) => { try { const veiculos = await Veiculo.find(); res.json(veiculos); } catch (error) { res.status(500).json({ message: error.message }); } }; // Atualizar um veículo exports.updateVeiculo = async (req, res) => { try { const { id } = req.params; const { nome, capacidade, status } = req.body; const veiculo = await Veiculo.findByIdAndUpdate(id, { nome, capacidade, status }, { new: true }); if (!veiculo) { return res.status(404).json({ message: 'Veículo não encontrado' }); } res.json(veiculo); } catch (error) { res.status(400).json({ message: error.message }); } }; // Deletar um veículo exports.deleteVeiculo = async (req, res) => { try { const { id } = req.params; const veiculo = await Veiculo.findByIdAndDelete(id); if (!veiculo) { return res.status(404).json({ message: 'Veículo não encontrado' }); } res.json({ message: 'Veículo deletado com sucesso' }); } catch (error) { res.status(500).json({ message: error.message }); } };
Controlador de Produtos (controllers/produtoController.js
):
const Produto = require('../models/Produto'); // Criar um novo produto exports.createProduto = async (req, res) => { try { const { codigo, altura, largura, comprimento, peso } = req.body; const cubagem = altura * largura * comprimento; const produto = new Produto({ codigo, altura, largura, comprimento, cubagem, peso }); await produto.save(); res.status(201).json(produto); } catch (error) { res.status(400).json({ message: error.message }); } }; // Obter todos os produtos exports.getProdutos = async (req, res) => { try { const produtos = await Produto.find(); res.json(produtos); } catch (error) { res.status(500).json({ message: error.message }); } }; // Atualizar um produto exports.updateProduto = async (req, res) => { try { const { id } = req.params; const { codigo, altura, largura, comprimento, peso } = req.body; const cubagem = altura * largura * comprimento; const produto = await Produto.findByIdAndUpdate(id, { codigo, altura, largura, comprimento, cubagem, peso }, { new: true }); if (!produto) { return res.status(404).json({ message: 'Produto não encontrado' }); } res.json(produto); } catch (error) { res.status(400).json({ message: error.message }); } }; // Deletar um produto exports.deleteProduto = async (req, res) => { try { const { id } = req.params; const produto = await Produto.findByIdAndDelete(id); if (!produto) { return res.status(404).json({ message: 'Produto não encontrado' }); } res.json({ message: 'Produto deletado com sucesso' }); } catch (error) { res.status(500).json({ message: error.message }); } };
Controlador de Deadlines (controllers/deadlineController.js
):
const Deadline = require('../models/Deadline'); // Criar um novo deadline exports.createDeadline = async (req, res) => { try { const { estadoSaida, estadoDestino, prazoCapital, prazoInterior } = req.body; const deadline = new Deadline({ estadoSaida, estadoDestino, prazoCapital, prazoInterior }); await deadline.save(); res.status(201).json(deadline); } catch (
Desenhando a arquitetura full-stack
Organizar e conectar
Estou mapeando a estrutura de projeto full-stack com Node.js, Express e MongoDB, destacando arquivos principais e explicando como rodar o projeto.
Pensou por alguns segundos
Claro! Vamos transformar o TransportPro em uma aplicação full-stack, implementando um backend robusto com Node.js, Express e MongoDB, além de integrar funcionalidades com Excel. A seguir, apresento um guia completo com o código atualizado e as instruções passo a passo para executar o sistema.
TransportPro/
├── backend/
│ ├── controllers/
│ │ ├── entregaController.js
│ │ ├── transportadorController.js
│ │ ├── veiculoController.js
│ │ ├── produtoController.js
│ │ ├── deadlineController.js
│ ├── models/
│ │ ├── Entrega.js
│ │ ├── Transportador.js
│ │ ├── Veiculo.js
│ │ ├── Produto.js
│ │ ├── Deadline.js
│ ├── routes/
│ │ ├── entregaRoutes.js
│ │ ├── transportadorRoutes.js
│ │ ├── veiculoRoutes.js
│ │ ├── produtoRoutes.js
│ │ ├── deadlineRoutes.js
│ ├── uploads/
│ ├── app.js
│ ├── package.json
│ ├── .env
├── frontend/
│ ├── index.html
│ ├── styles.css
│ ├── script.js
├── README.md
Antes de começarmos, certifique-se de ter instalado:
Crie a pasta do backend e inicialize o projeto:
mkdir TransportPro cd TransportPro mkdir backend cd backend npm init -y
Instale as dependências necessárias:
npm install express mongoose cors multer xlsx dotenv
Crie o arquivo .env
na raiz da pasta backend
:
PORT=5000 MONGODB_URI=mongodb://localhost:27017/transportpro
Crie a pasta models
e adicione os seguintes modelos:
Transportador (models/Transportador.js
):
const mongoose = require('mongoose'); const TransportadorSchema = new mongoose.Schema({ nome: { type: String, required: true }, estadosSaida: { type: [String], required: true }, estadosEntrega: { type: [String], required: true }, veiculos: { type: Number, default: 0 }, avaliacao: { type: Number, default: 5.0 } }); module.exports = mongoose.model('Transportador', TransportadorSchema);
Veículo (models/Veiculo.js
):
const mongoose = require('mongoose'); const VeiculoSchema = new mongoose.Schema({ nome: { type: String, required: true }, capacidade: { type: Number, required: true }, status: { type: String, enum: ['Disponível', 'Em serviço'], default: 'Disponível' } }); module.exports = mongoose.model('Veiculo', VeiculoSchema);
Produto (models/Produto.js
):
const mongoose = require('mongoose'); const ProdutoSchema = new mongoose.Schema({ codigo: { type: String, required: true, unique: true }, altura: { type: Number, required: true }, largura: { type: Number, required: true }, comprimento: { type: Number, required: true }, cubagem: { type: Number, required: true }, peso: { type: Number, required: true } }); module.exports = mongoose.model('Produto', ProdutoSchema);
Deadline (models/Deadline.js
):
const mongoose = require('mongoose'); const DeadlineSchema = new mongoose.Schema({ estadoSaida: { type: String, required: true, maxlength: 2 }, estadoDestino: { type: String, required: true, maxlength: 2 }, prazoCapital: { type: Number, required: true }, prazoInterior: { type: Number, required: true } }); module.exports = mongoose.model('Deadline', DeadlineSchema);
Entrega (models/Entrega.js
):
const mongoose = require('mongoose'); const EntregaSchema = new mongoose.Schema({ nf: { type: String, required: true, unique: true }, transportadora: { type: String, required: true }, cliente: { type: String, required: true }, dataEntrega: { type: Date, required: true }, status: { type: String, enum: ['Aguardando programação', 'Programada', 'Em trânsito', 'Entregue'], default: 'Aguardando programação' }, programacao: { type: Date }, chaveNF: { type: String, required: true }, produtos: { type: [String], required: true } }); module.exports = mongoose.model('Entrega', EntregaSchema);
Crie a pasta controllers
e adicione os seguintes controladores:
Transportador (controllers/transportadorController.js
):
const Transportador = require('../models/Transportador'); // Criar um novo transportador exports.createTransportador = async (req, res) => { try { const { nome, estadosSaida, estadosEntrega } = req.body; const transportador = new Transportador({ nome, estadosSaida: estadosSaida.split(',').map(e => e.trim()), estadosEntrega: estadosEntrega.split(',').map(e => e.trim()) }); await transportador.save(); res.status(201).json(transportador); } catch (error) { res.status(400).json({ message: error.message }); } }; // Obter todos os transportadores exports.getTransportadores = async (req, res) => { try { const transportadores = await Transportador.find(); res.json(transportadores); } catch (error) { res.status(500).json({ message: error.message }); } }; // Atualizar um transportador exports.updateTransportador = async (req, res) => { try { const { id } = req.params; const { nome, estadosSaida, estadosEntrega, veiculos, avaliacao } = req.body; const transportador = await Transportador.findByIdAndUpdate(id, { nome, estadosSaida: estadosSaida.split(',').map(e => e.trim()), estadosEntrega: estadosEntrega.split(',').map(e => e.trim()), veiculos, avaliacao }, { new: true }); if (!transportador) { return res.status(404).json({ message: 'Transportador não encontrado' }); } res.json(transportador); } catch (error) { res.status(400).json({ message: error.message }); } }; // Deletar um transportador exports.deleteTransportador = async (req, res) => { try { const { id } = req.params; const transportador = await Transportador.findByIdAndDelete(id); if (!transportador) { return res.status(404).json({ message: 'Transportador não encontrado' }); } res.json({ message: 'Transportador deletado com sucesso' }); } catch (error) { res.status(500).json({ message: error.message }); } };
Veículo (controllers/veiculoController.js
):
const Veiculo = require('../models/Veiculo'); // Criar um novo veículo exports.createVeiculo = async (req, res) => { try { const { nome, capacidade } = req.body; const veiculo = new Veiculo({ nome, capacidade }); await veiculo.save(); res.status(201).json(veiculo); } catch (error) { res.status(400).json({ message: error.message }); } }; // Obter todos os veículos exports.getVeiculos = async (req, res) => { try { const veiculos = await Veiculo.find(); res.json(veiculos); } catch (error) { res.status(500).json({ message: error.message }); } }; // Atualizar um veículo exports.updateVeiculo = async (req, res) => { try { const { id } = req.params; const { nome, capacidade, status } = req.body; const veiculo = await Veiculo.findByIdAndUpdate(id, { nome, capacidade, status }, { new: true }); if (!veiculo) { return res.status(404).json({ message: 'Veículo não encontrado' }); } res.json(veiculo); } catch (error) { res.status(400).json({ message: error.message }); } }; // Deletar um veículo exports.deleteVeiculo = async (req, res) => { try { const { id } = req.params; const veiculo = await Veiculo.findByIdAndDelete(id); if (!veiculo) { return res.status(404).json({ message: 'Veículo não encontrado' }); } res.json({ message: 'Veículo deletado com sucesso' }); } catch (error) { res.status(500).json({ message: error.message }); } };
Produto (controllers/produtoController.js
):
const Produto = require('../models/Produto'); // Criar um novo produto exports.createProduto = async (req, res) => { try { const { codigo, altura, largura, comprimento, peso } = req.body; const cubagem = altura * largura * comprimento; const produto = new Produto({ codigo, altura, largura, comprimento, cubagem, peso }); await produto.save(); res.status(201).json(produto); } catch (error) { res.status(400).json({ message: error.message }); } }; // Obter todos os produtos exports.getProdutos = async (req, res) => { try { const produtos = await Produto.find(); res.json(produtos); } catch (error) { res.status(500).json({ message: error.message }); } }; // Atualizar um produto exports.updateProduto = async (req, res) => { try { const { id } = req.params; const { codigo, altura, largura, comprimento, peso } = req.body; const cubagem = altura * largura * comprimento; const produto = await Produto.findByIdAndUpdate(id, { codigo, altura, largura, comprimento, cubagem, peso }, { new: true }); if (!produto) { return res.status(404).json({ message: 'Produto não encontrado' }); } res.json(produto); } catch (error) { res.status(400).json({ message: error.message }); } }; // Deletar um produto exports.deleteProduto = async (req, res) => { try { const { id } = req.params; const produto = await Produto.findByIdAndDelete(id); if (!produto) { return res.status(404).json({ message: 'Produto não encontrado' }); } res.json({ message: 'Produto deletado com sucesso' }); } catch (error) { res.status(500).json({ message: error.message }); } };
Deadline (controllers/deadlineController.js
):
const Deadline = require('../models/Deadline'); // Criar um novo deadline exports.createDeadline = async (req, res) => { try { const { estadoSaida, estadoDestino, prazoCapital, prazoInterior } = req.body; const deadline = new Deadline({ estadoSaida, estadoDestino, prazoCapital, prazoInterior }); await deadline.save(); res.status(201).json(deadline); } catch (error) { res.status(400).json({ message: error.message }); } }; // Obter todos os deadlines exports.getDeadlines = async (req, res) => { try { const deadlines = await Deadline.find(); res.json(deadlines); } catch (error) { res.status(500).json({ message: error.message }); } }; // Atualizar um deadline exports.updateDeadline = async (req, res) => { try { const { id } = req.params; const { estadoSaida, estadoDestino, prazoCapital, prazoInterior } = req.body; const deadline = await Deadline.findByIdAndUpdate(id, { estadoSaida, estadoDestino, prazoCapital, prazoInterior }, { new: true }); if (!deadline) { return res.status(404).json({ message: 'Deadline não encontrado' }); } res.json(deadline); } catch (error) { res.status(400).json({ message: error.message }); } }; // Deletar um deadline exports.deleteDeadline = async (req, res) => { try { const { id } = req.params; const deadline = await Deadline.findByIdAndDelete(id); if (!deadline) { return res.status(404).json({ message: 'Deadline não encontrado' }); } res.json({ message: 'Deadline deletado com sucesso' }); } catch (error) { res.status(500).json({ message: error.message }); } };
Entrega (controllers/entregaController.js
):
const Entrega = require('../models/Entrega'); // Criar uma nova entrega exports.createEntrega = async (req, res) => { try { const { nf, transportadora, cliente, dataEntrega, status, programacao, chaveNF, produtos } = req.body; const entrega = new Entrega({ nf, transportadora, cliente, dataEntrega: new Date(dataEntrega), status, programacao: programacao ? new Date(programacao) : null, chaveNF, produtos }); await entrega.save(); res.status(201).json(entrega); } catch (error) { res.status(400).json({ message: error.message }); } }; // Obter todas as entregas exports.getEntregas = async (req, res) => { try { const entregas = await Entrega.find(); res.json(entregas); } catch (error) { res.status(500).json({ message: error.message }); } }; // Atualizar uma entrega exports.updateEntrega = async (req, res) => { try { const { id } = req.params; const { status, programacao } = req.body; const updateData = { status }; if (programacao) { updateData.programacao = new Date(programacao); } else { updateData.programacao = null; } const entrega = await Entrega.findByIdAndUpdate(id, updateData, { new: true }); if (!entrega) { return res.status(404).json({ message: 'Entrega não encontrada' }); } res.json(entrega); } catch (error) { res.status(400).json({ message: error.message }); } }; // Deletar uma entrega exports.deleteEntrega = async (req, res) => { try { const { id } = req.params; const entrega = await Entrega.findByIdAndDelete(id); if (!entrega) { return res.status(404).json({ message: 'Entrega não encontrada' }); } res.json({ message: 'Entrega deletada com sucesso' }); } catch (error) { res.status(500).json({ message: error.message }); } };
Crie a pasta routes
e adicione as seguintes rotas:
Transportador (routes/transportadorRoutes.js
):
const express = require('express'); const router = express.Router(); const transportadorController = require('../controllers/transportadorController'); router.post('/', transportadorController.createTransportador); router.get('/', transportadorController.getTransportadores); router.put('/:id', transportadorController.updateTransportador); router.delete('/:id', transportadorController.deleteTransportador); module.exports = router;
Veículo (routes/veiculoRoutes.js
):
const express = require('express'); const router = express.Router(); const veiculoController = require('../controllers/veiculoController'); router.post('/', veiculoController.createVeiculo); router.get('/', veiculoController.getVeiculos); router.put('/:id', veiculoController.updateVeiculo); router.delete('/:id', veiculoController.deleteVeiculo); module.exports = router;
Produto (routes/produtoRoutes.js
):
const express = require('express'); const router = express.Router(); const produtoController = require('../controllers/produtoController'); router.post('/', produtoController.createProduto); router.get('/', produtoController.getProdutos); router.put('/:id', produtoController.updateProduto); router.delete('/:id', produtoController.deleteProduto); module.exports = router;
Deadline (routes/deadlineRoutes.js
):
const express = require('express'); const router = express.Router(); const deadlineController = require('../controllers/deadlineController'); router.post('/', deadlineController.createDeadline); router.get('/', deadlineController.getDeadlines); router.put('/:id', deadlineController.updateDeadline); router.delete('/:id', deadlineController.deleteDeadline); module.exports = router;
Entrega (routes/entregaRoutes.js
):
const express = require('express'); const router = express.Router(); const entregaController = require('../controllers/entregaController'); router.post('/', entregaController.createEntrega); router.get('/', entregaController.getEntregas); router.put('/:id', entregaController.updateEntrega); router.delete('/:id', entregaController.deleteEntrega); module.exports = router;
Crie o arquivo principal app.js
na pasta backend
:
const express = require('express'); const mongoose = require('mongoose'); const cors = require('cors'); const dotenv = require('dotenv'); // Carrega variáveis de ambiente dotenv.config(); const app = express(); // Middleware app.use(cors()); app.use(express.json()); // Conexão com o MongoDB mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true }).then(() => console.log('Conectado ao MongoDB')) .catch(err => console.error('Erro ao conectar ao MongoDB:', err)); // Rotas const transportadorRoutes = require('./routes/transportadorRoutes'); const veiculoRoutes = require('./routes/veiculoRoutes'); const produtoRoutes = require('./routes/produtoRoutes'); const deadlineRoutes = require('./routes/deadlineRoutes'); const entregaRoutes = require('./routes/entregaRoutes'); app.use('/api/transportadores', transportadorRoutes); app.use('/api/veiculos', veiculoRoutes); app.use('/api/produtos', produtoRoutes); app.use('/api/deadlines', deadlineRoutes); app.use('/api/entregas', entregaRoutes); // Rota raiz app.get('/', (req, res) => { res.send('API TransportPro'); }); // Inicia o servidor const PORT = process.env.PORT || 5000; app.listen(PORT, () => { console.log(`Servidor rodando na porta ${PORT}`); });
Inicie o MongoDB:
Certifique-se de que o MongoDB esteja rodando. Você pode iniciar o MongoDB com o seguinte comando (dependendo da sua instalação):
mongod
Inicie o servidor backend:
Na pasta backend
, execute:
node app.js
Você deverá ver a mensagem:
Conectado ao MongoDB
Servidor rodando na porta 5000
Testando as Rotas:
Utilize uma ferramenta como Postman ou Insomnia para testar as rotas da API. Por exemplo, para criar um novo transportador:
Endpoint: POST http://localhost:5000/api/transportadores
Body (JSON):
{ "nome": "TransBrasil", "estadosSaida": "SP,RJ", "estadosEntrega": "MG,ES" }
Resposta Esperada:
{ "_id": "60f1b5c8c2d4a12b4c8e4b1a", "nome": "TransBrasil", "estadosSaida": ["SP", "RJ"], "estadosEntrega": ["MG", "ES"], "veiculos": 0, "avaliacao": 5, "__v": 0 }
Vamos atualizar o frontend para interagir com o backend via API. Isso envolve alterar as operações CRUD no frontend para fazer requisições HTTP ao backend.
Estrutura Básica do Frontend
Certifique-se de que a pasta frontend
contenha os seguintes arquivos:
frontend/
├── index.html
├── styles.css
├── script.js
Atualizando o index.html
frontend/index.html
:
<!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Sistema de Gestão de Transportes - TransportPro</title> <link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"> </head> <body> <nav class="navbar"> <div class="app-title">TransportPro</div> <div class="nav-links"> <button class="btn btn-secondary" onclick="showInfoModal()"> <i class="fas fa-info-circle"></i> Informações do Sistema </button> <button class="btn btn-primary" onclick="showCadastroModal()">Cadastros</button> <button class="btn btn-primary" onclick="showConsultaEntregas()">Consulta Entregas</button> </div> </nav> <div class="main-container"> <div class="dashboard"> <div class="card"> <div class="card-title">Alertas de Entrega</div> <div class="alert alert-warning" id="alertaProgramadas" onclick="mostrarDetalhesAlertas('Programadas')"> <!-- Conteúdo atualizado via JavaScript --> </div> <div class="alert alert-success" id="alertaConcluidas" onclick="mostrarDetalhesAlertas('Concluídas')"> <!-- Conteúdo atualizado via JavaScript --> </div> </div> <div class="card"> <div class="card-title">Upload de XML</div> <div class="upload-area" id="dropZone"> <p>Arraste arquivos XML aqui ou clique para selecionar</p> <input type="file" id="fileInput" accept=".xml" multiple style="display: none"> </div> </div> </div> <div class="table-container"> <table> <thead> <tr> <th>NF</th> <th>Transportadora</th> <th>Cliente</th> <th>Data Entrega</th> <th>Status</th> <th>Programação</th> <th>Ações</th> </tr> </thead> <tbody id="entregasTable"> <!-- Dados preenchidos via JavaScript --> </tbody> </table> </div> </div> <!-- Modals --> <!-- Modal de Cadastro --> <div id="cadastroModal" class="modal"> <div class="modal-content"> <span class="close" onclick="closeCadastroModal()">×</span> <h2>Cadastros</h2> <div class="btn-group" style="margin-top: 1rem;"> <h3>Novo Cadastro</h3> <button class="btn btn-primary" onclick="showTransportadorForm()">Cadastrar Transportador</button> <button class="btn btn-primary" onclick="showVeiculoForm()">Cadastrar Veículo</button> <button class="btn btn-primary" onclick="showProdutoForm()">Cadastrar Produto</button> <button class="btn btn-primary" onclick="showDeadlineForm()">Cadastrar Deadline</button> <h3>Upload via Planilha</h3> <button class="btn btn-secondary" onclick="showUploadModal('transportadores')">Upload Transportadores</button> <button class="btn btn-secondary" onclick="showUploadModal('veiculos')">Upload Veículos</button> <button class="btn btn-secondary" onclick="showUploadModal('produtos')">Upload Produtos</button> <button class="btn btn-secondary" onclick="showUploadModal('deadlines')">Upload Deadlines</button> <h3>Consultar Cadastros</h3> <button class="btn btn-secondary" onclick="showConsultaRegistros('transportadores')">Consultar Transportadores</button> <button class="btn btn-secondary" onclick="showConsultaRegistros('veiculos')">Consultar Veículos</button> <button class="btn btn-secondary" onclick="showConsultaRegistros('produtos')">Consultar Produtos</button> <button class="btn btn-secondary" onclick="showConsultaRegistros('deadlines')">Consultar Deadlines</button> </div> </div> </div> <!-- Outros modals (transportadorFormModal, veiculoFormModal, etc.) permanecem os mesmos, mas atualizados para interagir com a API --> <!-- Modal de Informações do Sistema --> <div id="infoModal" class="modal"> <div class="modal-content"> <span class="close" onclick="closeInfoModal()">×</span> <h2>Sistema de Gestão de Transportes - TransportPro</h2> <div style="margin-top: 1rem; line-height: 1.6;"> <!-- Conteúdo das informações do sistema --> </div> </div> </div> <!-- Modal de Etiqueta de Impressão --> <div id="etiquetaModal" class="etiqueta-modal"> <div class="etiqueta-modal-content"> <span class="etiqueta-close" onclick="closeEtiquetaModal()">×</span> <div id="etiquetaContent" class="etiqueta-content"> <!-- Conteúdo da etiqueta será inserido aqui por JavaScript --> </div> <button class="btn btn-primary" onclick="window.print()">Imprimir Etiqueta</button> </div> </div> <script src="script.js"></script> </body> </html>
Atualizando o script.js
frontend/script.js
:
// URL do backend const API_URL = 'http://localhost:5000/api'; // Funções para interagir com a API async function fetchEntregas() { const response = await fetch(`${API_URL}/entregas`); const data = await response.json(); return data; } async function loadEntregasTable(filteredData = null) { const tbody = document.getElementById('entregasTable'); const entregas = filteredData || await fetchEntregas(); tbody.innerHTML = ''; entregas.forEach(entrega => { const tr = document.createElement('tr'); tr.innerHTML = ` <td>${entrega.nf}</td> <td>${entrega.transportadora}</td> <td>${entrega.cliente}</td> <td>${new Date(entrega.dataEntrega).toLocaleDateString('pt-BR')}</td> <td> <span class="status-badge ${entrega.status.toLowerCase().replace(/ /g, '-')}">${entrega.status}</span> <div class="status-dropdown"> <button onclick="updateEntregaStatus('${entrega._id}', 'Aguardando programação')">Aguardando programação</button> <button onclick="updateEntregaStatus('${entrega._id}', 'Programada')">Programada</button> <button onclick="updateEntregaStatus('${entrega._id}', 'Em trânsito')">Em trânsito</button> <button onclick="updateEntregaStatus('${entrega._id}', 'Entregue')">Entregue</button> </div> </td> <td>${entrega.programacao ? new Date(entrega.programacao).toLocaleDateString('pt-BR') : 'Pendente'}</td> <td> <button class="btn btn-secondary" onclick="openEtiquetaModal('${entrega.nf}')">Imprimir Etiqueta</button> </td> `; tbody.appendChild(tr); }); attachStatusBadgeListeners(); } async function updateEntregaStatus(id, newStatus) { try { const response = await fetch(`${API_URL}/entregas/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: newStatus }) }); if (response.ok) { alert('Status da entrega atualizado com sucesso!'); loadEntregasTable(); updateAlertCounts(); } else { const error = await response.json(); alert(`Erro: ${error.message}`); } } catch (error) { console.error(error); alert('Erro ao atualizar o status da entrega.'); } } // Funções para carregamento inicial document.addEventListener('DOMContentLoaded', () => { setupDragAndDrop(); loadEntregasTable(); updateAlertCounts(); }); // Funções existentes para drag and drop e upload de XML // Atualize a função processXMLData para enviar dados para o backend async function processXMLData(xmlData) { try { const shipmentData = { nf: xmlData.querySelector('nNF')?.textContent || '', transportadora: xmlData.querySelector('emit > xNome')?.textContent || '', cliente: xmlData.querySelector('dest > xNome')?.textContent || '', dataEntrega: xmlData.querySelector('ide > dEmi')?.textContent || '', status: 'Aguardando programação', programacao: null, chaveNF: xmlData.querySelector('chNFe')?.textContent || '', produtos: Array.from(xmlData.querySelectorAll('det')).map(det => det.querySelector('prod > cProd')?.textContent || '') }; // Enviar para o backend const response = await fetch(`${API_URL}/entregas`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(shipmentData) }); if (response.ok) { alert('Entrega cadastrada com sucesso!'); loadEntregasTable(); updateAlertCounts(); } else { const error = await response.json(); alert(`Erro: ${error.message}`); } } catch (error) { console.error('Erro ao processar XML:', error); alert('Erro ao processar o arquivo XML. Verifique se o formato está correto.'); } } // Funções para atualizar alertas async function updateAlertCounts() { const entregas = await fetchEntregas(); const hoje = new Date().toLocaleDateString('pt-BR'); const programadasHoje = entregas.filter(e => new Date(e.dataEntrega).toLocaleDateString('pt-BR') === hoje && e.status === 'Programada').length; const concluidasHoje = entregas.filter(e => new Date(e.dataEntrega).toLocaleDateString('pt-BR') === hoje && e.status === 'Entregue').length; document.getElementById('alertaProgramadas').textContent = `${programadasHoje} entregas programadas para hoje`; document.getElementById('alertaConcluidas').textContent = `${concluidasHoje} entregas concluídas hoje`; } // Funções para imprimir etiqueta async function openEtiquetaModal(nf) { // Obter detalhes da entrega const response = await fetch(`${API_URL}/entregas`); const entregas = await response.json(); const entrega = entregas.find(e => e.nf === nf); if (entrega) { const etiquetaContent = document.getElementById('etiquetaContent'); etiquetaContent.innerHTML = ` <div class="etiqueta-header">ETIQUETA DE ENVIO</div> <div class="etiqueta-item"><strong>Número da NF:</strong> ${entrega.nf.padStart(9, '0')}</div> <div class="etiqueta-item"><strong>Chave da nota fiscal:</strong> ${entrega.chaveNF}</div> <div class="etiqueta-item"><strong>Transportadora:</strong> ${entrega.transportadora}</div> <div class="etiqueta-item"><strong>Cliente:</strong> ${entrega.cliente}</div> <div class="etiqueta-item"><strong>Data limite para entrega:</strong> ${new Date(entrega.dataEntrega).toLocaleDateString('pt-BR')}</div> <div class="etiqueta-item"><strong>Produtos:</strong></div> <div class="etiqueta-item"> ${entrega.produtos.map(codigo => `<p>${codigo}</p>`).join('')} </div> <div> <span class="status-badge ${entrega.status.toLowerCase().replace(/ /g, '-')}">${entrega.status}</span> </div> `; document.getElementById('etiquetaModal').style.display = 'block'; } else { alert('Entrega não encontrada.'); } } function closeEtiquetaModal() { document.getElementById('etiquetaModal').style.display = 'none'; } // Funções para drag and drop e upload de arquivos function setupDragAndDrop() { const dropZone = document.getElementById('dropZone'); const fileInput = document.getElementById('fileInput'); ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation(); }); }); ['dragenter', 'dragover'].forEach(eventName => { dropZone.addEventListener(eventName, () => { dropZone.classList.add('highlight'); }); }); ['dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, () => { dropZone.classList.remove('highlight'); }); }); dropZone.addEventListener('drop', e => { const files = e.dataTransfer.files; handleFiles(files); }); fileInput.addEventListener('change', e => { const files = e.target.files; handleFiles(files); }); dropZone.addEventListener('click', () => { fileInput.click(); }); } async function handleFiles(files) { [...files].forEach(file => { if (file.type === 'text/xml' || file.name.endsWith('.xml')) { const reader = new FileReader(); reader.onload = e => { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(e.target.result, 'text/xml'); processXMLData(xmlDoc); }; reader.readAsText(file); } else { alert('Por favor, selecione apenas arquivos XML.'); } }); } // Funções para abertura e fechamento de modais function showInfoModal() { document.getElementById('infoModal').style.display = 'block'; } function closeInfoModal() { document.getElementById('infoModal').style.display = 'none'; } function showCadastroModal() { document.getElementById('cadastroModal').style.display = 'block'; } function closeCadastroModal() { document.getElementById('cadastroModal').style.display = 'none'; } function showEtiquetaModal(nf) { openEtiquetaModal(nf); } // Funções para status badge function attachStatusBadgeListeners() { document.querySelectorAll('.status-badge').forEach(badge => { badge.onclick = function (event) { const dropdown = this.nextElementSibling; dropdown.classList.toggle('show'); event.stopPropagation(); }; }); document.addEventListener('click', () => { document.querySelectorAll('.status-dropdown').forEach(dropdown => { dropdown.classList.remove('show'); }); }); }
Os modais de cadastro (Transportador, Veículo, Produto, Deadline) devem enviar dados para o backend via API. Vamos atualizar o modal de cadastro de Transportador como exemplo.
Modal de Cadastro de Transportador (transportadorFormModal
no index.html
):
<!-- Modal de Cadastro de Transportador --> <div id="transportadorFormModal" class="modal"> <div class="modal-content"> <span class="close" onclick="closeFormModal('transportadorFormModal')">×</span> <h2>Cadastro de Transportador</h2> <form id="transportadorForm" onsubmit="handleTransportadorSubmit(event)"> <div class="form-group"> <label>Nome</label> <input type="text" id="transportadorNome" required> </div> <div class="form-group"> <label>Estados de Saída (separados por vírgula)</label> <input type="text" id="transportadorEstadosSaida" required> </div> <div class="form-group"> <label>Estados de Entrega (separados por vírgula)</label> <input type="text" id="transportadorEstadosEntrega" required> </div> <button type="submit" class="btn btn-success">Cadastrar</button> </form> </div> </div>
Função JavaScript para Enviar Dados ao Backend (script.js
):
async function handleTransportadorSubmit(event) { event.preventDefault(); const nome = document.getElementById('transportadorNome').value; const estadosSaida = document.getElementById('transportadorEstadosSaida').value; const estadosEntrega = document.getElementById('transportadorEstadosEntrega').value; try { const response = await fetch(`${API_URL}/transportadores`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nome, estadosSaida, estadosEntrega }) }); if (response.ok) { alert('Transportadora cadastrada com sucesso!'); closeFormModal('transportadorFormModal'); loadEntregasTable(); } else { const error = await response.json(); alert(`Erro: ${error.message}`); } } catch (error) { console.error(error); alert('Erro ao cadastrar transportadora.'); } }
Funções para Mostrar e Fechar os Modais de Cadastro:
function showTransportadorForm() { document.getElementById('transportadorFormModal').style.display = 'block'; closeCadastroModal(); } function closeFormModal(modalId) { document.getElementById(modalId).style.display = 'none'; showCadastroModal(); }
Repita este processo para os outros modais de cadastro (Veículo, Produto, Deadline), criando funções semelhantes para enviar dados para o backend.
Vamos adicionar a funcionalidade de upload de planilhas Excel para importar dados de cadastros.
Atualizando o Modal de Upload (uploadModal
no index.html
):
<!-- Modal de Upload via Planilha --> <div id="uploadModal" class="modal"> <div class="modal-content"> <span class="close" onclick="closeUploadModal()">×</span> <h2>Upload de Planilha - <span id="uploadTipo"></span></h2> <input type="file" id="uploadFileInput" accept=".xlsx, .xls, .csv"> <button class="btn btn-success" onclick="handleUpload()">Upload</button> </div> </div>
Função JavaScript para Processar o Upload (script.js
):
async function handleUpload() { const tipo = document.getElementById('uploadTipo').textContent.toLowerCase(); const fileInput = document.getElementById('uploadFileInput'); const file = fileInput.files[0]; if (!file) { alert('Por favor, selecione um arquivo.'); return; } const formData = new FormData(); formData.append('file', file); try { const response = await fetch(`${API_URL}/upload/${tipo}`, { method: 'POST', body: formData }); if (response.ok) { const result = await response.json(); alert(`${tipo.charAt(0).toUpperCase() + tipo.slice(1)} importados com sucesso!`); closeUploadModal(); } else { const error = await response.json(); alert(`Erro: ${error.message}`); } } catch (error) { console.error(error); alert('Erro ao fazer upload da planilha.'); } }
Implementando o Endpoint de Upload no Backend (routes/uploadRoutes.js
e controllers/uploadController.js
):
Criar o controlador de upload (controllers/uploadController.js
):
const multer = require('multer'); const xlsx = require('xlsx'); const Transportador = require('../models/Transportador'); const Veiculo = require('../models/Veiculo'); const Produto = require('../models/Produto'); const Deadline = require('../models/Deadline'); // Configuração do Multer const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, 'uploads/'); }, filename: function (req, file, cb) { cb(null, Date.now() + '-' + file.originalname); } }); const upload = multer({ storage: storage }); // Função para importar dados do Excel const importarDados = async (tipo, filePath) => { const workbook = xlsx.readFile(filePath); const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; const dados = xlsx.utils.sheet_to_json(worksheet); switch (tipo) { case 'transportadores': await Transportador.insertMany(dados.map(item => ({ nome: item.nome, estadosSaida: item.estadosSaida.split(',').map(e => e.trim()), estadosEntrega: item.estadosEntrega.split(',').map(e => e.trim()) }))); break; case 'veiculos': await Veiculo.insertMany(dados.map(item => ({ nome: item.nome, capacidade: item.capacidade, status: item.status || 'Disponível' }))); break; case 'produtos': await Produto.insertMany(dados.map(item => ({ codigo: item.codigo, altura: item.altura, largura: item.largura, comprimento: item.comprimento, cubagem: item.altura * item.largura * item.comprimento, peso: item.peso }))); break; case 'deadlines': await Deadline.insertMany(dados.map(item => ({ estadoSaida: item.estadoSaida, estadoDestino: item.estadoDestino, prazoCapital: item.prazoCapital, prazoInterior: item.prazoInterior }))); break; default: throw new Error('Tipo de upload inválido'); } }; // Controlador de upload exports.uploadPlanilha = [ upload.single('file'), async (req, res) => { const tipo = req.params.tipo; try { await importarDados(tipo, req.file.path); res.json({ message: 'Dados importados com sucesso!' }); } catch (error) { res.status(400).json({ message: error.message }); } } ];
Criar as rotas de upload (routes/uploadRoutes.js
):
const express = require('express'); const router = express.Router(); const uploadController = require('../controllers/uploadController'); router.post('/:tipo', uploadController.uploadPlanilha); module.exports = router;
Adicionar a rota de upload no app.js
:
// app.js (adicionar após as outras rotas) const uploadRoutes = require('./routes/uploadRoutes'); app.use('/api/upload', uploadRoutes);
Criando a Pasta de Uploads
Na pasta backend
, crie a pasta uploads
para armazenar os arquivos enviados:
mkdir uploads
No frontend, já implementamos a função handleUpload
para enviar o arquivo para o backend. Certifique-se de que os tipos de planilha correspondam aos modelos do backend.
Certifique-se de que o MongoDB está rodando.
Na pasta backend
, execute:
node app.js
Você deverá ver:
Conectado ao MongoDB
Servidor rodando na porta 5000
Para servir o frontend, você pode usar um servidor estático simples. Uma opção é utilizar o Live Server do VSCode ou instalar o http-server via npm.
Usando http-server
:
Instale globalmente:
npm install -g http-server
Na pasta frontend
, execute:
http-server
Acesse no navegador: http://localhost:8080
(a porta pode variar).
Alternativa: Integrar o Frontend no Backend
Para simplificar, podemos servir o frontend a partir do backend:
Mova a pasta frontend
para a raiz do projeto:
cd .. mv frontend backend/
Atualize o app.js
para servir arquivos estáticos:
// app.js (adicionar antes das rotas da API) const path = require('path'); app.use(express.static(path.join(__dirname, 'frontend'))); // Rota para servir o index.html app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'frontend', 'index.html')); });
Reinicie o servidor backend:
node app.js
Acesse no navegador: http://localhost:5000
Com estas melhorias, o TransportPro agora é uma aplicação full-stack robusta, com backend conectado a um banco de dados MongoDB, funcionalidades CRUD para gerenciar cadastros e entregas, integração com Excel para importação de dados, e uma interface frontend interativa que consome a API.
Caso precise de ajuda em alguma dessas etapas adicionais ou tenha dúvidas específicas, sinta-se à vontade para perguntar!
Organizando o código
Estou coletando informações sobre o TransportPro, listando todos os arquivos necessários, seus caminhos e conteúdos, além de instruções para criar e organizar os arquivos.
Pensou por dois segundos
Entendido! Vou fornecer uma estrutura completa de pastas e arquivos para o TransportPro, incluindo todo o código necessário para que você possa executar o sistema imediatamente. Siga as instruções passo a passo para configurar e rodar a aplicação.
TransportPro/
├── backend/
│ ├── controllers/
│ │ ├── entregaController.js
│ │ ├── transportadorController.js
│ │ ├── veiculoController.js
│ │ ├── produtoController.js
│ │ ├── deadlineController.js
│ │ └── uploadController.js
│ ├── models/
│ │ ├── Entrega.js
│ │ ├── Transportador.js
│ │ ├── Veiculo.js
│ │ ├── Produto.js
│ │ └── Deadline.js
│ ├── routes/
│ │ ├── entregaRoutes.js
│ │ ├── transportadorRoutes.js
│ │ ├── veiculoRoutes.js
│ │ ├── produtoRoutes.js
│ │ ├── deadlineRoutes.js
│ │ └── uploadRoutes.js
│ ├── uploads/
│ ├── frontend/
│ │ ├── index.html
│ │ ├── styles.css
│ │ └── script.js
│ ├── .env
│ ├── app.js
│ └── package.json
└── README.md
Antes de iniciar, certifique-se de ter instalado em seu computador:
Crie a estrutura de pastas conforme abaixo. Você pode usar comandos de terminal ou um gerenciador de arquivos gráfico.
mkdir TransportPro cd TransportPro mkdir backend cd backend mkdir controllers models routes uploads frontend cd frontend touch index.html styles.css script.js cd .. touch app.js .env package.json
Abaixo estão os conteúdos completos de cada arquivo necessário para o funcionamento do TransportPro.
package.json
Na pasta backend
, inicialize o package.json
executando:
npm init -y
Ainda na pasta backend
, instale as dependências necessárias:
npm install express mongoose cors multer xlsx dotenv
.env
)Crie o arquivo .env
na pasta backend
com o seguinte conteúdo:
PORT=5000 MONGODB_URI=mongodb://localhost:27017/transportpro
Notas:
Crie os seguintes arquivos na pasta models
com o conteúdo indicado.
const mongoose = require('mongoose'); const TransportadorSchema = new mongoose.Schema({ nome: { type: String, required: true }, estadosSaida: { type: [String], required: true }, estadosEntrega: { type: [String], required: true }, veiculos: { type: Number, default: 0 }, avaliacao: { type: Number, default: 5.0 } }); module.exports = mongoose.model('Transportador', TransportadorSchema);
const mongoose = require('mongoose'); const VeiculoSchema = new mongoose.Schema({ nome: { type: String, required: true }, capacidade: { type: Number, required: true }, status: { type: String, enum: ['Disponível', 'Em serviço'], default: 'Disponível' } }); module.exports = mongoose.model('Veiculo', VeiculoSchema);
const mongoose = require('mongoose'); const ProdutoSchema = new mongoose.Schema({ codigo: { type: String, required: true, unique: true }, altura: { type: Number, required: true }, largura: { type: Number, required: true }, comprimento: { type: Number, required: true }, cubagem: { type: Number, required: true }, peso: { type: Number, required: true } }); module.exports = mongoose.model('Produto', ProdutoSchema);
const mongoose = require('mongoose'); const DeadlineSchema = new mongoose.Schema({ estadoSaida: { type: String, required: true, maxlength: 2 }, estadoDestino: { type: String, required: true, maxlength: 2 }, prazoCapital: { type: Number, required: true }, prazoInterior: { type: Number, required: true } }); module.exports = mongoose.model('Deadline', DeadlineSchema);
const mongoose = require('mongoose'); const EntregaSchema = new mongoose.Schema({ nf: { type: String, required: true, unique: true }, transportadora: { type: String, required: true }, cliente: { type: String, required: true }, dataEntrega: { type: Date, required: true }, status: { type: String, enum: ['Aguardando programação', 'Programada', 'Em trânsito', 'Entregue'], default: 'Aguardando programação' }, programacao: { type: Date }, chaveNF: { type: String, required: true }, produtos: { type: [String], required: true } }); module.exports = mongoose.model('Entrega', EntregaSchema);
Crie os seguintes arquivos na pasta controllers
com o conteúdo indicado.
const Transportador = require('../models/Transportador'); // Criar um novo transportador exports.createTransportador = async (req, res) => { try { const { nome, estadosSaida, estadosEntrega } = req.body; const transportador = new Transportador({ nome, estadosSaida: estadosSaida.split(',').map(e => e.trim()), estadosEntrega: estadosEntrega.split(',').map(e => e.trim()) }); await transportador.save(); res.status(201).json(transportador); } catch (error) { res.status(400).json({ message: error.message }); } }; // Obter todos os transportadores exports.getTransportadores = async (req, res) => { try { const transportadores = await Transportador.find(); res.json(transportadores); } catch (error) { res.status(500).json({ message: error.message }); } }; // Atualizar um transportador exports.updateTransportador = async (req, res) => { try { const { id } = req.params; const { nome, estadosSaida, estadosEntrega, veiculos, avaliacao } = req.body; const transportador = await Transportador.findByIdAndUpdate(id, { nome, estadosSaida: estadosSaida.split(',').map(e => e.trim()), estadosEntrega: estadosEntrega.split(',').map(e => e.trim()), veiculos, avaliacao }, { new: true }); if (!transportador) { return res.status(404).json({ message: 'Transportador não encontrado' }); } res.json(transportador); } catch (error) { res.status(400).json({ message: error.message }); } }; // Deletar um transportador exports.deleteTransportador = async (req, res) => { try { const { id } = req.params; const transportador = await Transportador.findByIdAndDelete(id); if (!transportador) { return res.status(404).json({ message: 'Transportador não encontrado' }); } res.json({ message: 'Transportador deletado com sucesso' }); } catch (error) { res.status(500).json({ message: error.message }); } };
const Veiculo = require('../models/Veiculo'); // Criar um novo veículo exports.createVeiculo = async (req, res) => { try { const { nome, capacidade } = req.body; const veiculo = new Veiculo({ nome, capacidade }); await veiculo.save(); res.status(201).json(veiculo); } catch (error) { res.status(400).json({ message: error.message }); } }; // Obter todos os veículos exports.getVeiculos = async (req, res) => { try { const veiculos = await Veiculo.find(); res.json(veiculos); } catch (error) { res.status(500).json({ message: error.message }); } }; // Atualizar um veículo exports.updateVeiculo = async (req, res) => { try { const { id } = req.params; const { nome, capacidade, status } = req.body; const veiculo = await Veiculo.findByIdAndUpdate(id, { nome, capacidade, status }, { new: true }); if (!veiculo) { return res.status(404).json({ message: 'Veículo não encontrado' }); } res.json(veiculo); } catch (error) { res.status(400).json({ message: error.message }); } }; // Deletar um veículo exports.deleteVeiculo = async (req, res) => { try { const { id } = req.params; const veiculo = await Veiculo.findByIdAndDelete(id); if (!veiculo) { return res.status(404).json({ message: 'Veículo não encontrado' }); } res.json({ message: 'Veículo deletado com sucesso' }); } catch (error) { res.status(500).json({ message: error.message }); } };
const Produto = require('../models/Produto'); // Criar um novo produto exports.createProduto = async (req, res) => { try { const { codigo, altura, largura, comprimento, peso } = req.body; const cubagem = altura * largura * comprimento; const produto = new Produto({ codigo, altura, largura, comprimento, cubagem, peso }); await produto.save(); res.status(201).json(produto); } catch (error) { res.status(400).json({ message: error.message }); } }; // Obter todos os produtos exports.getProdutos = async (req, res) => { try { const produtos = await Produto.find(); res.json(produtos); } catch (error) { res.status(500).json({ message: error.message }); } }; // Atualizar um produto exports.updateProduto = async (req, res) => { try { const { id } = req.params; const { codigo, altura, largura, comprimento, peso } = req.body; const cubagem = altura * largura * comprimento; const produto = await Produto.findByIdAndUpdate(id, { codigo, altura, largura, comprimento, cubagem, peso }, { new: true }); if (!produto) { return res.status(404).json({ message: 'Produto não encontrado' }); } res.json(produto); } catch (error) { res.status(400).json({ message: error.message }); } }; // Deletar um produto exports.deleteProduto = async (req, res) => { try { const { id } = req.params; const produto = await Produto.findByIdAndDelete(id); if (!produto) { return res.status(404).json({ message: 'Produto não encontrado' }); } res.json({ message: 'Produto deletado com sucesso' }); } catch (error) { res.status(500).json({ message: error.message }); } };
const Deadline = require('../models/Deadline'); // Criar um novo deadline exports.createDeadline = async (req, res) => { try { const { estadoSaida, estadoDestino, prazoCapital, prazoInterior } = req.body; const deadline = new Deadline({ estadoSaida, estadoDestino, prazoCapital, prazoInterior }); await deadline.save(); res.status(201).json(deadline); } catch (error) { res.status(400).json({ message: error.message }); } }; // Obter todos os deadlines exports.getDeadlines = async (req, res) => { try { const deadlines = await Deadline.find(); res.json(deadlines); } catch (error) { res.status(500).json({ message: error.message }); } }; // Atualizar um deadline exports.updateDeadline = async (req, res) => { try { const { id } = req.params; const { estadoSaida, estadoDestino, prazoCapital, prazoInterior } = req.body; const deadline = await Deadline.findByIdAndUpdate(id, { estadoSaida, estadoDestino, prazoCapital, prazoInterior }, { new: true }); if (!deadline) { return res.status(404).json({ message: 'Deadline não encontrado' }); } res.json(deadline); } catch (error) { res.status(400).json({ message: error.message }); } }; // Deletar um deadline exports.deleteDeadline = async (req, res) => { try { const { id } = req.params; const deadline = await Deadline.findByIdAndDelete(id); if (!deadline) { return res.status(404).json({ message: 'Deadline não encontrado' }); } res.json({ message: 'Deadline deletado com sucesso' }); } catch (error) { res.status(500).json({ message: error.message }); } };
const Entrega = require('../models/Entrega'); // Criar uma nova entrega exports.createEntrega = async (req, res) => { try { const { nf, transportadora, cliente, dataEntrega, status, programacao, chaveNF, produtos } = req.body; const entrega = new Entrega({ nf, transportadora, cliente, dataEntrega: new Date(dataEntrega), status, programacao: programacao ? new Date(programacao) : null, chaveNF, produtos }); await entrega.save(); res.status(201).json(entrega); } catch (error) { res.status(400).json({ message: error.message }); } }; // Obter todas as entregas exports.getEntregas = async (req, res) => { try { const entregas = await Entrega.find(); res.json(entregas); } catch (error) { res.status(500).json({ message: error.message }); } }; // Atualizar uma entrega exports.updateEntrega = async (req, res) => { try { const { id } = req.params; const { status, programacao } = req.body; const updateData = { status }; if (programacao) { updateData.programacao = new Date(programacao); } else { updateData.programacao = null; } const entrega = await Entrega.findByIdAndUpdate(id, updateData, { new: true }); if (!entrega) { return res.status(404).json({ message: 'Entrega não encontrada' }); } res.json(entrega); } catch (error) { res.status(400).json({ message: error.message }); } }; // Deletar uma entrega exports.deleteEntrega = async (req, res) => { try { const { id } = req.params; const entrega = await Entrega.findByIdAndDelete(id); if (!entrega) { return res.status(404).json({ message: 'Entrega não encontrada' }); } res.json({ message: 'Entrega deletada com sucesso' }); } catch (error) { res.status(500).json({ message: error.message }); } };
const multer = require('multer'); const xlsx = require('xlsx'); const Transportador = require('../models/Transportador'); const Veiculo = require('../models/Veiculo'); const Produto = require('../models/Produto'); const Deadline = require('../models/Deadline'); const fs = require('fs'); const path = require('path'); // Configuração do Multer const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, 'uploads/'); }, filename: function (req, file, cb) { cb(null, Date.now() + '-' + file.originalname); } }); const upload = multer({ storage: storage }); // Função para importar dados do Excel const importarDados = async (tipo, filePath) => { const workbook = xlsx.readFile(filePath); const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; const dados = xlsx.utils.sheet_to_json(worksheet); switch (tipo) { case 'transportadores': await Transportador.insertMany(dados.map(item => ({ nome: item.nome, estadosSaida: item.estadosSaida.split(',').map(e => e.trim()), estadosEntrega: item.estadosEntrega.split(',').map(e => e.trim()) }))); break; case 'veiculos': await Veiculo.insertMany(dados.map(item => ({ nome: item.nome, capacidade: item.capacidade, status: item.status || 'Disponível' }))); break; case 'produtos': await Produto.insertMany(dados.map(item => ({ codigo: item.codigo, altura: item.altura, largura: item.largura, comprimento: item.comprimento, cubagem: item.altura * item.largura * item.comprimento, peso: item.peso }))); break; case 'deadlines': await Deadline.insertMany(dados.map(item => ({ estadoSaida: item.estadoSaida, estadoDestino: item.estadoDestino, prazoCapital: item.prazoCapital, prazoInterior: item.prazoInterior }))); break; default: throw new Error('Tipo de upload inválido'); } }; // Controlador de upload exports.uploadPlanilha = [ upload.single('file'), async (req, res) => { const tipo = req.params.tipo; try { await importarDados(tipo, req.file.path); // Remove o arquivo após a importação fs.unlinkSync(path.resolve(req.file.path)); res.json({ message: 'Dados importados com sucesso!' }); } catch (error) { // Remove o arquivo em caso de erro fs.unlinkSync(path.resolve(req.file.path)); res.status(400).json({ message: error.message }); } } ];
Crie os seguintes arquivos na pasta routes
com o conteúdo indicado.
const express = require('express'); const router = express.Router(); const transportadorController = require('../controllers/transportadorController'); router.post('/', transportadorController.createTransportador); router.get('/', transportadorController.getTransportadores); router.put('/:id', transportadorController.updateTransportador); router.delete('/:id', transportadorController.deleteTransportador); module.exports = router;
const express = require('express'); const router = express.Router(); const veiculoController = require('../controllers/veiculoController'); router.post('/', veiculoController.createVeiculo); router.get('/', veiculoController.getVeiculos); router.put('/:id', veiculoController.updateVeiculo); router.delete('/:id', veiculoController.deleteVeiculo); module.exports = router;
const express = require('express'); const router = express.Router(); const produtoController = require('../controllers/produtoController'); router.post('/', produtoController.createProduto); router.get('/', produtoController.getProdutos); router.put('/:id', produtoController.updateProduto); router.delete('/:id', produtoController.deleteProduto); module.exports = router;
const express = require('express'); const router = express.Router(); const deadlineController = require('../controllers/deadlineController'); router.post('/', deadlineController.createDeadline); router.get('/', deadlineController.getDeadlines); router.put('/:id', deadlineController.updateDeadline); router.delete('/:id', deadlineController.deleteDeadline); module.exports = router;
const express = require('express'); const router = express.Router(); const entregaController = require('../controllers/entregaController'); router.post('/', entregaController.createEntrega); router.get('/', entregaController.getEntregas); router.put('/:id', entregaController.updateEntrega); router.delete('/:id', entregaController.deleteEntrega); module.exports = router;
const express = require('express'); const router = express.Router(); const uploadController = require('../controllers/uploadController'); router.post('/:tipo', uploadController.uploadPlanilha); module.exports = router;
app.js
)Crie o arquivo app.js
na pasta backend
com o seguinte conteúdo:
const express = require('express'); const mongoose = require('mongoose'); const cors = require('cors'); const dotenv = require('dotenv'); const path = require('path'); // Carrega variáveis de ambiente dotenv.config(); const app = express(); // Middleware app.use(cors()); app.use(express.json()); // Conexão com o MongoDB mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true }).then(() => console.log('Conectado ao MongoDB')) .catch(err => console.error('Erro ao conectar ao MongoDB:', err)); // Rotas da API const transportadorRoutes = require('./routes/transportadorRoutes'); const veiculoRoutes = require('./routes/veiculoRoutes'); const produtoRoutes = require('./routes/produtoRoutes'); const deadlineRoutes = require('./routes/deadlineRoutes'); const entregaRoutes = require('./routes/entregaRoutes'); const uploadRoutes = require('./routes/uploadRoutes'); app.use('/api/transportadores', transportadorRoutes); app.use('/api/veiculos', veiculoRoutes); app.use('/api/produtos', produtoRoutes); app.use('/api/deadlines', deadlineRoutes); app.use('/api/entregas', entregaRoutes); app.use('/api/upload', uploadRoutes); // Servindo o frontend app.use(express.static(path.join(__dirname, 'frontend'))); // Rota para servir o index.html app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'frontend', 'index.html')); }); // Inicia o servidor const PORT = process.env.PORT || 5000; app.listen(PORT, () => { console.log(`Servidor rodando na porta ${PORT}`); });
Crie a pasta uploads
dentro de backend
para armazenar os arquivos enviados:
mkdir backend/uploads
Nota: Essa pasta será usada pelo Multer para armazenar temporariamente os arquivos de upload antes de processá-los.
Crie os seguintes arquivos na pasta frontend
com o conteúdo indicado.
<!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Sistema de Gestão de Transportes - TransportPro</title> <link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"> </head> <body> <nav class="navbar"> <div class="app-title">TransportPro</div> <div class="nav-links"> <button class="btn btn-secondary" onclick="showInfoModal()"> <i class="fas fa-info-circle"></i> Informações do Sistema </button> <button class="btn btn-primary" onclick="showCadastroModal()">Cadastros</button> <button class="btn btn-primary" onclick="showConsultaEntregas()">Consulta Entregas</button> </div> </nav> <div class="main-container"> <div class="dashboard"> <div class="card"> <div class="card-title">Alertas de Entrega</div> <div class="alert alert-warning" id="alertaProgramadas" onclick="mostrarDetalhesAlertas('Programadas')"> <!-- Conteúdo atualizado via JavaScript --> </div> <div class="alert alert-success" id="alertaConcluidas" onclick="mostrarDetalhesAlertas('Concluídas')"> <!-- Conteúdo atualizado via JavaScript --> </div> </div> <div class="card"> <div class="card-title">Upload de XML</div> <div class="upload-area" id="dropZone"> <p>Arraste arquivos XML aqui ou clique para selecionar</p> <input type="file" id="fileInput" accept=".xml" multiple style="display: none"> </div> </div> </div> <div class="table-container"> <table> <thead> <tr> <th>NF</th> <th>Transportadora</th> <th>Cliente</th> <th>Data Entrega</th> <th>Status</th> <th>Programação</th> <th>Ações</th> </tr> </thead> <tbody id="entregasTable"> <!-- Dados preenchidos via JavaScript --> </tbody> </table> </div> </div> <!-- Modals --> <!-- Modal de Cadastro --> <div id="cadastroModal" class="modal"> <div class="modal-content"> <span class="close" onclick="closeCadastroModal()">×</span> <h2>Cadastros</h2> <div class="btn-group" style="margin-top: 1rem;"> <h3>Novo Cadastro</h3> <button class="btn btn-primary" onclick="showTransportadorForm()">Cadastrar Transportador</button> <button class="btn btn-primary" onclick="showVeiculoForm()">Cadastrar Veículo</button> <button class="btn btn-primary" onclick="showProdutoForm()">Cadastrar Produto</button> <button class="btn btn-primary" onclick="showDeadlineForm()">Cadastrar Deadline</button> <h3>Upload via Planilha</h3> <button class="btn btn-secondary" onclick="showUploadModal('transportadores')">Upload Transportadores</button> <button class="btn btn-secondary" onclick="showUploadModal('veiculos')">Upload Veículos</button> <button class="btn btn-secondary" onclick="showUploadModal('produtos')">Upload Produtos</button> <button class="btn btn-secondary" onclick="showUploadModal('deadlines')">Upload Deadlines</button> <h3>Consultar Cadastros</h3> <button class="btn btn-secondary" onclick="showConsultaRegistros('transportadores')">Consultar Transportadores</button> <button class="btn btn-secondary" onclick="showConsultaRegistros('veiculos')">Consultar Veículos</button> <button class="btn btn-secondary" onclick="showConsultaRegistros('produtos')">Consultar Produtos</button> <button class="btn btn-secondary" onclick="showConsultaRegistros('deadlines')">Consultar Deadlines</button> </div> </div> </div> <!-- Modal de Informações do Sistema --> <div id="infoModal" class="modal"> <div class="modal-content"> <span class="close" onclick="closeInfoModal()">×</span> <h2>Sistema de Gestão de Transportes - TransportPro</h2> <div style="margin-top: 1rem; line-height: 1.6;"> <p>O <strong>TransportPro</strong> é um sistema completo para gestão de transportes, permitindo o gerenciamento de transportadoras, veículos, produtos e entregas. Funcionalidades incluem:</p> <ul> <li>Cadastro e gestão de transportadoras, veículos, produtos e deadlines.</li> <li>Upload de arquivos XML para importação de entregas.</li> <li>Dashboard com alertas de entregas programadas e concluídas.</li> <li>Consulta avançada de entregas com filtros.</li> <li>Geração e impressão de etiquetas de envio.</li> </ul> <p>Desenvolvido com Node.js, Express e MongoDB no backend, e HTML, CSS e JavaScript no frontend.</p> </div> </div> </div> <!-- Modal de Formulários de Cadastro --> <!-- Transportador --> <div id="transportadorFormModal" class="modal"> <div class="modal-content"> <span class="close" onclick="closeFormModal('transportadorFormModal')">×</span> <h2>Cadastro de Transportador</h2> <form id="transportadorForm" onsubmit="handleTransportadorSubmit(event)"> <div class="form-group"> <label>Nome</label> <input type="text" id="transportadorNome" required> </div> <div class="form-group"> <label>Estados de Saída (separados por vírgula)</label> <input type="text" id="transportadorEstadosSaida" required> </div> <div class="form-group"> <label>Estados de Entrega (separados por vírgula)</label> <input type="text" id="transportadorEstadosEntrega" required> </div> <button type="submit" class="btn btn-success">Cadastrar</button> </form> </div> </div> <!-- Veículo --> <div id="veiculoFormModal" class="modal"> <div class="modal-content"> <span class="close" onclick="closeFormModal('veiculoFormModal')">×</span> <h2>Cadastro de Veículo</h2> <form id="veiculoForm" onsubmit="handleVeiculoSubmit(event)"> <div class="form-group"> <label>Nome</label> <input type="text" id="veiculoNome" required> </div> <div class="form-group"> <label>Capacidade</label> <input type="number" id="veiculoCapacidade" required> </div> <div class="form-group"> <label>Status</label> <select id="veiculoStatus" required> <option value="Disponível">Disponível</option> <option value="Em serviço">Em serviço</option> </select> </div> <button type="submit" class="btn btn-success">Cadastrar</button> </form> </div> </div> <!-- Produto --> <div id="produtoFormModal" class="modal"> <div class="modal-content"> <span class="close" onclick="closeFormModal('produtoFormModal')">×</span> <h2>Cadastro de Produto</h2> <form id="produtoForm" onsubmit="handleProdutoSubmit(event)"> <div class="form-group"> <label>Código</label> <input type="text" id="produtoCodigo" required> </div> <div class="form-group"> <label>Altura</label> <input type="number" id="produtoAltura" step="0.01" required> </div> <div class="form-group"> <label>Largura</label> <input type="number" id="produtoLargura" step="0.01" required> </div> <div class="form-group"> <label>Comprimento</label> <input type="number" id="produtoComprimento" step="0.01" required> </div> <div class="form-group"> <label>Peso</label> <input type="number" id="produtoPeso" step="0.01" required> </div> <button type="submit" class="btn btn-success">Cadastrar</button> </form> </div> </div> <!-- Deadline --> <div id="deadlineFormModal" class="modal"> <div class="modal-content"> <span class="close" onclick="closeFormModal('deadlineFormModal')">×</span> <h2>Cadastro de Deadline</h2> <form id="deadlineForm" onsubmit="handleDeadlineSubmit(event)"> <div class="form-group"> <label>Estado de Saída (sigla, ex: SP)</label> <input type="text" id="deadlineEstadoSaida" maxlength="2" required> </div> <div class="form-group"> <label>Estado de Destino (sigla, ex: MG)</label> <input type="text" id="deadlineEstadoDestino" maxlength="2" required> </div> <div class="form-group"> <label>Prazo Capital (dias)</label> <input type="number" id="deadlinePrazoCapital" required> </div> <div class="form-group"> <label>Prazo Interior (dias)</label> <input type="number" id="deadlinePrazoInterior" required> </div> <button type="submit" class="btn btn-success">Cadastrar</button> </form> </div> </div> <!-- Modal de Upload via Planilha --> <div id="uploadModal" class="modal"> <div class="modal-content"> <span class="close" onclick="closeUploadModal()">×</span> <h2>Upload de Planilha - <span id="uploadTipo"></span></h2> <input type="file" id="uploadFileInput" accept=".xlsx, .xls, .csv"> <button class="btn btn-success" onclick="handleUpload()">Upload</button> </div> </div> <!-- Modal de Consulta de Registros --> <div id="consultaRegistrosModal" class="modal"> <div class="modal-content"> <span class="close" onclick="closeConsultaRegistrosModal()">×</span> <h2>Consulta de <span id="consultaTipo"></span></h2> <table> <thead> <tr> <!-- Cabeçalhos dinâmicos --> </tr> </thead> <tbody id="consultaRegistrosBody"> <!-- Dados preenchidos via JavaScript --> </tbody> </table> </div> </div> <!-- Modal de Etiqueta de Impressão --> <div id="etiquetaModal" class="etiqueta-modal"> <div class="etiqueta-modal-content"> <span class="etiqueta-close" onclick="closeEtiquetaModal()">×</span> <div id="etiquetaContent" class="etiqueta-content"> <!-- Conteúdo da etiqueta será inserido aqui por JavaScript --> </div> <button class="btn btn-primary" onclick="window.print()">Imprimir Etiqueta</button> </div> </div> <script src="script.js"></script> </body> </html>
/* Variáveis CSS */ :root { --primary: #007bff; --secondary: #6c757d; --success: #28a745; --warning: #ffc107; --danger: #dc3545; --light: #f8f9fa; --dark: #343a40; --font-family: 'Arial, sans-serif'; } /* Reset */ * { box-sizing: border-box; margin: 0; padding: 0; } /* Body */ body { font-family: var(--font-family); background-color: var(--light); color: var(--dark); } /* Navbar */ .navbar { display: flex; justify-content: space-between; align-items: center; background-color: var(--primary); color: white; padding: 1rem 2rem; } .nav-links .btn { margin-left: 0.5rem; } /* Main Container */ .main-container { display: grid; grid-template-columns: 1fr 2fr; gap: 1rem; padding: 1rem 2rem; } /* Dashboard */ .dashboard { display: grid; grid-template-rows: repeat(2, 1fr); gap: 1rem; } /* Cards */ .card { background-color: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } .card-title { font-size: 1.2rem; margin-bottom: 0.5rem; } /* Alerts */ .alert { padding: 1rem; border-radius: 4px; cursor: pointer; margin-bottom: 0.5rem; } .alert-warning { background-color: var(--warning); color: #856404; } .alert-success { background-color: var(--success); color: white; } /* Upload Area */ .upload-area { border: 2px dashed var(--secondary); padding: 2rem; text-align: center; border-radius: 8px; cursor: pointer; transition: background-color 0.3s; } .upload-area.highlight { background-color: #e9ecef; } /* Table */ .table-container { overflow-x: auto; } table { width: 100%; border-collapse: collapse; background-color: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } th, td { padding: 0.75rem 1rem; text-align: left; border-bottom: 1px solid #dee2e6; } th { background-color: var(--primary); color: white; } tr:hover { background-color: #f1f1f1; } /* Status Badge */ .status-badge { padding: 0.25rem 0.5rem; border-radius: 4px; color: white; cursor: pointer; position: relative; } .status-badge.agurandando-programacao { background-color: var(--warning); } .status-badge.programada { background-color: var(--primary); } .status-badge.em-transito { background-color: var(--secondary); } .status-badge.entregue { background-color: var(--success); } .status-dropdown { display: none; position: absolute; top: 100%; left: 0; background-color: white; border: 1px solid #dee2e6; box-shadow: 0 2px 5px rgba(0,0,0,0.1); z-index: 1000; width: 200px; } .status-dropdown button { width: 100%; padding: 0.5rem 1rem; border: none; background: none; text-align: left; cursor: pointer; } .status-dropdown button:hover { background-color: #f1f1f1; } /* Modals */ .modal { display: none; position: fixed; z-index: 2000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4); } .modal-content { background-color: #fefefe; margin: 5% auto; padding: 2rem; border: 1px solid #888; width: 80%; border-radius: 8px; position: relative; } .close { color: #aaa; position: absolute; top: 1rem; right: 1rem; font-size: 1.5rem; font-weight: bold; cursor: pointer; } .close:hover, .close:focus { color: black; } /* Formulários */ .form-group { margin-bottom: 1rem; } .form-group label { display: block; margin-bottom: 0.5rem; } .form-group input, .form-group select { width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px; } .btn { padding: 0.5rem 1rem; border: none; border-radius: 4px; cursor: pointer; } .btn-primary { background-color: var(--primary); color: white; } .btn-secondary { background-color: var(--secondary); color: white; } .btn-success { background-color: var(--success); color: white; } .btn-warning { background-color: var(--warning); color: white; } .btn-danger { background-color: var(--danger); color: white; } /* Etiqueta Modal */ .etiqueta-modal { display: none; position: fixed; z-index: 3000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.6); } .etiqueta-modal-content { background-color: #fefefe; margin: 10% auto; padding: 2rem; border: 1px solid #888; width: 50%; border-radius: 8px; position: relative; } .etiqueta-close { color: #aaa; position: absolute; top: 1rem; right: 1rem; font-size: 1.5rem; font-weight: bold; cursor: pointer; } .etiqueta-close:hover, .etiqueta-close:focus { color: black; } .etiqueta-header { font-size: 1.5rem; text-align: center; margin-bottom: 1rem; } .etiqueta-item { margin-bottom: 0.5rem; } /* Responsividade */ @media (max-width: 768px) { .main-container { grid-template-columns: 1fr; } .modal-content, .etiqueta-modal-content { width: 90%; } }
// URL do backend const API_URL = 'http://localhost:5000/api'; // Funções para interagir com a API async function fetchEntregas() { const response = await fetch(`${API_URL}/entregas`); const data = await response.json(); return data; } async function loadEntregasTable(filteredData = null) { const tbody = document.getElementById('entregasTable'); const entregas = filteredData || await fetchEntregas(); tbody.innerHTML = ''; entregas.forEach(entrega => { const tr = document.createElement('tr'); tr.innerHTML = ` <td>${entrega.nf}</td> <td>${entrega.transportadora}</td> <td>${entrega.cliente}</td> <td>${new Date(entrega.dataEntrega).toLocaleDateString('pt-BR')}</td> <td> <span class="status-badge ${entrega.status.toLowerCase().replace(/ /g, '-')}">${entrega.status}</span> <div class="status-dropdown"> <button onclick="updateEntregaStatus('${entrega._id}', 'Aguardando programação')">Aguardando programação</button> <button onclick="updateEntregaStatus('${entrega._id}', 'Programada')">Programada</button> <button onclick="updateEntregaStatus('${entrega._id}', 'Em trânsito')">Em trânsito</button> <button onclick="updateEntregaStatus('${entrega._id}', 'Entregue')">Entregue</button> </div> </td> <td>${entrega.programacao ? new Date(entrega.programacao).toLocaleDateString('pt-BR') : 'Pendente'}</td> <td> <button class="btn btn-secondary" onclick="openEtiquetaModal('${entrega.nf}')">Imprimir Etiqueta</button> <button class="btn btn-danger" onclick="deleteEntrega('${entrega._id}')">Deletar</button> </td> `; tbody.appendChild(tr); }); attachStatusBadgeListeners(); } async function updateEntregaStatus(id, newStatus) { try { const response = await fetch(`${API_URL}/entregas/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: newStatus }) }); if (response.ok) { alert('Status da entrega atualizado com sucesso!'); loadEntregasTable(); updateAlertCounts(); } else { const error = await response.json(); alert(`Erro: ${error.message}`); } } catch (error) { console.error(error); alert('Erro ao atualizar o status da entrega.'); } } async function deleteEntrega(id) { if (!confirm('Tem certeza que deseja deletar esta entrega?')) return; try { const response = await fetch(`${API_URL}/entregas/${id}`, { method: 'DELETE' }); if (response.ok) { alert('Entrega deletada com sucesso!'); loadEntregasTable(); updateAlertCounts(); } else { const error = await response.json(); alert(`Erro: ${error.message}`); } } catch (error) { console.error(error); alert('Erro ao deletar a entrega.'); } } // Funções para carregamento inicial document.addEventListener('DOMContentLoaded', () => { setupDragAndDrop(); loadEntregasTable(); updateAlertCounts(); }); // Funções existentes para drag and drop e upload de XML // Atualize a função processXMLData para enviar dados para o backend async function processXMLData(xmlData) { try { const shipmentData = { nf: xmlData.querySelector('nNF')?.textContent || '', transportadora: xmlData.querySelector('emit > xNome')?.textContent || '', cliente: xmlData.querySelector('dest > xNome')?.textContent || '', dataEntrega: xmlData.querySelector('ide > dEmi')?.textContent || '', status: 'Aguardando programação', programacao: null, chaveNF: xmlData.querySelector('chNFe')?.textContent || '', produtos: Array.from(xmlData.querySelectorAll('det')).map(det => det.querySelector('prod > cProd')?.textContent || '') }; // Enviar para o backend const response = await fetch(`${API_URL}/entregas`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(shipmentData) }); if (response.ok) { alert('Entrega cadastrada com sucesso!'); loadEntregasTable(); updateAlertCounts(); } else { const error = await response.json(); alert(`Erro: ${error.message}`); } } catch (error) { console.error('Erro ao processar XML:', error); alert('Erro ao processar o arquivo XML. Verifique se o formato está correto.'); } } // Funções para atualizar alertas async function updateAlertCounts() { const entregas = await fetchEntregas(); const hoje = new Date().toLocaleDateString('pt-BR'); const programadasHoje = entregas.filter(e => new Date(e.dataEntrega).toLocaleDateString('pt-BR') === hoje && e.status === 'Programada').length; const concluidasHoje = entregas.filter(e => new Date(e.dataEntrega).toLocaleDateString('pt-BR') === hoje && e.status === 'Entregue').length; document.getElementById('alertaProgramadas').textContent = `${programadasHoje} entregas programadas para hoje`; document.getElementById('alertaConcluidas').textContent = `${concluidasHoje} entregas concluídas hoje`; } // Funções para imprimir etiqueta async function openEtiquetaModal(nf) { // Obter detalhes da entrega const response = await fetch(`${API_URL}/entregas`); const entregas = await response.json(); const entrega = entregas.find(e => e.nf === nf); if (entrega) { const etiquetaContent = document.getElementById('etiquetaContent'); etiquetaContent.innerHTML = ` <div class="etiqueta-header">ETIQUETA DE ENVIO</div> <div class="etiqueta-item"><strong>Número da NF:</strong> ${entrega.nf.padStart(9, '0')}</div> <div class="etiqueta-item"><strong>Chave da nota fiscal:</strong> ${entrega.chaveNF}</div> <div class="etiqueta-item"><strong>Transportadora:</strong> ${entrega.transportadora}</div> <div class="etiqueta-item"><strong>Cliente:</strong> ${entrega.cliente}</div> <div class="etiqueta-item"><strong>Data limite para entrega:</strong> ${new Date(entrega.dataEntrega).toLocaleDateString('pt-BR')}</div> <div class="etiqueta-item"><strong>Produtos:</strong></div> <div class="etiqueta-item"> ${entrega.produtos.map(codigo => `<p>${codigo}</p>`).join('')} </div> <div> <span class="status-badge ${entrega.status.toLowerCase().replace(/ /g, '-')}">${entrega.status}</span> </div> `; document.getElementById('etiquetaModal').style.display = 'block'; } else { alert('Entrega não encontrada.'); } } function closeEtiquetaModal() { document.getElementById('etiquetaModal').style.display = 'none'; } // Funções para drag and drop e upload de arquivos function setupDragAndDrop() { const dropZone = document.getElementById('dropZone'); const fileInput = document.getElementById('fileInput'); ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation(); }); }); ['dragenter', 'dragover'].forEach(eventName => { dropZone.addEventListener(eventName, () => { dropZone.classList.add('highlight'); }); }); ['dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, () => { dropZone.classList.remove('highlight'); }); }); dropZone.addEventListener('drop', e => { const files = e.dataTransfer.files; handleFiles(files); }); fileInput.addEventListener('change', e => { const files = e.target.files; handleFiles(files); }); dropZone.addEventListener('click', () => { fileInput.click(); }); } async function handleFiles(files) { [...files].forEach(file => { if (file.type === 'text/xml' || file.name.endsWith('.xml')) { const reader = new FileReader(); reader.onload = e => { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(e.target.result, 'text/xml'); processXMLData(xmlDoc); }; reader.readAsText(file); } else { alert('Por favor, selecione apenas arquivos XML.'); } }); } // Funções para abertura e fechamento de modais function showInfoModal() { document.getElementById('infoModal').style.display = 'block'; } function closeInfoModal() { document.getElementById('infoModal').style.display = 'none'; } function showCadastroModal() { document.getElementById('cadastroModal').style.display = 'block'; } function closeCadastroModal() { document.getElementById('cadastroModal').style.display = 'none'; } function showUploadModal(tipo) { document.getElementById('uploadTipo').textContent = capitalizeFirstLetter(tipo); document.getElementById('uploadModal').style.display = 'block'; } function closeUploadModal() { document.getElementById('uploadModal').style.display = 'none'; document.getElementById('uploadFileInput').value = ''; } function showConsultaRegistros(tipo) { document.getElementById('consultaTipo').textContent = capitalizeFirstLetter(tipo); fetch(`${API_URL}/${tipo}`) .then(response => response.json()) .then(data => { populateConsultaRegistros(tipo, data); document.getElementById('consultaRegistrosModal').style.display = 'block'; }) .catch(error => { console.error(error); alert('Erro ao buscar registros.'); }); } function closeConsultaRegistrosModal() { document.getElementById('consultaRegistrosModal').style.display = 'none'; } // Funções para capitalizar a primeira letra function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); } // Funções para consultar registros function populateConsultaRegistros(tipo, data) { const thead = document.querySelector('#consultaRegistrosModal table thead tr'); const tbody = document.getElementById('consultaRegistrosBody'); thead.innerHTML = ''; tbody.innerHTML = ''; if (data.length === 0) { tbody.innerHTML = '<tr><td colspan="100%">Nenhum registro encontrado.</td></tr>'; return; } // Define os cabeçalhos com base nas chaves do primeiro objeto const headers = Object.keys(data[0]); headers.forEach(header => { const th = document.createElement('th'); th.textContent = capitalizeFirstLetter(header); thead.appendChild(th); }); const actionTh = document.createElement('th'); actionTh.textContent = 'Ações'; thead.appendChild(actionTh); // Preenche as linhas data.forEach(item => { const tr = document.createElement('tr'); headers.forEach(header => { const td = document.createElement('td'); td.textContent = item[header]; tr.appendChild(td); }); const actionTd = document.createElement('td'); actionTd.innerHTML = `<button class="btn btn-danger" onclick="deleteRegistro('${tipo}', '${item._id}')">Deletar</button>`; tr.appendChild(actionTd); tbody.appendChild(tr); }); } // Função para deletar registro function deleteRegistro(tipo, id) { if (!confirm('Tem certeza que deseja deletar este registro?')) return; fetch(`${API_URL}/${tipo}/${id}`, { method: 'DELETE' }) .then(response => response.json()) .then(data => { alert(data.message); showConsultaRegistros(tipo); }) .catch(error => { console.error(error); alert('Erro ao deletar o registro.'); }); } // Funções para abertura de formulários de cadastro function showTransportadorForm() { document.getElementById('transportadorFormModal').style.display = 'block'; closeCadastroModal(); } function showVeiculoForm() { document.getElementById('veiculoFormModal').style.display = 'block'; closeCadastroModal(); } function showProdutoForm() { document.getElementById('produtoFormModal').style.display = 'block'; closeCadastroModal(); } function showDeadlineForm() { document.getElementById('deadlineFormModal').style.display = 'block'; closeCadastroModal(); } // Funções para fechar formulários de cadastro function closeFormModal(modalId) { document.getElementById(modalId).style.display = 'none'; showCadastroModal(); } // Funções de submissão de formulários de cadastro async function handleTransportadorSubmit(event) { event.preventDefault(); const nome = document.getElementById('transportadorNome').value; const estadosSaida = document.getElementById('transportadorEstadosSaida').value; const estadosEntrega = document.getElementById('transportadorEstadosEntrega').value; try { const response = await fetch(`${API_URL}/transportadores`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nome, estadosSaida, estadosEntrega }) }); if (response.ok) { alert('Transportadora cadastrada com sucesso!'); closeFormModal('transportadorFormModal'); loadEntregasTable(); } else { const error = await response.json(); alert(`Erro: ${error.message}`); } } catch (error) { console.error(error); alert('Erro ao cadastrar transportadora.'); } } async function handleVeiculoSubmit(event) { event.preventDefault(); const nome = document.getElementById('veiculoNome').value; const capacidade = document.getElementById('veiculoCapacidade').value; const status = document.getElementById('veiculoStatus').value; try { const response = await fetch(`${API_URL}/veiculos`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nome, capacidade, status }) }); if (response.ok) { alert('Veículo cadastrado com sucesso!'); closeFormModal('veiculoFormModal'); loadEntregasTable(); } else { const error = await response.json(); alert(`Erro: ${error.message}`); } } catch (error) { console.error(error); alert('Erro ao cadastrar veículo.'); } } async function handleProdutoSubmit(event) { event.preventDefault(); const codigo = document.getElementById('produtoCodigo').value; const altura = parseFloat(document.getElementById('produtoAltura').value); const largura = parseFloat(document.getElementById('produtoLargura').value); const comprimento = parseFloat(document.getElementById('produtoComprimento').value); const peso = parseFloat(document.getElementById('produtoPeso').value); try { const response = await fetch(`${API_URL}/produtos`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ codigo, altura, largura, comprimento, peso }) }); if (response.ok) { alert('Produto cadastrado com sucesso!'); closeFormModal('produtoFormModal'); loadEntregasTable(); } else { const error = await response.json(); alert(`Erro: ${error.message}`); } } catch (error) { console.error(error); alert('Erro ao cadastrar produto.'); } } async function handleDeadlineSubmit(event) { event.preventDefault(); const estadoSaida = document.getElementById('deadlineEstadoSaida').value.toUpperCase(); const estadoDestino = document.getElementById('deadlineEstadoDestino').value.toUpperCase(); const prazoCapital = document.getElementById('deadlinePrazoCapital').value; const prazoInterior = document.getElementById('deadlinePrazoInterior').value; try { const response = await fetch(`${API_URL}/deadlines`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ estadoSaida, estadoDestino, prazoCapital, prazoInterior }) }); if (response.ok) { alert('Deadline cadastrado com sucesso!'); closeFormModal('deadlineFormModal'); loadEntregasTable(); } else { const error = await response.json(); alert(`Erro: ${error.message}`); } } catch (error) { console.error(error); alert('Erro ao cadastrar deadline.'); } } // Funções para geração de etiqueta // Já implementadas anteriormente // Funções para upload de planilha async function handleUpload() { const tipo = document.getElementById('uploadTipo').textContent.toLowerCase(); const fileInput = document.getElementById('uploadFileInput'); const file = fileInput.files[0]; if (!file) { alert('Por favor, selecione um arquivo.'); return; } const formData = new FormData(); formData.append('file', file); try { const response = await fetch(`${API_URL}/upload/${tipo}`, { method: 'POST', body: formData }); if (response.ok) { const result = await response.json(); alert(`${capitalizeFirstLetter(tipo)} importados com sucesso!`); closeUploadModal(); loadEntregasTable(); } else { const error = await response.json(); alert(`Erro: ${error.message}`); } } catch (error) { console.error(error); alert('Erro ao fazer upload da planilha.'); } } // Funções para status badge function attachStatusBadgeListeners() { document.querySelectorAll('.status-badge').forEach(badge => { badge.onclick = function (event) { const dropdown = this.nextElementSibling; dropdown.classList.toggle('show'); event.stopPropagation(); }; }); document.addEventListener('click', () => { document.querySelectorAll('.status-dropdown').forEach(dropdown => { dropdown.classList.remove('show'); }); }); } // Funções para consulta de entregas function mostrarDetalhesAlertas(tipo) { // Implementar conforme necessidade alert(`Detalhes das entregas ${tipo}`); }
O frontend está configurado para interagir com o backend via API RESTful. Ele permite:
Certifique-se de que o MongoDB está rodando.
No terminal, inicie o MongoDB (dependendo da sua instalação):
mongod
Inicie o servidor backend.
Navegue até a pasta backend
e execute:
node app.js
Nota: Se preferir reiniciar automaticamente em caso de mudanças, instale o nodemon globalmente e use-o para iniciar o servidor:
npm install -g nodemon nodemon app.js
Você deverá ver as mensagens:
Conectado ao MongoDB
Servidor rodando na porta 5000
O frontend está integrado ao backend e será servido automaticamente pelo Express.
Acesse o frontend no navegador:
Abra o seu navegador e vá para:
http://localhost:5000
Você deverá ver a interface do TransportPro.
Cadastre Transportadoras, Veículos, Produtos e Deadlines:
Upload de Planilhas Excel:
Transportadores:
nome | estadosSaida | estadosEntrega |
---|---|---|
TransBrasil | SP,RJ | MG,ES |
... | ... | ... |
Veículos:
nome | capacidade | status |
---|---|---|
Caminhão | 1000 | Disponível |
... | ... | ... |
Produtos:
codigo | altura | largura | comprimento | peso |
---|---|---|---|---|
P001 | 10 | 20 | 30 | 5 |
... | ... | ... | ... | ... |
Deadlines:
estadoSaida | estadoDestino | prazoCapital | prazoInterior |
---|---|---|---|
SP | MG | 5 | 7 |
... | ... | ... | ... |
Importação de Entregas via XML:
nNF
, chNFe
, emit > xNome
, dest > xNome
, ide > dEmi
, e det > prod > cProd
.Visualização e Gestão de Entregas:
Consulta de Cadastros:
Com essa estrutura, o TransportPro está pronto para uso. No entanto, há várias melhorias que você pode considerar para aprimorar ainda mais o sistema:
Autenticação e Autorização:
Validações Mais Robustes:
Melhorias na Interface do Usuário:
Documentação da API:
Deploy da Aplicação:
Testes Automatizados:
Integração com Outros Sistemas:
Caso precise de ajuda em alguma dessas etapas adicionais ou tenha dúvidas específicas sobre o código ou a configuração, sinta-se à vontade para perguntar!
Para facilitar, abaixo está um resumo de todos os arquivos e seus conteúdos. Copie e cole cada trecho de código nos respectivos arquivos conforme a estrutura mencionada.
Após a execução de npm init -y
e instalação das dependências, seu package.json
deve se parecer com isto:
{ "name": "transportpro-backend", "version": "1.0.0", "description": "Backend para o sistema de gestão de transportes TransportPro", "main": "app.js", "scripts": { "start": "node app.js", "dev": "nodemon app.js" }, "keywords": [], "author": "Seu Nome", "license": "ISC", "dependencies": { "cors": "^2.8.5", "dotenv": "^16.0.3", "express": "^4.18.2", "mongoose": "^7.2.2", "multer": "^1.4.5-lts.1", "xlsx": "^0.18.5" } }
Notas:
start
: Inicia o servidor usando node
.dev
: Inicia o servidor usando nodemon
para reiniciar automaticamente em caso de mudanças.[Consulte o conteúdo acima]
[Consulte o conteúdo acima]
[Consulte o conteúdo acima]
[Consulte o conteúdo acima]
Cada um com o conteúdo conforme listado acima.
Cada um com o conteúdo conforme listado acima.
Cada um com o conteúdo conforme listado acima.
Nota: O Multer armazenará temporariamente os arquivos enviados aqui antes de processá-los e removê-los.
Crie um arquivo README.md
na raiz do projeto para documentar o sistema. Abaixo está um exemplo básico:
# TransportPro **TransportPro** é um sistema completo para gestão de transportes, permitindo o gerenciamento de transportadoras, veículos, produtos e entregas. Funcionalidades incluem: - Cadastro e gestão de transportadoras, veículos, produtos e deadlines. - Upload de arquivos XML e planilhas Excel para importação de entregas e cadastros. - Dashboard com alertas de entregas programadas e concluídas. - Consulta avançada de entregas com filtros. - Geração e impressão de etiquetas de envio. ## **Tecnologias Utilizadas** - **Backend:** Node.js, Express, MongoDB, Mongoose, Multer, XLSX - **Frontend:** HTML, CSS, JavaScript ## **Configuração e Execução** ### **1. Backend** 1. **Instale as dependências:** ```bash npm install
Configure as variáveis de ambiente:
Crie um arquivo .env
na pasta backend
com o seguinte conteúdo:
PORT=5000 MONG