878 lines
37 KiB
JavaScript
878 lines
37 KiB
JavaScript
// нахожу экран входа и главное окно
|
||
const authScreen = document.getElementById("authScreen");
|
||
const app = document.getElementById("app");
|
||
|
||
// нахожу формы входа и регистрации
|
||
const loginFormContainer = document.getElementById("loginFormContainer");
|
||
const registerFormContainer = document.getElementById("registerFormContainer");
|
||
const loginForm = document.getElementById("loginForm");
|
||
const registerForm = document.getElementById("registerForm");
|
||
|
||
// сюда пишу ошибки
|
||
const loginError = document.getElementById("loginError");
|
||
const registerError = document.getElementById("registerError");
|
||
|
||
// кнопки переключения вход / регистрация
|
||
const showRegisterButton = document.getElementById("showRegister");
|
||
const showLoginButton = document.getElementById("showLogin");
|
||
|
||
// кнопка выйти
|
||
const logoutButton = document.getElementById("logoutButton");
|
||
|
||
// где показываю кто вошел
|
||
const currentUser = document.getElementById("currentUser");
|
||
const currentRole = document.getElementById("currentRole");
|
||
const userAvatar = document.getElementById("userAvatar");
|
||
|
||
// заголовок страницы и место для таблицы
|
||
const pageTitle = document.getElementById("pageTitle");
|
||
const pageDescription = document.getElementById("pageDescription");
|
||
const pageContent = document.getElementById("pageContent");
|
||
const addButton = document.getElementById("addButton");
|
||
|
||
// всплывающее окно
|
||
const modal = document.getElementById("modal");
|
||
const modalTitle = document.getElementById("modalTitle");
|
||
const modalForm = document.getElementById("modalForm");
|
||
const closeModal = document.getElementById("closeModal");
|
||
|
||
// маленькое сообщение снизу
|
||
const toast = document.getElementById("toast");
|
||
|
||
// список всех страниц и откуда брать данные
|
||
const pages = {
|
||
products: { title: "Товары", description: "Все товары магазина", endpoint: "/api/products" },
|
||
categories: { title: "Категории", description: "Категории товаров", endpoint: "/api/categories" },
|
||
warehouses: { title: "Склады", description: "Все склады", endpoint: "/api/warehouses" },
|
||
stock: { title: "Остатки", description: "Что лежит на складах", endpoint: "/api/stocks" },
|
||
users: { title: "Пользователи", description: "Все люди в системе", endpoint: "/api/users" },
|
||
orders: { title: "Заказы", description: "Все заказы", endpoint: "/api/orders" }
|
||
};
|
||
|
||
// какая страница открыта сейчас
|
||
let currentPage = "products";
|
||
// кого изменяю (если null значит добавляю нового)
|
||
let editingId = null;
|
||
// кто вошел в систему
|
||
let currentAuthUser = null;
|
||
|
||
// показываю сообщение снизу на 2 секунды
|
||
function showToast(message) {
|
||
toast.textContent = message; // пишу текст
|
||
toast.classList.add("show"); // показываю
|
||
setTimeout(() => {
|
||
toast.classList.remove("show"); // прячу обратно
|
||
}, 2500);
|
||
}
|
||
|
||
// защита от плохих символов чтоб не ломали страницу
|
||
function escapeHtml(value) {
|
||
if (value === null || value === undefined) {
|
||
return "";
|
||
}
|
||
return String(value)
|
||
.replaceAll("&", "&")
|
||
.replaceAll("<", "<")
|
||
.replaceAll(">", ">")
|
||
.replaceAll('"', """)
|
||
.replaceAll("'", "'");
|
||
}
|
||
|
||
// делаю цену красивой: 1000 -> 1 000,00 ₽
|
||
function formatMoney(value) {
|
||
return Number(value).toLocaleString("ru-RU", {
|
||
minimumFractionDigits: 2,
|
||
maximumFractionDigits: 2
|
||
}) + " ₽";
|
||
}
|
||
|
||
// делаю дату понятной
|
||
function formatDate(value) {
|
||
if (!value) {
|
||
return "";
|
||
}
|
||
return new Date(value).toLocaleString("ru-RU");
|
||
}
|
||
|
||
// одна функция для всех запросов на сервер
|
||
async function api(url, options = {}) {
|
||
// отправляю запрос
|
||
const response = await fetch(url, {
|
||
headers: { "Content-Type": "application/json" },
|
||
...options
|
||
});
|
||
// если ошибка то показываю что случилось
|
||
if (!response.ok) {
|
||
let message = "Ошибка.";
|
||
const text = await response.text();
|
||
if (text) {
|
||
try {
|
||
const data = JSON.parse(text);
|
||
message = data.title || data.message || text;
|
||
} catch {
|
||
message = text;
|
||
}
|
||
}
|
||
throw new Error(message);
|
||
}
|
||
// если пусто то возвращаю null
|
||
if (response.status === 204) {
|
||
return null;
|
||
}
|
||
const text = await response.text();
|
||
if (!text) {
|
||
return null;
|
||
}
|
||
return JSON.parse(text); // превращаю ответ в объект
|
||
}
|
||
|
||
// показываю форму входа
|
||
function showLoginForm() {
|
||
loginFormContainer.classList.remove("hidden");
|
||
registerFormContainer.classList.add("hidden");
|
||
loginError.textContent = "";
|
||
registerError.textContent = "";
|
||
}
|
||
|
||
// показываю форму регистрации
|
||
function showRegisterForm() {
|
||
loginFormContainer.classList.add("hidden");
|
||
registerFormContainer.classList.remove("hidden");
|
||
loginError.textContent = "";
|
||
registerError.textContent = "";
|
||
}
|
||
|
||
// захожу в программу после входа
|
||
function enterApplication(user) {
|
||
currentAuthUser = user; // запоминаю кто вошел
|
||
sessionStorage.setItem("demoUser", JSON.stringify(user)); // сохраняю чтоб не выкидывало
|
||
currentUser.textContent = user.login; // пишу логин
|
||
currentRole.textContent = user.role || "Client"; // пишу роль
|
||
userAvatar.textContent = user.login ? user.login.charAt(0).toUpperCase() : "U"; // первая буква в кружок
|
||
authScreen.classList.add("hidden"); // прячу вход
|
||
app.classList.remove("hidden"); // показываю программу
|
||
showPage("products"); // открываю товары
|
||
}
|
||
|
||
// выхожу из аккаунта
|
||
function logout() {
|
||
sessionStorage.removeItem("demoUser"); // забываю пользователя
|
||
currentAuthUser = null;
|
||
app.classList.add("hidden"); // прячу программу
|
||
authScreen.classList.remove("hidden"); // показываю вход
|
||
loginForm.reset(); // чищу поля
|
||
registerForm.reset();
|
||
showLoginForm();
|
||
}
|
||
|
||
// когда жму войти
|
||
loginForm.addEventListener("submit", async (event) => {
|
||
event.preventDefault(); // чтоб страница не перезагружалась
|
||
loginError.textContent = "";
|
||
const login = document.getElementById("login").value.trim();
|
||
const password = document.getElementById("password").value;
|
||
if (!login || !password) {
|
||
loginError.textContent = "Напишите логин и пароль.";
|
||
return;
|
||
}
|
||
try {
|
||
const user = await api("/api/auth/login", {
|
||
method: "POST",
|
||
body: JSON.stringify({ login, password })
|
||
});
|
||
enterApplication(user); // захожу
|
||
loginForm.reset();
|
||
} catch (error) {
|
||
loginError.textContent = error.message || "Не получилось войти.";
|
||
}
|
||
});
|
||
|
||
// когда жму зарегистрироваться
|
||
registerForm.addEventListener("submit", async (event) => {
|
||
event.preventDefault();
|
||
registerError.textContent = "";
|
||
const login = document.getElementById("registerLogin").value.trim();
|
||
const password = document.getElementById("registerPassword").value;
|
||
const passwordRepeat = document.getElementById("registerPasswordRepeat").value;
|
||
// простые проверки
|
||
if (!login || !password || !passwordRepeat) {
|
||
registerError.textContent = "Заполните все поля.";
|
||
return;
|
||
}
|
||
if (login.length < 2) {
|
||
registerError.textContent = "Логин слишком короткий.";
|
||
return;
|
||
}
|
||
if (password.length < 4) {
|
||
registerError.textContent = "Пароль слишком короткий.";
|
||
return;
|
||
}
|
||
if (password !== passwordRepeat) {
|
||
registerError.textContent = "Пароли не совпадают.";
|
||
return;
|
||
}
|
||
try {
|
||
await api("/api/auth/register", {
|
||
method: "POST",
|
||
body: JSON.stringify({ login, password, roleId: 2 })
|
||
});
|
||
registerForm.reset();
|
||
showLoginForm(); // иду на вход
|
||
document.getElementById("login").value = login;
|
||
loginError.textContent = "Готово. Теперь войдите.";
|
||
} catch (error) {
|
||
registerError.textContent = error.message || "Не получилось.";
|
||
}
|
||
});
|
||
|
||
// кнопки переключения
|
||
showRegisterButton.addEventListener("click", showRegisterForm);
|
||
showLoginButton.addEventListener("click", showLoginForm);
|
||
logoutButton.addEventListener("click", logout);
|
||
|
||
// кнопки меню слева
|
||
document.querySelectorAll(".nav-button").forEach((button) => {
|
||
button.addEventListener("click", () => {
|
||
// убираю синий цвет у всех
|
||
document.querySelectorAll(".nav-button").forEach((b) => b.classList.remove("active"));
|
||
button.classList.add("active"); // крашу нажатую
|
||
showPage(button.dataset.page); // открываю страницу
|
||
});
|
||
});
|
||
|
||
// открываю страницу (товары, заказы и тд)
|
||
async function showPage(page) {
|
||
currentPage = page; // запоминаю где я
|
||
const config = pages[page];
|
||
if (!config) {
|
||
return;
|
||
}
|
||
pageTitle.textContent = config.title; // пишу название
|
||
pageDescription.textContent = config.description;
|
||
pageContent.innerHTML = "<div class='empty-page'><strong>Загрузка...</strong></div>"; // пока грузится
|
||
try {
|
||
const data = await api(config.endpoint); // беру данные с сервера
|
||
renderPage(page, data); // рисую
|
||
} catch (error) {
|
||
pageContent.innerHTML = "<div class='error-page'>Не получилось: " + escapeHtml(error.message) + "</div>";
|
||
}
|
||
}
|
||
|
||
// выбираю что рисовать
|
||
function renderPage(page, data) {
|
||
// если пусто то так и пишу
|
||
if (!Array.isArray(data) || data.length === 0) {
|
||
pageContent.innerHTML = "<div class='empty-page'><strong>Пока пусто</strong><span>Нажмите Добавить</span></div>";
|
||
return;
|
||
}
|
||
if (page === "products") {
|
||
renderProducts(data);
|
||
}
|
||
if (page === "categories") {
|
||
renderCategories(data);
|
||
}
|
||
if (page === "warehouses") {
|
||
renderWarehouses(data);
|
||
}
|
||
if (page === "stock") {
|
||
renderStock(data);
|
||
}
|
||
if (page === "users") {
|
||
renderUsers(data);
|
||
}
|
||
if (page === "orders") {
|
||
renderOrders(data);
|
||
}
|
||
}
|
||
|
||
// рисую товары карточками
|
||
function renderProducts(products) {
|
||
let html = "<div class=\"cards-grid\">"; // начало сетки
|
||
for (const p of products) {
|
||
html += "<div class=\"card\">";
|
||
html += "<div class=\"card-id\">#" + p.id + "</div>";
|
||
html += "<h3>" + escapeHtml(p.name) + "</h3>";
|
||
html += "<div class=\"card-price\">" + formatMoney(p.price) + "</div>";
|
||
html += "<div class=\"card-info\">Категория: <strong>" + escapeHtml(p.category ? p.category.name : "—") + "</strong></div>";
|
||
html += "<div class=\"card-actions\">";
|
||
html += "<button class=\"action-button\" onclick=\"editProduct(" + p.id + ")\">Изменить</button>";
|
||
html += "<button class=\"action-button delete\" onclick=\"deleteItem('products'," + p.id + ")\">Удалить</button>";
|
||
html += "</div></div>";
|
||
}
|
||
html += "</div>";
|
||
pageContent.innerHTML = html; // показываю все
|
||
}
|
||
|
||
// рисую категории таблицей
|
||
function renderCategories(categories) {
|
||
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Название</th><th>Действия</th></tr></thead><tbody>";
|
||
for (const c of categories) {
|
||
html += "<tr><td>#" + c.id + "</td><td>" + escapeHtml(c.name) + "</td><td><div class=\"table-actions\">";
|
||
html += "<button class=\"action-button\" onclick=\"editCategory(" + c.id + ")\">Изменить</button>";
|
||
html += "<button class=\"action-button delete\" onclick=\"deleteItem('categories'," + c.id + ")\">Удалить</button>";
|
||
html += "</div></td></tr>";
|
||
}
|
||
html += "</tbody></table></div>";
|
||
pageContent.innerHTML = html;
|
||
}
|
||
|
||
// рисую склады
|
||
function renderWarehouses(warehouses) {
|
||
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Название</th><th>Адрес</th><th>Действия</th></tr></thead><tbody>";
|
||
for (const w of warehouses) {
|
||
html += "<tr><td>#" + w.id + "</td><td>" + escapeHtml(w.name) + "</td><td>" + escapeHtml(w.address) + "</td><td><div class=\"table-actions\">";
|
||
html += "<button class=\"action-button\" onclick=\"editWarehouse(" + w.id + ")\">Изменить</button>";
|
||
html += "<button class=\"action-button delete\" onclick=\"deleteItem('warehouses'," + w.id + ")\">Удалить</button>";
|
||
html += "</div></td></tr>";
|
||
}
|
||
html += "</tbody></table></div>";
|
||
pageContent.innerHTML = html;
|
||
}
|
||
|
||
// рисую остатки
|
||
function renderStock(stocks) {
|
||
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Товар</th><th>Склад</th><th>Кол-во</th><th>Действия</th></tr></thead><tbody>";
|
||
for (const s of stocks) {
|
||
const pname = s.product ? s.product.name : "—";
|
||
const wname = s.warehouse ? s.warehouse.name : "—";
|
||
html += "<tr><td>#" + s.id + "</td><td>" + escapeHtml(pname) + "</td><td>" + escapeHtml(wname) + "</td><td>" + s.quantity + "</td><td><div class=\"table-actions\">";
|
||
html += "<button class=\"action-button\" onclick=\"editStock(" + s.id + ")\">Изменить</button>";
|
||
html += "<button class=\"action-button delete\" onclick=\"deleteItem('stock'," + s.id + ")\">Удалить</button>";
|
||
html += "</div></td></tr>";
|
||
}
|
||
html += "</tbody></table></div>";
|
||
pageContent.innerHTML = html;
|
||
}
|
||
|
||
// рисую пользователей
|
||
function renderUsers(users) {
|
||
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Логин</th><th>Роль</th><th>Действия</th></tr></thead><tbody>";
|
||
for (const u of users) {
|
||
const rname = u.role ? u.role.name : "—";
|
||
html += "<tr><td>#" + u.id + "</td><td>" + escapeHtml(u.login) + "</td><td><span class=\"status\">" + escapeHtml(rname) + "</span></td><td><div class=\"table-actions\">";
|
||
html += "<button class=\"action-button\" onclick=\"editUser(" + u.id + ")\">Изменить</button>";
|
||
html += "<button class=\"action-button delete\" onclick=\"deleteItem('users'," + u.id + ")\">Удалить</button>";
|
||
html += "</div></td></tr>";
|
||
}
|
||
html += "</tbody></table></div>";
|
||
pageContent.innerHTML = html;
|
||
}
|
||
|
||
// рисую заказы
|
||
function renderOrders(orders) {
|
||
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Кто заказал</th><th>Дата</th><th>Статус</th><th>Действия</th></tr></thead><tbody>";
|
||
for (const o of orders) {
|
||
const uname = o.user ? o.user.login : "—";
|
||
html += "<tr><td>#" + o.id + "</td><td>" + escapeHtml(uname) + "</td><td>" + formatDate(o.date) + "</td><td><span class=\"status\">" + escapeHtml(o.status) + "</span></td><td><div class=\"table-actions\">";
|
||
html += "<button class=\"action-button\" onclick=\"showOrderItems(" + o.id + ")\">Товары</button>";
|
||
html += "<button class=\"action-button\" onclick=\"editOrder(" + o.id + ")\">Изменить</button>";
|
||
html += "<button class=\"action-button delete\" onclick=\"deleteItem('orders'," + o.id + ")\">Удалить</button>";
|
||
html += "</div></td></tr>";
|
||
}
|
||
html += "</tbody></table></div>";
|
||
pageContent.innerHTML = html;
|
||
}
|
||
|
||
// кнопка Добавить сверху
|
||
addButton.addEventListener("click", () => {
|
||
editingId = null; // значит добавляю а не изменяю
|
||
if (currentPage === "products") {
|
||
openProductForm();
|
||
}
|
||
if (currentPage === "categories") {
|
||
openCategoryForm();
|
||
}
|
||
if (currentPage === "warehouses") {
|
||
openWarehouseForm();
|
||
}
|
||
if (currentPage === "stock") {
|
||
openStockForm();
|
||
}
|
||
if (currentPage === "users") {
|
||
openUserForm();
|
||
}
|
||
if (currentPage === "orders") {
|
||
openOrderForm();
|
||
}
|
||
});
|
||
|
||
// открываю всплывающее окно
|
||
function openModal(title, content) {
|
||
modalTitle.textContent = title; // пишу заголовок
|
||
modalForm.innerHTML = content; // рисую поля
|
||
modal.classList.remove("hidden"); // показываю
|
||
}
|
||
|
||
// закрываю окно
|
||
function closeModalWindow() {
|
||
modal.classList.add("hidden"); // прячу
|
||
modalForm.innerHTML = ""; // чищу
|
||
editingId = null; // забываю кого изменял
|
||
}
|
||
|
||
// закрытие по крестику и по темному фону
|
||
closeModal.addEventListener("click", closeModalWindow);
|
||
document.querySelector(".modal-overlay").addEventListener("click", closeModalWindow);
|
||
|
||
// форма товара
|
||
async function openProductForm(product = null) {
|
||
const categories = await api("/api/categories"); // беру категории для списка
|
||
let options = "";
|
||
for (const c of categories) {
|
||
const sel = product && product.categoryId === c.id ? "selected" : "";
|
||
options += "<option value=\"" + c.id + "\" " + sel + ">" + escapeHtml(c.name) + "</option>";
|
||
}
|
||
const title = product ? "Изменить товар" : "Добавить товар";
|
||
const name = product ? escapeHtml(product.name) : "";
|
||
const price = product ? product.price : "";
|
||
openModal(title,
|
||
"<div class=\"form\">" +
|
||
"<label>Название<input id=\"productName\" type=\"text\" value=\"" + name + "\"></label>" +
|
||
"<label>Цена<input id=\"productPrice\" type=\"number\" min=\"0.01\" step=\"0.01\" value=\"" + price + "\"></label>" +
|
||
"<label>Категория<select id=\"productCategory\">" + options + "</select></label>" +
|
||
"<div class=\"form-actions\"><button type=\"button\" class=\"secondary-button\" onclick=\"closeModalWindow()\">Отмена</button>" +
|
||
"<button type=\"button\" class=\"primary-button\" onclick=\"saveProduct()\">Сохранить</button></div></div>"
|
||
);
|
||
}
|
||
|
||
// сохраняю товар
|
||
async function saveProduct() {
|
||
const name = document.getElementById("productName").value.trim();
|
||
const price = Number(document.getElementById("productPrice").value);
|
||
const categoryId = Number(document.getElementById("productCategory").value);
|
||
if (!name || name.length < 2) {
|
||
showToast("Название слишком короткое.");
|
||
return;
|
||
}
|
||
if (!price || price <= 0) {
|
||
showToast("Цена должна быть больше 0.");
|
||
return;
|
||
}
|
||
try {
|
||
const data = { name, price, categoryId };
|
||
if (editingId) {
|
||
await api("/api/products/" + editingId, { method: "PUT", body: JSON.stringify(data) }); // изменяю
|
||
showToast("Товар изменен.");
|
||
} else {
|
||
await api("/api/products", { method: "POST", body: JSON.stringify(data) }); // добавляю
|
||
showToast("Товар добавлен.");
|
||
}
|
||
closeModalWindow();
|
||
showPage("products"); // обновляю страницу
|
||
} catch (error) {
|
||
showToast(error.message);
|
||
}
|
||
}
|
||
|
||
// жму изменить на товаре
|
||
async function editProduct(id) {
|
||
const products = await api("/api/products"); // беру все
|
||
const product = products.find((x) => x.id === id); // ищу нужный
|
||
if (!product) {
|
||
return;
|
||
}
|
||
editingId = id; // запоминаю кого меняю
|
||
await openProductForm(product);
|
||
}
|
||
|
||
// форма категории
|
||
function openCategoryForm(category = null) {
|
||
const title = category ? "Изменить категорию" : "Добавить категорию";
|
||
const name = category ? escapeHtml(category.name) : "";
|
||
openModal(title,
|
||
"<div class=\"form\">" +
|
||
"<label>Название<input id=\"categoryName\" type=\"text\" value=\"" + name + "\"></label>" +
|
||
"<div class=\"form-actions\"><button type=\"button\" class=\"secondary-button\" onclick=\"closeModalWindow()\">Отмена</button>" +
|
||
"<button type=\"button\" class=\"primary-button\" onclick=\"saveCategory()\">Сохранить</button></div></div>"
|
||
);
|
||
}
|
||
|
||
// сохраняю категорию
|
||
async function saveCategory() {
|
||
const name = document.getElementById("categoryName").value.trim();
|
||
if (!name) {
|
||
showToast("Напишите название.");
|
||
return;
|
||
}
|
||
try {
|
||
if (editingId) {
|
||
await api("/api/categories/" + editingId, { method: "PUT", body: JSON.stringify({ name }) });
|
||
showToast("Категория изменена.");
|
||
} else {
|
||
await api("/api/categories", { method: "POST", body: JSON.stringify({ name }) });
|
||
showToast("Категория добавлена.");
|
||
}
|
||
closeModalWindow();
|
||
showPage("categories");
|
||
} catch (error) {
|
||
showToast(error.message);
|
||
}
|
||
}
|
||
|
||
// жму изменить на категории
|
||
async function editCategory(id) {
|
||
const categories = await api("/api/categories");
|
||
const category = categories.find((x) => x.id === id);
|
||
if (!category) {
|
||
return;
|
||
}
|
||
editingId = id;
|
||
openCategoryForm(category);
|
||
}
|
||
|
||
// форма склада
|
||
function openWarehouseForm(warehouse = null) {
|
||
const title = warehouse ? "Изменить склад" : "Добавить склад";
|
||
const name = warehouse ? escapeHtml(warehouse.name) : "";
|
||
const address = warehouse ? escapeHtml(warehouse.address) : "";
|
||
openModal(title,
|
||
"<div class=\"form\">" +
|
||
"<label>Название<input id=\"warehouseName\" type=\"text\" value=\"" + name + "\"></label>" +
|
||
"<label>Адрес<input id=\"warehouseAddress\" type=\"text\" value=\"" + address + "\"></label>" +
|
||
"<div class=\"form-actions\"><button type=\"button\" class=\"secondary-button\" onclick=\"closeModalWindow()\">Отмена</button>" +
|
||
"<button type=\"button\" class=\"primary-button\" onclick=\"saveWarehouse()\">Сохранить</button></div></div>"
|
||
);
|
||
}
|
||
|
||
// сохраняю склад
|
||
async function saveWarehouse() {
|
||
const name = document.getElementById("warehouseName").value.trim();
|
||
const address = document.getElementById("warehouseAddress").value.trim();
|
||
if (!name || !address) {
|
||
showToast("Заполните все поля.");
|
||
return;
|
||
}
|
||
try {
|
||
const data = { name, address };
|
||
if (editingId) {
|
||
await api("/api/warehouses/" + editingId, { method: "PUT", body: JSON.stringify(data) });
|
||
showToast("Склад изменен.");
|
||
} else {
|
||
await api("/api/warehouses", { method: "POST", body: JSON.stringify(data) });
|
||
showToast("Склад добавлен.");
|
||
}
|
||
closeModalWindow();
|
||
showPage("warehouses");
|
||
} catch (error) {
|
||
showToast(error.message);
|
||
}
|
||
}
|
||
|
||
// жму изменить на складе
|
||
async function editWarehouse(id) {
|
||
const warehouses = await api("/api/warehouses");
|
||
const warehouse = warehouses.find((x) => x.id === id);
|
||
if (!warehouse) {
|
||
return;
|
||
}
|
||
editingId = id;
|
||
openWarehouseForm(warehouse);
|
||
}
|
||
|
||
// форма остатка
|
||
async function openStockForm(stock = null) {
|
||
const products = await api("/api/products"); // беру товары
|
||
const warehouses = await api("/api/warehouses"); // беру склады
|
||
let pOptions = "";
|
||
for (const p of products) {
|
||
const sel = stock && stock.productId === p.id ? "selected" : "";
|
||
pOptions += "<option value=\"" + p.id + "\" " + sel + ">" + escapeHtml(p.name) + "</option>";
|
||
}
|
||
let wOptions = "";
|
||
for (const w of warehouses) {
|
||
const sel = stock && stock.warehouseId === w.id ? "selected" : "";
|
||
wOptions += "<option value=\"" + w.id + "\" " + sel + ">" + escapeHtml(w.name) + "</option>";
|
||
}
|
||
const qty = stock ? stock.quantity : 0;
|
||
const title = stock ? "Изменить остаток" : "Добавить остаток";
|
||
openModal(title,
|
||
"<div class=\"form\">" +
|
||
"<label>Товар<select id=\"stockProduct\">" + pOptions + "</select></label>" +
|
||
"<label>Склад<select id=\"stockWarehouse\">" + wOptions + "</select></label>" +
|
||
"<label>Количество<input id=\"stockQuantity\" type=\"number\" min=\"0\" value=\"" + qty + "\"></label>" +
|
||
"<div class=\"form-actions\"><button type=\"button\" class=\"secondary-button\" onclick=\"closeModalWindow()\">Отмена</button>" +
|
||
"<button type=\"button\" class=\"primary-button\" onclick=\"saveStock()\">Сохранить</button></div></div>"
|
||
);
|
||
}
|
||
|
||
// сохраняю остаток
|
||
async function saveStock() {
|
||
const productId = Number(document.getElementById("stockProduct").value);
|
||
const warehouseId = Number(document.getElementById("stockWarehouse").value);
|
||
const quantity = Number(document.getElementById("stockQuantity").value);
|
||
if (quantity < 0) {
|
||
showToast("Количество не может быть минусом.");
|
||
return;
|
||
}
|
||
try {
|
||
const data = { productId, warehouseId, quantity };
|
||
if (editingId) {
|
||
await api("/api/stocks/" + editingId, { method: "PUT", body: JSON.stringify(data) });
|
||
showToast("Остаток изменен.");
|
||
} else {
|
||
await api("/api/stocks", { method: "POST", body: JSON.stringify(data) });
|
||
showToast("Остаток добавлен.");
|
||
}
|
||
closeModalWindow();
|
||
showPage("stock");
|
||
} catch (error) {
|
||
showToast(error.message);
|
||
}
|
||
}
|
||
|
||
// жму изменить на остатке
|
||
async function editStock(id) {
|
||
const stocks = await api("/api/stocks");
|
||
const stock = stocks.find((x) => x.id === id);
|
||
if (!stock) {
|
||
return;
|
||
}
|
||
editingId = id;
|
||
await openStockForm(stock);
|
||
}
|
||
|
||
// форма пользователя
|
||
async function openUserForm(user = null) {
|
||
const roles = await api("/api/roles"); // беру роли
|
||
let options = "";
|
||
for (const r of roles) {
|
||
const sel = user && user.roleId === r.id ? "selected" : "";
|
||
options += "<option value=\"" + r.id + "\" " + sel + ">" + escapeHtml(r.name) + "</option>";
|
||
}
|
||
const title = user ? "Изменить пользователя" : "Добавить пользователя";
|
||
const login = user ? escapeHtml(user.login) : "";
|
||
const password = user ? escapeHtml(user.password) : "";
|
||
openModal(title,
|
||
"<div class=\"form\">" +
|
||
"<label>Логин<input id=\"userLogin\" type=\"text\" value=\"" + login + "\"></label>" +
|
||
"<label>Пароль<input id=\"userPassword\" type=\"text\" value=\"" + password + "\"></label>" +
|
||
"<label>Роль<select id=\"userRole\">" + options + "</select></label>" +
|
||
"<div class=\"form-actions\"><button type=\"button\" class=\"secondary-button\" onclick=\"closeModalWindow()\">Отмена</button>" +
|
||
"<button type=\"button\" class=\"primary-button\" onclick=\"saveUser()\">Сохранить</button></div></div>"
|
||
);
|
||
}
|
||
|
||
// сохраняю пользователя
|
||
async function saveUser() {
|
||
const login = document.getElementById("userLogin").value.trim();
|
||
const password = document.getElementById("userPassword").value;
|
||
const roleId = Number(document.getElementById("userRole").value);
|
||
if (!login || !password) {
|
||
showToast("Заполните все поля.");
|
||
return;
|
||
}
|
||
try {
|
||
const data = { login, password, roleId };
|
||
if (editingId) {
|
||
await api("/api/users/" + editingId, { method: "PUT", body: JSON.stringify(data) });
|
||
showToast("Пользователь изменен.");
|
||
} else {
|
||
await api("/api/users", { method: "POST", body: JSON.stringify(data) });
|
||
showToast("Пользователь добавлен.");
|
||
}
|
||
closeModalWindow();
|
||
showPage("users");
|
||
} catch (error) {
|
||
showToast(error.message);
|
||
}
|
||
}
|
||
|
||
// жму изменить на пользователе
|
||
async function editUser(id) {
|
||
const users = await api("/api/users");
|
||
const user = users.find((x) => x.id === id);
|
||
if (!user) {
|
||
return;
|
||
}
|
||
editingId = id;
|
||
openUserForm(user); // тут без await специально, форма и так откроется
|
||
}
|
||
|
||
// форма заказа
|
||
async function openOrderForm(order = null) {
|
||
const users = await api("/api/users"); // беру людей для списка
|
||
let options = "";
|
||
for (const u of users) {
|
||
const sel = order && order.userId === u.id ? "selected" : "";
|
||
options += "<option value=\"" + u.id + "\" " + sel + ">" + escapeHtml(u.login) + "</option>";
|
||
}
|
||
// дата для поля (просто сегодня или дата заказа)
|
||
const dateValue = order ? new Date(order.date).toISOString().slice(0, 16) : new Date().toISOString().slice(0, 16);
|
||
const title = order ? "Изменить заказ" : "Добавить заказ";
|
||
// какой статус выбран
|
||
const st = order ? order.status : "New";
|
||
openModal(title,
|
||
"<div class=\"form\">" +
|
||
"<label>Пользователь<select id=\"orderUser\">" + options + "</select></label>" +
|
||
"<label>Дата<input id=\"orderDate\" type=\"datetime-local\" value=\"" + dateValue + "\"></label>" +
|
||
"<label>Статус<select id=\"orderStatus\">" +
|
||
"<option value=\"New\"" + (st === "New" ? " selected" : "") + ">New</option>" +
|
||
"<option value=\"Processing\"" + (st === "Processing" ? " selected" : "") + ">Processing</option>" +
|
||
"<option value=\"Completed\"" + (st === "Completed" ? " selected" : "") + ">Completed</option>" +
|
||
"<option value=\"Cancelled\"" + (st === "Cancelled" ? " selected" : "") + ">Cancelled</option>" +
|
||
"</select></label>" +
|
||
"<div class=\"form-actions\"><button type=\"button\" class=\"secondary-button\" onclick=\"closeModalWindow()\">Отмена</button>" +
|
||
"<button type=\"button\" class=\"primary-button\" onclick=\"saveOrder()\">Сохранить</button></div></div>"
|
||
);
|
||
}
|
||
|
||
// сохраняю заказ
|
||
async function saveOrder() {
|
||
const userId = Number(document.getElementById("orderUser").value);
|
||
const date = document.getElementById("orderDate").value;
|
||
const status = document.getElementById("orderStatus").value;
|
||
if (!date) {
|
||
showToast("Выберите дату.");
|
||
return;
|
||
}
|
||
try {
|
||
const data = { userId, date: new Date(date).toISOString(), status };
|
||
if (editingId) {
|
||
await api("/api/orders/" + editingId, { method: "PUT", body: JSON.stringify(data) });
|
||
showToast("Заказ изменен.");
|
||
} else {
|
||
await api("/api/orders", { method: "POST", body: JSON.stringify(data) });
|
||
showToast("Заказ добавлен.");
|
||
}
|
||
closeModalWindow();
|
||
showPage("orders");
|
||
} catch (error) {
|
||
showToast(error.message);
|
||
}
|
||
}
|
||
|
||
// жму изменить на заказе
|
||
async function editOrder(id) {
|
||
const orders = await api("/api/orders");
|
||
const order = orders.find((x) => x.id === id);
|
||
if (!order) {
|
||
return;
|
||
}
|
||
editingId = id;
|
||
await openOrderForm(order);
|
||
}
|
||
|
||
// показываю товары внутри заказа
|
||
async function showOrderItems(orderId) {
|
||
try {
|
||
const items = await api("/api/orderitems"); // беру все позиции
|
||
const products = await api("/api/products"); // беру товары чтоб узнать названия
|
||
const orderItems = items.filter((i) => i.orderId === orderId); // оставляю только этот заказ
|
||
let html = "<div>";
|
||
if (orderItems.length === 0) {
|
||
html += "<div class=\"empty-page\"><strong>Тут пока пусто</strong></div>"; // если ничего нет
|
||
} else {
|
||
for (const item of orderItems) {
|
||
const prod = products.find((x) => x.id === item.productId); // ищу название товара
|
||
const pname = prod ? prod.name : "Товар";
|
||
html += "<div class=\"order-item\"><div class=\"order-item-info\"><strong>" + escapeHtml(pname) + "</strong><span>Кол-во: " + item.quantity + "</span></div>";
|
||
html += "<strong>" + formatMoney(item.price) + "</strong></div>";
|
||
}
|
||
}
|
||
html += "</div><div class=\"form-actions\"><button type=\"button\" class=\"primary-button\" onclick=\"openOrderItemForm(" + orderId + ")\">+ Добавить товар</button></div>";
|
||
openModal("Товары заказа #" + orderId, html);
|
||
} catch (error) {
|
||
showToast(error.message);
|
||
}
|
||
}
|
||
|
||
// форма добавить товар в заказ
|
||
async function openOrderItemForm(orderId) {
|
||
const products = await api("/api/products");
|
||
let options = "";
|
||
for (const p of products) {
|
||
options += "<option value=\"" + p.id + "\">" + escapeHtml(p.name) + "</option>";
|
||
}
|
||
openModal("Товар в заказ #" + orderId,
|
||
"<div class=\"form\">" +
|
||
"<label>Товар<select id=\"orderItemProduct\">" + options + "</select></label>" +
|
||
"<label>Количество<input id=\"orderItemQuantity\" type=\"number\" min=\"1\" value=\"1\"></label>" +
|
||
"<label>Цена<input id=\"orderItemPrice\" type=\"number\" min=\"0.01\" step=\"0.01\" value=\"0\"></label>" +
|
||
"<div class=\"form-actions\"><button type=\"button\" class=\"secondary-button\" onclick=\"showOrderItems(" + orderId + ")\">Назад</button>" +
|
||
"<button type=\"button\" class=\"primary-button\" onclick=\"saveOrderItem(" + orderId + ")\">Добавить</button></div></div>"
|
||
);
|
||
}
|
||
|
||
// сохраняю товар в заказ
|
||
async function saveOrderItem(orderId) {
|
||
const productId = Number(document.getElementById("orderItemProduct").value);
|
||
const quantity = Number(document.getElementById("orderItemQuantity").value);
|
||
const price = Number(document.getElementById("orderItemPrice").value);
|
||
if (quantity <= 0) {
|
||
showToast("Количество должно быть больше 0.");
|
||
return;
|
||
}
|
||
if (price <= 0) {
|
||
showToast("Цена должна быть больше 0.");
|
||
return;
|
||
}
|
||
try {
|
||
await api("/api/orderitems", {
|
||
method: "POST",
|
||
body: JSON.stringify({ orderId, productId, quantity, price })
|
||
});
|
||
showToast("Товар добавлен.");
|
||
showOrderItems(orderId); // обновляю список
|
||
} catch (error) {
|
||
showToast(error.message);
|
||
}
|
||
}
|
||
|
||
// удаляю что угодно (товар, заказ и тд)
|
||
async function deleteItem(type, id) {
|
||
// красивые названия для вопроса
|
||
const names = {
|
||
products: "товар",
|
||
categories: "категорию",
|
||
warehouses: "склад",
|
||
stock: "остаток",
|
||
users: "пользователя",
|
||
orders: "заказ"
|
||
};
|
||
// откуда удалять
|
||
const endpoints = {
|
||
products: "/api/products",
|
||
categories: "/api/categories",
|
||
warehouses: "/api/warehouses",
|
||
stock: "/api/stocks",
|
||
users: "/api/users",
|
||
orders: "/api/orders"
|
||
};
|
||
const name = names[type] || "запись";
|
||
if (!confirm("Удалить " + name + "?")) { // спрашиваю точно ли надо
|
||
return;
|
||
}
|
||
try {
|
||
await api(endpoints[type] + "/" + id, { method: "DELETE" }); // удаляю
|
||
showToast("Удалено.");
|
||
showPage(currentPage); // обновляю страницу
|
||
} catch (error) {
|
||
showToast(error.message);
|
||
}
|
||
}
|
||
|
||
// при запуске смотрю входил ли уже
|
||
const savedUser = sessionStorage.getItem("demoUser");
|
||
if (savedUser) {
|
||
try {
|
||
const user = JSON.parse(savedUser);
|
||
if (user && user.login) {
|
||
enterApplication(user); // да, уже входил, сразу захожу
|
||
} else {
|
||
app.classList.add("hidden");
|
||
authScreen.classList.remove("hidden");
|
||
showLoginForm();
|
||
}
|
||
} catch {
|
||
sessionStorage.removeItem("demoUser");
|
||
app.classList.add("hidden");
|
||
authScreen.classList.remove("hidden");
|
||
showLoginForm();
|
||
}
|
||
} else {
|
||
// нет, показываю вход
|
||
app.classList.add("hidden");
|
||
authScreen.classList.remove("hidden");
|
||
showLoginForm();
|
||
}
|