Dashboard is élő, bekötve!

This commit is contained in:
2026-09-14 11:14:22 +02:00
parent 289992ce5e
commit d00e087b35
8 changed files with 347 additions and 99 deletions
@@ -17,6 +17,24 @@ namespace WorkFlowCheck.API.Controllers
_checkListService = checkListService;
}
[HttpGet("GetDashboardInfo")]
public async Task<ApiResponseDTO<DashboardDTO>> GetDashboardInfo()
{
var retVal = new ApiResponseDTO<DashboardDTO>();
try
{
retVal.Data = await _checkListService.GetDashboardInfoAsync();
retVal.IsSuccess = true;
}
catch (Exception ex)
{
retVal.IsSuccess = false;
retVal.Errors.Add("A műszerfal adatainak lekérése sikertelen.");
Log.Error(ex, "GetDashboardInfo failed");
}
return retVal;
}
[HttpGet("GetCheckListHeader/{id}")]
public async Task<ApiResponseDTO<CheckListHeaderDTO>> GetCheckListHeader(int id)
{
@@ -18,6 +18,7 @@ using WorkFlowCheck.BL.Services.Interfaces;
using WorkFlowCheck.Common.DataTables;
using WorkFlowCheck.Common.DTO;
using WorkFlowCheck.Common.Enums;
using WorkFlowCheck.Common.Helper;
using WorkFlowCheck.DL;
using WorkFlowCheck.DL.Entities;
@@ -39,6 +40,171 @@ namespace WorkFlowCheck.BL.Services
_messageService = messageService;
}
public async Task<DashboardDTO> GetDashboardInfoAsync()
{
var now = DateTime.UtcNow;
var currentMonthStart = new DateTime(now.Year, now.Month, 1, 0, 0, 0, DateTimeKind.Utc);
var nextMonthStart = currentMonthStart.AddMonths(1);
var previousMonthStart = currentMonthStart.AddMonths(-1);
var sixMonthStart = currentMonthStart.AddMonths(-5);
var fortyEightHoursAgo = now.AddHours(-48);
var headers = _dbContext.CheckListHeaders.AsNoTracking();
var statusCounts = await headers
.GroupBy(x => x.CheckStatus)
.Select(x => new { Status = x.Key, Count = x.Count() })
.ToListAsync();
var monthlyRaw = await headers
.Where(x => x.CreatedAt >= sixMonthStart && x.CreatedAt < nextMonthStart)
.GroupBy(x => new { x.CreatedAt.Year, x.CreatedAt.Month })
.Select(x => new
{
x.Key.Year,
x.Key.Month,
Total = x.Count(),
Approved = x.Count(y => y.CheckStatus == CheckStatus.Signed || y.CheckStatus == CheckStatus.Closed)
})
.ToListAsync();
var topUsers = await headers
.Where(x => x.CreatedAt >= currentMonthStart && x.CreatedAt < nextMonthStart)
.GroupBy(x => new { x.UserId, x.User.LastName, x.User.FirstName })
.Select(x => new DashboardUserItemDTO
{
Name = (x.Key.LastName + " " + x.Key.FirstName).Trim(),
Count = x.Count()
})
.OrderByDescending(x => x.Count)
.ThenBy(x => x.Name)
.Take(5)
.ToListAsync();
var recentItems = await headers
.OrderByDescending(x => x.LastModAt)
.Select(x => new
{
x.Id,
x.DocumentNumber,
x.ShortName,
x.User.LastName,
x.User.FirstName,
x.LastModAt,
x.CheckStatus
})
.Take(8)
.ToListAsync();
var delayedApprovalCount = await headers.CountAsync(x =>
x.CheckStatus == CheckStatus.Sent && x.LastModAt < fortyEightHoursAgo);
var blockedCount = statusCounts.FirstOrDefault(x => x.Status == CheckStatus.Blocked)?.Count ?? 0;
var currentMonthCount = monthlyRaw
.FirstOrDefault(x => x.Year == currentMonthStart.Year && x.Month == currentMonthStart.Month)?.Total ?? 0;
var previousMonthCount = monthlyRaw
.FirstOrDefault(x => x.Year == previousMonthStart.Year && x.Month == previousMonthStart.Month)?.Total ?? 0;
var approvedThisMonth = monthlyRaw
.FirstOrDefault(x => x.Year == currentMonthStart.Year && x.Month == currentMonthStart.Month)?.Approved ?? 0;
var culture = System.Globalization.CultureInfo.GetCultureInfo("hu-HU");
var result = new DashboardDTO
{
GeneratedAt = now,
TotalCheckLists = statusCounts.Sum(x => x.Count),
CheckListsThisMonth = currentMonthCount,
MonthlyChangePercent = previousMonthCount == 0
? (currentMonthCount > 0 ? 100 : 0)
: Math.Round((currentMonthCount - previousMonthCount) * 100m / previousMonthCount, 1),
OpenAndInProgressCount = statusCounts
.Where(x => x.Status == CheckStatus.Open || x.Status == CheckStatus.InProgress)
.Sum(x => x.Count),
AwaitingApprovalCount = statusCounts.FirstOrDefault(x => x.Status == CheckStatus.Sent)?.Count ?? 0,
BlockedCount = blockedCount,
ApprovedThisMonthCount = approvedThisMonth,
ApprovalRate = currentMonthCount == 0 ? 0 : Math.Round(approvedThisMonth * 100m / currentMonthCount, 1),
ActiveUserCount = await _dbContext.Users.AsNoTracking().CountAsync(x => x.Active),
TemplateCount = await _dbContext.CheckListTemplateHeaders.AsNoTracking().CountAsync(),
TopUsers = topUsers,
AttentionItems = new List<DashboardAttentionItemDTO>(),
RecentCheckLists = recentItems.Select(x => new DashboardRecentCheckListDTO
{
Id = x.Id,
DocumentNumber = x.DocumentNumber,
ShortName = x.ShortName,
Responsible = (x.LastName + " " + x.FirstName).Trim(),
ModifiedAt = x.LastModAt,
Status = x.CheckStatus,
StatusName = DisplayNameHelper.GetEnumDisplayName(x.CheckStatus)
}).ToList()
};
for (var i = 0; i < 6; i++)
{
var month = sixMonthStart.AddMonths(i);
var value = monthlyRaw.FirstOrDefault(x => x.Year == month.Year && x.Month == month.Month);
result.MonthlyItems.Add(new DashboardMonthlyItemDTO
{
Month = month.ToString("MMM", culture).TrimEnd('.'),
Total = value?.Total ?? 0,
Approved = value?.Approved ?? 0
});
}
foreach (var status in Enum.GetValues<CheckStatus>())
{
result.StatusItems.Add(new DashboardStatusItemDTO
{
Status = status,
Name = DisplayNameHelper.GetEnumDisplayName(status),
Count = statusCounts.FirstOrDefault(x => x.Status == status)?.Count ?? 0,
Color = GetDashboardStatusColor(status)
});
}
if (delayedApprovalCount > 0)
{
result.AttentionItems.Add(new DashboardAttentionItemDTO
{
Title = $"{delayedApprovalCount} munkalap több mint 48 órája jóváhagyásra vár",
Description = "Érdemes ellenőrizni a beküldött munkalapokat.",
IsCritical = true
});
}
if (blockedCount > 0)
{
result.AttentionItems.Add(new DashboardAttentionItemDTO
{
Title = $"{blockedCount} blokkolt munkalap",
Description = "A folytatásuk felhasználói beavatkozást igényel.",
IsCritical = true
});
}
if (result.AwaitingApprovalCount > delayedApprovalCount)
{
result.AttentionItems.Add(new DashboardAttentionItemDTO
{
Title = $"{result.AwaitingApprovalCount - delayedApprovalCount} friss beküldött munkalap",
Description = "Jóváhagyásra váró, 48 óránál frissebb tételek.",
IsCritical = false
});
}
return result;
}
private static string GetDashboardStatusColor(CheckStatus status) => status switch
{
CheckStatus.Open => "#64748b",
CheckStatus.InProgress => "#e28a17",
CheckStatus.Blocked => "#dc2626",
CheckStatus.Storno => "#7c3aed",
CheckStatus.Sent => "#38bdf8",
CheckStatus.Signed => "#16875b",
CheckStatus.Closed => "#123c69",
_ => "#94a3b8"
};
public async Task<CheckListHeaderDTO> GetCheckListHeaderAsync(int Id)
{
var retVal = new CheckListHeaderDTO()
@@ -6,6 +6,7 @@ namespace WorkFlowCheck.BL.Services.Interfaces
{
public interface ICheckListService
{
Task<DashboardDTO> GetDashboardInfoAsync();
Task<CheckListHeaderDTO> GetCheckListHeaderAsync(int Id);
Task<ApiResponseListDTO<List<CheckListHeaderDTO>>> GetFilteredCheckListHeaderAsync(DataTablesRequest dataTablesRequest);
Task<List<CheckListHeaderDTO>> GetAllCheckListHeaderAsync(int mode = 0);
@@ -0,0 +1,63 @@
using WorkFlowCheck.Common.Enums;
namespace WorkFlowCheck.Common.DTO
{
public class DashboardDTO
{
public DateTime GeneratedAt { get; set; }
public int TotalCheckLists { get; set; }
public int CheckListsThisMonth { get; set; }
public decimal MonthlyChangePercent { get; set; }
public int OpenAndInProgressCount { get; set; }
public int AwaitingApprovalCount { get; set; }
public int BlockedCount { get; set; }
public int ApprovedThisMonthCount { get; set; }
public decimal ApprovalRate { get; set; }
public int ActiveUserCount { get; set; }
public int TemplateCount { get; set; }
public List<DashboardMonthlyItemDTO> MonthlyItems { get; set; } = new();
public List<DashboardStatusItemDTO> StatusItems { get; set; } = new();
public List<DashboardUserItemDTO> TopUsers { get; set; } = new();
public List<DashboardAttentionItemDTO> AttentionItems { get; set; } = new();
public List<DashboardRecentCheckListDTO> RecentCheckLists { get; set; } = new();
}
public class DashboardMonthlyItemDTO
{
public string Month { get; set; } = string.Empty;
public int Total { get; set; }
public int Approved { get; set; }
}
public class DashboardStatusItemDTO
{
public CheckStatus Status { get; set; }
public string Name { get; set; } = string.Empty;
public int Count { get; set; }
public string Color { get; set; } = string.Empty;
}
public class DashboardUserItemDTO
{
public string Name { get; set; } = string.Empty;
public int Count { get; set; }
}
public class DashboardAttentionItemDTO
{
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public bool IsCritical { get; set; }
}
public class DashboardRecentCheckListDTO
{
public int Id { get; set; }
public string DocumentNumber { get; set; } = string.Empty;
public string ShortName { get; set; } = string.Empty;
public string Responsible { get; set; } = string.Empty;
public DateTime ModifiedAt { get; set; }
public CheckStatus Status { get; set; }
public string StatusName { get; set; } = string.Empty;
}
}
+18 -17
View File
@@ -46,6 +46,7 @@
.status-badge { display:inline-flex; align-items:center; gap:.3rem; padding:.3rem .52rem; border-radius:999px; font-size:.7rem; font-weight:720; white-space:nowrap; }
.status-approved { color:#0d6b47; background:#dcf5e9; } .status-submitted { color:#075985; background:#e0f2fe; }
.status-progress { color:#945500; background:#fff1d2; } .status-closed { color:#475569; background:#e9eef4; }
.status-blocked { color:#991b1b; background:#fee2e2; } .status-storno { color:#6d28d9; background:#ede9fe; }
@@media(max-width:1199.98px){.kpi-grid{grid-template-columns:repeat(2,minmax(0,1fr));}}
@@media(max-width:991.98px){.dashboard-grid,.dashboard-grid.equal{grid-template-columns:1fr;}}
@@media(max-width:575.98px){.dashboard-heading{align-items:stretch;flex-direction:column}.demo-badge{align-self:flex-start}.kpi-grid{grid-template-columns:1fr}.chart-container{height:245px}.dashboard-card-header{align-items:flex-start;flex-direction:column}.dashboard-table{min-width:720px}}
@@ -54,15 +55,15 @@
<div class="dashboard-page">
<div class="dashboard-heading">
<div><h1>Gyártási ellenőrzések áttekintése</h1><p>@Model.PeriodLabel · Frissítve: @Model.GeneratedAt.ToString("yyyy. MM. dd. HH:mm")</p></div>
<span class="demo-badge"><i class="bi bi-stars"></i> Mintaadatokkal</span>
<div><h1>Ellenőrzések áttekintése</h1><p>@Model.PeriodLabel · Frissítve: @Model.Dashboard.GeneratedAt.ToLocalTime().ToString("yyyy. MM. dd. HH:mm")</p></div>
<span class="demo-badge"><i class="bi bi-database-check"></i> Élő adatok</span>
</div>
<section class="kpi-grid" aria-label="Fő mutatók">
<article class="kpi-card"><div class="kpi-topline"><span class="kpi-icon"><i class="bi bi-clipboard2-check"></i></span><span class="kpi-trend"><i class="bi bi-arrow-up-right"></i> @Model.MonthlyChangePercent.ToString("0.0")%</span></div><div class="kpi-value">@Model.InspectionsThisMonth</div><div class="kpi-label">Ellenőrzés ebben a hónapban</div></article>
<article class="kpi-card"><div class="kpi-topline"><span class="kpi-icon"><i class="bi bi-hourglass-split"></i></span><span class="kpi-trend warning">@Model.AwaitingApproval jóváhagyásra vár</span></div><div class="kpi-value">@Model.InProgress</div><div class="kpi-label">Folyamatban lévő ellenőrzés</div></article>
<article class="kpi-card"><div class="kpi-topline"><span class="kpi-icon"><i class="bi bi-patch-check"></i></span><span class="kpi-trend">@Model.ApprovalRate.ToString("0.0")% arány</span></div><div class="kpi-value">@Model.ApprovedThisMonth</div><div class="kpi-label">Jóváhagyás ebben a hónapban</div></article>
<article class="kpi-card"><div class="kpi-topline"><span class="kpi-icon"><i class="bi bi-stopwatch"></i></span><span class="kpi-trend neutral">átlagos idő</span></div><div class="kpi-value">@Model.AverageDurationMinutes.ToString("0.0") <small>perc</small></div><div class="kpi-label">Egy ellenőrzés végrehajtása</div></article>
<article class="kpi-card"><div class="kpi-topline"><span class="kpi-icon"><i class="bi bi-clipboard2-check"></i></span><span class="kpi-trend @((Model.Dashboard.MonthlyChangePercent < 0) ? "warning" : "")"><i class="bi @(Model.Dashboard.MonthlyChangePercent < 0 ? "bi-arrow-down-right" : "bi-arrow-up-right")"></i> @Model.Dashboard.MonthlyChangePercent.ToString("0.0")%</span></div><div class="kpi-value">@Model.Dashboard.CheckListsThisMonth.ToString("N0")</div><div class="kpi-label">Ellenőrzés ebben a hónapban</div></article>
<article class="kpi-card"><div class="kpi-topline"><span class="kpi-icon"><i class="bi bi-hourglass-split"></i></span><span class="kpi-trend warning">@Model.Dashboard.AwaitingApprovalCount jóváhagyásra vár</span></div><div class="kpi-value">@Model.Dashboard.OpenAndInProgressCount.ToString("N0")</div><div class="kpi-label">Nyitott vagy folyamatban</div></article>
<article class="kpi-card"><div class="kpi-topline"><span class="kpi-icon"><i class="bi bi-patch-check"></i></span><span class="kpi-trend">@Model.Dashboard.ApprovalRate.ToString("0.0")% arány</span></div><div class="kpi-value">@Model.Dashboard.ApprovedThisMonthCount.ToString("N0")</div><div class="kpi-label">Jóváhagyás ebben a hónapban</div></article>
<article class="kpi-card"><div class="kpi-topline"><span class="kpi-icon"><i class="bi bi-slash-circle"></i></span><span class="kpi-trend neutral">összesen @Model.Dashboard.TotalCheckLists.ToString("N0")</span></div><div class="kpi-value">@Model.Dashboard.BlockedCount.ToString("N0")</div><div class="kpi-label">Blokkolt munkalap</div></article>
</section>
<section class="dashboard-grid">
@@ -71,26 +72,26 @@
<div class="dashboard-card-body"><div class="chart-container"><canvas id="monthlyInspectionsChart"></canvas></div></div>
</article>
<article class="dashboard-card">
<header class="dashboard-card-header"><div><h2 class="dashboard-card-title">Aktuális állapotok</h2><p class="dashboard-card-subtitle">Az e havi ellenőrzések megoszlása</p></div></header>
<div class="dashboard-card-body"><div class="chart-container compact"><canvas id="statusChart"></canvas></div><div class="status-summary">@foreach(var status in Model.Statuses){<div class="status-item"><span class="status-dot" style="background:@status.Color"></span><span>@status.Name</span><strong>@status.Count</strong></div>}</div></div>
<header class="dashboard-card-header"><div><h2 class="dashboard-card-title">Aktuális állapotok</h2><p class="dashboard-card-subtitle">Az összes ellenőrzés jelenlegi megoszlása</p></div></header>
<div class="dashboard-card-body"><div class="chart-container compact"><canvas id="statusChart"></canvas></div><div class="status-summary">@foreach(var status in Model.Dashboard.StatusItems){<div class="status-item"><span class="status-dot" style="background:@status.Color"></span><span>@status.Name</span><strong>@status.Count</strong></div>}</div></div>
</article>
</section>
<section class="dashboard-grid equal">
<article class="dashboard-card">
<header class="dashboard-card-header"><div><h2 class="dashboard-card-title">Jóváhagyások személyenként</h2><p class="dashboard-card-subtitle">Aktuális hónap · jóváhagyott ellenőrzések száma</p></div></header>
<header class="dashboard-card-header"><div><h2 class="dashboard-card-title">Ellenőrzések személyenként</h2><p class="dashboard-card-subtitle">Aktuális hónap · elindított ellenőrzések száma</p></div></header>
<div class="dashboard-card-body"><div class="chart-container compact"><canvas id="approversChart"></canvas></div></div>
</article>
<article class="dashboard-card">
<header class="dashboard-card-header"><div><h2 class="dashboard-card-title">Figyelmet igényel</h2><p class="dashboard-card-subtitle">Elakadt vagy határidős tételek</p></div><span class="badge rounded-pill text-bg-danger">@Model.AttentionItems.Count</span></header>
<div class="dashboard-card-body"><div class="attention-list">@foreach(var item in Model.AttentionItems){<div class="attention-item @(item.IsCritical ? "danger" : "")"><span class="attention-icon"><i class="bi @(item.IsCritical ? "bi-exclamation-triangle" : "bi-clock-history")"></i></span><div><div class="attention-title">@item.Title</div><div class="attention-meta">@item.Description</div></div></div>}</div></div>
<header class="dashboard-card-header"><div><h2 class="dashboard-card-title">Figyelmet igényel</h2><p class="dashboard-card-subtitle">Elakadt vagy jóváhagyásra váró tételek</p></div><span class="badge rounded-pill text-bg-danger">@Model.Dashboard.AttentionItems.Count</span></header>
<div class="dashboard-card-body"><div class="attention-list">@if(Model.Dashboard.AttentionItems.Count == 0){<div class="attention-item"><span class="attention-icon"><i class="bi bi-check2-circle"></i></span><div><div class="attention-title">Nincs figyelmet igénylő tétel</div><div class="attention-meta">Minden munkalap rendben halad.</div></div></div>}else{@foreach(var item in Model.Dashboard.AttentionItems){<div class="attention-item @(item.IsCritical ? "danger" : "")"><span class="attention-icon"><i class="bi @(item.IsCritical ? "bi-exclamation-triangle" : "bi-clock-history")"></i></span><div><div class="attention-title">@item.Title</div><div class="attention-meta">@item.Description</div></div></div>}}</div></div>
</article>
</section>
<section class="dashboard-card">
<header class="dashboard-card-header"><div><h2 class="dashboard-card-title">Legutóbbi ellenőrzések</h2><p class="dashboard-card-subtitle">A rendszerben legutóbb módosított tételek</p></div><a asp-page="/CheckList/CheckListHeader/CheckListHeaderPage" asp-route-mode="4" class="btn btn-sm btn-outline-primary">Összes megtekintése <i class="bi bi-arrow-right ms-1"></i></a></header>
<div class="table-responsive"><table class="table dashboard-table"><thead><tr><th>Azonosító</th><th>Ellenőrzés</th><th>Lokáció</th><th>Felelős</th><th>Módosítva</th><th>Állapot</th></tr></thead><tbody>
@foreach(var item in Model.RecentInspections){<tr><td><span class="inspection-code">@item.Code</span></td><td>@item.Name</td><td>@item.Location</td><td>@item.Responsible</td><td>@item.ModifiedAt.ToString("yyyy. MM. dd. HH:mm")</td><td><span class="status-badge @item.StatusCss"><i class="bi @item.StatusIcon"></i>@item.Status</span></td></tr>}
<div class="table-responsive"><table class="table dashboard-table"><thead><tr><th>Azonosító</th><th>Ellenőrzés</th><th>Felelős</th><th>Módosítva</th><th>Állapot</th></tr></thead><tbody>
@if(Model.Dashboard.RecentCheckLists.Count == 0){<tr><td colspan="5" class="text-center py-4 text-muted">Még nincs megjeleníthető ellenőrzés.</td></tr>}else{@foreach(var item in Model.Dashboard.RecentCheckLists){<tr><td><span class="inspection-code">@item.DocumentNumber</span></td><td>@item.ShortName</td><td>@item.Responsible</td><td>@item.ModifiedAt.ToLocalTime().ToString("yyyy. MM. dd. HH:mm")</td><td><span class="status-badge @IndexModel.GetStatusCss(item.Status)"><i class="bi @IndexModel.GetStatusIcon(item.Status)"></i>@item.StatusName</span></td></tr>}}
</tbody></table></div>
</section>
</div>
@@ -99,14 +100,14 @@
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
<script>
(() => {
const monthly = @Html.Raw(JsonSerializer.Serialize(Model.MonthlyInspections, jsonOptions));
const statuses = @Html.Raw(JsonSerializer.Serialize(Model.Statuses, jsonOptions));
const approvers = @Html.Raw(JsonSerializer.Serialize(Model.Approvers, jsonOptions));
const monthly = @Html.Raw(JsonSerializer.Serialize(Model.Dashboard.MonthlyItems, jsonOptions));
const statuses = @Html.Raw(JsonSerializer.Serialize(Model.Dashboard.StatusItems, jsonOptions));
const approvers = @Html.Raw(JsonSerializer.Serialize(Model.Dashboard.TopUsers, jsonOptions));
const gridColor = 'rgba(100,116,139,.14)';
Chart.defaults.font.family = 'Inter, "Segoe UI", Arial, sans-serif'; Chart.defaults.color = '#64748b';
new Chart(document.getElementById('monthlyInspectionsChart'),{type:'line',data:{labels:monthly.map(x=>x.month),datasets:[{label:'Összes ellenőrzés',data:monthly.map(x=>x.total),borderColor:'#123c69',backgroundColor:'rgba(56,189,248,.13)',borderWidth:3,pointBackgroundColor:'#123c69',pointRadius:4,fill:true,tension:.36},{label:'Jóváhagyott',data:monthly.map(x=>x.approved),borderColor:'#38bdf8',backgroundColor:'#38bdf8',borderWidth:2,pointRadius:3,borderDash:[6,4],tension:.36}]},options:{responsive:true,maintainAspectRatio:false,interaction:{mode:'index',intersect:false},plugins:{legend:{position:'bottom',labels:{usePointStyle:true,boxWidth:8,padding:18}}},scales:{x:{grid:{display:false}},y:{beginAtZero:true,grid:{color:gridColor},ticks:{precision:0}}}}});
new Chart(document.getElementById('statusChart'),{type:'doughnut',data:{labels:statuses.map(x=>x.name),datasets:[{data:statuses.map(x=>x.count),backgroundColor:statuses.map(x=>x.color),borderColor:'#fff',borderWidth:4,hoverOffset:5}]},options:{responsive:true,maintainAspectRatio:false,cutout:'68%',plugins:{legend:{display:false}}}});
new Chart(document.getElementById('approversChart'),{type:'bar',data:{labels:approvers.map(x=>x.name),datasets:[{label:'Jóváhagyás',data:approvers.map(x=>x.count),backgroundColor:['#123c69','#1c5c91','#2687bd','#38bdf8','#7dd3fc'],borderRadius:7,borderSkipped:false,barThickness:24}]},options:{indexAxis:'y',responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:{x:{beginAtZero:true,grid:{color:gridColor},ticks:{precision:0}},y:{grid:{display:false}}}}});
new Chart(document.getElementById('approversChart'),{type:'bar',data:{labels:approvers.map(x=>x.name),datasets:[{label:'Ellenőrzés',data:approvers.map(x=>x.count),backgroundColor:['#123c69','#1c5c91','#2687bd','#38bdf8','#7dd3fc'],borderRadius:7,borderSkipped:false,barThickness:24}]},options:{indexAxis:'y',responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:{x:{beginAtZero:true,grid:{color:gridColor},ticks:{precision:0}},y:{grid:{display:false}}}}});
})();
</script>
}
+39 -58
View File
@@ -1,76 +1,57 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Globalization;
using WorkFlowCheck.Common.DTO;
using WorkFlowCheck.Common.Enums;
using WorkFlowCheck.Web.Services.Interfaces;
namespace WorkFlowCheck.Web.Pages
{
public class IndexModel : PageModel
{
private readonly ICheckListService _checkListService;
private readonly ILogger<IndexModel> _logger;
public DateTime GeneratedAt { get; private set; }
public string PeriodLabel { get; private set; } = string.Empty;
public int InspectionsThisMonth { get; private set; }
public decimal MonthlyChangePercent { get; private set; }
public int InProgress { get; private set; }
public int AwaitingApproval { get; private set; }
public int ApprovedThisMonth { get; private set; }
public decimal ApprovalRate { get; private set; }
public decimal AverageDurationMinutes { get; private set; }
public List<MonthlyInspectionPoint> MonthlyInspections { get; private set; } = [];
public List<DashboardStatus> Statuses { get; private set; } = [];
public List<ApproverStatistic> Approvers { get; private set; } = [];
public List<AttentionItem> AttentionItems { get; private set; } = [];
public List<RecentInspection> RecentInspections { get; private set; } = [];
public DashboardDTO Dashboard { get; private set; } = new();
public string PeriodLabel => Dashboard.GeneratedAt.ToLocalTime()
.ToString("yyyy. MMMM", CultureInfo.GetCultureInfo("hu-HU"));
public IndexModel(ILogger<IndexModel> logger) => _logger = logger;
public void OnGet()
public IndexModel(ICheckListService checkListService, ILogger<IndexModel> logger)
{
// TODO: A következő körben ezt az egy blokkot cseréljük IDashboardService hívásra.
LoadDemoData();
_checkListService = checkListService;
_logger = logger;
}
private void LoadDemoData()
public async Task OnGetAsync()
{
GeneratedAt = DateTime.Now;
PeriodLabel = GeneratedAt.ToString("yyyy. MMMM", CultureInfo.GetCultureInfo("hu-HU"));
InspectionsThisMonth = 128;
MonthlyChangePercent = 18.5m;
InProgress = 17;
AwaitingApproval = 9;
ApprovedThisMonth = 96;
ApprovalRate = 75.0m;
AverageDurationMinutes = 14.2m;
var totals = new[] { 76, 89, 84, 105, 112, 128 };
var approved = new[] { 55, 63, 61, 79, 86, 96 };
var firstMonth = new DateTime(GeneratedAt.Year, GeneratedAt.Month, 1).AddMonths(-5);
var culture = CultureInfo.GetCultureInfo("hu-HU");
MonthlyInspections = Enumerable.Range(0, 6).Select(i => new MonthlyInspectionPoint(firstMonth.AddMonths(i).ToString("MMM", culture).TrimEnd('.'), totals[i], approved[i])).ToList();
Statuses = [new("Jóváhagyott",96,"#16875b"),new("Beküldött",9,"#38bdf8"),new("Folyamatban",17,"#e28a17"),new("Lezárt",6,"#94a3b8")];
Approvers = [new("Kovács Péter",28),new("Nagy Anna",24),new("Tóth Gábor",19),new("Szabó Éva",15),new("Varga Tamás",10)];
AttentionItems =
[
new("3 ellenőrzés több mint 48 órája beküldve", "Jóváhagyásra várnak a C2 gyártócsarnokban.", true),
new("Kalibrálás hamarosan lejár", "A CNC–04 berendezés következő határideje 3 nap.", false),
new("Hiányzó fényképes dokumentáció", "2 folyamatban lévő ellenőrzésnél nincs csatolmány.", true),
new("Heti ellenőrzési terv", "A tervezett 34 ellenőrzésből még 7 nem kezdődött el.", false)
];
RecentInspections =
[
new("WFC-260912-018","Műszakindító gépellenőrzés","C2 gyártócsarnok","Kiss Dániel",GeneratedAt.AddMinutes(-18),"Jóváhagyott","status-approved","bi-check-circle"),
new("WFC-260912-017","Hegesztőcella biztonsági ellenőrzés","A1 csarnok","Farkas András",GeneratedAt.AddMinutes(-42),"Beküldött","status-submitted","bi-send-check"),
new("WFC-260912-016","Csomagolósor minőségellenőrzés","B3 csomagoló","Molnár Rita",GeneratedAt.AddHours(-1.4),"Folyamatban","status-progress","bi-hourglass-split"),
new("WFC-260912-015","Napi karbantartási ellenőrzés","CNC terület","Kiss Dániel",GeneratedAt.AddHours(-2.1),"Lezárt","status-closed","bi-lock"),
new("WFC-260912-014","Beérkező alapanyag vizsgálata","Központi raktár","Nagy Júlia",GeneratedAt.AddHours(-3.3),"Jóváhagyott","status-approved","bi-check-circle")
];
try
{
Dashboard = await _checkListService.GetDashboardInfoAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "A műszerfal betöltése sikertelen.");
Dashboard = new DashboardDTO { GeneratedAt = DateTime.UtcNow };
}
}
public sealed record MonthlyInspectionPoint(string Month, int Total, int Approved);
public sealed record DashboardStatus(string Name, int Count, string Color);
public sealed record ApproverStatistic(string Name, int Count);
public sealed record AttentionItem(string Title, string Description, bool IsCritical);
public sealed record RecentInspection(string Code, string Name, string Location, string Responsible, DateTime ModifiedAt, string Status, string StatusCss, string StatusIcon);
public static string GetStatusCss(CheckStatus status) => status switch
{
CheckStatus.Signed => "status-approved",
CheckStatus.Sent => "status-submitted",
CheckStatus.Open or CheckStatus.InProgress => "status-progress",
CheckStatus.Blocked => "status-blocked",
CheckStatus.Storno => "status-storno",
_ => "status-closed"
};
public static string GetStatusIcon(CheckStatus status) => status switch
{
CheckStatus.Signed => "bi-check-circle",
CheckStatus.Sent => "bi-send-check",
CheckStatus.Blocked => "bi-slash-circle",
CheckStatus.Storno => "bi-x-circle",
CheckStatus.Closed => "bi-lock",
_ => "bi-hourglass-split"
};
}
}
@@ -16,6 +16,23 @@ namespace WorkFlowCheck.Web.Services
public CheckListService(HttpClient httpClient, IConfiguration configuration, IHttpContextAccessor httpContextAccessor) : base(httpClient, configuration, httpContextAccessor)
{
}
public async Task<DashboardDTO> GetDashboardInfoAsync()
{
var endpoint = $"{_httpClient.BaseAddress}api/CheckList/GetDashboardInfo";
try
{
var response = await _httpClient.GetFromJsonAsync<ApiResponseDTO<DashboardDTO>>(endpoint);
if (response?.IsSuccess == true && response.Data != null)
{
return response.Data;
}
}
catch (Exception ex)
{
Log.Error(ex, "GetDashboardInfoAsync failed");
}
return new DashboardDTO { GeneratedAt = DateTime.Now };
}
public async Task<ApiResponseListDTO<List<CheckListHeaderDTO>>> GetFilteredCheckListHeaderAsync(DataTablesRequest dataTablesRequest)
{
try
@@ -6,6 +6,7 @@ namespace WorkFlowCheck.Web.Services.Interfaces
{
public interface ICheckListService
{
Task<DashboardDTO> GetDashboardInfoAsync();
Task<CheckListHeaderDTO> GetCheckListHeader(int id);
Task<ApiResponseListDTO<List<CheckListHeaderDTO>>> GetFilteredCheckListHeaderAsync(DataTablesRequest dataTablesRequest);
Task<List<CheckListHeaderDTO>> GetAllCheckListHeaderAsync(int mode = 0);