DBE S04: cadastro e listagem de instrumentos musicais

This commit is contained in:
anderson committed 2026-11-11 16:30:56 -03:00
commit bcf1a6d218
17 files changed
+2152

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
node_modules/
.env
npm-debug.log*
*.log
.DS_Store
+21
View File
@@ -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.
+149
View File
@@ -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
![Listagem de instrumentos](docs/listagem.png)
![Formulário de cadastro](docs/cadastro.png)
![Confirmação do cadastro](docs/confirmacao.png)
A listagem também funciona em telas pequenas (a tabela rola na horizontal):
[docs/listagem-mobile.png](docs/listagem-mobile.png).
+68
View File
@@ -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);
});
BIN
View File
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

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

+52
View File
@@ -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;
+1157
View File
File diff suppressed because it is too large. Load diff
+30
View File
@@ -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"
}
}
+76
View File
@@ -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;
+83
View File
@@ -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());
+83
View File
@@ -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) &bull; Semana 04 &bull; 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>
+166
View File
@@ -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) &bull; Semana 04 &bull; 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>
+123
View File
@@ -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) &bull; Semana 04 &bull; 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>
+139
View File
@@ -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) &bull; Semana 04 &bull; 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>