Добавьте файлы проекта.

This commit is contained in:
homyachelus 2026-10-31 19:36:02 +04:00
parent f934daf796
commit 556a3a6c88
102 changed files with 2032 additions and 0 deletions

3
ChudoObuv.slnx Normal file
View File

@ -0,0 +1,3 @@
<Solution>
<Project Path="ChudoObuv/ChudoObuv.csproj" />
</Solution>

57
ChudoObuv/App.xaml Normal file
View File

@ -0,0 +1,57 @@
<Application x:Class="ChudoObuv.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml"
DispatcherUnhandledException="OnDispatcherUnhandledException">
<Application.Resources>
<!-- Цвета из руководства по стилю -->
<SolidColorBrush x:Key="MainBackgroundBrush" Color="#FFFFFF" />
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#D2F6E7" />
<SolidColorBrush x:Key="AccentBrush" Color="#70B2AF" />
<SolidColorBrush x:Key="LowStockBrush" Color="#FF8080" />
<Style TargetType="Button">
<Setter Property="FontFamily" Value="Calibri" />
<Setter Property="FontSize" Value="16" />
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
<Setter Property="Padding" Value="12,4" />
<Setter Property="Margin" Value="4" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Cursor" Value="Hand" />
</Style>
<!-- Кнопка целевого действия (акцентный цвет) -->
<Style x:Key="AccentButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{StaticResource AccentBrush}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style TargetType="TextBox">
<Setter Property="FontFamily" Value="Calibri" />
<Setter Property="FontSize" Value="16" />
<Setter Property="Padding" Value="4,2" />
<Setter Property="Margin" Value="4" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="MinHeight" Value="30" />
</Style>
<Style TargetType="ComboBox">
<Setter Property="FontFamily" Value="Calibri" />
<Setter Property="FontSize" Value="16" />
<Setter Property="Margin" Value="4" />
<Setter Property="MinHeight" Value="30" />
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style>
<Style TargetType="DataGrid">
<Setter Property="FontFamily" Value="Calibri" />
<Setter Property="FontSize" Value="16" />
<Setter Property="AutoGenerateColumns" Value="False" />
<Setter Property="IsReadOnly" Value="True" />
<Setter Property="CanUserAddRows" Value="False" />
<Setter Property="SelectionMode" Value="Single" />
<Setter Property="HeadersVisibility" Value="Column" />
<Setter Property="AlternatingRowBackground" Value="{StaticResource
SecondaryBackgroundBrush}" />
<Setter Property="Margin" Value="4" />
</Style>
</Application.Resources>
</Application>

32
ChudoObuv/App.xaml.cs Normal file
View File

@ -0,0 +1,32 @@
using System.Configuration;
using System.Data;
using System.Windows;
using System.Globalization;
using System.Windows.Markup;
using System.Windows.Threading;
using ChudoObuv.Logic;
namespace ChudoObuv
{
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
// Русский формат чисел, цен и дат во всем приложении (в том числе в привязках XAML)
CultureInfo russian = new CultureInfo("ru-RU");
CultureInfo.DefaultThreadCurrentCulture = russian;
CultureInfo.DefaultThreadCurrentUICulture = russian;
Thread.CurrentThread.CurrentCulture = russian;
Thread.CurrentThread.CurrentUICulture = russian;
FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement),
new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(russian.IetfLanguageTag)));
base.OnStartup(e);
}
// Последняя защита от аварийного завершения: любая необработанная ошибка показывается пользователю
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
Messages.ShowError($"Произошла непредвиденная ошибка: {e.Exception.Message}\nПовторите действие.Если ошибка повторяется, перезапустите приложение.");
e.Handled = true;
}
}
}

10
ChudoObuv/AssemblyInfo.cs Normal file
View File

