some code and stuff

This commit is contained in:
Mike 2025-01-16 16:28:53 -05:00
parent 75b83e30ea
commit 17d664eaeb
19 changed files with 762 additions and 4 deletions

View file

@ -0,0 +1,84 @@
/* 1. Use a more-intuitive box-sizing model */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* 2. Remove default margin */
* {
margin: 0;
}
body {
/* 3. Add accessible line-height */
line-height: 1.5;
/* 4. Improve text rendering */
-webkit-font-smoothing: antialiased;
font-size: 18px;
}
/* 5. Improve media defaults */
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
}
/* 6. Inherit fonts for form controls */
input,
button,
textarea,
select {
font: inherit;
}
/* 7. Avoid text overflows */
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
}
/* 8. Improve line wrapping */
p {
text-wrap: pretty;
}
h1,
h2,
h3,
h4,
h5,
h6 {
text-wrap: balance;
}
/*
9. Create a root stacking context
*/
#root,
#__next {
isolation: isolate;
}
li {
list-style-type: none;
}
ul {
display: flex;
justify-content: space-around;
max-width: 50vw;
margin: 0 auto;
}
.flex {
display: flex;
}

View file

@ -0,0 +1,58 @@
import { categoryPicker, storePicker } from "./selectComponent.js";
function formInput(props) {
const formItemDiv = document.createElement("div");
formItemDiv.classList.add("form-item");
const input = document.createElement("input");
input.name = props.name;
const label = document.createElement("label");
label.textContent = props.labelText;
formItemDiv.append(label, input);
return formItemDiv;
}
async function modal() {
const div = document.createElement("div");
div.classList.add("modal");
div.style = `
display: none;
positino: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 50vh;
overflow: auto;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.4);
`;
const form = document.createElement("form");
form.method = "POST";
form.action = "/item";
const name = formInput({ labelText: "Name", name: "name" });
const price = formInput({ labelText: "Price", name: "price" });
const qty = formInput({ labelText: "QTY", name: "qty" });
const categories = await categoryPicker();
const stores = await storePicker();
const submitButton = document.createElement("button");
submitButton.type = "submit";
submitButton.classList.add("btn");
submitButton.textContent = "Add Item";
const closeButton = document.createElement("button");
closeButton.textContent = "Close";
closeButton.addEventListener("click", () => {
div.style.display = "none";
});
form.append(name, price, qty, categories, stores, submitButton, closeButton);
div.append(form);
return div;
}
export { modal };

View file

@ -0,0 +1,59 @@
async function getData(path) {
const categories = await fetch(`/${path}`);
return await categories.json();
}
function selectComponent(data, select) {
data.forEach((opt) => {
const option = document.createElement("option");
option.value = opt.name;
option.textContent = opt.name;
select.append(option);
});
}
async function categoryPicker() {
const selectQuery = document.querySelector(".categories-list-modal");
if (selectQuery) document.body.remove(selectQuery);
const categories = await getData("categories");
const formItemDiv = document.createElement("div");
formItemDiv.classList.add("form-item");
const formLabel = document.createElement("label");
formLabel.textContent = "Categories";
const select = document.createElement("select");
select.classList.add("categories-list-modal");
select.name = "category";
selectComponent(categories, select);
formItemDiv.append(formLabel, select);
return formItemDiv;
}
async function storePicker() {
const selectQuery = document.querySelector(".store-list-modal");
if (selectQuery) document.body.remove(selectQuery);
const stores = await getData("stores");
const formItemDiv = document.createElement("div");
formItemDiv.classList.add("form-item");
const formLabel = document.createElement("label");
formLabel.textContent = "stores";
const select = document.createElement("select");
select.classList.add("stores-list-modal");
select.name = "store";
selectComponent(stores, select);
formItemDiv.append(formLabel, select);
return formItemDiv;
}
export { categoryPicker, storePicker };

View file

