From 6822472102952833fbf566bd23296f34b9e4a072 Mon Sep 17 00:00:00 2001 From: Naz Date: Thu, 4 Jun 2026 10:15:56 +0800 Subject: [PATCH] Update Bookings --- .../Controllers/BookingsController.cs | 12 +- Areas/Bookings/Models/BookingManager.cs | 2 + Areas/Bookings/Views/Bookings/Calendar.cshtml | 116 ++++++++++-------- Areas/Bookings/Views/Bookings/Create.cshtml | 12 +- Areas/Bookings/Views/Bookings/Index.cshtml | 48 ++++++-- Areas/Bookings/Views/Bookings/Managers.cshtml | 7 +- Controllers/API/BookingsAPI.cs | 65 ++++++---- Models/RoleModel.cs | 3 + 8 files changed, 172 insertions(+), 93 deletions(-) diff --git a/Areas/Bookings/Controllers/BookingsController.cs b/Areas/Bookings/Controllers/BookingsController.cs index 9130802..496547e 100644 --- a/Areas/Bookings/Controllers/BookingsController.cs +++ b/Areas/Bookings/Controllers/BookingsController.cs @@ -26,11 +26,17 @@ namespace PSTW_CentralSystem.Areas.Bookings.Controllers } // DB-backed manager check (NO Identity roles here) - private Task IsManagerAsync() + private async Task IsManagerAsync() { var me = GetCurrentUserId(); - if (me is null) return Task.FromResult(false); - return _db.BookingManager.AsNoTracking() + if (me is null) return false; + + if (User.IsInRole("SuperAdmin") || User.IsInRole("SystemAdmin")) + { + return true; + } + + return await _db.BookingManager.AsNoTracking() .AnyAsync(x => x.UserId == me.Value && x.IsActive); } diff --git a/Areas/Bookings/Models/BookingManager.cs b/Areas/Bookings/Models/BookingManager.cs index a4d768f..067bddf 100644 --- a/Areas/Bookings/Models/BookingManager.cs +++ b/Areas/Bookings/Models/BookingManager.cs @@ -22,6 +22,8 @@ namespace PSTW_CentralSystem.Areas.Bookings.Models [Required] public DateTime CreatedUtc { get; set; } = DateTime.UtcNow; + [ForeignKey("CreatedByUserId")] public int? CreatedByUserId { get; set; } + } } diff --git a/Areas/Bookings/Views/Bookings/Calendar.cshtml b/Areas/Bookings/Views/Bookings/Calendar.cshtml index 370817c..b7c1494 100644 --- a/Areas/Bookings/Views/Bookings/Calendar.cshtml +++ b/Areas/Bookings/Views/Bookings/Calendar.cshtml @@ -243,7 +243,8 @@ box-shadow: 0 2px 6px rgba(0,0,0,.06); padding: 6px 8px; font-size: 12px; - overflow: hidden + overflow: hidden; + z-index: 2; /* Tambah baris ini supaya kotak berada DI ATAS garisan */ } .booking .t { @@ -387,59 +388,66 @@ dowRow.dataset.done = "1"; } - // --- Lookups (rooms + users) --- - let userMap = new Map(), roomMap = new Map(), users = [], rooms = []; - async function loadLookups() { - try { - const r = await fetch(`${api}?scope=${calendarScope}&lookups=1`); - if (!r.ok) throw new Error(`Lookups ${r.status} ${r.statusText}`); - const js = await r.json(); - rooms = js.rooms ?? []; - users = js.users ?? []; - } catch (e) { - alertMsg(e.message); - rooms = []; - users = []; - } - - // Fill selects - ddlRoom.innerHTML = ''; - rooms.forEach(r => { - const id = r.roomId; - const name = r.roomName ?? `Room ${id}`; - if (id == null) return; - const opt = document.createElement("option"); - opt.value = id; - opt.textContent = name; - ddlRoom.appendChild(opt); - }); - - ddlUser.innerHTML = ''; - users.forEach(u => { - const id = u.Id ?? u.id; - const name = u.UserName ?? u.userName ?? ""; - const email = u.Email ?? ""; - const opt = document.createElement("option"); - opt.value = id; - opt.textContent = email ? `${name} (${email})` : name; - ddlUser.appendChild(opt); - }); - - userMap = new Map( - users.map(u => [ - Number(u.Id ?? u.id), - (u.UserName ?? u.userName ?? "") + (u.Email ? ` (${u.Email})` : "") - ]) - ); - roomMap = new Map( - rooms.map(r => { - const id = Number(r.roomId ?? r.RoomId); - const name = r.roomName ?? r.Name ?? r.RoomName ?? `Room ${id}`; - return [id, name]; - }) - ); + // --- Lookups (rooms + users) --- + let userMap = new Map(), roomMap = new Map(), users = [], rooms = []; + async function loadLookups() { + try { + const r = await fetch(`${api}?scope=${calendarScope}&lookups=1`); + if (!r.ok) throw new Error(`Lookups ${r.status} ${r.statusText}`); + const js = await r.json(); + rooms = js.rooms ?? []; + users = js.users ?? []; + } catch (e) { + alertMsg(e.message); + rooms = []; + users = []; } + // Fill selects + ddlRoom.innerHTML = ''; + rooms.forEach(r => { + const id = r.roomId; + const name = r.roomName ?? `Room ${id}`; + if (id == null) return; + const opt = document.createElement("option"); + opt.value = id; + opt.textContent = name; + ddlRoom.appendChild(opt); + }); + + ddlUser.innerHTML = ''; + + // Filter out the Admin + const nonAdminUsers = users.filter(u => { + const name = u.UserName ?? u.userName ?? ""; + return name !== "MAAdmin" && name !== "SysAdmin"; + }); + + nonAdminUsers.forEach(u => { + const id = u.Id ?? u.id; + const name = u.UserName ?? u.userName ?? ""; + const email = u.Email ?? ""; + const opt = document.createElement("option"); + opt.value = id; + opt.textContent = email ? `${name} (${email})` : name; + ddlUser.appendChild(opt); + }); + + userMap = new Map( + users.map(u => [ + Number(u.Id ?? u.id), + (u.UserName ?? u.userName ?? "") + (u.Email ? ` (${u.Email})` : "") + ]) + ); + roomMap = new Map( + rooms.map(r => { + const id = Number(r.roomId ?? r.RoomId); + const name = r.roomName ?? r.Name ?? r.RoomName ?? `Room ${id}`; + return [id, name]; + }) + ); + } + // --- Data fetching for month grid --- let curMonth = startOfMonth(new Date()), currentData = []; async function fetchGridData(month) { @@ -556,7 +564,7 @@ function getUserId(b) { const v = [b.requestedByUserId, b.UserId, b.userId, b.targetUserId, b.TargetUserId, b.bookedByUserId, b.BookedByUserId, b.approvedByUserId, b.ApprovedByUserId].find(x => x != null); const n = Number(v); return Number.isNaN(n) ? null : n; } function openDayBoard(dayDate) { - boardTitle.textContent = `Schedule • ${dayDate.toLocaleDateString()}`; + boardTitle.textContent = `Schedule • ${dayDate.toLocaleDateString('en-GB')}`; boardFilters.textContent = `Filters: ${ddlRoom.options[ddlRoom.selectedIndex]?.text || "All rooms"}`; // Left time labels @@ -592,7 +600,7 @@ colsHead.appendChild(h); }); - canvas.style.height = `${TOTAL_INTERVALS * SLOT_PX}px`; + canvas.style.height = `${(TOTAL_INTERVALS + 1) * SLOT_PX}px`; // Build grid columns colGrid.innerHTML = ""; diff --git a/Areas/Bookings/Views/Bookings/Create.cshtml b/Areas/Bookings/Views/Bookings/Create.cshtml index a191689..d1d0c5a 100644 --- a/Areas/Bookings/Views/Bookings/Create.cshtml +++ b/Areas/Bookings/Views/Bookings/Create.cshtml @@ -352,7 +352,9 @@ // ---------- Lookups ---------- async function loadLookups(selectedRoomId, selectedUserId) { - const res = await fetch(`${api}?lookups=1`); + + const res = await fetch(`${api}?lookups=1&onlyActive=true`); + if (!res.ok) throw new Error(await res.text()); const { rooms = [], users = [] } = await res.json(); @@ -388,7 +390,13 @@ // Users const userDdl = document.getElementById("RequestedByUserId"); userDdl.innerHTML = ''; - users.forEach(u => { + + const nonAdminUsers = users.filter(u => { + const name = pick(u, "UserName", "userName") ?? ""; + return name !== "MAAdmin" && name !== "SysAdmin"; + }); + + nonAdminUsers.forEach(u => { const id = pick(u, "Id", "id"); if (id == null) return; diff --git a/Areas/Bookings/Views/Bookings/Index.cshtml b/Areas/Bookings/Views/Bookings/Index.cshtml index 4ac175a..6cc2d15 100644 --- a/Areas/Bookings/Views/Bookings/Index.cshtml +++ b/Areas/Bookings/Views/Bookings/Index.cshtml @@ -1,12 +1,22 @@ @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @using System.Security.Claims +@inject PSTW_CentralSystem.DBContext.CentralSystemContext _db @{ ViewData["Title"] = "Bookings"; Layout = "_Layout"; - var isMgr = User?.IsInRole("Manager") == true; var idStr = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; var meId = int.TryParse(idStr, out var tmp) ? tmp : 0; + + bool isAdmin = User?.IsInRole("SuperAdmin") == true || User?.IsInRole("SystemAdmin") == true; + + bool isBookingManager = false; + if (meId > 0) + { + isBookingManager = _db.BookingManager.Any(x => x.UserId == meId && x.IsActive); + } + + var isMgr = isAdmin || isBookingManager; }