diff --git a/Controllers/DevicesController.cs b/Controllers/DevicesController.cs new file mode 100644 index 0000000..4a0a086 --- /dev/null +++ b/Controllers/DevicesController.cs @@ -0,0 +1,247 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using NoticeBoard.Data; +using NoticeBoard.Models; + +namespace NoticeBoard.Controllers; + +public class DevicesController : Controller +{ + private readonly AppDbContext _db; + + public DevicesController(AppDbContext db) + { + _db = db; + } + + public async Task Index() + { + var devices = await _db.Devices + .Include(d => d.DeviceSlides) + .OrderBy(d => d.Name) + .ToListAsync(); + return View(devices); + } + + public IActionResult Create() + { + return View(new Device()); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Create(Device device) + { + if (string.IsNullOrWhiteSpace(device.Slug)) + device.Slug = device.Name.ToLower().Replace(" ", "").Replace("-", ""); + + device.Slug = device.Slug.ToLower().Trim(); + + if (await _db.Devices.AnyAsync(d => d.Slug == device.Slug)) + { + ModelState.AddModelError("Slug", "This slug is already in use."); + return View(device); + } + + if (!ModelState.IsValid) return View(device); + + device.CreatedAt = DateTime.UtcNow; + _db.Devices.Add(device); + await _db.SaveChangesAsync(); + + TempData["Success"] = $"Device '{device.Name}' created. URL: /{device.Slug}"; + return RedirectToAction(nameof(Index)); + } + + public async Task Edit(int id) + { + var device = await _db.Devices.FindAsync(id); + if (device == null) return NotFound(); + return View(device); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Edit(int id, Device device) + { + if (id != device.Id) return NotFound(); + + device.Slug = device.Slug.ToLower().Trim(); + + if (await _db.Devices.AnyAsync(d => d.Slug == device.Slug && d.Id != id)) + { + ModelState.AddModelError("Slug", "This slug is already in use."); + return View(device); + } + + if (!ModelState.IsValid) return View(device); + + var existing = await _db.Devices.FindAsync(id); + if (existing == null) return NotFound(); + + existing.Name = device.Name; + existing.Slug = device.Slug; + existing.ResolutionWidth = device.ResolutionWidth; + existing.ResolutionHeight = device.ResolutionHeight; + existing.Transition = device.Transition; + + await _db.SaveChangesAsync(); + TempData["Success"] = $"Device '{device.Name}' updated."; + return RedirectToAction(nameof(Index)); + } + + public async Task Delete(int id) + { + var device = await _db.Devices + .Include(d => d.DeviceSlides) + .FirstOrDefaultAsync(d => d.Id == id); + if (device == null) return NotFound(); + return View(device); + } + + [HttpPost, ActionName("Delete")] + [ValidateAntiForgeryToken] + public async Task DeleteConfirmed(int id) + { + var device = await _db.Devices.FindAsync(id); + if (device == null) return NotFound(); + + _db.Devices.Remove(device); + await _db.SaveChangesAsync(); + + TempData["Success"] = $"Device '{device.Name}' deleted."; + return RedirectToAction(nameof(Index)); + } + + // === Playlist Management === + + public async Task Playlist(int id) + { + var device = await _db.Devices + .Include(d => d.DeviceSlides.OrderBy(ds => ds.DisplayOrder)) + .ThenInclude(ds => ds.Slide) + .FirstOrDefaultAsync(d => d.Id == id); + + if (device == null) return NotFound(); + + var assignedSlideIds = device.DeviceSlides.Select(ds => ds.SlideId).ToHashSet(); + ViewBag.AvailableSlides = await _db.Slides + .Where(s => !assignedSlideIds.Contains(s.Id)) + .OrderBy(s => s.Name) + .ToListAsync(); + + return View(device); + } + + [HttpPost] + public async Task AddToPlaylist(int id, int slideId, int durationSeconds = 30) + { + var device = await _db.Devices.Include(d => d.DeviceSlides).FirstOrDefaultAsync(d => d.Id == id); + if (device == null) return NotFound(); + + var maxOrder = device.DeviceSlides.Any() ? device.DeviceSlides.Max(ds => ds.DisplayOrder) : 0; + + var ds = new DeviceSlide + { + DeviceId = id, + SlideId = slideId, + DisplayOrder = maxOrder + 1, + DurationSeconds = durationSeconds, + Enabled = true + }; + + _db.DeviceSlides.Add(ds); + await _db.SaveChangesAsync(); + return RedirectToAction(nameof(Playlist), new { id }); + } + + [HttpPost] + public async Task RemoveFromPlaylist(int id, int deviceSlideId) + { + var ds = await _db.DeviceSlides.FindAsync(deviceSlideId); + if (ds == null) return NotFound(); + + _db.DeviceSlides.Remove(ds); + await _db.SaveChangesAsync(); + return RedirectToAction(nameof(Playlist), new { id }); + } + + [HttpPost] + public async Task UpdatePlaylist(int id, int[] slideIds, int[] durations, bool[] enabled) + { + var deviceSlides = await _db.DeviceSlides + .Where(ds => ds.DeviceId == id) + .ToListAsync(); + + for (int i = 0; i < slideIds.Length; i++) + { + var ds = deviceSlides.FirstOrDefault(x => x.Id == slideIds[i]); + if (ds != null) + { + ds.DisplayOrder = i + 1; + if (i < durations.Length) ds.DurationSeconds = durations[i]; + ds.Enabled = i < enabled.Length && enabled[i]; + } + } + + await _db.SaveChangesAsync(); + TempData["Success"] = "Playlist updated."; + return RedirectToAction(nameof(Playlist), new { id }); + } + + [HttpPost] + public async Task ReorderPlaylist([FromBody] ReorderRequest request) + { + if (request?.ItemIds == null) return BadRequest(); + + var deviceSlides = await _db.DeviceSlides + .Where(ds => request.ItemIds.Contains(ds.Id)) + .ToListAsync(); + + for (int i = 0; i < request.ItemIds.Length; i++) + { + var ds = deviceSlides.FirstOrDefault(x => x.Id == request.ItemIds[i]); + if (ds != null) ds.DisplayOrder = i + 1; + } + + await _db.SaveChangesAsync(); + return Ok(); + } + + [HttpPost] + public async Task UpdateDuration([FromBody] UpdateDurationRequest request) + { + var ds = await _db.DeviceSlides.FindAsync(request.DeviceSlideId); + if (ds == null) return NotFound(); + ds.DurationSeconds = request.DurationSeconds; + await _db.SaveChangesAsync(); + return Ok(); + } + + [HttpPost] + public async Task ToggleEnabled([FromBody] ToggleEnabledRequest request) + { + var ds = await _db.DeviceSlides.FindAsync(request.DeviceSlideId); + if (ds == null) return NotFound(); + ds.Enabled = request.Enabled; + await _db.SaveChangesAsync(); + return Ok(); + } +} + +public class ReorderRequest +{ + public int[]? ItemIds { get; set; } +} + +public class UpdateDurationRequest +{ + public int DeviceSlideId { get; set; } + public int DurationSeconds { get; set; } +} + +public class ToggleEnabledRequest +{ + public int DeviceSlideId { get; set; } + public bool Enabled { get; set; } +}