@ -0,0 +1,63 @@
import { modal } from "./components/modal.js";
const newItemLink = document.querySelector("a[href='/add']");
//
//if (newItemLink && window.location !== "/item/") {
const modalElement = await modal();
let modalToggle = false;
async function handleDelete(e) {
console.log(e.target);
}
document.querySelectorAll(".delete-button").forEach((button) =>
button.addEventListener("click", async (e) => {
const itemName = e.target.dataset.id;
const data = new URLSearchParams(new FormData());
data.append("name", itemName);
const res = await fetch("/item", {
method: "DELETE",
body: data,
});
window.location.href = "/";
}),
);
document.querySelectorAll(".edit-button").forEach((button) => {
button.addEventListener("click", async (e) => {
const itemId = e.target.parentNode.dataset.itemId;
window.location.href = `/item/${itemId}`;
});
});
document.querySelectorAll(".delete-btn").forEach((button) => {
button.addEventListener("click", async (e) => {
const itemId = e.target.dataset.id;
const data = new URLSearchParams(new FormData());
data.append("id", itemId);
const res = await fetch("/category", {
method: "DELETE",
body: data,
});
const message = await res.text();
if (message.includes("error")) {
alert("unable to delete as item is using category");
} else {
window.location.href = "/category";
}
});
});
newItemLink.addEventListener("click", (e) => {
e.preventDefault();
modalToggle
? (modalElement.style.display = "none")
: (modalElement.style.display = "block");
modalToggle = !modalToggle;
});
document.body.append(modalElement);
//}

View file

@ -7,8 +7,9 @@ const port = 8080;
const { indexRouter } = require("./routes/indexRouter");
const assetsPath = path.join(path.dirname(__dirname), "public");
app.set("view engine", "ejs");
app.set("views", path.join(__dirname, "views"));
app.set(express.static(assetsPath));
app.use(express.static(assetsPath));
app.use(express.urlencoded({ extended: true }));

6
inventory/src/config.js Normal file
View file

@ -0,0 +1,6 @@
exports.links = [
{ name: "home", href: "/" },
{ name: "store", href: "/store" },
{ name: "categories", href: "/category" },
{ name: "new item", href: "/add" },
];

View file

@ -0,0 +1,150 @@
const db = require("../db/queries");
const { links } = require("../config");
async function indexGet(req, res) {
const items = await db.getAllItemsWithRelationships();
res.render("base", {
links: links,
pageTitle: "Iventory Home",
items: items,
page: "",
});
}
async function storeGet(req, res) {
const stores = await db.getStores();
res.render("base", {
page: "add",
links: links,
pageTitle: "Inventory | Stores",
type: "store",
data: stores,
});
}
async function storesGetAll(req, res) {
const stores = await db.getStores();
res.json(stores);
}
async function storePost(req, res) {
const r = await handlePostRequets({ reqBody: req.body, dbName: "store" });
res.redirect("/store");
}
async function categoryGet(req, res) {
const categories = await db.getCategories();
res.render("base", {
page: "add",
links: links,
pageTitle: "Inventory | Categories",
type: "category",
data: categories,
});
}
async function categoryGetAll(req, res) {
const categories = await db.getCategories();
res.json(categories);
}
async function categoryPost(req, res) {
const r = await handlePostRequets({ reqBody: req.body, dbName: "category" });
if (r) {
console.log("something went wrong");
}
res.redirect("/category");
}
async function handlePostRequets(data) {
const { dbName } = data;
const { name } = data.reqBody;
let res;
if (dbName === "store") {
res = db.insertStore(name);
} else if (dbName === "category") {
res = db.insertCategory(name);
}
return res;
}
async function itemPost(req, res) {
const categoryId = await db.getCategoryId({ name: req.body.category });
const storeId = await db.getStoreId({ name: req.body.store });
let formData = req.body;
const data = {
name: formData.name,
price: formData.price,
qty: formData.qty,
store_id: storeId.store_id,
category_id: categoryId.category_id,
};
try {
const r = await db.insertItem(data);
if (r) throw new Error(r);
res.redirect("/");
} catch (e) {
console.log(e);
res.send({ error: e });
}
}
async function itemEditGet(req, res, next) {
const itemId = req.params.id;
const items = await db.getAllItemsWithRelationships();
const item = await db.getItemById(Number(itemId));
const categories = await db.getCategories();
const stores = await db.getStores();
if (item)
res.render("base", {
item: item,
categories: categories,
stores: stores,
pageTitle: `Inventory | Editing Item: ${item.name}`,
page: "edit",
links: links,
});
}
async function itemEditPost(req, res, next) {
const itemId = req.params.id;
console.log(itemId);
res.redirect("/");
}
async function itemDelete(req, res) {
console.log(req.body.name);
const itemId = await db.getItemByName(req.body.name);
if (itemId) db.deleteItem(itemId);
console.log(`Delted item: ${itemId}`);
res.redirect("deleted item: ${item}");
}
async function categoryDelete(req, res) {
const query = await db.deleteCategory(req.body.id);
if (query) {
res.send(`${query}`);
}
res.send("category deleted");
}
module.exports = {
indexGet,
itemPost,
itemEditGet,
itemEditPost,
itemDelete,
storeGet,
storePost,
storesGetAll,
categoryGet,
categoryPost,
categoryGetAll,
categoryDelete,
};