@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>disable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<ApplicationIcon>Resources\app.ico</ApplicationIcon>
<AssemblyTitle>Чудо Обувь</AssemblyTitle>
<Product>Чудо Обувь</Product>
<Company>Чудо Обувь</Company>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MySqlConnector" Version="2.6.2" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\app.ico" />
<Resource Include="Resources\logo.png" />
<Content Include="Resources\picture.png" CopyToOutputDirectory="PreserveNewest" />
<Content Include="Resources\Images\*.png" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,321 @@
using ChudoObuv.Logic;
using ChudoObuv.Models;
using MySqlConnector;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace ChudoObuv.Data
{
/// <summary>
/// Все запросы приложения к базе данных.
/// </summary>
public static class DataService
{
// ---------------------------- Пользователи ----------------------------
public static User FindUserByLogin(string login)
{
const string sql = @"
SELECT u.user_id, u.last_name, u.first_name, u.patronymic, u.login, r.role_name
FROM users u
JOIN roles r ON r.role_id = u.role_id
WHERE u.login = @login";
using MySqlConnection connection = Database.OpenConnection();
using MySqlCommand command = new MySqlCommand(sql, connection);
command.Parameters.AddWithValue("@login", login);
using MySqlDataReader reader = command.ExecuteReader();
return reader.Read() ? ReadUser(reader) : null;
}
public static List<User> GetClients()
{
const string sql = @"
SELECT u.user_id, u.last_name, u.first_name, u.patronymic, u.login, r.role_name
FROM users u
JOIN roles r ON r.role_id = u.role_id
WHERE r.role_name = @role
ORDER BY u.last_name, u.first_name";
List<User> clients = new List<User>();
using MySqlConnection connection = Database.OpenConnection();
using MySqlCommand command = new MySqlCommand(sql, connection);
command.Parameters.AddWithValue("@role", User.ClientRole);
using MySqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
clients.Add(ReadUser(reader));
}
return clients;
}
private static User ReadUser(MySqlDataReader reader)
{
return new User
{
Id = reader.GetInt32("user_id"),
LastName = reader.GetString("last_name"),
FirstName = reader.GetString("first_name"),
Patronymic = reader.IsDBNull(reader.GetOrdinal("patronymic")) ? "" : reader.GetString("patronymic"),
Login = reader.GetString("login"),
Role = reader.GetString("role_name")
};
}
// ------------------------------- Товары -------------------------------
public static List<Product> GetProducts()
{
const string sql = @"
SELECT p.product_id, p.product_name, c.category_name, s.subcategory_name,m.manufacture_name,p.description, p.composition, p.price, p.image_file,
COALESCE((SELECT SUM(si.quantity) FROM stock_items si WHERE si.product_id = p.product_id), 0) AS total_quantity,
EXISTS (SELECT 1 FROM order_items oi
JOIN orders o ON o.order_id = oi.order_id
JOIN stock_items si ON si.stock_item_id = oi.stock_item_id
WHERE si.product_id = p.product_id
AND o.order_date >= @previousMonthStart
AND o.order_date < @currentMonthStart) AS has_previous_month_orders
FROM products p
JOIN categories c ON c.category_id = p.category_id
JOIN subcategories s ON s.subcategory_id = p.subcategory_id
JOIN manufacturers m ON m.manufacture_id = p.manufacturer_id
ORDER BY p.product_id";
DateTime today = DateTime.Today;
List<Product> products = new List<Product>();
using MySqlConnection connection = Database.OpenConnection();
using MySqlCommand command = new MySqlCommand(sql, connection);
command.Parameters.AddWithValue("@previousMonthStart", PriceCalculator.GetPreviousMonthStart(today));
command.Parameters.AddWithValue("@currentMonthStart", PriceCalculator.GetCurrentMonthStart(today));
using MySqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
products.Add(new Product
{
Id = reader.GetInt32("product_id"),
Name = reader.GetString("product_name"),
Category = reader.GetString("category_name"),
Subcategory = reader.GetString("subcategory_name"),
Manufacturer = reader.GetString("manufacture_name"),
Description = reader.IsDBNull(reader.GetOrdinal("description")) ? "" :
reader.GetString("description"),
Composition = reader.IsDBNull(reader.GetOrdinal("composition")) ? "" :
reader.GetString("composition"),
Price = reader.GetDecimal("price"),
ImageFile = reader.IsDBNull(reader.GetOrdinal("image_file")) ? null :
reader.GetString("image_file"),
TotalQuantity = Convert.ToInt32(reader["total_quantity"]),
HasOrdersInPreviousMonth = Convert.ToInt32(reader["has_previous_month_orders"]) == 1
});
}
return products;
}
public static List<string> GetCategories()
{
List<string> categories = new List<string>();
using MySqlConnection connection = Database.OpenConnection();
using MySqlCommand command = new MySqlCommand("SELECT category_name FROM categories ORDER BY category_name", connection);
using MySqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
categories.Add(reader.GetString(0));
}
return categories;
}
/// <summary>
/// Все товарные позиции модели (размерный ряд) с доступным количеством.
/// </summary>
public static List<StockItem> GetStockItems(int productId)
{
const string sql = @"
SELECT si.stock_item_id, si.product_id, s.size_value, si.quantity
FROM stock_items si
JOIN sizes s ON s.size_id = si.size_id
WHERE si.product_id = @productId
ORDER BY s.size_value";
List<StockItem> items = new List<StockItem>();
using MySqlConnection connection = Database.OpenConnection();
using MySqlCommand command = new MySqlCommand(sql, connection);
command.Parameters.AddWithValue("@productId", productId);
using MySqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
items.Add(new StockItem
{
Id = reader.GetInt32("stock_item_id"),
ProductId = reader.GetInt32("product_id"),
Size = reader.GetDecimal("size_value"),
Quantity = reader.GetInt32("quantity")
});
}
return items;
}
// ------------------------------- Заказы -------------------------------
public static List<Order> GetOrders()
{
const string sql = @"
SELECT o.order_id, o.order_date, u.user_id,
CONCAT_WS(' ', u.last_name, u.first_name, u.patronymic) AS client_name,
COUNT(oi.order_item_id) AS items_count,
COALESCE(SUM(oi.quantity * oi.unit_price), 0) AS total_sum
FROM orders o
JOIN users u ON u.user_id = o.user_id
LEFT JOIN order_items oi ON oi.order_id = o.order_id
GROUP BY o.order_id, o.order_date, u.user_id, client_name
ORDER BY o.order_date DESC, o.order_id DESC";
List<Order> orders = new List<Order>();
using MySqlConnection connection = Database.OpenConnection();
using MySqlCommand command = new MySqlCommand(sql, connection);
using MySqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
orders.Add(new Order
{
Id = reader.GetInt32("order_id"),
OrderDate = reader.GetDateTime("order_date"),
ClientId = reader.GetInt32("user_id"),
ClientFullName = reader.GetString("client_name"),
ItemsCount = Convert.ToInt32(reader["items_count"]),
TotalSum = reader.GetDecimal("total_sum")
});
}
return orders;
}
public static List<OrderItem> GetOrderItems(int orderId)
{
const string sql = @"
SELECT oi.order_item_id, oi.stock_item_id, p.product_name, m.manufacturer_name, s.size_value, oi.quantity, oi.unit_price
FROM order_items oi
JOIN stock_items si ON si.stock_item_id = oi.stock_item_id
JOIN products p ON p.product_id = si.product_id
JOIN manufacturers m ON m.manufacturer_id = p.manufacturer_id
JOIN sizes s ON s.size_id = si.size_id
WHERE oi.order_id = @orderId
ORDER BY oi.order_item_id";
List<OrderItem> items = new List<OrderItem>();
using MySqlConnection connection = Database.OpenConnection();
using MySqlCommand command = new MySqlCommand(sql, connection);
command.Parameters.AddWithValue("@orderId", orderId);
using MySqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
items.Add(new OrderItem
{
Id = reader.GetInt32("order_item_id"),
StockItemId = reader.GetInt32("stock_item_id"),
ProductName = reader.GetString("product_name"),
Manufacturer = reader.GetString("manufacturer_name"),
Size = reader.GetDecimal("size_value"),
Quantity = reader.GetInt32("quantity"),
UnitPrice = reader.GetDecimal("unit_price")
});
}
return items;
}
/// <summary>
/// Сохраняет заказ и уменьшает остатки. Возвращает номер заказа.
/// Если какого-то размера уже не хватает — заказ не сохраняется (исключение).
/// </summary>
public static int CreateOrder(int clientId, List<OrderItem> items)
{
using MySqlConnection connection = Database.OpenConnection();
using MySqlTransaction transaction = connection.BeginTransaction();
MySqlCommand orderCommand = new MySqlCommand(
"INSERT INTO orders (order_date, user_id) VALUES (@date, @userId)", connection,
transaction);
orderCommand.Parameters.AddWithValue("@date", DateTime.Today);
orderCommand.Parameters.AddWithValue("@userId", clientId);
orderCommand.ExecuteNonQuery();
int orderId = (int)orderCommand.LastInsertedId;
foreach (OrderItem item in items)
{
// Остаток уменьшается только если его хватает — защита от одновременных заказов
MySqlCommand stockCommand = new MySqlCommand(
"UPDATE stock_items SET quantity = quantity - @quantity WHERE stock_item_id = @id AND quantity >= @quantity",
connection, transaction);
stockCommand.Parameters.AddWithValue("@quantity", item.Quantity);
stockCommand.Parameters.AddWithValue("@id", item.StockItemId);
if (stockCommand.ExecuteNonQuery() == 0)
{
transaction.Rollback();
throw new InvalidOperationException(
$"Недостаточно товара «{item.ProductName}», размер {item.SizeText}. Обновите каталог и измените количество.");
}
MySqlCommand itemCommand = new MySqlCommand(
"INSERT INTO order_items (order_id, stock_item_id, quantity, unit_price) VALUES (@orderId, @stockItemId, @quantity, @price)",
connection, transaction);
itemCommand.Parameters.AddWithValue("@orderId", orderId);
itemCommand.Parameters.AddWithValue("@stockItemId", item.StockItemId);
itemCommand.Parameters.AddWithValue("@quantity", item.Quantity);
itemCommand.Parameters.AddWithValue("@price", item.UnitPrice);
itemCommand.ExecuteNonQuery();
}
transaction.Commit();
return orderId;
}
/// <summary>
/// Удаляет заказ и возвращает его товары на склад.
/// </summary>
public static void DeleteOrder(int orderId)
{
using MySqlConnection connection = Database.OpenConnection();
using MySqlTransaction transaction = connection.BeginTransaction();
MySqlCommand returnCommand = new MySqlCommand(@"
UPDATE stock_items si
JOIN order_items oi ON oi.stock_item_id = si.stock_item_id
SET si.quantity = si.quantity + oi.quantity
WHERE oi.order_id = @orderId", connection, transaction);
returnCommand.Parameters.AddWithValue("@orderId", orderId);
returnCommand.ExecuteNonQuery();
// Строки состава удаляются каскадно (ON DELETE CASCADE)
MySqlCommand deleteCommand = new MySqlCommand("DELETE FROM orders WHERE order_id = @orderId", connection, transaction);
deleteCommand.Parameters.AddWithValue("@orderId", orderId);
deleteCommand.ExecuteNonQuery();
transaction.Commit();
}
/// <summary>
/// Удаляет товарную позицию из заказа и возвращает ее количество на склад.
/// </summary>
public static void DeleteOrderItem(int orderItemId)
{
using MySqlConnection connection = Database.OpenConnection();
using MySqlTransaction transaction = connection.BeginTransaction();
MySqlCommand returnCommand = new MySqlCommand(@"
UPDATE stock_items si
JOIN order_items oi ON oi.stock_item_id = si.stock_item_id
SET si.quantity = si.quantity + oi.quantity
WHERE oi.order_item_id = @id", connection, transaction);
returnCommand.Parameters.AddWithValue("@id", orderItemId);
returnCommand.ExecuteNonQuery();
MySqlCommand deleteCommand = new MySqlCommand("DELETE FROM order_items WHERE order_item_id = @id", connection, transaction);
deleteCommand.Parameters.AddWithValue("@id", orderItemId);
deleteCommand.ExecuteNonQuery();
transaction.Commit();
}
public static void UpdateOrderDate(int orderId, DateTime orderDate)
{
using MySqlConnection connection = Database.OpenConnection();
using MySqlCommand command = new MySqlCommand("UPDATE orders SET order_date = @date WHERE order_id = @orderId", connection);
command.Parameters.AddWithValue("@date", orderDate.Date);
command.Parameters.AddWithValue("@orderId", orderId);
command.ExecuteNonQuery();
}
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MySqlConnector;
namespace ChudoObuv.Data
{
/// <summary>
/// Подключение к базе данных MySQL.
/// </summary>
public static class Database
{
// Перед запуском укажите свои сервер, имя базы, пользователя и пароль MySQL
private const string ConnectionString = "Server=177.1.181.207;Port=3306;Database=isp-41_lipatova_yuv_chudo_obuv;UserID=isp-41_lipatova_yuv;Password=isp-41_lipatova_yuv;CharSet=utf8mb4;";
public static MySqlConnection OpenConnection()
{
MySqlConnection connection = new MySqlConnection(ConnectionString);
connection.Open();
return connection;
}
}
}

View File

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using MySqlConnector;
namespace ChudoObuv.Logic
{
/// <summary>
/// Окна сообщений с заголовком и пиктограммой нужного типа.
/// </summary>
public static class Messages
{
private const string AppName = "Чудо Обувь";
public static void ShowError(string text)
{
MessageBox.Show(text, $"{AppName} — Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
}
public static void ShowWarning(string text)
{
MessageBox.Show(text, $"{AppName} — Предупреждение", MessageBoxButton.OK,
MessageBoxImage.Warning);
}
public static void ShowInfo(string text)
{
MessageBox.Show(text, $"{AppName} — Информация", MessageBoxButton.OK,
MessageBoxImage.Information);
}
/// <summary>
/// Подтверждение необратимой операции. Возвращает true, если пользователь нажал «Да».
/// </summary>
public static bool Confirm(string text)
{
MessageBoxResult result = MessageBox.Show(text, $"{AppName} — Подтверждение",
MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
return result == MessageBoxResult.Yes;
}
public static void ShowDatabaseError(Exception exception)
{
if (exception is MySqlException)
{
ShowError("Не удалось выполнить операцию с базой данных.\n" +
$"Причина: {exception.Message}\n\n" +
"Проверьте, что сервер MySQL запущен, а в файле Data\\Database.cs указаны верные сервер, пользователь и пароль.");
}
else
{
ShowError(exception.Message);
}
}
}
}

View File

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChudoObuv.Logic
{
/// <summary>
/// Алгоритмы расчета показателей товара.
/// </summary>
public static class PriceCalculator
{
public const decimal DiscountPercent = 25m;
public const int ManyQuantityThreshold = 5;
public const int LowStockThreshold = 3;
/// <summary>
/// Начало предыдущего календарного месяца относительно даты расчета.
/// </summary>
public static DateTime GetPreviousMonthStart(DateTime calculationDate)
{
return GetCurrentMonthStart(calculationDate).AddMonths(-1);
}
public static DateTime GetCurrentMonthStart(DateTime calculationDate)
{
return new DateTime(calculationDate.Year, calculationDate.Month, 1);
}
/// <summary>
/// Цена со скидкой 25%, если в предыдущем календарном месяце заказов товара не было.
/// </summary>
public static decimal CalculateDiscountedPrice(decimal price, bool hasOrdersInPreviousMonth)
{
if (hasOrdersInPreviousMonth)
{
return price;
}
return Math.Round(price * (100m - DiscountPercent) / 100m, 2);
}
/// <summary>
/// «много», если пар больше пяти, иначе «мало».
/// </summary>
public static string GetQuantityLabel(int totalQuantity)
{
return totalQuantity > ManyQuantityThreshold ? "много" : "мало";
}
public static bool IsLowStock(int totalQuantity)
{
return totalQuantity <= LowStockThreshold;
}
}
}

View File

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ChudoObuv.Models;
namespace ChudoObuv.Logic
{
/// <summary>
/// Текущий пользователь и формируемый (еще не подтвержденный) заказ.
/// </summary>
public static class Session
{
public static User CurrentUser { get; private set; }
/// <summary>
/// Клиент, для которого формируется заказ: сам пользователь или клиент, выбранный менеджером.
/// </summary>
public static User OrderClient { get; private set; }
public static List<OrderItem> Cart { get; } = new List<OrderItem>();
public static bool IsAuthorized => CurrentUser != null;
public static void Login(User user)
{
CurrentUser = user;
OrderClient = user;
Cart.Clear();
}
public static void Logout()
{
CurrentUser = null;
OrderClient = null;
Cart.Clear();
}
public static void StartOrderFor(User client)
{
OrderClient = client;
Cart.Clear();
}
public static void CancelOrder()
{
Cart.Clear();
OrderClient = CurrentUser;
}
/// <summary>
/// Сколько пар данной товарной позиции уже лежит в формируемом заказе.
/// </summary>
public static int GetQuantityInCart(int stockItemId)
{
return Cart.Where(item => item.StockItemId == stockItemId).Sum(item => item.Quantity);
}
public static void AddToCart(OrderItem newItem)
{
OrderItem existing = Cart.FirstOrDefault(item => item.StockItemId == newItem.StockItemId);
if (existing != null)
{
existing.Quantity += newItem.Quantity;
}
else
{
Cart.Add(newItem);
}
}
}
}

43
ChudoObuv/MainWindow.xaml Normal file
View File

@ -0,0 +1,43 @@
<Window x:Class="ChudoObuv.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Чудо Обувь"
Icon="Resources/app.ico"
Width="1100" Height="750" MinWidth="900" MinHeight="600"
WindowStartupLocation="CenterScreen"
FontFamily="Calibri" FontSize="16"
Background="{StaticResource MainBackgroundBrush}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Шапка: логотип, заголовок страницы, ФИО пользователя -->
<Grid Grid.Row="0" Background="{StaticResource SecondaryBackgroundBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="Resources/logo.png" Height="64" Margin="10,6"
Stretch="Uniform" RenderOptions.BitmapScalingMode="HighQuality" />
<Button Grid.Column="1" x:Name="BackButton" Content="← Назад" VerticalAlignment="Center"
Click="BackButton_Click" />
<TextBlock Grid.Column="2" x:Name="PageTitleText" Text="Чудо Обувь" FontSize="26"
FontWeight="Bold"
VerticalAlignment="Center" Margin="16,0" TextTrimming="CharacterEllipsis" />
<StackPanel Grid.Column="3" Orientation="Vertical" VerticalAlignment="Top"
HorizontalAlignment="Right" Margin="10,6">
<TextBlock x:Name="UserNameText" FontSize="18" FontWeight="Bold"
HorizontalAlignment="Right" />
<TextBlock x:Name="UserRoleText" FontSize="14" HorizontalAlignment="Right" />
<Button x:Name="LogoutButton" Content="Выйти" HorizontalAlignment="Right"
Click="LogoutButton_Click" />
</StackPanel>
</Grid>
<Frame Grid.Row="1" x:Name="MainFrame" NavigationUIVisibility="Hidden"
Navigated="MainFrame_Navigated" />
</Grid>
</Window>

View File

@ -0,0 +1,79 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using ChudoObuv.Logic;
using ChudoObuv.Pages;
namespace ChudoObuv
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainFrame.Navigate(new LoginPage());
}
private void MainFrame_Navigated(object sender, NavigationEventArgs e)
{
// Заголовок окна и шапки берется из заголовка открытой страницы
if (e.Content is Page page)
{
Title = $"Чудо Обувь — {page.Title}";
PageTitleText.Text = page.Title;
}
BackButton.Visibility = MainFrame.CanGoBack ? Visibility.Visible : Visibility.Collapsed;
UpdateUserInfo();
}
private void UpdateUserInfo()
{
bool isLoginPage = MainFrame.Content is LoginPage;
if (Session.IsAuthorized)
{
UserNameText.Text = Session.CurrentUser.FullName;
UserRoleText.Text = Session.CurrentUser.Role;
LogoutButton.Content = "Выйти";
}
else
{
UserNameText.Text = isLoginPage ? "" : "Гость";
UserRoleText.Text = "";
LogoutButton.Content = "Войти";
}
LogoutButton.Visibility = isLoginPage ? Visibility.Collapsed : Visibility.Visible;
}
private void BackButton_Click(object sender, RoutedEventArgs e)
{
if (MainFrame.CanGoBack)
{
MainFrame.GoBack();
}
}
private void LogoutButton_Click(object sender, RoutedEventArgs e)
{
if (Session.Cart.Count > 0 && !Messages.Confirm("Неподтвержденный заказ будет отменен. Выйти из системы?"))
{
return;
}
Session.Logout();
MainFrame.Navigate(new LoginPage());
ClearHistory();
}
/// <summary>
/// Очищает историю переходов, чтобы кнопкой «Назад» нельзя было вернуться в чужой сеанс.
/// </summary>
public void ClearHistory()
{
// Навигация выполняется асинхронно, поэтому очищаем журнал после завершения перехода
Dispatcher.BeginInvoke(new Action(() =>
{
while (MainFrame.CanGoBack)
{
MainFrame.RemoveBackEntry();
}
BackButton.Visibility = Visibility.Collapsed;
UpdateUserInfo();
}), System.Windows.Threading.DispatcherPriority.ContextIdle);
}
}
}

18
ChudoObuv/Models/Order.cs Normal file
View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChudoObuv.Models
{
public class Order
{
public int Id { get; set; }
public DateTime OrderDate { get; set; }
public int ClientId { get; set; }
public string ClientFullName { get; set; }
public int ItemsCount { get; set; }
public decimal TotalSum { get; set; }
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChudoObuv.Models
{
/// <summary>
/// Строка состава заказа (сохраненная в БД или в оформляемом заказе).
/// </summary>
public class OrderItem
{
public int Id { get; set; }
public int StockItemId { get; set; }
public string ProductName { get; set; }
public string Manufacturer { get; set; }
public decimal Size { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public string Position => $"{ProductName}, {Manufacturer}, размер {Size:0.#}";
public string SizeText => Size.ToString("0.#");
public decimal Sum => UnitPrice * Quantity;
}
}

View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using ChudoObuv.Logic;
namespace ChudoObuv.Models
{
/// <summary>
/// Товар (модель обуви) для вывода в каталоге.
/// </summary>
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public string Subcategory { get; set; }
public string Manufacturer { get; set; }
public string Description { get; set; }
public string Composition { get; set; }
public decimal Price { get; set; }
public string ImageFile { get; set; }
public int TotalQuantity { get; set; }
public bool HasOrdersInPreviousMonth { get; set; }
public string Title => $"{Manufacturer} | {Name}";
public decimal DiscountedPrice => PriceCalculator.CalculateDiscountedPrice(Price,
HasOrdersInPreviousMonth);
public bool HasDiscount => DiscountedPrice < Price;
public string QuantityLabel => PriceCalculator.GetQuantityLabel(TotalQuantity);
public bool IsLowStock => PriceCalculator.IsLowStock(TotalQuantity);
/// <summary>
/// Путь к изображению товара; если файла нет — картинка-заглушка.
/// </summary>
public string ImagePath
{
get
{
string folder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources");
if (!string.IsNullOrWhiteSpace(ImageFile))
{
string path = Path.Combine(folder, "Images", ImageFile);
if (File.Exists(path))
{
return path;
}
}
return Path.Combine(folder, "picture.png");
}
}
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChudoObuv.Models
{
/// <summary>
/// Товарная позиция: модель + размер с доступным количеством.
/// </summary>
public class StockItem
{
public int Id { get; set; }
public int ProductId { get; set; }
public decimal Size { get; set; }
public int Quantity { get; set; }
public string SizeText => Size.ToString("0.#");
}
}

24
ChudoObuv/Models/User.cs Normal file
View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChudoObuv.Models
{
public class User
{
public const string AdminRole = "Администратор";
public const string ManagerRole = "Менеджер";
public const string ClientRole = "Авторизованный пользователь";
public int Id { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Patronymic { get; set; }
public string Login { get; set; }
public string Role { get; set; }
public string FullName => $"{LastName} {FirstName} {Patronymic}".Trim();
public bool IsAdmin => Role == AdminRole;
public bool IsManagerOrAdmin => Role == ManagerRole || Role == AdminRole;
}
}

View File

@ -0,0 +1,45 @@
<Page x:Class="ChudoObuv.Pages.CartPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FontFamily="Calibri" FontSize="16" Background="{StaticResource MainBackgroundBrush}"
Title="Оформление заказа"
Loaded="Page_Loaded">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" x:Name="ClientText" FontSize="18" Margin="4" TextWrapping="Wrap" />
<DataGrid Grid.Row="1" x:Name="ItemsDataGrid">
<DataGrid.Columns>
<DataGridTextColumn Header="Товар" Binding="{Binding ProductName}" Width="*" />
<DataGridTextColumn Header="Производство" Binding="{Binding Manufacturer}" Width="170"
/>
<DataGridTextColumn Header="Размер" Binding="{Binding SizeText}" Width="80" />
<DataGridTextColumn Header="Количество" Binding="{Binding Quantity}" Width="100" />
<DataGridTextColumn Header="Цена за ед., ₽" Binding="{Binding UnitPrice,
StringFormat=N2}" Width="120" />
<DataGridTextColumn Header="Сумма, ₽" Binding="{Binding Sum, StringFormat=N2}"
Width="120" />
<DataGridTemplateColumn Header="" Width="110">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Удалить" Margin="2" Padding="6,0" MinHeight="26"
Click="RemoveItemButton_Click" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<TextBlock Grid.Row="2" x:Name="TotalText" FontSize="22" FontWeight="Bold"
HorizontalAlignment="Right" Margin="4,8" />
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="Продолжить выбор товаров" Click="ContinueButton_Click" />
<Button Content="Отказаться от заказа" Click="CancelOrderButton_Click" />
<Button Content="Подтвердить заказ" Style="{StaticResource AccentButton}"
Click="ConfirmButton_Click" />
</StackPanel>
</Grid>
</Page>

View File

@ -0,0 +1,102 @@
using ChudoObuv.Pages;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ChudoObuv.Data;
using ChudoObuv.Logic;
using ChudoObuv.Models;
namespace ChudoObuv.Pages
{
public partial class CartPage : Page
{
public CartPage()
{
InitializeComponent();
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
RefreshCart();
}
private void RefreshCart()
{
ClientText.Text = $"Клиент: {Session.OrderClient.FullName} Дата заказа: { DateTime.Today:dd.MM.yyyy}";
ItemsDataGrid.ItemsSource = null;
ItemsDataGrid.ItemsSource = Session.Cart;
TotalText.Text = $"Итого: {Session.Cart.Sum(item => item.Sum):N2} ₽";
}
private void RemoveItemButton_Click(object sender, RoutedEventArgs e)
{
if (((Button)sender).DataContext is OrderItem item &&
Messages.Confirm($"Удалить из заказа «{item.ProductName}», размер {item.SizeText}?"))
{
Session.Cart.Remove(item);
RefreshCart();
}
}
private void ContinueButton_Click(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(new CatalogPage());
}
private void CancelOrderButton_Click(object sender, RoutedEventArgs e)
{
if (!Messages.Confirm("Отказаться от заказа? Все выбранные товары будут удалены из заказа. Действие нельзя отменить."))
{
return;
}
Session.CancelOrder();
GoHome();
}
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
{
if (Session.Cart.Count == 0)
{
Messages.ShowWarning("В заказе нет товаров. Выберите товар в каталоге, укажите размер и количество и нажмите «Добавить в заказ».");
return;
}
if (!Messages.Confirm($"Подтвердить заказ на сумму {Session.Cart.Sum(item => item.Sum):N2} ₽для клиента { Session.OrderClient.FullName}?"))
{
return;
}
try
{
int orderId = DataService.CreateOrder(Session.OrderClient.Id, Session.Cart);
Messages.ShowInfo($"Заказ No {orderId} оформлен {DateTime.Today:dd.MM.yyyy}.");
}
catch (Exception ex)
{
Messages.ShowDatabaseError(ex);
return;
}
Session.CancelOrder();
GoHome();
}
/// <summary>
/// Менеджер и администратор возвращаются к списку заказов, клиент — в каталог.
/// </summary>
private void GoHome()
{
if (Session.CurrentUser.IsManagerOrAdmin)
{
NavigationService.Navigate(new OrdersPage());
}
else
{
NavigationService.Navigate(new CatalogPage());
}
((MainWindow)Application.Current.MainWindow).ClearHistory();
}
}
}

View File

@ -0,0 +1,133 @@
<Page x:Class="ChudoObuv.Pages.CatalogPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FontFamily="Calibri" FontSize="16" Background="{StaticResource MainBackgroundBrush}"
Title="Каталог товаров"
Loaded="Page_Loaded">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Поиск, фильтрация и сортировка: только для авторизованных пользователей -->
<Grid Grid.Row="0" x:Name="FilterPanel">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="230" />
<ColumnDefinition Width="230" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="Поиск по наименованию и описанию:" Margin="4,0" />
<TextBox x:Name="SearchTextBox" TextChanged="Filter_Changed" MaxLength="100" />
</StackPanel>
<StackPanel Grid.Column="1">
<TextBlock Text="Категория:" Margin="4,0" />
<ComboBox x:Name="CategoryComboBox" SelectionChanged="Filter_Changed" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Text="Сортировка по цене:" Margin="4,0" />
<ComboBox x:Name="SortComboBox" SelectionChanged="Filter_Changed">
<ComboBoxItem Content="Без сортировки" IsSelected="True" />
<ComboBoxItem Content="По возрастанию цены" />
<ComboBoxItem Content="По убыванию цены" />
</ComboBox>
</StackPanel>
</Grid>
<!-- Действия с заказом -->
<DockPanel Grid.Row="1" Margin="0,4">
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
<Button x:Name="CartButton" Style="{StaticResource AccentButton}"
Click="CartButton_Click" />
<Button x:Name="OrdersButton" Content="Список заказов" Click="OrdersButton_Click" />
</StackPanel>
<TextBlock x:Name="OrderClientText" VerticalAlignment="Center" Margin="4,0"
FontStyle="Italic" TextWrapping="Wrap" />
</DockPanel>
<ListView Grid.Row="2" x:Name="ProductsListView"
SelectionChanged="ProductsListView_SelectionChanged"
HorizontalContentAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
BorderThickness="0">
<ListView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1" Padding="8" Margin="2">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="{StaticResource
MainBackgroundBrush}" />
<Style.Triggers>
<!-- Остаток по всем размерам три пары и меньше — подсветка -->
<DataTrigger Binding="{Binding IsLowStock}" Value="True">
<Setter Property="Background" Value="{StaticResource
LowStockBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="170" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding ImagePath}" Height="110"
Stretch="Uniform" Margin="0,0,12,0" />
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Text="{Binding Title}" FontSize="20" FontWeight="Bold"
TextWrapping="Wrap" />
<TextBlock Margin="0,4,0,0" FontSize="17">
<Run Text="Категория:" /> <Run Text="{Binding Category,
Mode=OneWay}" />
</TextBlock>
<TextBlock FontSize="17">
<Run Text="Количество:" /> <Run Text="{Binding QuantityLabel,
Mode=OneWay}" FontWeight="Bold" />
</TextBlock>
<TextBlock FontSize="15" TextWrapping="Wrap">
<Run Text="Состав:" /> <Run Text="{Binding Composition,
Mode=OneWay}" />
</TextBlock>
</StackPanel>
<StackPanel Grid.Column="2" VerticalAlignment="Center"
HorizontalAlignment="Right">
<TextBlock Text="{Binding Price, StringFormat={}{0:N2} ₽}" FontSize="16"
TextDecorations="Strikethrough" Foreground="DimGray"
HorizontalAlignment="Right">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasDiscount}"
Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="{Binding DiscountedPrice, StringFormat={}{0:N2} ₽}"
FontSize="24"
FontWeight="Bold" HorizontalAlignment="Right" />
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock Grid.Row="3" x:Name="CountText" Margin="4" Foreground="DimGray" />
</Grid>
</Page>

