Поиск, права админ клиент, почта, пароль от 8 символов
This commit is contained in:
parent
f09fa1ecc9
commit
33a8506f9b
|
|
@ -18,6 +18,22 @@ public class AuthController : ControllerBase
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// простая проверка почты: есть @ и точка после нее
|
||||||
|
private static bool IsEmailOk(string email)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(email))
|
||||||
|
return false; // пустая не годится
|
||||||
|
|
||||||
|
int at = email.IndexOf('@'); // ищу собаку
|
||||||
|
|
||||||
|
if (at < 1)
|
||||||
|
return false; // @ нет или стоит первой
|
||||||
|
|
||||||
|
int dot = email.IndexOf('.', at); // ищу точку после @
|
||||||
|
|
||||||
|
return dot > at + 1 && dot < email.Length - 1; // точка не сразу и не в конце
|
||||||
|
}
|
||||||
|
|
||||||
// войти, адрес POST api/auth/login
|
// войти, адрес POST api/auth/login
|
||||||
[HttpPost("login")]
|
[HttpPost("login")]
|
||||||
public async Task<IActionResult> Login(User user) // приходят логин и пароль
|
public async Task<IActionResult> Login(User user) // приходят логин и пароль
|
||||||
|
|
@ -38,21 +54,43 @@ public class AuthController : ControllerBase
|
||||||
{
|
{
|
||||||
existingUser.Id,
|
existingUser.Id,
|
||||||
existingUser.Login,
|
existingUser.Login,
|
||||||
|
existingUser.Email,
|
||||||
Role = existingUser.Role?.Name
|
Role = existingUser.Role?.Name
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// регистрация, адрес POST api/auth/register
|
// регистрация, адрес POST api/auth/register
|
||||||
[HttpPost("register")]
|
[HttpPost("register")]
|
||||||
public async Task<IActionResult> Register(User user) // приходят логин и пароль
|
public async Task<IActionResult> Register(User user) // приходят логин пароль и почта
|
||||||
{
|
{
|
||||||
|
// чищу пробелы
|
||||||
|
user.Login = (user.Login ?? "").Trim();
|
||||||
|
user.Email = (user.Email ?? "").Trim();
|
||||||
|
|
||||||
|
// проверки по порядку, чтоб человек понял что не так
|
||||||
|
if (user.Login.Length < 2)
|
||||||
|
return BadRequest("Логин слишком короткий, надо минимум 2 буквы.");
|
||||||
|
|
||||||
|
if (user.Password == null || user.Password.Length < 8)
|
||||||
|
return BadRequest("Пароль слишком короткий, надо минимум 8 символов.");
|
||||||
|
|
||||||
|
if (!IsEmailOk(user.Email))
|
||||||
|
return BadRequest("Почта неправильная, проверьте ее.");
|
||||||
|
|
||||||
// проверяю нет ли уже такого логина
|
// проверяю нет ли уже такого логина
|
||||||
var exists = await _context.Users
|
var loginExists = await _context.Users
|
||||||
.AnyAsync(u => u.Login == user.Login);
|
.AnyAsync(u => u.Login == user.Login);
|
||||||
|
|
||||||
if (exists)
|
if (loginExists)
|
||||||
return BadRequest("Такой логин уже занят.");
|
return BadRequest("Такой логин уже занят.");
|
||||||
|
|
||||||
|
// проверяю не занята ли почта
|
||||||
|
var emailExists = await _context.Users
|
||||||
|
.AnyAsync(u => u.Email == user.Email);
|
||||||
|
|
||||||
|
if (emailExists)
|
||||||
|
return BadRequest("Такая почта уже зарегистрирована.");
|
||||||
|
|
||||||
// ищу роль клиент чтоб дать ее новичку
|
// ищу роль клиент чтоб дать ее новичку
|
||||||
var clientRole = await _context.Roles
|
var clientRole = await _context.Roles
|
||||||
.FirstOrDefaultAsync(r => r.Name == "Client");
|
.FirstOrDefaultAsync(r => r.Name == "Client");
|
||||||
|
|
@ -70,6 +108,7 @@ public class AuthController : ControllerBase
|
||||||
{
|
{
|
||||||
user.Id,
|
user.Id,
|
||||||
user.Login,
|
user.Login,
|
||||||
|
user.Email,
|
||||||
Role = clientRole.Name
|
Role = clientRole.Name
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,11 @@ public class CategoriesController : ControllerBase
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> CreateCategory(Category category) // приходит название
|
public async Task<IActionResult> CreateCategory(Category category) // приходит название
|
||||||
{
|
{
|
||||||
|
category.Name = (category.Name ?? "").Trim(); // убираю пробелы
|
||||||
|
|
||||||
|
if (category.Name.Length < 2)
|
||||||
|
return BadRequest("Название слишком короткое, надо минимум 2 буквы.");
|
||||||
|
|
||||||
_context.Categories.Add(category); // добавляю
|
_context.Categories.Add(category); // добавляю
|
||||||
await _context.SaveChangesAsync(); // сохраняю
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
return Ok(category);
|
return Ok(category);
|
||||||
|
|
@ -44,6 +49,11 @@ public class CategoriesController : ControllerBase
|
||||||
if (existingCategory == null)
|
if (existingCategory == null)
|
||||||
return NotFound(); // нет такой
|
return NotFound(); // нет такой
|
||||||
|
|
||||||
|
category.Name = (category.Name ?? "").Trim();
|
||||||
|
|
||||||
|
if (category.Name.Length < 2)
|
||||||
|
return BadRequest("Название слишком короткое, надо минимум 2 буквы.");
|
||||||
|
|
||||||
existingCategory.Name = category.Name; // меняю название
|
existingCategory.Name = category.Name; // меняю название
|
||||||
await _context.SaveChangesAsync(); // сохраняю
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,12 @@ public class OrderItemsController : ControllerBase
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> CreateOrderItem(OrderItem orderItem) // приходит заказ товар колво цена
|
public async Task<IActionResult> CreateOrderItem(OrderItem orderItem) // приходит заказ товар колво цена
|
||||||
{
|
{
|
||||||
|
if (orderItem.Quantity <= 0)
|
||||||
|
return BadRequest("Количество должно быть больше нуля.");
|
||||||
|
|
||||||
|
if (orderItem.Price <= 0)
|
||||||
|
return BadRequest("Цена должна быть больше нуля.");
|
||||||
|
|
||||||
_context.OrderItems.Add(orderItem); // добавляю
|
_context.OrderItems.Add(orderItem); // добавляю
|
||||||
await _context.SaveChangesAsync(); // сохраняю
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
return Ok(orderItem);
|
return Ok(orderItem);
|
||||||
|
|
@ -49,6 +55,12 @@ public class OrderItemsController : ControllerBase
|
||||||
if (existingItem == null)
|
if (existingItem == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
|
if (orderItem.Quantity <= 0)
|
||||||
|
return BadRequest("Количество должно быть больше нуля.");
|
||||||
|
|
||||||
|
if (orderItem.Price <= 0)
|
||||||
|
return BadRequest("Цена должна быть больше нуля.");
|
||||||
|
|
||||||
// меняю поля
|
// меняю поля
|
||||||
existingItem.OrderId = orderItem.OrderId;
|
existingItem.OrderId = orderItem.OrderId;
|
||||||
existingItem.ProductId = orderItem.ProductId;
|
existingItem.ProductId = orderItem.ProductId;
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,16 @@ public class OrdersController : ControllerBase
|
||||||
return Ok(orders);
|
return Ok(orders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// какие статусы можно ставить
|
||||||
|
private static readonly string[] OkStatuses = ["New", "Processing", "Completed", "Cancelled"];
|
||||||
|
|
||||||
// добавить заказ
|
// добавить заказ
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> CreateOrder(Order order) // приходит человек дата статус
|
public async Task<IActionResult> CreateOrder(Order order) // приходит человек дата статус
|
||||||
{
|
{
|
||||||
|
if (!OkStatuses.Contains(order.Status))
|
||||||
|
return BadRequest("Такого статуса нет.");
|
||||||
|
|
||||||
_context.Orders.Add(order); // добавляю
|
_context.Orders.Add(order); // добавляю
|
||||||
await _context.SaveChangesAsync(); // сохраняю
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
return Ok(order);
|
return Ok(order);
|
||||||
|
|
@ -48,6 +54,9 @@ public class OrdersController : ControllerBase
|
||||||
if (existingOrder == null)
|
if (existingOrder == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
|
if (!OkStatuses.Contains(order.Status))
|
||||||
|
return BadRequest("Такого статуса нет.");
|
||||||
|
|
||||||
// меняю поля
|
// меняю поля
|
||||||
existingOrder.UserId = order.UserId;
|
existingOrder.UserId = order.UserId;
|
||||||
existingOrder.Date = order.Date;
|
existingOrder.Date = order.Date;
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,14 @@ public class ProductsController : ControllerBase
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> CreateProduct(Product product) // приходит товар
|
public async Task<IActionResult> CreateProduct(Product product) // приходит товар
|
||||||
{
|
{
|
||||||
|
product.Name = (product.Name ?? "").Trim(); // убираю пробелы
|
||||||
|
|
||||||
|
if (product.Name.Length < 2)
|
||||||
|
return BadRequest("Название слишком короткое, надо минимум 2 буквы.");
|
||||||
|
|
||||||
|
if (product.Price <= 0)
|
||||||
|
return BadRequest("Цена должна быть больше нуля.");
|
||||||
|
|
||||||
// проверяю есть ли такая категория
|
// проверяю есть ли такая категория
|
||||||
var categoryExists = await _context.Categories
|
var categoryExists = await _context.Categories
|
||||||
.AnyAsync(c => c.Id == product.CategoryId);
|
.AnyAsync(c => c.Id == product.CategoryId);
|
||||||
|
|
@ -57,6 +65,14 @@ public class ProductsController : ControllerBase
|
||||||
if (existingProduct == null)
|
if (existingProduct == null)
|
||||||
return NotFound(); // нет такого
|
return NotFound(); // нет такого
|
||||||
|
|
||||||
|
product.Name = (product.Name ?? "").Trim();
|
||||||
|
|
||||||
|
if (product.Name.Length < 2)
|
||||||
|
return BadRequest("Название слишком короткое, надо минимум 2 буквы.");
|
||||||
|
|
||||||
|
if (product.Price <= 0)
|
||||||
|
return BadRequest("Цена должна быть больше нуля.");
|
||||||
|
|
||||||
// меняю поля
|
// меняю поля
|
||||||
existingProduct.Name = product.Name;
|
existingProduct.Name = product.Name;
|
||||||
existingProduct.Price = product.Price;
|
existingProduct.Price = product.Price;
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ public class StocksController : ControllerBase
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> CreateStock(Stock stock) // приходит товар склад и количество
|
public async Task<IActionResult> CreateStock(Stock stock) // приходит товар склад и количество
|
||||||
{
|
{
|
||||||
|
if (stock.Quantity < 0)
|
||||||
|
return BadRequest("Количество не может быть меньше нуля.");
|
||||||
|
|
||||||
_context.Stocks.Add(stock); // добавляю
|
_context.Stocks.Add(stock); // добавляю
|
||||||
await _context.SaveChangesAsync(); // сохраняю
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
return Ok(stock);
|
return Ok(stock);
|
||||||
|
|
@ -49,6 +52,9 @@ public class StocksController : ControllerBase
|
||||||
if (existingStock == null)
|
if (existingStock == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
|
if (stock.Quantity < 0)
|
||||||
|
return BadRequest("Количество не может быть меньше нуля.");
|
||||||
|
|
||||||
// меняю поля
|
// меняю поля
|
||||||
existingStock.ProductId = stock.ProductId;
|
existingStock.ProductId = stock.ProductId;
|
||||||
existingStock.WarehouseId = stock.WarehouseId;
|
existingStock.WarehouseId = stock.WarehouseId;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,54 @@ public class UsersController : ControllerBase
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// простая проверка почты: есть @ и точка после нее
|
||||||
|
private static bool IsEmailOk(string email)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(email))
|
||||||
|
return false; // пустая не годится
|
||||||
|
|
||||||
|
int at = email.IndexOf('@'); // ищу собаку
|
||||||
|
|
||||||
|
if (at < 1)
|
||||||
|
return false; // @ нет или стоит первой
|
||||||
|
|
||||||
|
int dot = email.IndexOf('.', at); // ищу точку после @
|
||||||
|
|
||||||
|
return dot > at + 1 && dot < email.Length - 1; // точка не сразу и не в конце
|
||||||
|
}
|
||||||
|
|
||||||
|
// проверяю человека перед сохранением, возвращаю текст ошибки или пусто
|
||||||
|
private async Task<string> CheckUser(User user, int skipId)
|
||||||
|
{
|
||||||
|
user.Login = (user.Login ?? "").Trim(); // убираю пробелы
|
||||||
|
user.Email = (user.Email ?? "").Trim();
|
||||||
|
|
||||||
|
if (user.Login.Length < 2)
|
||||||
|
return "Логин слишком короткий, надо минимум 2 буквы.";
|
||||||
|
|
||||||
|
if (user.Password == null || user.Password.Length < 8)
|
||||||
|
return "Пароль слишком короткий, надо минимум 8 символов.";
|
||||||
|
|
||||||
|
if (!IsEmailOk(user.Email))
|
||||||
|
return "Почта неправильная, проверьте ее.";
|
||||||
|
|
||||||
|
// логин должен быть только у одного (себя не считаю)
|
||||||
|
bool loginBusy = await _context.Users
|
||||||
|
.AnyAsync(u => u.Login == user.Login && u.Id != skipId);
|
||||||
|
|
||||||
|
if (loginBusy)
|
||||||
|
return "Такой логин уже занят.";
|
||||||
|
|
||||||
|
// почта тоже только у одного
|
||||||
|
bool emailBusy = await _context.Users
|
||||||
|
.AnyAsync(u => u.Email == user.Email && u.Id != skipId);
|
||||||
|
|
||||||
|
if (emailBusy)
|
||||||
|
return "Такая почта уже зарегистрирована.";
|
||||||
|
|
||||||
|
return ""; // все хорошо
|
||||||
|
}
|
||||||
|
|
||||||
// отдать всех людей
|
// отдать всех людей
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> GetUsers()
|
public async Task<IActionResult> GetUsers()
|
||||||
|
|
@ -32,8 +80,13 @@ public class UsersController : ControllerBase
|
||||||
|
|
||||||
// добавить человека
|
// добавить человека
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> CreateUser(User user) // приходит логин пароль роль
|
public async Task<IActionResult> CreateUser(User user) // приходит логин пароль почта роль
|
||||||
{
|
{
|
||||||
|
string error = await CheckUser(user, 0); // проверяю, 0 значит нового
|
||||||
|
|
||||||
|
if (error != "")
|
||||||
|
return BadRequest(error); // что-то не так, говорю что
|
||||||
|
|
||||||
_context.Users.Add(user); // добавляю
|
_context.Users.Add(user); // добавляю
|
||||||
await _context.SaveChangesAsync(); // сохраняю
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
return Ok(user);
|
return Ok(user);
|
||||||
|
|
@ -48,9 +101,15 @@ public class UsersController : ControllerBase
|
||||||
if (existingUser == null)
|
if (existingUser == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
|
string error = await CheckUser(user, id); // проверяю, себя пропускаю
|
||||||
|
|
||||||
|
if (error != "")
|
||||||
|
return BadRequest(error);
|
||||||
|
|
||||||
// меняю поля
|
// меняю поля
|
||||||
existingUser.Login = user.Login;
|
existingUser.Login = user.Login;
|
||||||
existingUser.Password = user.Password;
|
existingUser.Password = user.Password;
|
||||||
|
existingUser.Email = user.Email;
|
||||||
existingUser.RoleId = user.RoleId;
|
existingUser.RoleId = user.RoleId;
|
||||||
|
|
||||||
await _context.SaveChangesAsync(); // сохраняю
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,15 @@ public class WarehousesController : ControllerBase
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> CreateWarehouse(Warehouse warehouse) // приходит название и адрес
|
public async Task<IActionResult> CreateWarehouse(Warehouse warehouse) // приходит название и адрес
|
||||||
{
|
{
|
||||||
|
warehouse.Name = (warehouse.Name ?? "").Trim();
|
||||||
|
warehouse.Address = (warehouse.Address ?? "").Trim();
|
||||||
|
|
||||||
|
if (warehouse.Name.Length < 2)
|
||||||
|
return BadRequest("Название склада слишком короткое.");
|
||||||
|
|
||||||
|
if (warehouse.Address.Length < 3)
|
||||||
|
return BadRequest("Адрес слишком короткий.");
|
||||||
|
|
||||||
_context.Warehouses.Add(warehouse); // добавляю
|
_context.Warehouses.Add(warehouse); // добавляю
|
||||||
await _context.SaveChangesAsync(); // сохраняю
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
return Ok(warehouse);
|
return Ok(warehouse);
|
||||||
|
|
@ -44,6 +53,15 @@ public class WarehousesController : ControllerBase
|
||||||
if (existingWarehouse == null)
|
if (existingWarehouse == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
|
warehouse.Name = (warehouse.Name ?? "").Trim();
|
||||||
|
warehouse.Address = (warehouse.Address ?? "").Trim();
|
||||||
|
|
||||||
|
if (warehouse.Name.Length < 2)
|
||||||
|
return BadRequest("Название склада слишком короткое.");
|
||||||
|
|
||||||
|
if (warehouse.Address.Length < 3)
|
||||||
|
return BadRequest("Адрес слишком короткий.");
|
||||||
|
|
||||||
// меняю поля
|
// меняю поля
|
||||||
existingWarehouse.Name = warehouse.Name;
|
existingWarehouse.Name = warehouse.Name;
|
||||||
existingWarehouse.Address = warehouse.Address;
|
existingWarehouse.Address = warehouse.Address;
|
||||||
|
|
|
||||||
BIN
DEMO/DemoDB.db-shm
Normal file
BIN
DEMO/DemoDB.db-shm
Normal file
Binary file not shown.
BIN
DEMO/DemoDB.db-wal
Normal file
BIN
DEMO/DemoDB.db-wal
Normal file
Binary file not shown.
270
DEMO/Migrations/20260909001244_AddUserEmail.Designer.cs
generated
Normal file
270
DEMO/Migrations/20260909001244_AddUserEmail.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,270 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using DEMO.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DEMO.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260909001244_AddUserEmail")]
|
||||||
|
partial class AddUserEmail
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "10.0.12");
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Category", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Categories");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Order", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Date")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("Orders");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("OrderId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal>("Price")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("ProductId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.ToTable("OrderItems");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("CategoryId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("Price")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CategoryId");
|
||||||
|
|
||||||
|
b.ToTable("Products");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Role", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Roles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Stock", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ProductId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("WarehouseId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.HasIndex("WarehouseId");
|
||||||
|
|
||||||
|
b.ToTable("Stocks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Login")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("RoleId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Warehouse", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Address")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Warehouses");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Order", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Order", "Order")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("DEMO.Models.Product", "Product")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Category", "Category")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CategoryId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Category");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Stock", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Product", "Product")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("DEMO.Models.Warehouse", "Warehouse")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("WarehouseId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
|
||||||
|
b.Navigation("Warehouse");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Role", "Role")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Role");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
DEMO/Migrations/20260909001244_AddUserEmail.cs
Normal file
29
DEMO/Migrations/20260909001244_AddUserEmail.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DEMO.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddUserEmail : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Email",
|
||||||
|
table: "Users",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Email",
|
||||||
|
table: "Users");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -150,6 +150,10 @@ namespace DEMO.Migrations
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("Login")
|
b.Property<string>("Login")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ public class User
|
||||||
|
|
||||||
public string Password { get; set; } = string.Empty; // пароль
|
public string Password { get; set; } = string.Empty; // пароль
|
||||||
|
|
||||||
|
public string Email { get; set; } = string.Empty; // почта
|
||||||
|
|
||||||
public int RoleId { get; set; } // какая роль (номер)
|
public int RoleId { get; set; } // какая роль (номер)
|
||||||
|
|
||||||
public Role? Role { get; set; } // сама роль (подтянется)
|
public Role? Role { get; set; } // сама роль (подтянется)
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,11 @@
|
||||||
<input id="registerPasswordRepeat" type="password" placeholder="Еще раз пароль" required>
|
<input id="registerPasswordRepeat" type="password" placeholder="Еще раз пароль" required>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Почта
|
||||||
|
<input id="registerEmail" type="email" placeholder="Ваша почта" required>
|
||||||
|
</label>
|
||||||
|
|
||||||
<!-- сюда пишу ошибку регистрации -->
|
<!-- сюда пишу ошибку регистрации -->
|
||||||
<div id="registerError" class="auth-error"></div>
|
<div id="registerError" class="auth-error"></div>
|
||||||
|
|
||||||
|
|
@ -128,6 +133,9 @@
|
||||||
<p id="pageDescription">Управление товарами магазина</p>
|
<p id="pageDescription">Управление товарами магазина</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- поиск по странице -->
|
||||||
|
<input id="searchInput" class="search-input" type="text" placeholder="Поиск...">
|
||||||
|
|
||||||
<!-- кнопка добавить новое -->
|
<!-- кнопка добавить новое -->
|
||||||
<button id="addButton" class="primary-button">+ Добавить</button>
|
<button id="addButton" class="primary-button">+ Добавить</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ const pageTitle = document.getElementById("pageTitle");
|
||||||
const pageDescription = document.getElementById("pageDescription");
|
const pageDescription = document.getElementById("pageDescription");
|
||||||
const pageContent = document.getElementById("pageContent");
|
const pageContent = document.getElementById("pageContent");
|
||||||
const addButton = document.getElementById("addButton");
|
const addButton = document.getElementById("addButton");
|
||||||
|
const searchInput = document.getElementById("searchInput"); // поле поиска
|
||||||
|
|
||||||
// всплывающее окно
|
// всплывающее окно
|
||||||
const modal = document.getElementById("modal");
|
const modal = document.getElementById("modal");
|
||||||
|
|
@ -55,6 +56,29 @@ let currentPage = "products";
|
||||||
let editingId = null;
|
let editingId = null;
|
||||||
// кто вошел в систему
|
// кто вошел в систему
|
||||||
let currentAuthUser = 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 секунды
|
// показываю сообщение снизу на 2 секунды
|
||||||
function showToast(message) {
|
function showToast(message) {
|
||||||
|
|
@ -145,10 +169,13 @@ function showRegisterForm() {
|
||||||
// захожу в программу после входа
|
// захожу в программу после входа
|
||||||
function enterApplication(user) {
|
function enterApplication(user) {
|
||||||
currentAuthUser = user; // запоминаю кто вошел
|
currentAuthUser = user; // запоминаю кто вошел
|
||||||
|
isAdmin = user.role === "Admin"; // проверяю админ ли я
|
||||||
sessionStorage.setItem("demoUser", JSON.stringify(user)); // сохраняю чтоб не выкидывало
|
sessionStorage.setItem("demoUser", JSON.stringify(user)); // сохраняю чтоб не выкидывало
|
||||||
currentUser.textContent = user.login; // пишу логин
|
currentUser.textContent = user.login; // пишу логин
|
||||||
currentRole.textContent = user.role || "Client"; // пишу роль
|
currentRole.textContent = user.role || "Client"; // пишу роль
|
||||||
userAvatar.textContent = user.login ? user.login.charAt(0).toUpperCase() : "U"; // первая буква в кружок
|
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"); // прячу вход
|
authScreen.classList.add("hidden"); // прячу вход
|
||||||
app.classList.remove("hidden"); // показываю программу
|
app.classList.remove("hidden"); // показываю программу
|
||||||
showPage("products"); // открываю товары
|
showPage("products"); // открываю товары
|
||||||
|
|
@ -194,8 +221,9 @@ registerForm.addEventListener("submit", async (event) => {
|
||||||
const login = document.getElementById("registerLogin").value.trim();
|
const login = document.getElementById("registerLogin").value.trim();
|
||||||
const password = document.getElementById("registerPassword").value;
|
const password = document.getElementById("registerPassword").value;
|
||||||
const passwordRepeat = document.getElementById("registerPasswordRepeat").value;
|
const passwordRepeat = document.getElementById("registerPasswordRepeat").value;
|
||||||
|
const email = document.getElementById("registerEmail").value.trim();
|
||||||
// простые проверки
|
// простые проверки
|
||||||
if (!login || !password || !passwordRepeat) {
|
if (!login || !password || !passwordRepeat || !email) {
|
||||||
registerError.textContent = "Заполните все поля.";
|
registerError.textContent = "Заполните все поля.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -203,8 +231,12 @@ registerForm.addEventListener("submit", async (event) => {
|
||||||
registerError.textContent = "Логин слишком короткий.";
|
registerError.textContent = "Логин слишком короткий.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (password.length < 4) {
|
if (password.length < 8) {
|
||||||
registerError.textContent = "Пароль слишком короткий.";
|
registerError.textContent = "Пароль должен быть минимум 8 символов.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isEmailOk(email)) {
|
||||||
|
registerError.textContent = "Почта неправильная, проверьте ее.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (password !== passwordRepeat) {
|
if (password !== passwordRepeat) {
|
||||||
|
|
@ -214,7 +246,7 @@ registerForm.addEventListener("submit", async (event) => {
|
||||||
try {
|
try {
|
||||||
await api("/api/auth/register", {
|
await api("/api/auth/register", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ login, password, roleId: 2 })
|
body: JSON.stringify({ login, password, email, roleId: 2 })
|
||||||
});
|
});
|
||||||
registerForm.reset();
|
registerForm.reset();
|
||||||
showLoginForm(); // иду на вход
|
showLoginForm(); // иду на вход
|
||||||
|
|
@ -242,6 +274,13 @@ document.querySelectorAll(".nav-button").forEach((button) => {
|
||||||
|
|
||||||
// открываю страницу (товары, заказы и тд)
|
// открываю страницу (товары, заказы и тд)
|
||||||
async function showPage(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; // запоминаю где я
|
currentPage = page; // запоминаю где я
|
||||||
const config = pages[page];
|
const config = pages[page];
|
||||||
if (!config) {
|
if (!config) {
|
||||||
|
|
@ -249,15 +288,35 @@ async function showPage(page) {
|
||||||
}
|
}
|
||||||
pageTitle.textContent = config.title; // пишу название
|
pageTitle.textContent = config.title; // пишу название
|
||||||
pageDescription.textContent = config.description;
|
pageDescription.textContent = config.description;
|
||||||
|
// кнопку добавить показываю админу везде, а обычному только в заказах
|
||||||
|
addButton.classList.toggle("hidden", !(isAdmin || page === "orders"));
|
||||||
|
searchInput.value = ""; // чищу поиск
|
||||||
pageContent.innerHTML = "<div class='empty-page'><strong>Загрузка...</strong></div>"; // пока грузится
|
pageContent.innerHTML = "<div class='empty-page'><strong>Загрузка...</strong></div>"; // пока грузится
|
||||||
try {
|
try {
|
||||||
const data = await api(config.endpoint); // беру данные с сервера
|
const data = await api(config.endpoint); // беру данные с сервера
|
||||||
|
currentData = data; // запоминаю чтоб искать по ним
|
||||||
renderPage(page, data); // рисую
|
renderPage(page, data); // рисую
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
pageContent.innerHTML = "<div class='error-page'>Не получилось: " + escapeHtml(error.message) + "</div>";
|
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) {
|
function renderPage(page, data) {
|
||||||
// если пусто то так и пишу
|
// если пусто то так и пишу
|
||||||
|
|
@ -294,10 +353,14 @@ function renderProducts(products) {
|
||||||
html += "<h3>" + escapeHtml(p.name) + "</h3>";
|
html += "<h3>" + escapeHtml(p.name) + "</h3>";
|
||||||
html += "<div class=\"card-price\">" + formatMoney(p.price) + "</div>";
|
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-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>";
|
if (isAdmin) {
|
||||||
html += "<button class=\"action-button delete\" onclick=\"deleteItem('products'," + p.id + ")\">Удалить</button>";
|
html += "<div class=\"card-actions\">";
|
||||||
html += "</div></div>";
|
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>";
|
html += "</div>";
|
||||||
pageContent.innerHTML = html; // показываю все
|
pageContent.innerHTML = html; // показываю все
|
||||||
|
|
@ -308,8 +371,11 @@ function renderCategories(categories) {
|
||||||
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Название</th><th>Действия</th></tr></thead><tbody>";
|
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Название</th><th>Действия</th></tr></thead><tbody>";
|
||||||
for (const c of categories) {
|
for (const c of categories) {
|
||||||
html += "<tr><td>#" + c.id + "</td><td>" + escapeHtml(c.name) + "</td><td><div class=\"table-actions\">";
|
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>";
|
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 += "</div></td></tr>";
|
||||||
}
|
}
|
||||||
html += "</tbody></table></div>";
|
html += "</tbody></table></div>";
|
||||||
|
|
@ -321,8 +387,11 @@ function renderWarehouses(warehouses) {
|
||||||
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Название</th><th>Адрес</th><th>Действия</th></tr></thead><tbody>";
|
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) {
|
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 += "<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>";
|
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 += "</div></td></tr>";
|
||||||
}
|
}
|
||||||
html += "</tbody></table></div>";
|
html += "</tbody></table></div>";
|
||||||
|
|
@ -336,8 +405,11 @@ function renderStock(stocks) {
|
||||||
const pname = s.product ? s.product.name : "—";
|
const pname = s.product ? s.product.name : "—";
|
||||||
const wname = s.warehouse ? s.warehouse.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 += "<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>";
|
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 += "</div></td></tr>";
|
||||||
}
|
}
|
||||||
html += "</tbody></table></div>";
|
html += "</tbody></table></div>";
|
||||||
|
|
@ -346,10 +418,15 @@ function renderStock(stocks) {
|
||||||
|
|
||||||
// рисую пользователей
|
// рисую пользователей
|
||||||
function renderUsers(users) {
|
function renderUsers(users) {
|
||||||
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Логин</th><th>Роль</th><th>Действия</th></tr></thead><tbody>";
|
// сюда пускаю только админа (на всякий случай)
|
||||||
|
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) {
|
for (const u of users) {
|
||||||
const rname = u.role ? u.role.name : "—";
|
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 += "<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\" onclick=\"editUser(" + u.id + ")\">Изменить</button>";
|
||||||
html += "<button class=\"action-button delete\" onclick=\"deleteItem('users'," + u.id + ")\">Удалить</button>";
|
html += "<button class=\"action-button delete\" onclick=\"deleteItem('users'," + u.id + ")\">Удалить</button>";
|
||||||
html += "</div></td></tr>";
|
html += "</div></td></tr>";
|
||||||
|
|
@ -365,8 +442,11 @@ function renderOrders(orders) {
|
||||||
const uname = o.user ? o.user.login : "—";
|
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 += "<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=\"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>";
|
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 += "</div></td></tr>";
|
||||||
}
|
}
|
||||||
html += "</tbody></table></div>";
|
html += "</tbody></table></div>";
|
||||||
|
|
@ -376,6 +456,10 @@ function renderOrders(orders) {
|
||||||
// кнопка Добавить сверху
|
// кнопка Добавить сверху
|
||||||
addButton.addEventListener("click", () => {
|
addButton.addEventListener("click", () => {
|
||||||
editingId = null; // значит добавляю а не изменяю
|
editingId = null; // значит добавляю а не изменяю
|
||||||
|
// заказы создавать можно всем, остальное только админу
|
||||||
|
if (currentPage !== "orders" && !needAdmin()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (currentPage === "products") {
|
if (currentPage === "products") {
|
||||||
openProductForm();
|
openProductForm();
|
||||||
}
|
}
|
||||||
|
|
@ -466,6 +550,9 @@ async function saveProduct() {
|
||||||
|
|
||||||
// жму изменить на товаре
|
// жму изменить на товаре
|
||||||
async function editProduct(id) {
|
async function editProduct(id) {
|
||||||
|
if (!needAdmin()) { // только админ
|
||||||
|
return;
|
||||||
|
}
|
||||||
const products = await api("/api/products"); // беру все
|
const products = await api("/api/products"); // беру все
|
||||||
const product = products.find((x) => x.id === id); // ищу нужный
|
const product = products.find((x) => x.id === id); // ищу нужный
|
||||||
if (!product) {
|
if (!product) {
|
||||||
|
|
@ -511,6 +598,9 @@ async function saveCategory() {
|
||||||
|
|
||||||
// жму изменить на категории
|
// жму изменить на категории
|
||||||
async function editCategory(id) {
|
async function editCategory(id) {
|
||||||
|
if (!needAdmin()) { // только админ
|
||||||
|
return;
|
||||||
|
}
|
||||||
const categories = await api("/api/categories");
|
const categories = await api("/api/categories");
|
||||||
const category = categories.find((x) => x.id === id);
|
const category = categories.find((x) => x.id === id);
|
||||||
if (!category) {
|
if (!category) {
|
||||||
|
|
@ -560,6 +650,9 @@ async function saveWarehouse() {
|
||||||
|
|
||||||
// жму изменить на складе
|
// жму изменить на складе
|
||||||
async function editWarehouse(id) {
|
async function editWarehouse(id) {
|
||||||
|
if (!needAdmin()) { // только админ
|
||||||
|
return;
|
||||||
|
}
|
||||||
const warehouses = await api("/api/warehouses");
|
const warehouses = await api("/api/warehouses");
|
||||||
const warehouse = warehouses.find((x) => x.id === id);
|
const warehouse = warehouses.find((x) => x.id === id);
|
||||||
if (!warehouse) {
|
if (!warehouse) {
|
||||||
|
|
@ -622,6 +715,9 @@ async function saveStock() {
|
||||||
|
|
||||||
// жму изменить на остатке
|
// жму изменить на остатке
|
||||||
async function editStock(id) {
|
async function editStock(id) {
|
||||||
|
if (!needAdmin()) { // только админ
|
||||||
|
return;
|
||||||
|
}
|
||||||
const stocks = await api("/api/stocks");
|
const stocks = await api("/api/stocks");
|
||||||
const stock = stocks.find((x) => x.id === id);
|
const stock = stocks.find((x) => x.id === id);
|
||||||
if (!stock) {
|
if (!stock) {
|
||||||
|
|
@ -642,10 +738,12 @@ async function openUserForm(user = null) {
|
||||||
const title = user ? "Изменить пользователя" : "Добавить пользователя";
|
const title = user ? "Изменить пользователя" : "Добавить пользователя";
|
||||||
const login = user ? escapeHtml(user.login) : "";
|
const login = user ? escapeHtml(user.login) : "";
|
||||||
const password = user ? escapeHtml(user.password) : "";
|
const password = user ? escapeHtml(user.password) : "";
|
||||||
|
const email = user ? escapeHtml(user.email || "") : "";
|
||||||
openModal(title,
|
openModal(title,
|
||||||
"<div class=\"form\">" +
|
"<div class=\"form\">" +
|
||||||
"<label>Логин<input id=\"userLogin\" type=\"text\" value=\"" + login + "\"></label>" +
|
"<label>Логин<input id=\"userLogin\" type=\"text\" value=\"" + login + "\"></label>" +
|
||||||
"<label>Пароль<input id=\"userPassword\" type=\"text\" value=\"" + password + "\"></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>" +
|
"<label>Роль<select id=\"userRole\">" + options + "</select></label>" +
|
||||||
"<div class=\"form-actions\"><button type=\"button\" class=\"secondary-button\" onclick=\"closeModalWindow()\">Отмена</button>" +
|
"<div class=\"form-actions\"><button type=\"button\" class=\"secondary-button\" onclick=\"closeModalWindow()\">Отмена</button>" +
|
||||||
"<button type=\"button\" class=\"primary-button\" onclick=\"saveUser()\">Сохранить</button></div></div>"
|
"<button type=\"button\" class=\"primary-button\" onclick=\"saveUser()\">Сохранить</button></div></div>"
|
||||||
|
|
@ -656,13 +754,22 @@ async function openUserForm(user = null) {
|
||||||
async function saveUser() {
|
async function saveUser() {
|
||||||
const login = document.getElementById("userLogin").value.trim();
|
const login = document.getElementById("userLogin").value.trim();
|
||||||
const password = document.getElementById("userPassword").value;
|
const password = document.getElementById("userPassword").value;
|
||||||
|
const email = document.getElementById("userEmail").value.trim();
|
||||||
const roleId = Number(document.getElementById("userRole").value);
|
const roleId = Number(document.getElementById("userRole").value);
|
||||||
if (!login || !password) {
|
if (!login || !password || !email) {
|
||||||
showToast("Заполните все поля.");
|
showToast("Заполните все поля.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (password.length < 8) {
|
||||||
|
showToast("Пароль должен быть минимум 8 символов.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isEmailOk(email)) {
|
||||||
|
showToast("Почта неправильная, проверьте ее.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const data = { login, password, roleId };
|
const data = { login, password, email, roleId };
|
||||||
if (editingId) {
|
if (editingId) {
|
||||||
await api("/api/users/" + editingId, { method: "PUT", body: JSON.stringify(data) });
|
await api("/api/users/" + editingId, { method: "PUT", body: JSON.stringify(data) });
|
||||||
showToast("Пользователь изменен.");
|
showToast("Пользователь изменен.");
|
||||||
|
|
@ -679,6 +786,9 @@ async function saveUser() {
|
||||||
|
|
||||||
// жму изменить на пользователе
|
// жму изменить на пользователе
|
||||||
async function editUser(id) {
|
async function editUser(id) {
|
||||||
|
if (!needAdmin()) { // только админ
|
||||||
|
return;
|
||||||
|
}
|
||||||
const users = await api("/api/users");
|
const users = await api("/api/users");
|
||||||
const user = users.find((x) => x.id === id);
|
const user = users.find((x) => x.id === id);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
|
@ -743,6 +853,9 @@ async function saveOrder() {
|
||||||
|
|
||||||
// жму изменить на заказе
|
// жму изменить на заказе
|
||||||
async function editOrder(id) {
|
async function editOrder(id) {
|
||||||
|
if (!needAdmin()) { // только админ
|
||||||
|
return;
|
||||||
|
}
|
||||||
const orders = await api("/api/orders");
|
const orders = await api("/api/orders");
|
||||||
const order = orders.find((x) => x.id === id);
|
const order = orders.find((x) => x.id === id);
|
||||||
if (!order) {
|
if (!order) {
|
||||||
|
|
@ -820,6 +933,9 @@ async function saveOrderItem(orderId) {
|
||||||
|
|
||||||
// удаляю что угодно (товар, заказ и тд)
|
// удаляю что угодно (товар, заказ и тд)
|
||||||
async function deleteItem(type, id) {
|
async function deleteItem(type, id) {
|
||||||
|
if (!needAdmin()) { // удалять может только админ
|
||||||
|
return;
|
||||||
|
}
|
||||||
// красивые названия для вопроса
|
// красивые названия для вопроса
|
||||||
const names = {
|
const names = {
|
||||||
products: "товар",
|
products: "товар",
|
||||||
|
|
|
||||||
|
|
@ -235,6 +235,15 @@ button {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* поле поиска */
|
||||||
|
.search-input {
|
||||||
|
height: 44px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
/* синяя кнопка добавить */
|
/* синяя кнопка добавить */
|
||||||
.primary-button {
|
.primary-button {
|
||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user