11
inventory/src/db/pool.js Normal file
View file

@ -0,0 +1,11 @@
const { Pool } = require("pg");
// Todo make this env variables
module.exports = new Pool({
host: "smig-ca04.lab.smig.tech",
user: "odin",
database: "odin",
password: "odinproject",
port: 32343,
});
// 32343;

120
inventory/src/db/queries.js Normal file
View file

@ -0,0 +1,120 @@
const pool = require("./pool");
async function getAllItems() {
const { rows } = await pool.query("SELECT * FROM items");
return rows;
}
async function getItemByName(name) {
const { rows } = await pool.query("SELECT * FROM items WHERE name = $1", [
name,
]);
return rows[0];
}
async function getItemById(id) {
const { rows } = await pool.query(
"SELECT id, items.name, price, qty, category.name AS category, store.name AS store FROM items LEFT JOIN category on items.category_id = category.category_id LEFT JOIN store ON items.store_id = store.store_id WHERE items.id = $1 ",
[id],
);
return rows[0];
}
async function insertItem(data) {
const { name, price, qty, category_id, store_id } = data;
try {
await pool.query(
`INSERT INTO items (name, price, qty, category_id, store_id) VALUES ($1, $2, $3, $4, $5)`,
[name, price, qty, category_id, store_id],
);
return null;
} catch (e) {
return e;
}
}
async function deleteItem(item) {
try {
const res = await pool.query("DELETE FROM items WHERE id = $1", [item.id]);
return null;
} catch (e) {
return e;
}
}
async function getAllItemsWithRelationships() {
const { rows } = await pool.query(
`SELECT items.id, items.name, items.price, items.qty, category.name AS category_name, store.name AS store_name FROM items
LEFT JOIN category ON items.category_id = category.category_id
LEFT JOIN store ON items.store_id = store.store_id;`,
);
return rows;
}
async function getCategories() {
const { rows } = await pool.query("SELECT * FROM category");
return rows;
}
async function getCategoryId(item) {
const { rows } = await pool.query("SELECT * FROM category WHERE name = $1", [
item.name,
]);
return rows[0];
}
async function insertCategory(data) {
try {
await pool.query("INSERT INTO category (name) VALUES ($1)", [data]);
return null;
} catch (e) {
return e;
}
}
async function deleteCategory(itemId) {
try {
await pool.query("DELETE FROM category WHERE category_id = $1", [itemId]);
return null;
} catch (e) {
return e;
}
}
async function getStores() {
const { rows } = await pool.query("SELECT * FROM store");
return rows;
}
async function getStoreId(item) {
const { rows } = await pool.query("SELECT * FROM store WHERE name = $1", [
item.name,
]);
return rows[0];
}
async function insertStore(data) {
try {
await pool.query("INSERT INTO store (name) VALUES ($1)", [data]);
return null;
} catch (e) {
return e;
}
}
module.exports = {
insertItem,
getItemById,
getAllItems,
getItemByName,
deleteItem,
getCategories,
getCategoryId,
getStores,
getStoreId,
insertStore,
insertCategory,
deleteCategory,
getAllItemsWithRelationships,
};

View file

@ -0,0 +1,64 @@
#!/usr/bin/env node
const { Client } = require("pg");
const CREATE_DB = `
CREATE DATABASE inventory_app;
`;
const SQL = `
CREATE TABLE IF NOT EXISTS store (
store_id INTEGER PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
name varchar(80) NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS category (
category_id INTEGER PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
name varchar(40) NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
name VARCHAR(80) NOT NULL,
price INTEGER,
qty INTEGER,
store_id INTEGER REFERENCES store (store_id),
category_id INTEGER REFERENCES category (category_id)
);
`;
const POPULATE = `
INSERT INTO category (name)
VALUES ('grocery'), ('clothing'), ('household'), ('cleaning'), ('misc');
INSERT INTO store (name)
VALUES ('target'), ('wegmans'), ('giant');
INSERT INTO items (name, price, qty, store_id, category_id)
VALUES ('paper towels', '399', '3', '2','3' );
`;
async function main() {
console.log("DB Creation script");
const client = new Client({
host: "smig-ca05.lab.smig.tech",
user: "odin",
database: "odin",
password: "odinproject",
port: 32343,
});
try {
await client.connect();
await client.query(POPULATE);
await client.end();
} catch (e) {
console.log(`ERROR: ${e}`);
process.exit(1);
}
console.log("Done");
}
main();

View file

