/
kochkatech
/
Queue
Обзор
Документация
Войти
/
kochkatech
/
Queue
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
src/Queue.Admin/ViewModels/EmployeesViewModel.cs
246 строк
7 KB
kochkareal
init: Начальная структура проекта и панель администратора
24 июл 2026, 21:42
24 июл 2026, 21:42
cd5152a
Код
Авторство
О чём код?
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.EntityFrameworkCore; using Queue.Core.Models; using Queue.Data; namespace Queue.Admin.ViewModels; public record RoleOption(EmployeeRole Value, string Display); // Строка грида: сотрудник + прозрачный доступ к его полям (для инлайн-редактирования ФИО/логина/активности) // и подготовленные для отображения строки роли/окна. public class EmployeeRow { public Employee Employee { get; } public EmployeeRow(Employee employee) { Employee = employee; } public string FullName { get => Employee.FullName; set => Employee.FullName = value; } public string Login { get => Employee.Login; set => Employee.Login = value; } public bool IsActive { get => Employee.IsActive; set => Employee.IsActive = value; } public string RoleDisplay => Employee.Role == EmployeeRole.Administrator ? "Администратор" : "Оператор"; public string WindowDisplay { get; init; } = "—"; } public partial class EmployeesViewModel : ObservableObject { private static readonly ServiceWindow NoWindowOption = new() { Id = 0, Name = "Без окна" }; private readonly QueueDbContextFactory _dbContextFactory; public ObservableCollection<EmployeeRow> Employees { get; } = new(); // Первый пункт — синтетический "Без окна" (Id = 0), не хранится в БД. public ObservableCollection<ServiceWindow> WindowOptions { get; } = new(); public IReadOnlyList<RoleOption> Roles { get; } = new List<RoleOption> { new(EmployeeRole.Administrator, "Администратор"), new(EmployeeRole.Operator, "Оператор") }; [ObservableProperty] private string newFullName = string.Empty; [ObservableProperty] private string newLogin = string.Empty; [ObservableProperty] private EmployeeRole newRole = EmployeeRole.Operator; [ObservableProperty] private EmployeeRow? selectedEmployee; [ObservableProperty] private EmployeeRole editingRole; [ObservableProperty] private ServiceWindow? editingWindow; public EmployeesViewModel(QueueDbContextFactory dbContextFactory) { _dbContextFactory = dbContextFactory; _ = LoadAsync(); } [RelayCommand] private async Task LoadAsync() { using var db = _dbContextFactory.CreateDbContext(); var windows = await db.Windows.OrderBy(w => w.Name).ToListAsync(); WindowOptions.Clear(); WindowOptions.Add(NoWindowOption); foreach (var window in windows) { WindowOptions.Add(window); } var windowNames = windows.ToDictionary(w => w.Id, w => w.Name); var employees = await db.Employees.OrderBy(e => e.FullName).ToListAsync(); Employees.Clear(); foreach (var employee in employees) { Employees.Add(new EmployeeRow(employee) { WindowDisplay = employee.CurrentWindowId.HasValue && windowNames.TryGetValue(employee.CurrentWindowId.Value, out var name) ? name : "—" }); } } // password передаётся из code-behind, так как PasswordBox.Password не поддерживает биндинг. [RelayCommand] private async Task AddAsync(string? password) { if (string.IsNullOrWhiteSpace(NewFullName) || string.IsNullOrWhiteSpace(NewLogin) || string.IsNullOrWhiteSpace(password)) { return; } var employee = new Employee { FullName = NewFullName.Trim(), Login = NewLogin.Trim(), PasswordHash = HashPassword(password), Role = NewRole, IsActive = true }; using (var db = _dbContextFactory.CreateDbContext()) { db.Employees.Add(employee); await db.SaveChangesAsync(); } NewFullName = string.Empty; NewLogin = string.Empty; NewRole = EmployeeRole.Operator; await LoadAsync(); } // Сохраняет ФИО/логин/активность после редактирования строки в гриде. [RelayCommand] private async Task UpdateAsync(Employee? employee) { if (employee is null) { return; } using var db = _dbContextFactory.CreateDbContext(); db.Attach(employee); db.Entry(employee).Property(e => e.FullName).IsModified = true; db.Entry(employee).Property(e => e.Login).IsModified = true; db.Entry(employee).Property(e => e.IsActive).IsModified = true; await db.SaveChangesAsync(); } [RelayCommand] private async Task DeleteAsync(EmployeeRow? row) { if (row is null) { return; } using (var db = _dbContextFactory.CreateDbContext()) { db.Employees.Attach(row.Employee); db.Employees.Remove(row.Employee); await db.SaveChangesAsync(); } Employees.Remove(row); } partial void OnSelectedEmployeeChanged(EmployeeRow? value) { if (value is null) { return; } EditingRole = value.Employee.Role; EditingWindow = WindowOptions.FirstOrDefault(w => w.Id == (value.Employee.CurrentWindowId ?? 0)) ?? NoWindowOption; } [RelayCommand] private async Task SaveRoleAndWindowAsync() { if (SelectedEmployee is null) { return; } using var db = _dbContextFactory.CreateDbContext(); var employee = await db.Employees.FindAsync(SelectedEmployee.Employee.Id); if (employee is null) { return; } employee.Role = EditingRole; employee.CurrentWindowId = EditingWindow is null || EditingWindow.Id == 0 ? null : EditingWindow.Id; await db.SaveChangesAsync(); await LoadAsync(); } // newPassword передаётся из code-behind (см. AddAsync). [RelayCommand] private async Task UpdatePasswordAsync(string? newPassword) { if (SelectedEmployee is null || string.IsNullOrWhiteSpace(newPassword)) { return; } using var db = _dbContextFactory.CreateDbContext(); var employee = await db.Employees.FindAsync(SelectedEmployee.Employee.Id); if (employee is null) { return; } employee.PasswordHash = HashPassword(newPassword); await db.SaveChangesAsync(); } // Простое хеширование для MVP. Перед выходом в прод — заменить на PBKDF2/BCrypt с солью. private static string HashPassword(string password) { var bytes = Encoding.UTF8.GetBytes(password); var hash = SHA256.HashData(bytes); return Convert.ToHexString(hash); } }