DemO/DEMO/wwwroot/script.js

994 lines
42 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// нахожу экран входа и главное окно
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 searchInput = document.getElementById("searchInput"); // поле поиска
// всплывающее окно
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;
// я админ или нет (у админа больше кнопок)
let isAdmin = false;
// данные текущей страницы, чтоб искать по ним
let currentData = [];
// проверяю админ ли я, если нет то ругаюсь и не пускаю
function needAdmin() {
if (isAdmin) {
return true;
}
showToast("Только админ может это делать.");
return false;
}
// проверяю почту: есть @ и точка после нее
function isEmailOk(email) {
const at = email.indexOf("@"); // ищу собаку
if (at < 1) {
return false;
}
const dot = email.indexOf(".", at); // ищу точку после @
return dot > at + 1 && dot < email.length - 1;
}
// показываю сообщение снизу на 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("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
}
// делаю цену красивой: 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; // запоминаю кто вошел
isAdmin = user.role === "Admin"; // проверяю админ ли я
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"; // первая буква в кружок
// кнопку Пользователи показываю только админу
document.querySelector('.nav-button[data-page="users"]').classList.toggle("hidden", !isAdmin);
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;
const email = document.getElementById("registerEmail").value.trim();
// простые проверки
if (!login || !password || !passwordRepeat || !email) {
registerError.textContent = "Заполните все поля.";
return;
}
if (login.length < 2) {
registerError.textContent = "Логин слишком короткий.";
return;
}
if (password.length < 8) {
registerError.textContent = "Пароль должен быть минимум 8 символов.";
return;
}
if (!isEmailOk(email)) {
registerError.textContent = "Почта неправильная, проверьте ее.";
return;
}
if (password !== passwordRepeat) {
registerError.textContent = "Пароли не совпадают.";
return;
}
try {
await api("/api/auth/register", {
method: "POST",
body: JSON.stringify({ login, password, email, 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) {
// в пользователи пускаю только админа
if (page === "users" && !isAdmin) {
pageTitle.textContent = "Пользователи";
pageDescription.textContent = "Все люди в системе";
pageContent.innerHTML = "<div class='empty-page'><strong>Нет прав</strong><span>Только админ видит эту страницу</span></div>";
return;
}
currentPage = page; // запоминаю где я
const config = pages[page];
if (!config) {
return;
}
pageTitle.textContent = config.title; // пишу название
pageDescription.textContent = config.description;
// кнопку добавить показываю админу везде, а обычному только в заказах
addButton.classList.toggle("hidden", !(isAdmin || page === "orders"));
searchInput.value = ""; // чищу поиск
pageContent.innerHTML = "<div class='empty-page'><strong>Загрузка...</strong></div>"; // пока грузится
try {
const data = await api(config.endpoint); // беру данные с сервера
currentData = data; // запоминаю чтоб искать по ним
renderPage(page, data); // рисую
} catch (error) {
pageContent.innerHTML = "<div class='error-page'>Не получилось: " + escapeHtml(error.message) + "</div>";
}
}
// ищу по странице когда печатаю в поиск
searchInput.addEventListener("input", () => {
const q = searchInput.value.trim().toLowerCase(); // что ищу
if (!q) {
renderPage(currentPage, currentData); // пусто показываю все
return;
}
// ищу совпадение где угодно (название, логин, адрес и тд)
const found = currentData.filter((item) => JSON.stringify(item).toLowerCase().includes(q));
if (found.length === 0) {
pageContent.innerHTML = "<div class='empty-page'><strong>Ничего не нашлось</strong></div>";
return;
}
renderPage(currentPage, found); // рисую найденное
});
// выбираю что рисовать
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>";
// кнопки показываю только админу
if (isAdmin) {
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>";
}
html += "</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\">";
// кнопки только для админа
if (isAdmin) {
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\">";
// кнопки только для админа
if (isAdmin) {
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\">";
// кнопки только для админа
if (isAdmin) {
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) {
// сюда пускаю только админа (на всякий случай)
if (!isAdmin) {
pageContent.innerHTML = "<div class='empty-page'><strong>Нет прав</strong></div>";
return;
}
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 u of users) {
const rname = u.role ? u.role.name : "—";
html += "<tr><td>#" + u.id + "</td><td>" + escapeHtml(u.login) + "</td><td>" + escapeHtml(u.email || "—") + "</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>";
// менять и удалять заказы может только админ, смотреть товары могут все
if (isAdmin) {
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 !== "orders" && !needAdmin()) {
return;
}
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) {
if (!needAdmin()) { // только админ
return;
}
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) {
if (!needAdmin()) { // только админ
return;
}
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) {
if (!needAdmin()) { // только админ
return;
}
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) {
if (!needAdmin()) { // только админ
return;
}
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) : "";
const email = user ? escapeHtml(user.email || "") : "";
openModal(title,
"<div class=\"form\">" +
"<label>Логин<input id=\"userLogin\" type=\"text\" value=\"" + login + "\"></label>" +
"<label>Пароль<input id=\"userPassword\" type=\"text\" value=\"" + password + "\"></label>" +
"<label>Почта<input id=\"userEmail\" type=\"email\" value=\"" + email + "\"></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 email = document.getElementById("userEmail").value.trim();
const roleId = Number(document.getElementById("userRole").value);
if (!login || !password || !email) {
showToast("Заполните все поля.");
return;
}
if (password.length < 8) {
showToast("Пароль должен быть минимум 8 символов.");
return;
}
if (!isEmailOk(email)) {
showToast("Почта неправильная, проверьте ее.");
return;
}
try {
const data = { login, password, email, 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) {
if (!needAdmin()) { // только админ
return;
}
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) {
if (!needAdmin()) { // только админ
return;
}
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) {
if (!needAdmin()) { // удалять может только админ
return;
}
// красивые названия для вопроса
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();
}