View File

@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ChudoObuv.Data;
using ChudoObuv.Logic;
using ChudoObuv.Models;
namespace ChudoObuv.Pages
{
public partial class CatalogPage : Page
{
private const string AllCategories = "Все категории";
private List<Product> allProducts = new List<Product>();
private bool isInitialized;
public CatalogPage()
{
InitializeComponent();
}
// Вызывается при каждом открытии страницы, в том числе после возврата «Назад»,
// поэтому каталог и остатки всегда актуальны
private void Page_Loaded(object sender, RoutedEventArgs e)
{
bool isAuthorized = Session.IsAuthorized;
FilterPanel.Visibility = isAuthorized ? Visibility.Visible : Visibility.Collapsed;
CartButton.Visibility = isAuthorized ? Visibility.Visible : Visibility.Collapsed;
OrdersButton.Visibility = isAuthorized && Session.CurrentUser.IsManagerOrAdmin ?
Visibility.Visible : Visibility.Collapsed;
CartButton.Content = $"Оформление заказа ({Session.Cart.Sum(item => item.Quantity)})";
OrderClientText.Text = isAuthorized ? $"Заказ оформляется для клиента: {Session.OrderClient.FullName}" : "Для оформления заказа войдите в систему.";
try
{
if (!isInitialized)
{
List<string> categories = DataService.GetCategories();
categories.Insert(0, AllCategories);
CategoryComboBox.ItemsSource = categories;
CategoryComboBox.SelectedIndex = 0;
isInitialized = true;
}
allProducts = DataService.GetProducts();
}
catch (Exception ex)
{
Messages.ShowDatabaseError(ex);
allProducts = new List<Product>();
}
ApplyFilters();
}
private void Filter_Changed(object sender, EventArgs e)
{
ApplyFilters();
}
/// <summary>
/// Поиск, фильтр и сортировка применяются вместе при любом изменении.
/// </summary>
private void ApplyFilters()
{
if (ProductsListView == null || SortComboBox == null)
{
return;
}
IEnumerable<Product> result = allProducts;
if (Session.IsAuthorized)
{
string search = SearchTextBox.Text.Trim();
if (search.Length > 0)
{
result = result.Where(product =>
product.Name.Contains(search, StringComparison.OrdinalIgnoreCase) ||
product.Description.Contains(search, StringComparison.OrdinalIgnoreCase));
}
string category = CategoryComboBox.SelectedItem as string;
if (category != null && category != AllCategories)
{
result = result.Where(product => product.Category == category);
}
if (SortComboBox.SelectedIndex == 1)
{
result = result.OrderBy(product => product.DiscountedPrice);
}
else if (SortComboBox.SelectedIndex == 2)
{
result = result.OrderByDescending(product => product.DiscountedPrice);
}
}
List<Product> shown = result.ToList();
ProductsListView.ItemsSource = shown;
CountText.Text = $"Показано товаров: {shown.Count} из {allProducts.Count}";
}
private void ProductsListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ProductsListView.SelectedItem is not Product product)
{
return;
}
ProductsListView.SelectedItem = null;
if (!Session.IsAuthorized)
{
Messages.ShowInfo("Оформлять заказы могут только авторизованные пользователи.\nВыйдите на страницу входа и войдите под своим логином.");
return;
}
NavigationService.Navigate(new ProductPage(product));
}
private void CartButton_Click(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(new CartPage());
}
private void OrdersButton_Click(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(new OrdersPage());
}
}
}