@ -1,9 +1,20 @@
const { Router } = require("express");
const indexController = require("../controllers/indexController");
const indexRouter = Router();
indexRouter.get("/", (req, res) => {
res.send("index");
});
indexRouter.get("/", indexController.indexGet);
//indexRouter.post("/add")
indexRouter.get("/store", indexController.storeGet);
indexRouter.get("/stores", indexController.storesGetAll);
indexRouter.post("/store", indexController.storePost);
indexRouter.get("/category", indexController.categoryGet);
indexRouter.get("/categories", indexController.categoryGetAll);
indexRouter.post("/category", indexController.categoryPost);
indexRouter.delete("/category", indexController.categoryDelete);
indexRouter.post("/item", indexController.itemPost);
indexRouter.get("/item/:id", indexController.itemEditGet);
indexRouter.post("/item/:id", indexController.itemEditPost);
indexRouter.delete("/item", indexController.itemDelete);
module.exports = { indexRouter };

View file

@ -0,0 +1,16 @@
<h1><%= type %></h1>
<form method="POST" action="/<%= type %>">
<div class="container">
<div class="form-item">
<label for="name">Name</label>
<input type="text" name="name" placeholder="" required>
</div>
<div class="form-item btn-row">
<button type="submit">Submit</button>
<button type="button">Cancel</button>
</div>
</div>
</form>
<%- include("card", {items: data}) %>

View file

@ -0,0 +1,10 @@
<%- include('header', {links: links}); %>
<% if (page === "add") { %>
<%- include('addForm', {type: type}); %>
<% } else if (page === "edit") { %>
<%- include('editForm', {item: item, categories: categories, stores: stores}); %>
<% } else { %>
<%- include('itemTable', {items: items}); %>
<% }; %>
<%- include('footer'); %>

View file

@ -0,0 +1,14 @@
<div class="container">
<% items.forEach(item => { %>
<div class="container flex">
<p class="title">
<%= item.name %>
</p>
<button>Edit</button>
<% let itemId; %>
<% if (item.category_id) {itemId = item.category_id} else { itemId = item.store_id } %>
<button class="delete-btn" data-id="<%= itemId %>">Delete</button>
</div>
<% }); %>
</div>

View file

@ -0,0 +1,34 @@
<form method="POST" action="/item/id">
<div class="form-item"><label>Name</label><input name="name" value="<%= item.name %>" /></div>
<div class="form-item"><label>Price</label><input name="price" value=<%= item.price %> /></div>
<div class="form-item"><label>QTY</label><input name="qty" value=<%= item.qty %> /></div>
<div class="form-item">
<label>Categories</label>
<select class="categories-list" name="category">
<% categories.forEach((cat) => { %>
<% if (cat.name === item.category) { %>
<option name=<%= cat.value %> data-cat-id=<%= cat.category_id %> selected><%= cat.name %></option>
<% } else { %>
<option name=<%= cat.value %> data-cat-id=<%= cat.category_id%>><%= cat.name %></option>
<% }; %>
<% }); %>
</select>
</div>
<div class="form-item">
<label>stores</label>
<select class="stores-list" name="store">
<% stores.forEach((cat) => { %>
<% if (cat.name === item.store) { %>
<option name=<%= cat.name %> data-cat-id=<%= cat.store_id %> selected><%= cat.name %></option>
<% } else { %>
<option name=<%= cat.name %> data-cat-id=<%= cat.store_id %>><%= cat.name %></option>
<% } %>
<% }); %>
</select>
</div>
<button type="submit" class="btn">Add Item</button><button>Close</button>
</form>

View file

@ -0,0 +1,3 @@
<script type="module" src="/js/script.js"></script>
</body>
</html>

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><%= pageTitle %></title>
<link href="/css/style.css" rel="stylesheet">
</head>
<body>
<nav class="nav">
<ul>
<% links.forEach(link => { %>
<li><a href="<%= link.href %>"><%= link.name %></a></li>
<% }); %>
</ul>
</nav>

View file

@ -0,0 +1,10 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title></title>
<link href="/css/style.css" rel="stylesheet" />
</head>
<body></body>
</html>

View file

@ -0,0 +1,26 @@
<table class="items">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Price</th>
<th scope="col">QTY</th>
<th scope="col">Store</th>
<th scope="col">Category</th>
</tr>
</thead>
<tbody>
<% items.forEach(item => { %>
<tr data-item-id="<%=item.id %>">
<td><%= item.name %></td>
<td><%= item.price %></td>
<td><%= item.qty %></td>
<td><%= item.store_name %></td>
<td><%= item.category_name %></td>
<td class="edit-button">Edit</td>
<td class="delete-button" data-id="<%= item.name %>">Delete</td>
</tr>
<% }); %>
</tbody>
</table>