DBE S04: cadastro e listagem de instrumentos musicais
This commit is contained in:
commit
bcf1a6d218
17 files changed
+2152
No files matched your search
@@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
.env
|
||||
npm-debug.log*
|
||||
*.log
|
||||
.DS_Store
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Anderson Valadares Pinto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,149 @@
|
||||
# DBE-S04 - Cadastro e Listagem de Instrumentos Musicais
|
||||
|
||||
**Aluno:** Anderson Valadares Pinto (20251PL.TSIAD0018) [SLS]
|
||||
|
||||
Aplicação web desenvolvida para a disciplina de **Desenvolvimento Back-End (DBE)** - Semana 04. O projeto demonstra o cadastro e a listagem de instrumentos musicais em uma loja, utilizando **Node.js**, **Express**, **EJS**, **MongoDB** (com Mongoose) e estilização com **Bootstrap 5**.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Tecnologias Utilizadas
|
||||
|
||||
- **Node.js**: Ambiente de execução JavaScript no servidor com suporte a **ES Modules** (`"type": "module"`).
|
||||
- **Express.js**: Framework minimalista para criação de servidores HTTP e roteamento dinâmico.
|
||||
- **EJS (Embedded JavaScript)**: Mecanismo de templates para renderização dinâmica no lado do servidor (SSR).
|
||||
- **MongoDB + Mongoose**: Banco de dados orientado a documentos e modelagem dos instrumentos.
|
||||
- **Bootstrap 5 & Bootstrap Icons (CDN)**: Framework CSS responsivo e ícones para uma interface moderna e acessível.
|
||||
|
||||
---
|
||||
|
||||
## 🎸 Instrumentos de Teste
|
||||
|
||||
O script `seed.js` cadastra seis instrumentos, atendendo às orientações da atividade:
|
||||
|
||||
| Nome | Fabricante | Preço | Tipo |
|
||||
|---|---|---|---|
|
||||
| Guitarra Elétrica Stratocaster | Fender | R$ 5.490,00 | Cordas |
|
||||
| Guitarra Acústica Folk | Yamaha | R$ 2.190,00 | Cordas |
|
||||
| Baixo Elétrico SR300 | Ibanez | R$ 3.890,00 | Cordas |
|
||||
| Bateria Acústica Roadshow | Pearl | R$ 7.290,00 | Percussão |
|
||||
| Teclado Digital Juno-DS61 | Roland | R$ 4.590,00 | Teclas |
|
||||
| Saxofone Alto YAS-280 | Yamaha | R$ 6.290,00 | Sopro |
|
||||
|
||||
Há três instrumentos do tipo **Cordas**, preços diferentes e nomes que permitem testar uma pesquisa por "Guitarra", "Yamaha" ou "Elétrica" posteriormente.
|
||||
|
||||
---
|
||||
|
||||
## 📌 Rotas e Endpoints
|
||||
|
||||
| Método | Rota | Descrição |
|
||||
|---|---|---|
|
||||
| `GET` | `/` | Redireciona para a listagem de instrumentos. |
|
||||
| `GET` | `/instrumentos` | Lista todos os instrumentos cadastrados, com nome, fabricante, tipo e preço. |
|
||||
| `GET` | `/instrumentos/add` | Exibe o formulário de cadastro de um novo instrumento. |
|
||||
| `POST` | `/instrumentos` | Recebe os dados do formulário, grava no MongoDB e exibe a confirmação. |
|
||||
|
||||
### Exemplos de URLs para Testes:
|
||||
- `http://localhost:3000/instrumentos` — Listagem completa do estoque
|
||||
- `http://localhost:3000/instrumentos/add` — Formulário de cadastro
|
||||
- `http://localhost:3000/naoexiste` — Resposta de página não encontrada (404)
|
||||
|
||||
---
|
||||
|
||||
## 📂 Estrutura do Projeto
|
||||
|
||||
```
|
||||
DBE-S04/
|
||||
├── models/
|
||||
│ └── Instrumento.js # Schema e validações do instrumento
|
||||
├── routes/
|
||||
│ └── instrumento.js # Rotas de cadastro e listagem
|
||||
├── views/
|
||||
│ ├── instrumento/
|
||||
│ │ ├── add.ejs # Formulário de cadastro
|
||||
│ │ ├── addok.ejs # Confirmação do cadastro
|
||||
│ │ └── lst.ejs # Listagem tabular com loop EJS
|
||||
│ └── erro.ejs # Páginas de erro 404 e 500
|
||||
├── .gitignore
|
||||
├── app.js # Servidor Express, EJS e conexão com o MongoDB
|
||||
├── seed.js # Popula o banco com instrumentos de teste
|
||||
├── package.json # Configurações do projeto e dependências
|
||||
└── README.md # Documentação do projeto
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Como Executar o Projeto Localmente
|
||||
|
||||
Pré-requisitos: **Node.js 18+** e **MongoDB** escutando em `127.0.0.1:27017`.
|
||||
|
||||
1. **Clone o repositório:**
|
||||
```bash
|
||||
git clone https://gitea.tsiad.com.br/anderson/DBE-S04.git
|
||||
cd DBE-S04
|
||||
```
|
||||
|
||||
2. **Instale as dependências:**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. **Popule o banco com os instrumentos de teste (opcional):**
|
||||
```bash
|
||||
npm run seed
|
||||
```
|
||||
|
||||
4. **Inicie a aplicação:**
|
||||
- Modo padrão:
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
- Modo de desenvolvimento (watch mode):
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
5. **Acesse no navegador:**
|
||||
[http://localhost:3000/instrumentos](http://localhost:3000/instrumentos)
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuração
|
||||
|
||||
As variáveis de ambiente são opcionais:
|
||||
|
||||
| Variável | Padrão |
|
||||
|---|---|
|
||||
| `PORT` | `3000` |
|
||||
| `MONGODB_URI` | `mongodb://127.0.0.1:27017/loja_instrumentos` |
|
||||
|
||||
Exemplo:
|
||||
|
||||
```bash
|
||||
MONGODB_URI="mongodb://127.0.0.1:27017/loja_instrumentos" PORT=3000 npm start
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗃️ Modelo `Instrumento`
|
||||
|
||||
| Campo | Tipo | Regras |
|
||||
|---|---|---|
|
||||
| `nome` | String | obrigatório, até 120 caracteres |
|
||||
| `fabricante` | String | obrigatório, até 120 caracteres |
|
||||
| `preco` | Number | obrigatório, maior ou igual a zero |
|
||||
| `tipo` | String | obrigatório, um dos tipos da lista |
|
||||
|
||||
Tipos aceitos: Cordas, Percussão, Teclas, Sopro, Áudio e Acessórios.
|
||||
|
||||
---
|
||||
|
||||
## 📸 Telas
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
A listagem também funciona em telas pequenas (a tabela rola na horizontal):
|
||||
[docs/listagem-mobile.png](docs/listagem-mobile.png).
|
||||
@@ -0,0 +1,68 @@
|
||||
// Anderson Valadares Pinto (20251PL.TSIAD0018) [SLS]
|
||||
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import express from 'express';
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
import instrumentoRoutes from './routes/instrumento.js';
|
||||
|
||||
// Configuração para resolução de caminhos com ES Modules
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const app = express();
|
||||
const PORT = Number(process.env.PORT) || 3000;
|
||||
const MONGODB_URI =
|
||||
process.env.MONGODB_URI || 'mongodb://127.0.0.1:27017/loja_instrumentos';
|
||||
|
||||
// Configuração do mecanismo de visualização EJS
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// Funções de formatação disponíveis em todos os templates.
|
||||
app.locals.formatarPreco = (valor) =>
|
||||
new Intl.NumberFormat('pt-BR', {
|
||||
style: 'currency',
|
||||
currency: 'BRL',
|
||||
}).format(Number(valor) || 0);
|
||||
|
||||
app.use('/', instrumentoRoutes);
|
||||
|
||||
// Página não encontrada.
|
||||
app.use((req, res) => {
|
||||
res.status(404).render('erro', {
|
||||
titulo: 'Página Não Encontrada',
|
||||
codigo: 404,
|
||||
mensagem: 'O endereço que você tentou abrir não existe.',
|
||||
});
|
||||
});
|
||||
|
||||
// Qualquer erro inesperado cai aqui, em vez de derrubar o servidor.
|
||||
app.use((erro, req, res, next) => {
|
||||
console.error(erro);
|
||||
res.status(500).render('erro', {
|
||||
titulo: 'Erro Interno',
|
||||
codigo: 500,
|
||||
mensagem: 'Algo deu errado ao processar a solicitação. Tente novamente.',
|
||||
});
|
||||
});
|
||||
|
||||
async function iniciar() {
|
||||
await mongoose.connect(MONGODB_URI);
|
||||
console.log(`Conectado ao MongoDB em ${MONGODB_URI}`);
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(
|
||||
`Servidor rodando com sucesso na porta ${PORT}: http://localhost:${PORT}/instrumentos`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
iniciar().catch((erro) => {
|
||||
console.error('Não foi possível iniciar a aplicação:', erro.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
@@ -0,0 +1,52 @@
|
||||
// Anderson Valadares Pinto (20251PL.TSIAD0018) [SLS]
|
||||
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
// Tipos aceitos no formulário. A lista também alimenta o <select> de add.ejs.
|
||||
export const TIPOS = [
|
||||
'Cordas',
|
||||
'Percussão',
|
||||
'Teclas',
|
||||
'Sopro',
|
||||
'Áudio',
|
||||
'Acessórios',
|
||||
];
|
||||
|
||||
const instrumentoSchema = new mongoose.Schema(
|
||||
{
|
||||
nome: {
|
||||
type: String,
|
||||
required: [true, 'Informe o nome do instrumento.'],
|
||||
trim: true,
|
||||
maxlength: [120, 'O nome pode ter no máximo 120 caracteres.'],
|
||||
},
|
||||
fabricante: {
|
||||
type: String,
|
||||
required: [true, 'Informe o fabricante.'],
|
||||
trim: true,
|
||||
maxlength: [120, 'O fabricante pode ter no máximo 120 caracteres.'],
|
||||
},
|
||||
preco: {
|
||||
type: Number,
|
||||
required: [true, 'Informe o preço.'],
|
||||
min: [0, 'O preço não pode ser negativo.'],
|
||||
},
|
||||
tipo: {
|
||||
type: String,
|
||||
required: [true, 'Informe o tipo do instrumento.'],
|
||||
trim: true,
|
||||
enum: {
|
||||
values: TIPOS,
|
||||
message: 'Escolha um tipo da lista.',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
versionKey: false,
|
||||
}
|
||||
);
|
||||
|
||||
const Instrumento = mongoose.model('Instrumento', instrumentoSchema);
|
||||
|
||||
export default Instrumento;
|
||||
Generated
+1157
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "cadastro-instrumentos",
|
||||
"version": "1.0.0",
|
||||
"description": "Cadastro e Listagem de Instrumentos Musicais com Express, EJS e MongoDB - DBE Semana 04",
|
||||
"main": "app.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"dev": "node --watch app.js",
|
||||
"seed": "node seed.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://gitea.tsiad.com.br/anderson/DBE-S04.git"
|
||||
},
|
||||
"keywords": [
|
||||
"express",
|
||||
"ejs",
|
||||
"mongodb",
|
||||
"instrumentos",
|
||||
"dbe"
|
||||
],
|
||||
"author": "Anderson <[email protected]>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.21.2",
|
||||
"mongoose": "^8.19.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Anderson Valadares Pinto (20251PL.TSIAD0018) [SLS]
|
||||
|
||||
import express from 'express';
|
||||
|
||||
import Instrumento, { TIPOS } from '../models/Instrumento.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// "/" só manda para a listagem.
|
||||
router.get('/', (req, res) => res.redirect('/instrumentos'));
|
||||
|
||||
// Listagem: todos os instrumentos, do mais recente para o mais antigo.
|
||||
router.get('/instrumentos', async (req, res, next) => {
|
||||
try {
|
||||
const instrumentos = await Instrumento.find().sort({ createdAt: -1 }).lean();
|
||||
res.render('instrumento/lst', { titulo: 'Instrumentos', instrumentos });
|
||||
} catch (erro) {
|
||||
next(erro);
|
||||
}
|
||||
});
|
||||
|
||||
// Formulário de cadastro.
|
||||
router.get('/instrumentos/add', (req, res) => {
|
||||
res.render('instrumento/add', {
|
||||
titulo: 'Cadastrar Instrumento',
|
||||
tipos: TIPOS,
|
||||
valores: {},
|
||||
erro: null,
|
||||
});
|
||||
});
|
||||
|
||||
// Recebe o formulário, grava no MongoDB e mostra a confirmação.
|
||||
router.post('/instrumentos', async (req, res, next) => {
|
||||
const valores = {
|
||||
nome: String(req.body.nome ?? '').trim(),
|
||||
fabricante: String(req.body.fabricante ?? '').trim(),
|
||||
preco: String(req.body.preco ?? '').replace(',', '.').trim(),
|
||||
tipo: String(req.body.tipo ?? '').trim(),
|
||||
};
|
||||
|
||||
try {
|
||||
const instrumento = await Instrumento.create({
|
||||
nome: valores.nome,
|
||||
fabricante: valores.fabricante,
|
||||
preco: Number(valores.preco),
|
||||
tipo: valores.tipo,
|
||||
});
|
||||
|
||||
res.status(201).render('instrumento/addok', {
|
||||
titulo: 'Instrumento Cadastrado',
|
||||
instrumento: instrumento.toObject(),
|
||||
});
|
||||
} catch (erro) {
|
||||
// ValidationError cobre campos vazios e tipo inválido; CastError aparece
|
||||
// quando o preço chega como texto que não vira número.
|
||||
if (erro.name === 'ValidationError' || erro.name === 'CastError') {
|
||||
// O Mongoose embrulha o erro de conversão do preço num ValidationError,
|
||||
// então olhamos o primeiro erro interno para dar uma mensagem clara.
|
||||
const primeiro =
|
||||
erro.name === 'CastError' ? erro : Object.values(erro.errors || {})[0];
|
||||
const mensagem =
|
||||
primeiro?.name === 'CastError'
|
||||
? 'Informe o preço como um número.'
|
||||
: primeiro?.message || 'Confira os dados informados.';
|
||||
return res.status(400).render('instrumento/add', {
|
||||
titulo: 'Cadastrar Instrumento',
|
||||
tipos: TIPOS,
|
||||
valores,
|
||||
erro: mensagem,
|
||||
});
|
||||
}
|
||||
next(erro);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,83 @@
|
||||
// Anderson Valadares Pinto (20251PL.TSIAD0018) [SLS]
|
||||
//
|
||||
// Popula o banco com instrumentos para testar a listagem.
|
||||
// Uso: npm run seed (insere o que ainda não existe)
|
||||
// npm run seed -- --reset (apaga tudo e insere de novo)
|
||||
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
import Instrumento from './models/Instrumento.js';
|
||||
|
||||
const MONGODB_URI =
|
||||
process.env.MONGODB_URI || 'mongodb://127.0.0.1:27017/loja_instrumentos';
|
||||
|
||||
// Três cordas (dois do mesmo tipo, como pede o enunciado), preços variados
|
||||
// e nomes que permitem testar a busca depois.
|
||||
const instrumentos = [
|
||||
{
|
||||
nome: 'Guitarra Elétrica Stratocaster',
|
||||
fabricante: 'Fender',
|
||||
preco: 5490.0,
|
||||
tipo: 'Cordas',
|
||||
},
|
||||
{
|
||||
nome: 'Guitarra Acústica Folk',
|
||||
fabricante: 'Yamaha',
|
||||
preco: 2190.0,
|
||||
tipo: 'Cordas',
|
||||
},
|
||||
{
|
||||
nome: 'Baixo Elétrico SR300',
|
||||
fabricante: 'Ibanez',
|
||||
preco: 3890.0,
|
||||
tipo: 'Cordas',
|
||||
},
|
||||
{
|
||||
nome: 'Bateria Acústica Roadshow',
|
||||
fabricante: 'Pearl',
|
||||
preco: 7290.0,
|
||||
tipo: 'Percussão',
|
||||
},
|
||||
{
|
||||
nome: 'Teclado Digital Juno-DS61',
|
||||
fabricante: 'Roland',
|
||||
preco: 4590.0,
|
||||
tipo: 'Teclas',
|
||||
},
|
||||
{
|
||||
nome: 'Saxofone Alto YAS-280',
|
||||
fabricante: 'Yamaha',
|
||||
preco: 6290.0,
|
||||
tipo: 'Sopro',
|
||||
},
|
||||
];
|
||||
|
||||
async function executar() {
|
||||
await mongoose.connect(MONGODB_URI);
|
||||
|
||||
if (process.argv.includes('--reset')) {
|
||||
const removidos = await Instrumento.deleteMany({});
|
||||
console.log(`${removidos.deletedCount} instrumento(s) removido(s).`);
|
||||
}
|
||||
|
||||
let inseridos = 0;
|
||||
for (const instrumento of instrumentos) {
|
||||
// upsert por nome: rodar o seed duas vezes não duplica o estoque.
|
||||
const resultado = await Instrumento.updateOne(
|
||||
{ nome: instrumento.nome },
|
||||
{ $setOnInsert: instrumento },
|
||||
{ upsert: true }
|
||||
);
|
||||
if (resultado.upsertedCount) inseridos += 1;
|
||||
}
|
||||
|
||||
const total = await Instrumento.countDocuments();
|
||||
console.log(`${inseridos} instrumento(s) inserido(s); ${total} no total.`);
|
||||
}
|
||||
|
||||
executar()
|
||||
.catch((erro) => {
|
||||
console.error('Falha ao popular o banco:', erro.message);
|
||||
process.exitCode = 1;
|
||||
})
|
||||
.finally(() => mongoose.disconnect());
|
||||
@@ -0,0 +1,83 @@
|
||||
<!-- Anderson Valadares Pinto (20251PL.TSIAD0018) [SLS] -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><%= titulo %> | Loja de Instrumentos</title>
|
||||
<!-- Bootstrap 5 CSS via CDN -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Bootstrap Icons -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
footer {
|
||||
margin-top: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Barra de Navegação -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="/instrumentos">
|
||||
<i class="bi bi-music-note-beamed text-warning me-2"></i>Loja de Instrumentos
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navMenu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navMenu">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/instrumentos">
|
||||
<i class="bi bi-list-columns-reverse me-1"></i>Instrumentos
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/instrumentos/add">
|
||||
<i class="bi bi-plus-circle me-1"></i>Cadastrar
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Conteúdo Principal -->
|
||||
<main class="container my-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-6">
|
||||
<div class="card border-danger shadow-sm rounded-4 text-center p-4 p-md-5">
|
||||
<div class="text-danger mb-3" style="font-size: 4rem;">
|
||||
<i class="bi bi-exclamation-triangle-fill"></i>
|
||||
</div>
|
||||
<h1 class="h3 fw-bold text-danger mb-2"><%= titulo %></h1>
|
||||
<p class="text-secondary mb-4 lead"><%= mensagem %></p>
|
||||
<div class="d-flex flex-wrap justify-content-center gap-2">
|
||||
<a href="/instrumentos" class="btn btn-primary px-4 shadow-sm">
|
||||
<i class="bi bi-list-columns-reverse me-1"></i>Ir para a Listagem
|
||||
</a>
|
||||
<a href="/instrumentos/add" class="btn btn-outline-secondary px-4">
|
||||
<i class="bi bi-plus-lg me-1"></i>Cadastrar Instrumento
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Rodapé -->
|
||||
<footer class="bg-dark text-white-50 text-center py-4 border-top">
|
||||
<div class="container">
|
||||
<p class="mb-0">Desenvolvimento Back-End (DBE) • Semana 04 • Cadastro e Listagem de Instrumentos com Express, EJS e MongoDB</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,166 @@
|
||||
<!-- Anderson Valadares Pinto (20251PL.TSIAD0018) [SLS] -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><%= titulo %> | Loja de Instrumentos</title>
|
||||
<!-- Bootstrap 5 CSS via CDN -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Bootstrap Icons -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
footer {
|
||||
margin-top: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Barra de Navegação -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="/instrumentos">
|
||||
<i class="bi bi-music-note-beamed text-warning me-2"></i>Loja de Instrumentos
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navMenu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navMenu">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/instrumentos">
|
||||
<i class="bi bi-list-columns-reverse me-1"></i>Instrumentos
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/instrumentos/add">
|
||||
<i class="bi bi-plus-circle me-1"></i>Cadastrar
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Conteúdo Principal -->
|
||||
<main class="container my-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center mb-4 gap-2">
|
||||
<div>
|
||||
<h1 class="h2 fw-bold mb-1">
|
||||
<i class="bi bi-plus-square-fill text-primary me-2"></i><%= titulo %>
|
||||
</h1>
|
||||
<p class="text-muted mb-0">Preencha os dados para adicionar o instrumento ao estoque.</p>
|
||||
</div>
|
||||
<a href="/instrumentos" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i>Voltar à Listagem
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm rounded-4">
|
||||
<div class="card-body p-4 p-md-5">
|
||||
<% if (erro) { %>
|
||||
<div class="alert alert-danger d-flex align-items-start gap-2" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill fs-5"></i>
|
||||
<div>
|
||||
<strong>Não foi possível cadastrar.</strong><br>
|
||||
<%= erro %>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<form method="POST" action="/instrumentos">
|
||||
<div class="mb-3">
|
||||
<label for="nome" class="form-label fw-semibold">Nome</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="nome"
|
||||
name="nome"
|
||||
required
|
||||
maxlength="120"
|
||||
autocomplete="off"
|
||||
placeholder="Ex.: Guitarra Elétrica Stratocaster"
|
||||
value="<%= valores.nome || '' %>"
|
||||
<%= erro ? '' : 'autofocus' %>
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="fabricante" class="form-label fw-semibold">Fabricante</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="fabricante"
|
||||
name="fabricante"
|
||||
required
|
||||
maxlength="120"
|
||||
autocomplete="off"
|
||||
placeholder="Ex.: Fender"
|
||||
value="<%= valores.fabricante || '' %>"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="tipo" class="form-label fw-semibold">Tipo</label>
|
||||
<select class="form-select" id="tipo" name="tipo" required>
|
||||
<option value="">Selecione...</option>
|
||||
<% tipos.forEach((tipo) => { %>
|
||||
<option value="<%= tipo %>" <%= valores.tipo === tipo ? 'selected' : '' %>><%= tipo %></option>
|
||||
<% }) %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="preco" class="form-label fw-semibold">Preço (R$)</label>
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
id="preco"
|
||||
name="preco"
|
||||
required
|
||||
min="0"
|
||||
step="0.01"
|
||||
inputmode="decimal"
|
||||
placeholder="0.00"
|
||||
value="<%= valores.preco || '' %>"
|
||||
>
|
||||
<div class="form-text">
|
||||
Use ponto para os centavos — por exemplo, <code>1499.90</code>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 pt-4">
|
||||
<button type="submit" class="btn btn-primary px-4 shadow-sm">
|
||||
<i class="bi bi-check2-circle me-1"></i>Cadastrar Instrumento
|
||||
</button>
|
||||
<a href="/instrumentos" class="btn btn-outline-secondary px-4">
|
||||
<i class="bi bi-x-lg me-1"></i>Cancelar
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Rodapé -->
|
||||
<footer class="bg-dark text-white-50 text-center py-4 border-top">
|
||||
<div class="container">
|
||||
<p class="mb-0">Desenvolvimento Back-End (DBE) • Semana 04 • Cadastro e Listagem de Instrumentos com Express, EJS e MongoDB</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,123 @@
|
||||
<!-- Anderson Valadares Pinto (20251PL.TSIAD0018) [SLS] -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><%= titulo %> | Loja de Instrumentos</title>
|
||||
<!-- Bootstrap 5 CSS via CDN -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Bootstrap Icons -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
footer {
|
||||
margin-top: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Barra de Navegação -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="/instrumentos">
|
||||
<i class="bi bi-music-note-beamed text-warning me-2"></i>Loja de Instrumentos
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navMenu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navMenu">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/instrumentos">
|
||||
<i class="bi bi-list-columns-reverse me-1"></i>Instrumentos
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/instrumentos/add">
|
||||
<i class="bi bi-plus-circle me-1"></i>Cadastrar
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Conteúdo Principal -->
|
||||
<main class="container my-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="card border-0 shadow-sm rounded-4 overflow-hidden">
|
||||
<div class="card-header bg-success text-white py-3 px-4 d-flex justify-content-between align-items-center">
|
||||
<span class="badge bg-light text-success fs-6 px-3 py-2 rounded-pill">
|
||||
<i class="bi bi-check2-circle me-1"></i>Cadastro realizado
|
||||
</span>
|
||||
<span class="text-white-50"><i class="bi bi-music-note-list me-1"></i>Ficha do Instrumento</span>
|
||||
</div>
|
||||
<div class="card-body p-4 p-md-5 text-center">
|
||||
<div class="text-success mb-3" style="font-size: 4rem;">
|
||||
<i class="bi bi-check-circle-fill"></i>
|
||||
</div>
|
||||
<h1 class="h3 fw-bold text-dark mb-2"><%= titulo %></h1>
|
||||
<p class="lead text-secondary mb-4">
|
||||
<strong><%= instrumento.nome %></strong> já está no estoque da loja.
|
||||
</p>
|
||||
|
||||
<div class="list-group list-group-flush text-start mb-4">
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center px-0 py-3">
|
||||
<span class="text-secondary fw-semibold">
|
||||
<i class="bi bi-music-note text-primary me-2"></i>Nome:
|
||||
</span>
|
||||
<span class="fw-bold text-dark"><%= instrumento.nome %></span>
|
||||
</div>
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center px-0 py-3">
|
||||
<span class="text-secondary fw-semibold">
|
||||
<i class="bi bi-building text-muted me-2"></i>Fabricante:
|
||||
</span>
|
||||
<span class="fw-semibold text-dark"><%= instrumento.fabricante %></span>
|
||||
</div>
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center px-0 py-3">
|
||||
<span class="text-secondary fw-semibold">
|
||||
<i class="bi bi-tag-fill text-primary me-2"></i>Tipo:
|
||||
</span>
|
||||
<span class="badge bg-primary-subtle text-primary-emphasis rounded-pill px-3 py-2 fs-6">
|
||||
<%= instrumento.tipo %>
|
||||
</span>
|
||||
</div>
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center px-0 py-3">
|
||||
<span class="text-secondary fw-semibold">
|
||||
<i class="bi bi-currency-dollar text-success me-2"></i>Preço:
|
||||
</span>
|
||||
<span class="fw-bold text-success fs-5"><%= formatarPreco(instrumento.preco) %></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap justify-content-center gap-2">
|
||||
<a href="/instrumentos/add" class="btn btn-primary px-4 shadow-sm">
|
||||
<i class="bi bi-plus-lg me-1"></i>Cadastrar outro
|
||||
</a>
|
||||
<a href="/instrumentos" class="btn btn-outline-secondary px-4">
|
||||
<i class="bi bi-arrow-left me-1"></i>Voltar à Listagem
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Rodapé -->
|
||||
<footer class="bg-dark text-white-50 text-center py-4 border-top">
|
||||
<div class="container">
|
||||
<p class="mb-0">Desenvolvimento Back-End (DBE) • Semana 04 • Cadastro e Listagem de Instrumentos com Express, EJS e MongoDB</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,139 @@
|
||||
<!-- Anderson Valadares Pinto (20251PL.TSIAD0018) [SLS] -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><%= titulo %> | Loja de Instrumentos</title>
|
||||
<!-- Bootstrap 5 CSS via CDN -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Bootstrap Icons -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
footer {
|
||||
margin-top: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Barra de Navegação -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="/instrumentos">
|
||||
<i class="bi bi-music-note-beamed text-warning me-2"></i>Loja de Instrumentos
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navMenu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navMenu">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/instrumentos">
|
||||
<i class="bi bi-list-columns-reverse me-1"></i>Instrumentos
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/instrumentos/add">
|
||||
<i class="bi bi-plus-circle me-1"></i>Cadastrar
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Conteúdo Principal -->
|
||||
<main class="container my-5">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center mb-4 gap-2">
|
||||
<div>
|
||||
<h1 class="h2 fw-bold mb-1">
|
||||
<i class="bi bi-music-note-list text-primary me-2"></i><%= titulo %>
|
||||
</h1>
|
||||
<p class="text-muted mb-0">
|
||||
Estoque da loja cadastrado no MongoDB.
|
||||
</p>
|
||||
</div>
|
||||
<a href="/instrumentos/add" class="btn btn-primary shadow-sm">
|
||||
<i class="bi bi-plus-lg me-1"></i>Cadastrar Instrumento
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Tabela de Instrumentos -->
|
||||
<div class="card border-0 shadow-sm rounded-3 overflow-hidden">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover mb-0 align-middle">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th scope="col" class="text-center" style="width: 70px;">#</th>
|
||||
<th scope="col">Nome</th>
|
||||
<th scope="col">Fabricante</th>
|
||||
<th scope="col">Tipo</th>
|
||||
<th scope="col" class="text-end">Preço</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% if (instrumentos && instrumentos.length > 0) { %>
|
||||
<% for (let i = 0; i < instrumentos.length; i++) { %>
|
||||
<% const instrumento = instrumentos[i]; %>
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<span class="badge bg-secondary rounded-pill px-2 py-1">
|
||||
<%= i + 1 %>
|
||||
</span>
|
||||
</td>
|
||||
<td class="fw-bold text-dark">
|
||||
<i class="bi bi-music-note text-primary me-2"></i><%= instrumento.nome %>
|
||||
</td>
|
||||
<td>
|
||||
<i class="bi bi-building me-1 text-muted"></i><%= instrumento.fabricante %>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-primary-subtle text-primary-emphasis rounded-pill px-3 py-2">
|
||||
<%= instrumento.tipo %>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-end fw-semibold text-success">
|
||||
<%= formatarPreco(instrumento.preco) %>
|
||||
</td>
|
||||
</tr>
|
||||
<% } %>
|
||||
<% } else { %>
|
||||
<tr>
|
||||
<td colspan="5" class="text-center py-5">
|
||||
<div class="text-muted">
|
||||
<i class="bi bi-inbox fs-1 d-block mb-2"></i>
|
||||
<p class="lead mb-3">Nenhum instrumento cadastrado no estoque no momento.</p>
|
||||
<a href="/instrumentos/add" class="btn btn-primary">
|
||||
<i class="bi bi-plus-lg me-1"></i>Cadastrar o primeiro instrumento
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-muted text-end mt-3 mb-0">
|
||||
Quer incluir outro item?
|
||||
<a href="/instrumentos/add" class="fw-semibold">Cadastrar novo instrumento</a>
|
||||
</p>
|
||||
</main>
|
||||
|
||||
<!-- Rodapé -->
|
||||
<footer class="bg-dark text-white-50 text-center py-4 border-top">
|
||||
<div class="container">
|
||||
<p class="mb-0">Desenvolvimento Back-End (DBE) • Semana 04 • Cadastro e Listagem de Instrumentos com Express, EJS e MongoDB</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user