View File

@ -0,0 +1,26 @@
<Page x:Class="ChudoObuv.Pages.LoginPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FontFamily="Calibri" FontSize="16" Background="{StaticResource MainBackgroundBrush}"
Title="Вход в систему">
<Grid>
<Border Width="420" VerticalAlignment="Center" HorizontalAlignment="Center" Padding="24"
Background="{StaticResource SecondaryBackgroundBrush}" BorderBrush="{StaticResource
AccentBrush}"
BorderThickness="2" CornerRadius="8">
<StackPanel>
<TextBlock Text="Вход в систему" FontSize="24" FontWeight="Bold"
HorizontalAlignment="Center" Margin="0,0,0,16" />
<TextBlock Text="Логин:" Margin="4,0" />
<TextBox x:Name="LoginTextBox" MaxLength="50" KeyDown="LoginTextBox_KeyDown"
ToolTip="Введите логин из списка пользователей системы. Пароль не требуется."
/>
<TextBlock Text="Пароль вводить не требуется." FontSize="13" Foreground="DimGray"
Margin="4,0,4,12" />
<Button Content="Войти" Style="{StaticResource AccentButton}" Click="LoginButton_Click"
/>
<Button Content="Войти как гость" Click="GuestButton_Click" />
</StackPanel>
</Border>
</Grid>
</Page>

View File

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ChudoObuv.Data;
using ChudoObuv.Logic;
using ChudoObuv.Models;
namespace ChudoObuv.Pages
{
public partial class LoginPage : Page
{
public LoginPage()
{
InitializeComponent();
Loaded += (sender, e) => LoginTextBox.Focus();
}
private void LoginButton_Click(object sender, RoutedEventArgs e)
{
TryLogin();
}
private void LoginTextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
TryLogin();
}
}
private void TryLogin()
{
string login = LoginTextBox.Text.Trim();
if (login.Length == 0)
{
Messages.ShowWarning("Введите логин. Логин выдается администратором и указан в списке пользователей системы.");
LoginTextBox.Focus();
return;
}
User user;
try
{
user = DataService.FindUserByLogin(login);
}
catch (Exception ex)
{
Messages.ShowDatabaseError(ex);
return;
}
if (user == null)
{
Messages.ShowError($"Пользователь с логином «{login}» не найден.\nПроверьте раскладку клавиатуры и правильность ввода логина или войдите как гость.");
LoginTextBox.SelectAll();
LoginTextBox.Focus();
return;
}
Session.Login(user);
OpenCatalog();
}
private void GuestButton_Click(object sender, RoutedEventArgs e)
{
Session.Logout();
OpenCatalog();
}
private void OpenCatalog()
{
NavigationService.Navigate(new CatalogPage());
((MainWindow)Application.Current.MainWindow).ClearHistory();
}
}
}

View File

@ -0,0 +1,47 @@
<Page x:Class="ChudoObuv.Pages.OrderPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FontFamily="Calibri" FontSize="16" Background="{StaticResource MainBackgroundBrush}"
Title="Состав заказа"
Loaded="Page_Loaded">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" x:Name="HeaderText" FontSize="20" FontWeight="Bold" Margin="4"
TextWrapping="Wrap" />
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,4">
<TextBlock Text="Дата заказа:" VerticalAlignment="Center" Margin="4,0" />
<TextBlock x:Name="OrderDateText" VerticalAlignment="Center" FontWeight="Bold" />
<!-- Редактирование даты доступно только администратору -->
<DatePicker x:Name="OrderDatePicker" Width="160" Margin="4" FontSize="16" />
<Button x:Name="SaveDateButton" Content="Сохранить дату" Style="{StaticResource
AccentButton}" Click="SaveDateButton_Click" />
</StackPanel>
<DataGrid Grid.Row="2" x:Name="ItemsDataGrid">
<DataGrid.Columns>
<DataGridTextColumn Header="Товарная позиция (модель, производство, размер)"
Binding="{Binding Position}" Width="*" />
<DataGridTextColumn Header="Количество" Binding="{Binding Quantity}" Width="110" />
<DataGridTextColumn Header="Цена за ед., ₽" Binding="{Binding UnitPrice,
StringFormat=N2}" Width="130" />
<DataGridTextColumn Header="Сумма, ₽" Binding="{Binding Sum, StringFormat=N2}"
Width="130" />
<DataGridTemplateColumn x:Name="DeleteColumn" Header="" Width="170">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Удалить позицию" Margin="2" Padding="6,0" MinHeight="26"
Click="DeleteItemButton_Click" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<TextBlock Grid.Row="3" x:Name="TotalText" FontSize="22" FontWeight="Bold"
HorizontalAlignment="Right" Margin="4,8" />
</Grid>
</Page>

View File

@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Globalization;
using ChudoObuv.Data;
using ChudoObuv.Logic;
using ChudoObuv.Models;
namespace ChudoObuv.Pages
{
public partial class OrderPage : Page
{
private readonly Order order;
private List<OrderItem> items = new List<OrderItem>();
public OrderPage(Order order)
{
InitializeComponent();
this.order = order;
Title = $"Состав заказа No {order.Id}";
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
bool isAdmin = Session.CurrentUser.IsAdmin;
OrderDateText.Visibility = isAdmin ? Visibility.Collapsed : Visibility.Visible;
OrderDatePicker.Visibility = isAdmin ? Visibility.Visible : Visibility.Collapsed;
SaveDateButton.Visibility = isAdmin ? Visibility.Visible : Visibility.Collapsed;
DeleteColumn.Visibility = isAdmin ? Visibility.Visible : Visibility.Collapsed;
HeaderText.Text = $"Заказ No {order.Id}. Клиент: {order.ClientFullName}";
OrderDateText.Text = order.OrderDate.ToString("dd.MM.yyyy");
OrderDatePicker.SelectedDate = order.OrderDate;
LoadItems();
}
private void LoadItems()
{
try
{
items = DataService.GetOrderItems(order.Id);
}
catch (Exception ex)
{
Messages.ShowDatabaseError(ex);
items = new List<OrderItem>();
}
ItemsDataGrid.ItemsSource = items;
TotalText.Text = $"Итоговая сумма по заказу: {items.Sum(item => item.Sum):N2} ₽";
}
private void SaveDateButton_Click(object sender, RoutedEventArgs e)
{
// Дату, введенную с клавиатуры, разбираем сами: DatePicker применяет ее только при потере фокуса
if (!DateTime.TryParseExact(OrderDatePicker.Text.Trim(), "dd.MM.yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime newDate))
{
Messages.ShowWarning("Дата заказа указана неверно. Выберите ее в календаре или введите в формате ДД.ММ.ГГГГ, например 15.04.2026.");
OrderDatePicker.Focus();
return;
}
if (newDate.Date > DateTime.Today)
{
Messages.ShowWarning("Дата заказа не может быть позже сегодняшней. Выберите другую дату.");
return;
}
try
{
DataService.UpdateOrderDate(order.Id, newDate);
order.OrderDate = newDate;
Messages.ShowInfo($"Дата заказа No {order.Id} изменена на {newDate:dd.MM.yyyy}.");
}
catch (Exception ex)
{
Messages.ShowDatabaseError(ex);
}
}
private void DeleteItemButton_Click(object sender, RoutedEventArgs e)
{
if (((Button)sender).DataContext is not OrderItem item)
{
return;
}
if (items.Count == 1)
{
Messages.ShowWarning("Это последняя позиция заказа. Пустой заказ хранить нельзя — удалите весь заказ на странице «Список заказов».");
return;
}
if (!Messages.Confirm($"Удалить позицию «{item.Position}» из заказа No {order.Id}?\nТовар вернется на склад.Действие нельзя отменить."))
{
return;
}
try
{
DataService.DeleteOrderItem(item.Id);
LoadItems();
}
catch (Exception ex)
{
Messages.ShowDatabaseError(ex);
}
}
}
}

View File

@ -0,0 +1,43 @@
<Page x:Class="ChudoObuv.Pages.OrdersPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FontFamily="Calibri" FontSize="16" Background="{StaticResource MainBackgroundBrush}"
Title="Список заказов"
Loaded="Page_Loaded">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Новый заказ: сначала выбирается клиент -->
<Border Grid.Row="0" Background="{StaticResource SecondaryBackgroundBrush}" Padding="8"
CornerRadius="6" Margin="0,0,0,8">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Новый заказ для клиента:" VerticalAlignment="Center" Margin="4,0" />
<ComboBox x:Name="ClientComboBox" Width="320" DisplayMemberPath="FullName" />
<Button Content="Добавить заказ" Style="{StaticResource AccentButton}"
Click="AddOrderButton_Click" />
<Button Content="Каталог товаров" Click="CatalogButton_Click" />
</StackPanel>
</Border>
<DataGrid Grid.Row="1" x:Name="OrdersDataGrid"
MouseDoubleClick="OrdersDataGrid_MouseDoubleClick">
<DataGrid.Columns>
<DataGridTextColumn Header="No заказа" Binding="{Binding Id}" Width="90" />
<DataGridTextColumn Header="Дата заказа" Binding="{Binding OrderDate,
StringFormat=dd.MM.yyyy}" Width="130" />
<DataGridTextColumn Header="ФИО клиента" Binding="{Binding ClientFullName}" Width="*" />
<DataGridTextColumn Header="Позиций" Binding="{Binding ItemsCount}" Width="90" />
<DataGridTextColumn Header="Сумма, ₽" Binding="{Binding TotalSum, StringFormat=N2}"
Width="130" />
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,8,0,0">
<TextBlock Text="Двойной щелчок по заказу — просмотр состава" VerticalAlignment="Center"
Foreground="DimGray" Margin="8,0" />
<Button Content="Состав заказа" Click="OpenOrderButton_Click" />
<Button Content="Удалить заказ" Click="DeleteOrderButton_Click" />
</StackPanel>
</Grid>
</Page>

View File

@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ChudoObuv.Data;
using ChudoObuv.Logic;
using ChudoObuv.Models;
namespace ChudoObuv.Pages
{
public partial class OrdersPage : Page
{
public OrdersPage()
{
InitializeComponent();
}
// Список перечитывается при каждом открытии страницы (после добавления, изменения, удаления)
private void Page_Loaded(object sender, RoutedEventArgs e)
{
try
{
if (ClientComboBox.ItemsSource == null)
{
ClientComboBox.ItemsSource = DataService.GetClients();
}
OrdersDataGrid.ItemsSource = DataService.GetOrders();
}
catch (Exception ex)
{
Messages.ShowDatabaseError(ex);
}
}
private Order GetSelectedOrder()
{
if (OrdersDataGrid.SelectedItem is Order order)
{
return order;
}
Messages.ShowWarning("Сначала выберите заказ в списке (щелкните по строке).");
return null;
}
private void AddOrderButton_Click(object sender, RoutedEventArgs e)
{
if (ClientComboBox.SelectedItem is not User client)
{
Messages.ShowWarning("Выберите клиента, для которого оформляется заказ.");
ClientComboBox.Focus();
return;
}
if (Session.Cart.Count > 0 && !Messages.Confirm("Ранее начатый неподтвержденный заказ будет отменен.Продолжить ? "))
{
return;
}
Session.StartOrderFor(client);
NavigationService.Navigate(new CatalogPage());
}
private void CatalogButton_Click(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(new CatalogPage());
}
private void OpenOrderButton_Click(object sender, RoutedEventArgs e)
{
Order order = GetSelectedOrder();
if (order != null)
{
NavigationService.Navigate(new OrderPage(order));
}
}
private void OrdersDataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (OrdersDataGrid.SelectedItem is Order order)
{
NavigationService.Navigate(new OrderPage(order));
}
}
private void DeleteOrderButton_Click(object sender, RoutedEventArgs e)
{
Order order = GetSelectedOrder();
if (order == null)
{
return;
}
if (!Messages.Confirm($"Удалить заказ No {order.Id} от {order.OrderDate:dd.MM.yyyy} ({ order.ClientFullName})?\n" + "Товары вернутся на склад. Действие нельзя отменить."))
{
return;
}
try
{
DataService.DeleteOrder(order.Id);
OrdersDataGrid.ItemsSource = DataService.GetOrders();
Messages.ShowInfo($"Заказ No {order.Id} удален.");
}
catch (Exception ex)
{
Messages.ShowDatabaseError(ex);
}
}
}
}

View File

@ -0,0 +1,76 @@
<Page x:Class="ChudoObuv.Pages.ProductPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FontFamily="Calibri" FontSize="16" Background="{StaticResource MainBackgroundBrush}"
Title="Просмотр товара"
Loaded="Page_Loaded">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid Margin="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="340" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" BorderBrush="{StaticResource AccentBrush}" BorderThickness="1"
Padding="8" VerticalAlignment="Top">
<Image x:Name="ProductImage" Height="280" Stretch="Uniform" />
</Border>
<StackPanel Grid.Column="1" Margin="20,0,0,0">
<TextBlock x:Name="NameText" FontSize="26" FontWeight="Bold" TextWrapping="Wrap" />
<TextBlock Margin="0,6,0,0"><Run Text="Производство:" FontWeight="Bold" /> <Run
x:Name="ManufacturerRun" /></TextBlock>
<TextBlock><Run Text="Категория:" FontWeight="Bold" /> <Run x:Name="CategoryRun"
/></TextBlock>
<StackPanel Orientation="Horizontal" Margin="0,6">
<TextBlock Text="Цена со скидкой:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBlock x:Name="DiscountedPriceText" FontSize="24" FontWeight="Bold" Margin="8,0"
VerticalAlignment="Center" />
<TextBlock x:Name="OldPriceText" TextDecorations="Strikethrough"
Foreground="DimGray" VerticalAlignment="Center" />
</StackPanel>
<TextBlock TextWrapping="Wrap"><Run Text="Состав:" FontWeight="Bold" /> <Run
x:Name="CompositionRun" /></TextBlock>
<TextBlock TextWrapping="Wrap" Margin="0,6,0,0"><Run Text="Описание:" FontWeight="Bold"
/> <Run x:Name="DescriptionRun" /></TextBlock>
<TextBlock TextWrapping="Wrap" Margin="0,6,0,0"><Run Text="Размерный ряд:"
FontWeight="Bold" /> <Run x:Name="SizeRangeRun" /></TextBlock>
<TextBlock TextWrapping="Wrap"><Run Text="Доступные для заказа размеры:"
FontWeight="Bold" /> <Run x:Name="AvailableSizesRun" /></TextBlock>
<Border Background="{StaticResource SecondaryBackgroundBrush}" Padding="12"
Margin="0,16,0,0" CornerRadius="6">
<StackPanel>
<TextBlock Text="Добавление в заказ" FontSize="20" FontWeight="Bold" />
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="160" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Размер:" VerticalAlignment="Center" />
<ComboBox Grid.Column="1" x:Name="SizeComboBox" DisplayMemberPath="SizeText"
SelectionChanged="SizeComboBox_SelectionChanged" />
<TextBlock Grid.Column="2" Text="Количество:" VerticalAlignment="Center"
Margin="12,0,0,0" />
<TextBox Grid.Column="3" x:Name="QuantityTextBox" Text="1" MaxLength="3"
PreviewTextInput="QuantityTextBox_PreviewTextInput"
ToolTip="Введите целое число пар от 1 до доступного количества" />
<TextBlock Grid.Column="4" x:Name="AvailableText" VerticalAlignment="Center"
Margin="8,0" Foreground="DimGray" />
</Grid>
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
<Button Content="Добавить в заказ" Style="{StaticResource AccentButton}"
Click="AddButton_Click" />
<Button Content="Перейти к оформлению заказа" Click="CartButton_Click" />
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
</Grid>
</ScrollViewer>
</Page>

View File

@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ChudoObuv.Data;
using ChudoObuv.Logic;
using ChudoObuv.Models;
namespace ChudoObuv.Pages
{
public partial class ProductPage : Page
{
private readonly Product product;
private List<StockItem> stockItems = new List<StockItem>();
public ProductPage(Product product)
{
InitializeComponent();
this.product = product;
Title = $"Просмотр товара: {product.Name}";
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
ProductImage.Source = new BitmapImage(new Uri(product.ImagePath));
NameText.Text = product.Name;
ManufacturerRun.Text = product.Manufacturer;
CategoryRun.Text = $"{product.Category} / {product.Subcategory}";
DiscountedPriceText.Text = $"{product.DiscountedPrice:N2} ₽";
OldPriceText.Text = product.HasDiscount ? $"{product.Price:N2} ₽" : "";
CompositionRun.Text = product.Composition;
DescriptionRun.Text = product.Description;
LoadSizes();
}
/// <summary>
/// Загружает актуальные остатки из БД и учитывает то, что уже добавлено в заказ.
/// </summary>
private void LoadSizes()
{
try
{
stockItems = DataService.GetStockItems(product.Id);
}
catch (Exception ex)
{
Messages.ShowDatabaseError(ex);
stockItems = new List<StockItem>();
}
SizeRangeRun.Text = stockItems.Count > 0 ? string.Join(", ", stockItems.Select(item =>
item.SizeText)) : "нет данных";
List<StockItem> available = stockItems
.Where(item => item.Quantity - Session.GetQuantityInCart(item.Id) > 0)
.ToList();
AvailableSizesRun.Text = available.Count > 0 ? string.Join(", ", available.Select(item =>
item.SizeText)) : "нет в наличии";
SizeComboBox.ItemsSource = available;
SizeComboBox.SelectedIndex = available.Count > 0 ? 0 : -1;
UpdateAvailableText();
}
private int GetAvailableQuantity(StockItem stockItem)
{
return stockItem.Quantity - Session.GetQuantityInCart(stockItem.Id);
}
private void SizeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
UpdateAvailableText();
}
private void UpdateAvailableText()
{
AvailableText.Text = SizeComboBox.SelectedItem is StockItem stockItem
? $"доступно: {GetAvailableQuantity(stockItem)} пар"
: "";
}
// В поле количества можно вводить только цифры
private void QuantityTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
e.Handled = !e.Text.All(char.IsDigit);
}
private void AddButton_Click(object sender, RoutedEventArgs e)
{
if (SizeComboBox.SelectedItem is not StockItem stockItem)
{
Messages.ShowWarning("Выберите размер обуви. Если список размеров пуст — товара нет в наличии.");
return;
}
int available = GetAvailableQuantity(stockItem);
if (!int.TryParse(QuantityTextBox.Text, out int quantity) || quantity <= 0)
{
Messages.ShowWarning("Количество указано неверно. Введите целое число пар больше нуля.");
QuantityTextBox.Focus();
return;
}
if (quantity > available)
{
Messages.ShowWarning($"Для размера {stockItem.SizeText} доступно только {available} пар. Уменьшите количество.");
QuantityTextBox.Focus();
return;
}
Session.AddToCart(new OrderItem
{
StockItemId = stockItem.Id,
ProductName = product.Name,
Manufacturer = product.Manufacturer,
Size = stockItem.Size,
Quantity = quantity,
UnitPrice = product.DiscountedPrice
});
Messages.ShowInfo($"Товар «{product.Name}», размер {stockItem.SizeText}, {quantity} пар добавлен в заказ.\n" +
"Добавьте другие товары или перейдите к оформлению заказа.");
QuantityTextBox.Text = "1";
LoadSizes();
}
private void CartButton_Click(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(new CartPage());
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
ChudoObuv/Resources/app.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,78 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v8.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v8.0": {
"ChudoObuv/1.0.0": {
"dependencies": {
"MySqlConnector": "2.6.2"
},
"runtime": {
"ChudoObuv.dll": {}
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions/8.0.2": {
"runtime": {
"lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.1024.46610"
}
}
},
"Microsoft.Extensions.Logging.Abstractions/8.0.2": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Logging.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.1024.46610"
}
}
},
"MySqlConnector/2.6.2": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2",
"Microsoft.Extensions.Logging.Abstractions": "8.0.2"
},
"runtime": {
"lib/net8.0/MySqlConnector.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.6.2.0"
}
}
}
}
},
"libraries": {
"ChudoObuv/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Microsoft.Extensions.DependencyInjection.Abstractions/8.0.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg==",
"path": "microsoft.extensions.dependencyinjection.abstractions/8.0.2",
"hashPath": "microsoft.extensions.dependencyinjection.abstractions.8.0.2.nupkg.sha512"
},
"Microsoft.Extensions.Logging.Abstractions/8.0.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-nroMDjS7hNBPtkZqVBbSiQaQjWRDxITI8Y7XnDs97rqG3EbzVTNLZQf7bIeUJcaHOV8bca47s1Uxq94+2oGdxA==",
"path": "microsoft.extensions.logging.abstractions/8.0.2",
"hashPath": "microsoft.extensions.logging.abstractions.8.0.2.nupkg.sha512"
},
"MySqlConnector/2.6.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-dlTc/tsBa42szdvpj4nrHvQVUb0gsMwR9HT6J0Zz2OSqfP0CdYlGw+qQPqCpbnO97idfvclSjKYtM6y5QaBQaQ==",
"path": "mysqlconnector/2.6.2",
"hashPath": "mysqlconnector.2.6.2.nupkg.sha512"
}
}
}

View File

@ -0,0 +1,19 @@
{
"runtimeOptions": {
"tfm": "net8.0",
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "8.0.0"
},
{
"name": "Microsoft.WindowsDesktop.App",
"version": "8.0.0"
}
],
"configProperties": {
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
"CSWINRT_USE_WINDOWS_UI_XAML_PROJECTIONS": false
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Some files were not shown because too many files have changed in this diff Show More