Author SHA1 Message Date
ivanszabo a6af9f2db7 Induláskor átmenjen a middlewareon a bejelentkezéshez szükséges elemek 2026-09-16 14:02:18 +02:00
ivanszabo 364c1b455d Felesleges footerek levétele 2026-09-14 11:48:33 +02:00
ivanszabo d00e087b35 Dashboard is élő, bekötve! 2026-09-14 11:14:22 +02:00
ivanszabo 289992ce5e Arculat javítása 2026-09-12 22:19:24 +02:00
ivanszabo 02ba8d4fe5 INIT GITEA 2026-09-12 18:19:29 +02:00
ivanszabo b2faeb2986 Arculat hozzáadása 2026-09-12 12:38:39 +02:00
ivanszabo 717becd1fc ServerSide Paging 2026-08-02 16:36:21 +02:00
ivanszabo 92acb42601 Na most jó a lapozás 2026-03-09 11:32:12 +01:00
ivanszabo 53dc9b326a Ez most szerver oldalon szűr és lapoz 2026-03-02 14:12:43 +01:00
438 changed files with 127029 additions and 125314 deletions
@@ -2,6 +2,7 @@
using Serilog;
using WorkFlowCheck.BL.Services;
using WorkFlowCheck.BL.Services.Interfaces;
using WorkFlowCheck.Common.DataTables;
using WorkFlowCheck.Common.DTO;
namespace WorkFlowCheck.API.Controllers
@@ -16,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)
{
@@ -39,6 +58,28 @@ namespace WorkFlowCheck.API.Controllers
return retVal;
}
[HttpPost("GetFilteredCheckListHeader")]
public async Task<ApiResponseListDTO<List<CheckListHeaderDTO>>> GetFilteredCheckListHeader([FromBody] DataTablesRequest dataTablesRequest)
{
var retVal = new ApiResponseListDTO<List<CheckListHeaderDTO>>()
{
IsSuccess = true
};
try
{
var result = await _checkListService.GetFilteredCheckListHeaderAsync(dataTablesRequest);
retVal.Data = result.Data;
retVal.Total = result.Total;
retVal.Filtered = result.Filtered;
}
catch (Exception ex)
{
retVal.IsSuccess = false;
Log.Error(ex.Message);
}
return retVal;
}
[HttpGet("CreatePDF/{id}")]
public async Task<ApiResponseDTO<byte[]>> CreatePDF(int id)
{
@@ -2,7 +2,7 @@
"ConnectionStrings": {
//"DefaultConnection": "Data Source=NSINB-37\\WFC;Initial Catalog=WFC;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False"
//"DefaultConnection": "Data Source=WS2016DC\\SQLEXPRESS;Initial Catalog=WFC;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False",
"DefaultConnection": "Data Source=WS2016DC\\SQLEXPRESS;Initial Catalog=WFCUAT;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False"
"DefaultConnection": "Data Source=MASERVER;Initial Catalog=WFCUAT;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False"
},
"Logging": {
"LogLevel": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=WS2016DC\\SQLEXPRESS;Initial Catalog=WFCUAT;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False"
"DefaultConnection": "Data Source=MASERVER;Initial Catalog=WFCUAT;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False"
},
"ApiKey": "RUJeLSpSMzVASUdaRCEzUyYxRSE0VyFISFRSJC0zRzhLM1hCSDU=",
"Jwt": {
@@ -15,8 +15,10 @@ using System.Text;
using System.Threading.Tasks;
using WorkFlowCheck.BL.DocumentGenerator;
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;
@@ -38,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()
@@ -81,6 +248,69 @@ namespace WorkFlowCheck.BL.Services
return retVal;
}
public async Task<ApiResponseListDTO<List<CheckListHeaderDTO>>> GetFilteredCheckListHeaderAsync(DataTablesRequest req)
{
var retVal = new ApiResponseListDTO<List<CheckListHeaderDTO>>();
try
{
var baseQuery = _dbContext.CheckListHeaders
.AsNoTracking()
.Where(x => !x.IsDeleted);
// MODE szűrés (csak WHERE, ORDER BY nélkül)
IQueryable<CheckListHeader> modeQuery = req.Mode switch
{
0 => baseQuery.Where(w => w.CheckStatus == CheckStatus.InProgress || w.CheckStatus == CheckStatus.Open),
1 => baseQuery.Where(w => w.CheckStatus == CheckStatus.Sent),
2 => baseQuery.Where(w => w.CheckStatus == CheckStatus.Closed),
3 => baseQuery.Where(w => w.CheckStatus == CheckStatus.Signed),
_ => baseQuery
};
// Total = mode szűrés után, keresés előtt
retVal.Total = await modeQuery.CountAsync();
// Search
var search = req.Search?.Value?.Trim();
IQueryable<CheckListHeader> filteredQuery = modeQuery;
if (!string.IsNullOrWhiteSpace(search))
{
filteredQuery = filteredQuery.Where(x =>
x.DocumentNumber.Contains(search) ||
(x.ShortName != null && x.ShortName.Contains(search)) ||
(x.Description != null && x.Description.Contains(search)) ||
(x.CreatedBy != null && x.CreatedBy.Contains(search)) ||
(x.AcceptUser != null && x.AcceptUser.UserName.Contains(search))
);
}
retVal.Filtered = await filteredQuery.CountAsync();
// Ordering (itt már jöhet)
filteredQuery = filteredQuery.OrderByDescending(o => o.CreatedAt);
// Paging
var list = await filteredQuery
.Include(x => x.CheckListRows)
.Include(x => x.User)
.Include(x => x.AcceptUser)
.AsSplitQuery()
.Skip(req.Start)
.Take(req.Length)
.ToListAsync();
retVal.Data = _mapper.Map<List<CheckListHeaderDTO>>(list);
}
catch (Exception ex)
{
Log.Error(ex, "GetFilteredCheckListHeaderAsync failed");
}
return retVal;
}
public async Task<List<CheckListHeaderDTO>> GetAllCheckListHeaderAsync(int mode = 0)
{
var retVal = new List<CheckListHeaderDTO>();
@@ -1,11 +1,14 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using WorkFlowCheck.Common.DataTables;
using WorkFlowCheck.Common.DTO;
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);
Task<CheckListHeaderDTO> UpdateCheckListHeaderAsync(CheckListHeaderDTO checkListHeaderDTO);
Task<bool> AcceptCheckListHeaderAsync(int id, int userid);
@@ -35,9 +35,11 @@ namespace WorkFlowCheck.Common.DTO
{
public T Data { get; set; }
}
public class ApiResponseListDTO<T> : ApiResponseBaseDTO
{
public T[] Data { get; set; }
public T Data { get; set; }
public int Total { get; set; }
public int Filtered { get; set; }
}
}
@@ -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;
}
}
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WorkFlowCheck.Common.DataTables
{
public class DataTablesRequest
{
public int Mode { get; set; }
public int Draw { get; set; }
public int Start { get; set; }
public int Length { get; set; }
public DataTablesSearch Search { get; set; } = new();
public List<DataTablesOrder> Order { get; set; } = new();
public List<DataTablesColumn> Columns { get; set; } = new();
}
public class DataTablesSearch { public string? Value { get; set; } }
public class DataTablesOrder { public int Column { get; set; } public string? Dir { get; set; } }
public class DataTablesColumn { public string? Data { get; set; } public bool Searchable { get; set; } public bool Orderable { get; set; } }
}
@@ -19,6 +19,16 @@ namespace WorkFlowCheck.Web.Middleware
public async Task Invoke(HttpContext context)
{
var pathignore = context.Request.Path;
if (pathignore.StartsWithSegments("/css") ||
pathignore.StartsWithSegments("/js") ||
pathignore.StartsWithSegments("/lib") ||
pathignore.StartsWithSegments("/images") ||
pathignore.StartsWithSegments("/favicon.ico"))
{
await _next(context);
return;
}
var token = context.Request.Cookies["AuthToken"];
if (!string.IsNullOrEmpty(token) && ValidateToken(token, out var claims))
@@ -15,15 +15,175 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>@ViewData["Title"]</title>
<link rel="icon" type="image/svg+xml" href="~/images/favicon/favicon.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="~/images/favicon/favicon-32.png" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
:root {
--wfc-navy-950: #071629;
--wfc-navy-900: #0b1f3a;
--wfc-navy-700: #123c69;
--wfc-blue-500: #38bdf8;
--wfc-blue-200: #bae6fd;
--wfc-border: #d7e2ee;
--wfc-text: #10243e;
--wfc-muted: #64748b;
}
body.wfc-account-page {
min-height: 100vh;
color: var(--wfc-text);
background:
radial-gradient(circle at 12% 18%, rgb(56 189 248 / 14%), transparent 27rem),
radial-gradient(circle at 88% 82%, rgb(18 60 105 / 12%), transparent 30rem),
linear-gradient(145deg, #f8fbff, #eaf3fb);
font-family: Inter, "Segoe UI", Arial, sans-serif;
}
.wfc-account-main {
padding: 2rem 1rem;
}
.wfc-account-card {
position: relative;
width: 100%;
max-width: 460px;
overflow: hidden;
border: 1px solid rgb(215 226 238 / 85%);
border-radius: 1rem;
background: #fff;
box-shadow: 0 24px 65px rgb(11 31 58 / 18%);
}
.wfc-account-brand {
display: flex;
min-height: 120px;
align-items: center;
justify-content: center;
padding: 1.55rem 2rem;
background:
radial-gradient(circle at 82% 12%, rgb(56 189 248 / 25%), transparent 12rem),
linear-gradient(135deg, var(--wfc-navy-900), var(--wfc-navy-950));
}
.wfc-account-logo {
display: block;
width: 100%;
max-width: 335px;
height: auto;
}
.wfc-account-body {
padding: 1.8rem 2.1rem 2.05rem;
}
.wfc-account-title {
margin: 0;
color: var(--wfc-navy-900);
font-size: 1.55rem;
font-weight: 750;
letter-spacing: -.02em;
}
.wfc-account-subtitle {
margin: .4rem 0 1.5rem;
color: var(--wfc-muted);
font-size: .92rem;
}
.wfc-account-card .form-label {
margin-bottom: .4rem;
color: var(--wfc-navy-900);
font-size: .87rem;
font-weight: 650;
}
.wfc-account-card .form-control {
min-height: 45px;
border-color: var(--wfc-border);
border-radius: .6rem;
color: var(--wfc-text);
background: #fbfdff;
}
.wfc-account-card .form-control:disabled,
.wfc-account-card .form-control[readonly] {
color: var(--wfc-muted);
background: #edf3f8;
}
.wfc-account-card .form-control:focus {
border-color: var(--wfc-blue-500);
background: #fff;
box-shadow: 0 0 0 .22rem rgb(56 189 248 / 20%);
}
.wfc-account-card .btn-primary,
.modal .btn-primary {
min-height: 45px;
border-color: var(--wfc-navy-700);
border-radius: .6rem;
background: linear-gradient(135deg, var(--wfc-navy-700), var(--wfc-navy-900));
font-weight: 700;
box-shadow: 0 8px 18px rgb(18 60 105 / 20%);
}
.wfc-account-card .btn-primary:hover,
.wfc-account-card .btn-primary:focus,
.modal .btn-primary:hover,
.modal .btn-primary:focus {
border-color: var(--wfc-navy-950);
background: linear-gradient(135deg, #174c82, var(--wfc-navy-900));
}
.wfc-account-card .spinner-border {
color: var(--wfc-navy-700);
}
.wfc-account-loading {
z-index: 10;
border-radius: 1rem;
background: rgb(255 255 255 / 84%);
backdrop-filter: blur(3px);
}
.wfc-account-footer {
padding: 1rem;
color: var(--wfc-muted);
font-size: .82rem;
text-align: center;
}
@@media (max-width: 575.98px) {
.wfc-account-main {
align-items: flex-start !important;
padding-top: 1rem;
}
.wfc-account-brand {
min-height: 104px;
padding: 1.25rem 1.5rem;
}
.wfc-account-body {
padding: 1.45rem 1.35rem 1.65rem;
}
}
</style>
</head>
<body class="bg-light d-flex justify-content-center align-items-center vh-100">
<div class="card shadow p-4" style="min-width: 350px; max-width: 400px; width: 100%;">
<h2 class="mb-4 text-center">Jelszó módosítása</h2>
<body class="wfc-account-page d-flex flex-column">
<main class="wfc-account-main flex-grow-1 d-flex justify-content-center align-items-center">
<section class="wfc-account-card" aria-labelledby="changePasswordTitle">
<div class="wfc-account-brand">
<img src="~/images/logo/workflow-check-logo-dark.svg" alt="Workflow Check" class="wfc-account-logo" />
</div>
<div class="wfc-account-body">
<h1 class="wfc-account-title" id="changePasswordTitle">Jelszó módosítása</h1>
<p class="wfc-account-subtitle">Add meg a jelenlegi és az új jelszavadat.</p>
<!-- Loading overlay -->
<div id="formLoading" class="position-absolute top-0 start-0 w-100 h-100 d-none align-items-center justify-content-center"
style="background: rgba(255,255,255,.75); z-index: 10; border-radius: .375rem;">
<div id="formLoading" class="wfc-account-loading position-absolute top-0 start-0 w-100 h-100 d-none align-items-center justify-content-center">
<div class="text-center">
<div class="spinner-border" role="status" aria-hidden="true"></div>
<div class="mt-2 small text-muted">Kérlek várj…</div>
@@ -44,25 +204,25 @@
<div class="mb-3">
<label asp-for="UserChangePassword2FADTO.OldPassword" class="form-label"></label>
<input asp-for="UserChangePassword2FADTO.OldPassword" type="password" class="form-control" />
<input asp-for="UserChangePassword2FADTO.OldPassword" type="password" class="form-control" autocomplete="current-password" autofocus />
<span asp-validation-for="UserChangePassword2FADTO.OldPassword" class="text-danger small"></span>
</div>
<div class="mb-3">
<label asp-for="UserChangePassword2FADTO.NewPassword1" class="form-label"></label>
<input asp-for="UserChangePassword2FADTO.NewPassword1" type="password" class="form-control" />
<input asp-for="UserChangePassword2FADTO.NewPassword1" type="password" class="form-control" autocomplete="new-password" />
<span asp-validation-for="UserChangePassword2FADTO.NewPassword1" class="text-danger small"></span>
</div>
<div class="mb-3">
<label asp-for="UserChangePassword2FADTO.NewPassword2" class="form-label"></label>
<input asp-for="UserChangePassword2FADTO.NewPassword2" type="password" class="form-control" />
<input asp-for="UserChangePassword2FADTO.NewPassword2" type="password" class="form-control" autocomplete="new-password" />
<span asp-validation-for="UserChangePassword2FADTO.NewPassword2" class="text-danger small"></span>
</div>
<div id="VerificationCode" class="mb-3">
<label asp-for="UserChangePassword2FADTO.Code" class="form-label"></label>
<input asp-for="UserChangePassword2FADTO.Code" class="form-control" />
<input asp-for="UserChangePassword2FADTO.Code" class="form-control" autocomplete="one-time-code" inputmode="numeric" />
<span asp-validation-for="UserChangePassword2FADTO.Code" class="text-danger small"></span>
</div>
<hr class="mt-4 mb-3 border-secondary">
<hr class="mt-4 mb-3">
<div id="login2F1" class="d-grid">
<button id="Login2F1Btn" type="button" class="btn btn-primary">Ellenőrzés</button>
</div>
@@ -70,7 +230,13 @@
<button id="Login2F2Btn" type="button" class="btn btn-primary">Megerősítés</button>
</div>
</form>
</div>
</div>
</section>
</main>
<footer class="wfc-account-footer">
&copy; @(DateTime.Now.Year) Workflow Check App &nbsp;•&nbsp; Version @(SystemHelper.ProgramVersion)
</footer>
<!-- Message Modal -->
<div class="modal fade" id="messageModal" tabindex="-1" aria-labelledby="messageModalLabel" aria-hidden="true">
@@ -90,11 +256,11 @@
</div>
</div>
<script src="~/js/site.js" asp-append-version="true"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-validation@1.19.5/dist/jquery.validate.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-validation-unobtrusive@4.0.0/dist/jquery.validate.unobtrusive.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
<script>
function setLoading(isLoading) {
+165 -16
View File
@@ -14,17 +14,167 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>@ViewData["Title"]</title>
<link rel="icon" type="image/svg+xml" href="~/images/favicon/favicon.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="~/images/favicon/favicon-32.png" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
:root {
--wfc-navy-950: #071629;
--wfc-navy-900: #0b1f3a;
--wfc-navy-700: #123c69;
--wfc-blue-500: #38bdf8;
--wfc-blue-200: #bae6fd;
--wfc-surface: #f5f8fc;
--wfc-border: #d7e2ee;
--wfc-text: #10243e;
--wfc-muted: #64748b;
}
body.wfc-login-page {
min-height: 100vh;
color: var(--wfc-text);
background:
radial-gradient(circle at 12% 18%, rgb(56 189 248 / 14%), transparent 27rem),
radial-gradient(circle at 88% 82%, rgb(18 60 105 / 12%), transparent 30rem),
linear-gradient(145deg, #f8fbff, #eaf3fb);
font-family: Inter, "Segoe UI", Arial, sans-serif;
}
.wfc-login-main {
padding: 2rem 1rem;
}
.wfc-login-card {
position: relative;
width: 100%;
max-width: 430px;
overflow: hidden;
border: 1px solid rgb(215 226 238 / 85%);
border-radius: 1rem;
background: #fff;
box-shadow: 0 24px 65px rgb(11 31 58 / 18%);
}
.wfc-login-brand {
display: flex;
min-height: 128px;
align-items: center;
justify-content: center;
padding: 1.7rem 2rem;
background:
radial-gradient(circle at 82% 12%, rgb(56 189 248 / 25%), transparent 12rem),
linear-gradient(135deg, var(--wfc-navy-900), var(--wfc-navy-950));
}
.wfc-login-logo {
display: block;
width: 100%;
max-width: 335px;
height: auto;
}
.wfc-login-body {
padding: 2rem 2.1rem 2.15rem;
}
.wfc-login-title {
margin: 0;
color: var(--wfc-navy-900);
font-size: 1.55rem;
font-weight: 750;
letter-spacing: -.02em;
}
.wfc-login-subtitle {
margin: .4rem 0 1.65rem;
color: var(--wfc-muted);
font-size: .92rem;
}
.wfc-login-card .form-label {
margin-bottom: .42rem;
color: var(--wfc-navy-900);
font-size: .88rem;
font-weight: 650;
}
.wfc-login-card .form-control {
min-height: 46px;
border-color: var(--wfc-border);
border-radius: .6rem;
color: var(--wfc-text);
background: #fbfdff;
}
.wfc-login-card .form-control:focus {
border-color: var(--wfc-blue-500);
background: #fff;
box-shadow: 0 0 0 .22rem rgb(56 189 248 / 20%);
}
.wfc-login-card .btn-primary {
min-height: 46px;
border-color: var(--wfc-navy-700);
border-radius: .6rem;
background: linear-gradient(135deg, var(--wfc-navy-700), var(--wfc-navy-900));
font-weight: 700;
box-shadow: 0 8px 18px rgb(18 60 105 / 20%);
}
.wfc-login-card .btn-primary:hover,
.wfc-login-card .btn-primary:focus {
border-color: var(--wfc-navy-950);
background: linear-gradient(135deg, #174c82, var(--wfc-navy-900));
}
.wfc-login-card .spinner-border {
color: var(--wfc-navy-700);
}
.wfc-login-loading {
z-index: 10;
border-radius: 1rem;
background: rgb(255 255 255 / 84%);
backdrop-filter: blur(3px);
}
.wfc-login-footer {
padding: 1rem;
color: var(--wfc-muted);
font-size: .82rem;
text-align: center;
}
@@media (max-width: 575.98px) {
.wfc-login-main {
align-items: flex-start !important;
padding-top: 1rem;
}
.wfc-login-brand {
min-height: 108px;
padding: 1.35rem 1.5rem;
}
.wfc-login-body {
padding: 1.55rem 1.35rem 1.7rem;
}
}
</style>
</head>
@* <body class="bg-light d-flex justify-content-center align-items-center vh-100"> *@
<body class="bg-light d-flex flex-column min-vh-100">
<main class="flex-grow-1 d-flex justify-content-center align-items-center">
<div class="card shadow p-4" style="min-width: 350px; max-width: 400px; width: 100%;">
<h2 class="mb-4 text-center">Bejelentkezés</h2>
<body class="wfc-login-page d-flex flex-column">
<main class="wfc-login-main flex-grow-1 d-flex justify-content-center align-items-center">
<section class="wfc-login-card" aria-labelledby="loginTitle">
<div class="wfc-login-brand">
<img src="~/images/logo/workflow-check-logo-dark.svg" alt="Workflow Check" class="wfc-login-logo" />
</div>
<div class="wfc-login-body">
<h1 class="wfc-login-title" id="loginTitle">Bejelentkezés</h1>
<p class="wfc-login-subtitle">Lépj be a Workflow Check adminisztrációs felületére.</p>
<!-- Loading overlay -->
<div id="formLoading" class="position-absolute top-0 start-0 w-100 h-100 d-none align-items-center justify-content-center"
style="background: rgba(255,255,255,.75); z-index: 10; border-radius: .375rem;">
<div id="formLoading" class="wfc-login-loading position-absolute top-0 start-0 w-100 h-100 d-none align-items-center justify-content-center">
<div class="text-center">
<div class="spinner-border" role="status" aria-hidden="true"></div>
<div class="mt-2 small text-muted">Kérlek várj…</div>
@@ -38,21 +188,21 @@
<input type="hidden" asp-for="User2FADTO.Token2FA" />
<div class="mb-3">
<label asp-for="User2FADTO.UserName" class="form-label">Felhasználónév</label>
<input asp-for="User2FADTO.UserName" class="form-control" />
<input asp-for="User2FADTO.UserName" class="form-control" autocomplete="username" autofocus />
<span asp-validation-for="User2FADTO.UserName" class="text-danger small"></span>
</div>
<div class="mb-3">
<label asp-for="User2FADTO.Password" class="form-label">Jelszó</label>
<input asp-for="User2FADTO.Password" type="password" class="form-control" />
<input asp-for="User2FADTO.Password" type="password" class="form-control" autocomplete="current-password" />
<span asp-validation-for="User2FADTO.Password" class="text-danger small"></span>
</div>
<div id="VerificationCode" class="mb-3">
<label asp-for="User2FADTO.Code" class="form-label">Megerősítő kód</label>
<input asp-for="User2FADTO.Code" class="form-control" />
<input asp-for="User2FADTO.Code" class="form-control" autocomplete="one-time-code" inputmode="numeric" />
<span asp-validation-for="User2FADTO.Code" class="text-danger small"></span>
</div>
<hr class="mt-4 mb-3 border-secondary">
<hr class="mt-4 mb-3">
<div id="login2F1" class="d-grid">
<button id="Login2F1Btn" type="button" class="btn btn-primary">Bejelentkezés</button>
</div>
@@ -60,12 +210,11 @@
<button id="Login2F2Btn" type="button" class="btn btn-primary">Megerősítés</button>
</div>
</form>
</div>
</div>
</section>
</main>
<footer class="border-top footer text-muted">
<div class="container">
&copy; @(DateTime.Now.Year) - Workflow Check App - Version @(SystemHelper.ProgramVersion)
</div>
<footer class="wfc-login-footer">
&copy; @(DateTime.Now.Year) Workflow Check App &nbsp;•&nbsp; Version @(SystemHelper.ProgramVersion)
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
@@ -27,16 +27,6 @@
<th class="text-center">Action</th>
</tr>
</thead>
<tfoot class="table-light">
<tr>
<th>ID</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(CheckPointDTO.IsEnabled), typeof(CheckPointDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(CheckPointDTO.ShortName), typeof(CheckPointDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(CheckPointDTO.Code), typeof(CheckPointDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(CheckPointDTO.RoleName), typeof(CheckPointDTO))</th>
<th>Action</th>
</tr>
</tfoot>
</table>
</div>
@@ -22,14 +22,6 @@
<th class="text-center">Action</th>
</tr>
</thead>
<tfoot class="table-light">
<tr>
<th>ID</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(EquipmentDTO.ShortName), typeof(EquipmentDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(EquipmentDTO.EquipmentNumber), typeof(EquipmentDTO))</th>
<th>Action</th>
</tr>
</tfoot>
</table>
</div>
@@ -21,13 +21,6 @@
<th class="text-center">Action</th>
</tr>
</thead>
<tfoot class="table-light">
<tr>
<th>ID</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(LocationDTO.FullName), typeof(LocationDTO))</th>
<th>Action</th>
</tr>
</tfoot>
</table>
</div>
@@ -18,15 +18,6 @@
<th class="text-center">Action</th>
</tr>
</thead>
<tfoot class="table-light">
<tr>
<th>ID</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(ImageFileDTO.FileName), typeof(ImageFileDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(ImageFileDTO.FileSize), typeof(ImageFileDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(ImageFileDTO.CreateDate), typeof(ImageFileDTO))</th>
<th>Action</th>
</tr>
</tfoot>
</table>
</div>
@@ -18,15 +18,6 @@
<th class="text-center">Action</th>
</tr>
</thead>
<tfoot class="table-light">
<tr>
<th>ID</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(ImageFileDTO.FileName), typeof(ImageFileDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(ImageFileDTO.FileSize), typeof(ImageFileDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(ImageFileDTO.CreateDate), typeof(ImageFileDTO))</th>
<th>Action</th>
</tr>
</tfoot>
</table>
</div>
@@ -18,15 +18,6 @@
<th class="text-center">Action</th>
</tr>
</thead>
<tfoot class="table-light">
<tr>
<th>ID</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(PDFFileDTO.FileName), typeof(PDFFileDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(PDFFileDTO.FileSize), typeof(PDFFileDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(PDFFileDTO.CreateDate), typeof(PDFFileDTO))</th>
<th>Action</th>
</tr>
</tfoot>
</table>
</div>
@@ -37,20 +37,6 @@
<th class="text-center">Action</th>
</tr>
</thead>
<tfoot class="table-light">
<tr>
<th>ID</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(CheckListHeaderDTO.CheckStatus), typeof(CheckListHeaderDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(CheckListHeaderDTO.IsEditable), typeof(CheckListHeaderDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(CheckListHeaderDTO.DocumentNumber), typeof(CheckListHeaderDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(CheckListHeaderDTO.DateExecution), typeof(CheckListHeaderDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(CheckListHeaderDTO.ShortName), typeof(CheckListHeaderDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(CheckListHeaderDTO.Description), typeof(CheckListHeaderDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(CheckListHeaderDTO.UserId), typeof(CheckListHeaderDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(CheckListHeaderDTO.AcceptUserId), typeof(CheckListHeaderDTO))</th>
<th class="text-center">Action</th>
</tr>
</tfoot>
</table>
</div>
@@ -58,11 +44,12 @@
<script>
let currentMode = @Model.Mode;
const table = new DataTable('#tbCheckListHeadersPage', {
serverSide: true,
ordering: true,
order: [],
ajax: {
url: "@Url.Page("./CheckListHeaderPage", "LoadCheckListHeaders")",
type: "GET",
type: "POST",
data: function (d) {
d.mode = currentMode;
},
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using System.ComponentModel.DataAnnotations;
@@ -9,6 +9,7 @@ using System.Security.Claims;
using WorkFlowCheck.Common.DTO;
namespace WorkFlowCheck.Web.Pages.CheckList.CheckListHeader
{
[IgnoreAntiforgeryToken]
public class CheckListHeaderPageModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
@@ -28,10 +29,50 @@ namespace WorkFlowCheck.Web.Pages.CheckList.CheckListHeader
}
public async Task<JsonResult> OnGetLoadCheckListHeaders(int mode = 0)
public async Task<JsonResult> OnPostLoadCheckListHeadersAsync()
{
var results = await _checkListService.GetAllCheckListHeaderAsync(mode);
return new JsonResult(new { data = results });
var mode = int.Parse(Request.Form["mode"]);
var draw = int.Parse(Request.Form["draw"]);
var start = int.Parse(Request.Form["start"]);
var length = int.Parse(Request.Form["length"]);
var search = Request.Form["search[value]"].ToString();
var orderColIndexStr = Request.Query["order[0][column]"].ToString();
var orderDir = Request.Query["order[0][dir]"].ToString(); // "asc" / "desc"
int? orderColIndex = int.TryParse(orderColIndexStr, out var idx) ? idx : null;
// columns[x][data] alapján tudod, melyik mezőt rendezi
string? orderColData = null;
if (orderColIndex.HasValue)
orderColData = Request.Query[$"columns[{orderColIndex.Value}][data]"].ToString();
//var results = await _checkListService.GetAllCheckListHeaderAsync(mode);
var dataTablesRequest = new Common.DataTables.DataTablesRequest()
{
Mode = mode,
Length = length,
Start = start,
Draw = draw,
};
dataTablesRequest.Search.Value = search;
var results = await _checkListService.GetFilteredCheckListHeaderAsync(dataTablesRequest);
return new JsonResult(new
{
draw = draw,
recordsTotal = results.Total,
recordsFiltered = results.Filtered,
data = results.Data
});
//var results = await _checkListService.GetAllCheckListHeaderAsync(mode);
//return new JsonResult(new
//{
// draw = draw,
// data = results
//});
}
public async Task<IActionResult> OnGetCreatePDF(int id)
@@ -23,14 +23,6 @@
<th class="text-center">Action</th>
</tr>
</thead>
<tfoot class="table-light">
<tr>
<th>ID</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(CheckListTemplateHeaderDTO.ShortName), typeof(CheckListTemplateHeaderDTO))</th>
<th>@DisplayNameHelper.GetDisplayName(nameof(CheckListTemplateHeaderDTO.Description), typeof(CheckListTemplateHeaderDTO))</th>
<th class="text-center">Action</th>
</tr>
</tfoot>
</table>
</div>
+108 -5
View File
@@ -1,10 +1,113 @@
@page
@page
@model IndexModel
@using System.Text.Json
@{
ViewData["Title"] = "Home page";
ViewData["Title"] = "Áttekintés";
var jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
}
<div class="text-center">
<h1 class="display-4">Üdvözlöm!</h1>
<p>WorkFlowCheck Tablet & WEB & API rendszer</a>.</p>
@section Styles {
<style>
.dashboard-page { --navy:#0b1f3a; --blue:#123c69; --sky:#38bdf8; --border:#d7e2ee; --muted:#64748b; --green:#16875b; --orange:#e28a17; }
.dashboard-heading { display:flex; align-items:flex-start; justify-content:space-between; gap:1rem; margin-bottom:1.25rem; }
.dashboard-heading h1 { margin:0; color:var(--navy); font-size:clamp(1.45rem,2vw,1.9rem); font-weight:800; letter-spacing:-.025em; }
.dashboard-heading p { margin:.3rem 0 0; color:var(--muted); font-size:.9rem; }
.demo-badge { display:inline-flex; flex:0 0 auto; align-items:center; gap:.4rem; padding:.45rem .7rem; border:1px solid #bae6fd; border-radius:999px; color:#075985; background:#f0f9ff; font-size:.75rem; font-weight:750; }
.kpi-grid { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:1rem; margin-bottom:1rem; }
.kpi-card,.dashboard-card { min-width:0; overflow:hidden; border:1px solid var(--border); border-radius:.85rem; background:#fff; box-shadow:0 8px 24px rgb(11 31 58 / 8%); }
.kpi-card { position:relative; padding:1.15rem 1.2rem; }
.kpi-card::after { position:absolute; top:0; right:0; width:72px; height:72px; border-radius:0 0 0 100%; content:""; background:rgb(56 189 248 / 9%); }
.kpi-topline { display:flex; align-items:center; justify-content:space-between; gap:.7rem; }
.kpi-icon { display:inline-flex; width:40px; height:40px; align-items:center; justify-content:center; border-radius:.65rem; color:var(--blue); background:#e8f7fe; font-size:1.15rem; }
.kpi-trend { color:var(--green); font-size:.76rem; font-weight:750; white-space:nowrap; }
.kpi-trend.warning { color:var(--orange); } .kpi-trend.neutral { color:var(--muted); }
.kpi-value { margin-top:.75rem; color:var(--navy); font-size:1.85rem; font-weight:820; line-height:1; }
.kpi-value small { font-size:.9rem; font-weight:700; }
.kpi-label { margin-top:.42rem; overflow:hidden; color:var(--muted); font-size:.83rem; text-overflow:ellipsis; white-space:nowrap; }
.dashboard-grid { display:grid; grid-template-columns:minmax(0,1.7fr) minmax(300px,.8fr); gap:1rem; margin-bottom:1rem; }
.dashboard-grid.equal { grid-template-columns:repeat(2,minmax(0,1fr)); }
.dashboard-card-header { display:flex; min-height:64px; align-items:center; justify-content:space-between; gap:1rem; padding:.95rem 1.15rem; border-bottom:1px solid #e3ebf3; }
.dashboard-card-title { margin:0; color:var(--navy); font-size:1rem; font-weight:780; }
.dashboard-card-subtitle { margin:.18rem 0 0; color:var(--muted); font-size:.76rem; }
.dashboard-card-body { padding:1rem 1.15rem 1.15rem; }
.chart-container { position:relative; width:100%; height:285px; } .chart-container.compact { height:250px; }
.status-summary { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.6rem; margin-top:.6rem; }
.status-item { display:flex; align-items:center; gap:.48rem; color:#475569; font-size:.76rem; }
.status-dot { width:8px; height:8px; flex:0 0 8px; border-radius:50%; } .status-item strong { margin-left:auto; color:var(--navy); }
.attention-list { display:flex; flex-direction:column; gap:.7rem; }
.attention-item { display:flex; align-items:flex-start; gap:.75rem; padding:.78rem; border:1px solid #e4ebf3; border-radius:.7rem; background:#fbfdff; }
.attention-icon { display:inline-flex; width:34px; height:34px; flex:0 0 34px; align-items:center; justify-content:center; border-radius:.55rem; color:#9a5a00; background:#fff3d6; }
.attention-item.danger .attention-icon { color:#a61b1b; background:#feecec; }
.attention-title { color:var(--navy); font-size:.84rem; font-weight:720; } .attention-meta { margin-top:.15rem; color:var(--muted); font-size:.73rem; }
.dashboard-table { margin:0; }
.dashboard-table thead th { padding:.72rem 1rem; border-bottom-color:var(--border); color:var(--muted); background:#f8fafc; font-size:.71rem; font-weight:750; letter-spacing:.035em; text-transform:uppercase; white-space:nowrap; }
.dashboard-table tbody td { padding:.78rem 1rem; border-bottom-color:#edf2f7; color:#334155; font-size:.82rem; vertical-align:middle; }
.inspection-code { color:var(--blue); font-weight:750; }
.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}}
</style>
}
<div class="dashboard-page">
<div class="dashboard-heading">
<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 @((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">
<article class="dashboard-card">
<header class="dashboard-card-header"><div><h2 class="dashboard-card-title">Ellenőrzések az utolsó 6 hónapban</h2><p class="dashboard-card-subtitle">Összes ellenőrzés és jóváhagyott tételek havi bontásban</p></div></header>
<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 ö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">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 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>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>
@section Scripts {
<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.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:'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>
}
+41 -6
View File
@@ -1,22 +1,57 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
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
{
//[Authorize]
public class IndexModel : PageModel
{
private readonly ICheckListService _checkListService;
private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger)
public DashboardDTO Dashboard { get; private set; } = new();
public string PeriodLabel => Dashboard.GeneratedAt.ToLocalTime()
.ToString("yyyy. MMMM", CultureInfo.GetCultureInfo("hu-HU"));
public IndexModel(ICheckListService checkListService, ILogger<IndexModel> logger)
{
_checkListService = checkListService;
_logger = logger;
}
public void OnGet()
public async Task OnGetAsync()
{
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 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"
};
}
}
+262 -170
View File
@@ -1,228 +1,320 @@
<!DOCTYPE html>
<html lang="hu">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WorkFlowCheck.Web</title>
<title>@ViewData["Title"] - Workflow Check</title>
<link rel="icon" type="image/svg+xml" href="~/images/favicon/favicon.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="~/images/favicon/favicon-32.png" />
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/css/workflow-check.css" asp-append-version="true" />
<link rel="stylesheet" href="~/WorkFlowCheck.Web.styles.css" asp-append-version="true" />
<link href="https://cdn.datatables.net/v/bs5/jq-3.7.0/jszip-3.10.1/dt-2.2.2/af-2.7.0/b-3.2.2/b-colvis-3.2.2/b-html5-3.2.2/b-print-3.2.2/cr-2.0.4/date-1.5.5/fc-5.0.4/fh-4.0.1/kt-2.12.1/r-3.0.4/rg-1.5.1/rr-1.5.0/sc-2.4.3/sb-1.8.2/sp-2.3.3/sl-3.0.0/sr-1.4.1/datatables.min.css" rel="stylesheet" integrity="sha384-6gM1RUmcWWtU9mNI98EhVNlLX1LDErxSDu2o/YRIeXq34o77tQYTXLzJ/JLBNkNV" crossorigin="anonymous">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet">
@* <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> *@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/v/bs5/jq-3.7.0/jszip-3.10.1/dt-2.2.2/af-2.7.0/b-3.2.2/b-colvis-3.2.2/b-html5-3.2.2/b-print-3.2.2/cr-2.0.4/date-1.5.5/fc-5.0.4/fh-4.0.1/kt-2.12.1/r-3.0.4/rg-1.5.1/rr-1.5.0/sc-2.4.3/sb-1.8.2/sp-2.3.3/sl-3.0.0/sr-1.4.1/datatables.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
<script>
document.documentElement.classList.add('wfc-sidebar-initializing');
try {
if (localStorage.getItem('wfc.sidebar.collapsed') === 'true') {
document.documentElement.classList.add('wfc-sidebar-collapsed');
}
const savedWidth = Number(localStorage.getItem('wfc.sidebar.expandedWidth'));
if (savedWidth >= 260 && savedWidth <= 360) {
document.documentElement.style.setProperty('--wfc-sidebar-expanded-width', `${savedWidth}px`);
}
} catch { }
window.setTimeout(() => {
document.documentElement.classList.remove('wfc-sidebar-initializing');
}, 1500);
</script>
@await RenderSectionAsync("Styles", required: false)
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-page="/Index">Workflow Check Admin Site</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
<div class="wfc-shell">
<aside class="wfc-sidebar" id="wfcSidebar">
<div class="wfc-sidebar-brand">
<a asp-area="" asp-page="/Index" aria-label="Workflow Check kezdőlap">
<img src="~/images/logo/workflow-check-logo-dark.svg" alt="Workflow Check" class="wfc-sidebar-logo" />
</a>
<button type="button" class="wfc-sidebar-close d-lg-none" id="wfcSidebarClose" aria-label="Menü bezárása">
<i class="bi bi-x-lg"></i>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
@* <li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
</li> *@
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle text-dark" href="#" id="baseStockDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Törzsadatok
</a>
<ul class="dropdown-menu" aria-labelledby="baseStockDropdown">
<li><a class="dropdown-item" asp-area="" asp-page="/BaseStock/CheckPoints/CheckPointsPage">Ellenőrzési pontok</a></li>
<li><a class="dropdown-item" asp-area="" asp-page="/BaseStock/Equipments/EquipmentsPage">Berendezések</a></li>
<li><a class="dropdown-item" asp-area="" asp-page="/BaseStock/Locations/LocationsPage">Lokációk</a></li>
<li><hr class="dropdown-divider"></li> <!-- EZ AZ ELVÁLASZTÓ VONAL -->
<li><a class="dropdown-item" asp-area="" asp-page="/BaseStock/ImageFiles/ImageFilesPage">Fényképek</a></li>
<li><a class="dropdown-item" asp-area="" asp-page="/BaseStock/PDFFiles/PDFFilesPage">PDF állomány</a></li>
<li><hr class="dropdown-divider"></li> <!-- EZ AZ ELVÁLASZTÓ VONAL -->
<li><a class="dropdown-item" asp-area="" asp-page="/BaseStock/Logs/Audit/AuditFilesPage">Audit események</a></li>
<li><a class="dropdown-item" asp-area="" asp-page="/BaseStock/Logs/Business/BusinessFilesPage">Folyamat események</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle text-dark" href="#" id="checkListDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Ellenőrzési adatok
</a>
<ul class="dropdown-menu" aria-labelledby="checkListDropdown">
<li>
<a class="dropdown-item" asp-area="" asp-page="/CheckList/CheckListHeader/CheckListHeaderPage"
asp-route-mode="0">Ellenőrzések (folyamatban)</a>
</li>
<li>
<a class="dropdown-item" asp-area="" asp-page="/CheckList/CheckListHeader/CheckListHeaderPage"
asp-route-mode="1">Ellenőrzések (beküldött)</a>
</li>
<li>
<a class="dropdown-item" asp-area="" asp-page="/CheckList/CheckListHeader/CheckListHeaderPage"
asp-route-mode="3">Ellenőrzések (jóváhagyott)</a>
</li>
<li>
<a class="dropdown-item" asp-area="" asp-page="/CheckList/CheckListHeader/CheckListHeaderPage"
asp-route-mode="2">Ellenőrzések (lezárt)</a>
</li>
<li>
<a class="dropdown-item" asp-area="" asp-page="/CheckList/CheckListHeader/CheckListHeaderPage"
asp-route-mode="4">Ellenőrzések (összes)</a>
</li>
<li><hr class="dropdown-divider"></li> <!-- EZ AZ ELVÁLASZTÓ VONAL -->
<li><a class="dropdown-item" asp-area="" asp-page="/CheckListTemplate/CheckListTemplateHeader/CheckListTemplateHeaderPage">Ellenőrzési sablonok</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle text-dark" href="#" id="adminDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Felhasználók - szabályok
</a>
<ul class="dropdown-menu" aria-labelledby="adminDropdown">
<li><a class="dropdown-item" asp-area="" asp-page="/UserAndRole/UserPage">Felhasználók</a></li>
<li><a class="dropdown-item" asp-area="" asp-page="/UserAndRole/RolePage">Szabályok</a></li>
<li><hr class="dropdown-divider"></li> <!-- EZ AZ ELVÁLASZTÓ VONAL -->
<li><a class="dropdown-item" asp-area="" asp-page="/UserAndRole/UserRolePage">Felhasználók - szabályok</a></li>
<li><a class="dropdown-item" asp-area="" asp-page="/UserAndRole/RoleCheckListTemplateHeadersPage">Szabályok - Ellenőrzési sablonok</a></li>
<li><a class="dropdown-item" asp-area="" asp-page="/UserAndRole/RoleCheckPointsPage">Szabályok - Ellenőrzési pontok</a></li>
</ul>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle text-dark" href="#" id="accountDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Adataim
</a>
<ul class="dropdown-menu" aria-labelledby="accountDropdown">
<li class="dropdown-item">
<a class="nav-link text-dark" href="#" data-bs-toggle="modal" data-bs-target="#logoutModal">
<i class="bi bi-box-arrow-right"></i>Logout (@User.Identity.Name)
</a>
</li>
<li>
<a class="dropdown-item" asp-area="" asp-page="/Account/ChangePassword">
Jelszó módosítása
</a>
</li>
<li><hr class="dropdown-divider"></li> <!-- EZ AZ ELVÁLASZTÓ VONAL -->
<li class="dropdown-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Adatvédelmi tájékoztatás</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
<nav class="wfc-sidebar-nav" aria-label="Főmenü">
<a class="wfc-sidebar-link @(Context.Request.Path == "/" ? "active" : "")" asp-area="" asp-page="/Index">
<i class="bi bi-speedometer2"></i><span>Áttekintés</span>
</a>
<div class="wfc-sidebar-section">ELLENŐRZÉSEK</div>
<button class="wfc-sidebar-link wfc-collapse-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#checkListMenu" aria-expanded="false">
<i class="bi bi-clipboard2-check"></i><span>Ellenőrzési adatok</span><i class="bi bi-chevron-down ms-auto wfc-toggle-chevron"></i>
</button>
<div class="collapse wfc-sidebar-submenu" id="checkListMenu">
<a class="wfc-sidebar-link" asp-area="" asp-page="/CheckList/CheckListHeader/CheckListHeaderPage" asp-route-mode="0">
<i class="bi bi-play-circle"></i><span>Folyamatban</span>
</a>
<a class="wfc-sidebar-link" asp-area="" asp-page="/CheckList/CheckListHeader/CheckListHeaderPage" asp-route-mode="1">
<i class="bi bi-send-check"></i><span>Beküldött</span>
</a>
<a class="wfc-sidebar-link" asp-area="" asp-page="/CheckList/CheckListHeader/CheckListHeaderPage" asp-route-mode="3">
<i class="bi bi-hand-thumbs-up"></i><span>Jóváhagyott</span>
</a>
<a class="wfc-sidebar-link" asp-area="" asp-page="/CheckList/CheckListHeader/CheckListHeaderPage" asp-route-mode="2">
<i class="bi bi-lock"></i><span>Lezárt</span>
</a>
<a class="wfc-sidebar-link" asp-area="" asp-page="/CheckList/CheckListHeader/CheckListHeaderPage" asp-route-mode="4">
<i class="bi bi-list-check"></i><span>Összes ellenőrzés</span>
</a>
<hr class="wfc-sidebar-divider" />
<a class="wfc-sidebar-link" asp-area="" asp-page="/CheckListTemplate/CheckListTemplateHeader/CheckListTemplateHeaderPage">
<i class="bi bi-clipboard-data"></i><span>Ellenőrzési sablonok</span>
</a>
</div>
<div class="wfc-sidebar-section">TÖRZSADATOK</div>
<button class="wfc-sidebar-link wfc-collapse-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#masterDataMenu" aria-expanded="false">
<i class="bi bi-database-gear"></i><span>Gyártási törzsadatok</span><i class="bi bi-chevron-down ms-auto wfc-toggle-chevron"></i>
</button>
<div class="collapse wfc-sidebar-submenu" id="masterDataMenu">
<a class="wfc-sidebar-link" asp-area="" asp-page="/BaseStock/CheckPoints/CheckPointsPage"><i class="bi bi-check2-circle"></i><span>Ellenőrzési pontok</span></a>
<a class="wfc-sidebar-link" asp-area="" asp-page="/BaseStock/Equipments/EquipmentsPage"><i class="bi bi-gear-wide-connected"></i><span>Berendezések</span></a>
<a class="wfc-sidebar-link" asp-area="" asp-page="/BaseStock/Locations/LocationsPage"><i class="bi bi-geo-alt"></i><span>Lokációk</span></a>
<hr class="wfc-sidebar-divider" />
<a class="wfc-sidebar-link" asp-area="" asp-page="/BaseStock/ImageFiles/ImageFilesPage"><i class="bi bi-images"></i><span>Fényképek</span></a>
<a class="wfc-sidebar-link" asp-area="" asp-page="/BaseStock/PDFFiles/PDFFilesPage"><i class="bi bi-file-earmark-pdf"></i><span>PDF állományok</span></a>
<hr class="wfc-sidebar-divider" />
<a class="wfc-sidebar-link" asp-area="" asp-page="/BaseStock/Logs/Audit/AuditFilesPage"><i class="bi bi-shield-check"></i><span>Audit események</span></a>
<a class="wfc-sidebar-link" asp-area="" asp-page="/BaseStock/Logs/Business/BusinessFilesPage"><i class="bi bi-activity"></i><span>Folyamat események</span></a>
</div>
<div class="wfc-sidebar-section">RENDSZER</div>
<button class="wfc-sidebar-link wfc-collapse-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#usersAndRolesMenu" aria-expanded="false">
<i class="bi bi-people"></i><span>Felhasználók és szabályok</span><i class="bi bi-chevron-down ms-auto wfc-toggle-chevron"></i>
</button>
<div class="collapse wfc-sidebar-submenu" id="usersAndRolesMenu">
<a class="wfc-sidebar-link" asp-area="" asp-page="/UserAndRole/UserPage"><i class="bi bi-person-gear"></i><span>Felhasználók</span></a>
<a class="wfc-sidebar-link" asp-area="" asp-page="/UserAndRole/RolePage"><i class="bi bi-shield-lock"></i><span>Szabályok</span></a>
<hr class="wfc-sidebar-divider" />
<a class="wfc-sidebar-link" asp-area="" asp-page="/UserAndRole/UserRolePage"><i class="bi bi-person-check"></i><span>Felhasználók – szabályok</span></a>
<a class="wfc-sidebar-link" asp-area="" asp-page="/UserAndRole/RoleCheckListTemplateHeadersPage"><i class="bi bi-ui-checks-grid"></i><span>Szabályok – sablonok</span></a>
<a class="wfc-sidebar-link" asp-area="" asp-page="/UserAndRole/RoleCheckPointsPage"><i class="bi bi-check2-square"></i><span>Szabályok – ellenőrzési pontok</span></a>
</div>
</nav>
<div class="wfc-sidebar-account">
<div class="wfc-sidebar-user"><i class="bi bi-person-circle"></i><span>@(User.Identity?.Name ?? "Felhasználó")</span></div>
<a asp-area="" asp-page="/Account/ChangePassword"><i class="bi bi-key"></i><span>Jelszó módosítása</span></a>
<a href="#" data-bs-toggle="modal" data-bs-target="#logoutModal"><i class="bi bi-box-arrow-right"></i><span>Kijelentkezés</span></a>
</div>
</aside>
<div class="wfc-sidebar-backdrop" id="wfcSidebarBackdrop"></div>
<div class="wfc-content">
<header class="wfc-topbar">
<button type="button" class="wfc-sidebar-open d-lg-none" id="wfcSidebarOpen" aria-label="Menü megnyitása"><i class="bi bi-list"></i></button>
<button type="button" class="wfc-sidebar-toggle d-none d-lg-inline-flex" id="wfcSidebarToggle" aria-controls="wfcSidebar" aria-expanded="true" title="Oldalsáv összecsukása">
<i class="bi bi-layout-sidebar-inset"></i>
</button>
<div class="wfc-topbar-title">@ViewData["Title"]</div>
</header>
<main role="main" class="wfc-main">
@RenderBody()
</main>
<footer class="wfc-footer">
<div class="wfc-footer-content">
<span>&copy; @(DateTime.Now.Year) Workflow Check App</span>
<span class="wfc-footer-separator">•</span>
<span>Version @(SystemHelper.ProgramVersion)</span>
<span class="wfc-footer-separator">•</span>
<span>@SystemHelper.DatabaseName</span>
<a asp-area="" asp-page="/Privacy">Adatvédelmi tájékoztatás</a>
</div>
</footer>
</div>
</div>
<!-- Logout Modal -->
<div class="modal fade" id="logoutModal" tabindex="-1" aria-labelledby="logoutModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="logoutModalLabel">Kijelentkezés</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Bezárás"></button>
</div>
<div class="modal-body">
Biztosan ki akarsz lépni?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Nem</button>
<a href="/Account/Logout" class="btn btn-primary">Igen</a>
</div>
<div class="modal-header"><h5 class="modal-title" id="logoutModalLabel">Kijelentkezés</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Bezárás"></button></div>
<div class="modal-body">Biztosan ki akarsz lépni?</div>
<div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Nem</button><a href="/Account/Logout" class="btn btn-primary">Igen</a></div>
</div>
</div>
</div>
<!-- Confirm Modal -->
<div class="modal fade" id="confirmModal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="confirmModalLabel">Megerősítés</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Bezárás"></button>
</div>
<div class="modal-body" id="confirmModalBody">
Biztos vagy benne?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="confirmModalCancelBtn" data-bs-dismiss="modal">Mégsem</button>
<button type="button" class="btn btn-primary" id="confirmModalOkBtn">Igen</button>
</div>
<div class="modal-header"><h5 class="modal-title" id="confirmModalLabel">Megerősítés</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Bezárás"></button></div>
<div class="modal-body" id="confirmModalBody">Biztos vagy benne?</div>
<div class="modal-footer"><button type="button" class="btn btn-secondary" id="confirmModalCancelBtn" data-bs-dismiss="modal">Mégsem</button><button type="button" class="btn btn-primary" id="confirmModalOkBtn">Igen</button></div>
</div>
</div>
</div>
<!-- Confirm Modal With Message-->
<div class="modal fade" id="confirmModalWithMessage" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
<div class="modal fade" id="confirmModalWithMessage" tabindex="-1" aria-labelledby="confirmModalWithMessageLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="confirmModalLabel">Megerősítés</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Bezárás"></button>
</div>
<div class="modal-body" id="confirmModalBody">
<div class="modal-header"><h5 class="modal-title" id="confirmModalWithMessageLabel">Megerősítés</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Bezárás"></button></div>
<div class="modal-body">
<label for="confirmModalTextarea" class="form-label">Írd le a megjegyzést vagy indoklást:</label>
<textarea class="form-control" id="confirmModalTextarea" rows="4" placeholder="Írd ide..."></textarea>
<div class="invalid-feedback">
A mező kitöltése kötelező.
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="confirmModalCancelBtn" data-bs-dismiss="modal">Mégsem</button>
<button type="button" class="btn btn-primary" id="confirmModalOkBtn">Igen</button>
<div class="invalid-feedback">A mező kitöltése kötelező.</div>
</div>
<div class="modal-footer"><button type="button" class="btn btn-secondary" id="confirmModalCancelBtn" data-bs-dismiss="modal">Mégsem</button><button type="button" class="btn btn-primary" id="confirmModalOkBtn">Igen</button></div>
</div>
</div>
</div>
<!-- Message Modal -->
<div class="modal fade" id="messageModal" tabindex="-1" aria-labelledby="messageModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="messageModalLabel">Üzenet</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Bezárás"></button>
</div>
<div class="modal-body" id="messageModalBody">
Ez egy információs üzenet.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id="messageModalOkBtn" data-bs-dismiss="modal">OK</button>
</div>
<div class="modal-header"><h5 class="modal-title" id="messageModalLabel">Üzenet</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Bezárás"></button></div>
<div class="modal-body" id="messageModalBody">Ez egy információs üzenet.</div>
<div class="modal-footer"><button type="button" class="btn btn-primary" id="messageModalOkBtn" data-bs-dismiss="modal">OK</button></div>
</div>
</div>
</div>
<footer class="border-top footer text-muted">
<div class="container">
&copy; @(DateTime.Now.Year) - Workflow Check App - Version @(SystemHelper.ProgramVersion) (@(SystemHelper.DatabaseName))<a asp-area="" asp-page="/Privacy"> Adatvédelmi tájékoztatás</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js" integrity="sha384-VFQrHzqBh5qiJIU0uGU5CIW3+OWpdGGJM9LBnGbuIH2mkICcFZ7lPd/AAtI7SNf7" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js" integrity="sha384-/RlQG9uf0M2vcTw3CX7fbqgbj/h8wKxw7C3zu9/GxcBPRKOEcESxaxufwRXqzq6n" crossorigin="anonymous"></script>
<script src="https://cdn.datatables.net/v/bs5/jq-3.7.0/jszip-3.10.1/dt-2.2.2/af-2.7.0/b-3.2.2/b-colvis-3.2.2/b-html5-3.2.2/b-print-3.2.2/cr-2.0.4/date-1.5.5/fc-5.0.4/fh-4.0.1/kt-2.12.1/r-3.0.4/rg-1.5.1/rr-1.5.0/sc-2.4.3/sb-1.8.2/sp-2.3.3/sl-3.0.0/sr-1.4.1/datatables.min.js" integrity="sha384-10kTwhFyUU637a6/7q0kLBdo8jQWjxteg63DT/K8Sdq/nCDaDAkH+Nq/MIrsp8wc" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js"></script>
<script src="https://cdn.datatables.net/v/bs5/jszip-3.10.1/dt-2.2.2/af-2.7.0/b-3.2.2/b-colvis-3.2.2/b-html5-3.2.2/b-print-3.2.2/cr-2.0.4/date-1.5.5/fc-5.0.4/fh-4.0.1/kt-2.12.1/r-3.0.4/rg-1.5.1/rr-1.5.0/sc-2.4.3/sb-1.8.2/sp-2.3.3/sl-3.0.0/sr-1.4.1/datatables.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-validation@1.19.5/dist/jquery.validate.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-validation-unobtrusive@4.0.0/dist/jquery.validate.unobtrusive.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
<partial name="_ValidationScriptsPartial" />
<script>
$(function () {
$('[data-bs-toggle="tooltip"]').tooltip();
const openMenusStorageKey = 'wfc.sidebar.openMenus';
let savedOpenMenuIds = [];
try {
savedOpenMenuIds = JSON.parse(localStorage.getItem(openMenusStorageKey) || '[]');
} catch { }
if (!Array.isArray(savedOpenMenuIds)) savedOpenMenuIds = [];
const openMenuIds = new Set(savedOpenMenuIds);
const currentUrl = new URL(window.location.href);
let bestMatch = null;
let bestScore = -1;
document.querySelectorAll('.wfc-sidebar-nav a[href]').forEach(link => {
const href = link.getAttribute('href');
if (!href) return;
const linkUrl = new URL(href, window.location.origin);
const samePath = currentUrl.pathname.toLowerCase() === linkUrl.pathname.toLowerCase();
const childPath = linkUrl.pathname !== '/' && currentUrl.pathname.toLowerCase().startsWith(`${linkUrl.pathname.toLowerCase()}/`);
const queryMatches = Array.from(linkUrl.searchParams.entries()).every(([key, value]) => currentUrl.searchParams.get(key) === value);
if ((!samePath && !childPath) || !queryMatches) return;
const score = linkUrl.pathname.length + linkUrl.search.length * 10;
if (score > bestScore) {
bestMatch = link;
bestScore = score;
}
});
if (bestMatch) {
document.querySelectorAll('.wfc-sidebar-link.active').forEach(link => link.classList.remove('active'));
bestMatch.classList.add('active');
const parentMenuId = bestMatch.closest('.collapse')?.id;
if (parentMenuId) openMenuIds.add(parentMenuId);
}
const saveOpenMenus = () => {
const ids = Array.from(document.querySelectorAll('.wfc-sidebar-submenu.collapse.show'), menu => menu.id).filter(Boolean);
try { localStorage.setItem(openMenusStorageKey, JSON.stringify(ids)); } catch { }
};
document.querySelectorAll('.wfc-sidebar-submenu.collapse').forEach(menu => {
const toggle = document.querySelector(`[data-bs-target="#${menu.id}"]`);
if (openMenuIds.has(menu.id)) {
menu.classList.add('show');
toggle?.setAttribute('aria-expanded', 'true');
}
menu.addEventListener('shown.bs.collapse', saveOpenMenus);
menu.addEventListener('hidden.bs.collapse', saveOpenMenus);
});
saveOpenMenus();
const sidebar = document.getElementById('wfcSidebar');
const backdrop = document.getElementById('wfcSidebarBackdrop');
const sidebarToggle = document.getElementById('wfcSidebarToggle');
const updateExpandedSidebarWidth = () => {
if (!sidebar || window.innerWidth < 992) return;
const textElements = sidebar.querySelectorAll('.wfc-sidebar-link > span, .wfc-sidebar-section, .wfc-sidebar-user > span, .wfc-sidebar-account > a > span');
const measureContext = document.createElement('canvas').getContext('2d');
const widestText = Math.max(0, ...Array.from(textElements, element => {
const style = window.getComputedStyle(element);
if (measureContext) measureContext.font = `${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`;
return Math.max(element.scrollWidth, measureContext?.measureText(element.textContent?.trim() || '').width ?? 0);
}));
const logoWidth = sidebar.querySelector('.wfc-sidebar-logo')?.scrollWidth ?? 0;
const expandedWidth = Math.min(360, Math.max(260, widestText + 94, logoWidth + 40));
document.documentElement.style.setProperty('--wfc-sidebar-expanded-width', `${expandedWidth}px`);
try { localStorage.setItem('wfc.sidebar.expandedWidth', String(expandedWidth)); } catch { }
};
const setMobileSidebar = open => {
sidebar?.classList.toggle('open', open);
backdrop?.classList.toggle('show', open);
document.body.classList.toggle('wfc-sidebar-open', open);
};
document.getElementById('wfcSidebarOpen')?.addEventListener('click', () => setMobileSidebar(true));
document.getElementById('wfcSidebarClose')?.addEventListener('click', () => setMobileSidebar(false));
backdrop?.addEventListener('click', () => setMobileSidebar(false));
updateExpandedSidebarWidth();
const setDesktopSidebar = collapsed => {
document.documentElement.classList.toggle('wfc-sidebar-collapsed', collapsed);
if (sidebarToggle) {
sidebarToggle.setAttribute('aria-expanded', String(!collapsed));
sidebarToggle.title = collapsed ? 'Oldalsáv kinyitása' : 'Oldalsáv összecsukása';
const icon = sidebarToggle.querySelector('i');
icon?.classList.toggle('bi-layout-sidebar-inset', !collapsed);
icon?.classList.toggle('bi-layout-sidebar-inset-reverse', collapsed);
}
try { localStorage.setItem('wfc.sidebar.collapsed', String(collapsed)); } catch { }
};
setDesktopSidebar(document.documentElement.classList.contains('wfc-sidebar-collapsed'));
sidebarToggle?.addEventListener('click', () => setDesktopSidebar(!document.documentElement.classList.contains('wfc-sidebar-collapsed')));
document.querySelectorAll('.wfc-collapse-toggle').forEach(toggle => {
toggle.addEventListener('click', event => {
if (window.innerWidth < 992 || !document.documentElement.classList.contains('wfc-sidebar-collapsed')) return;
event.preventDefault();
event.stopPropagation();
const target = toggle.getAttribute('data-bs-target');
const targetMenu = target ? document.querySelector(target) : null;
setDesktopSidebar(false);
if (targetMenu) bootstrap.Collapse.getOrCreateInstance(targetMenu, { toggle: false }).show();
});
});
requestAnimationFrame(() => document.documentElement.classList.remove('wfc-sidebar-initializing'));
});
</script>
</body>
@@ -1,48 +1,12 @@
/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
for details on configuring this project to bundle and minify static web assets. */
/* A teljes oldalsáv és a Workflow Check arculat a wwwroot/css/workflow-check.css fájlban van. */
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
/* Ez a fájl szándékosan csak a Layout Razor-specifikus finomhangolását tartalmazza. */
.modal-header,
.modal-footer {
border-color: #d7e2ee;
}
a {
color: #0077cc;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.border-top {
border-top: 1px solid #e5e5e5;
}
.border-bottom {
border-bottom: 1px solid #e5e5e5;
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
button.accept-policy {
font-size: 1rem;
line-height: inherit;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
line-height: 60px;
.modal-title {
color: #0b1f3a;
font-weight: 700;
}
@@ -20,14 +20,6 @@
<th class="text-center">Action</th>
</tr>
</thead>
<tfoot class="table-light">
<tr>
<th>ID</th>
<th>User Name</th>
<th>Role Name</th>
<th>Action</th>
</tr>
</tfoot>
</table>
</div>
@@ -4,6 +4,7 @@ using Serilog;
using System.ComponentModel.DataAnnotations;
using System.Net.Http;
using System.Reflection;
using WorkFlowCheck.Common.DataTables;
using WorkFlowCheck.Common.DTO;
using WorkFlowCheck.Common.Enums;
using WorkFlowCheck.Web.Services.Interfaces;
@@ -15,7 +16,51 @@ 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
{
string endpoint = $"{_httpClient.BaseAddress}api/CheckList/GetFilteredCheckListHeader";
using (HttpResponseMessage httpResponseMessage = await _httpClient.PostAsJsonAsync(endpoint, dataTablesRequest))
{
httpResponseMessage.EnsureSuccessStatusCode();
var jsonString = await httpResponseMessage.Content.ReadAsStringAsync();
var response = JsonConvert.DeserializeObject<ApiResponseListDTO<List<CheckListHeaderDTO>>>(jsonString);
return response ?? new ApiResponseListDTO<List<CheckListHeaderDTO>>
{
IsSuccess = false,
};
}
}
catch (Exception ex)
{
// Hiba visszaadása
return new ApiResponseListDTO<List<CheckListHeaderDTO>>
{
IsSuccess = false
};
}
}
public async Task<List<CheckListHeaderDTO>> GetAllCheckListHeaderAsync(int mode = 0)
{
var endpoint = $"{_httpClient.BaseAddress}api/CheckList/GetAllCheckListHeaders/{mode}";
@@ -1,11 +1,14 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using WorkFlowCheck.Common.DataTables;
using WorkFlowCheck.Common.DTO;
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);
Task<ApiResponseDTO<CheckListHeaderDTO>> UpdateCheckListHeader(CheckListHeaderDTO checkListHeaderDTO);
Task<ApiResponseDTO<byte[]>> CreatePDF(int id);
@@ -0,0 +1,2 @@
body {
}
@@ -0,0 +1,158 @@
/* Workflow Check – DataTables arculat */
/* Körbefutó keret és finoman lekerekített táblázatsarkok. */
table.dataTable {
border: 1px solid var(--wfc-border, #d7e2ee) !important;
border-collapse: separate !important;
border-spacing: 0 !important;
border-radius: .6rem;
overflow: hidden;
}
table.dataTable > thead > tr:first-child > th:first-child,
table.dataTable > thead > tr:first-child > td:first-child {
border-top-left-radius: .52rem;
}
table.dataTable > thead > tr:first-child > th:last-child,
table.dataTable > thead > tr:first-child > td:last-child {
border-top-right-radius: .52rem;
}
table.dataTable > tbody > tr:last-child > td:first-child,
table.dataTable > tbody > tr:last-child > th:first-child,
table.dataTable > tfoot > tr:last-child > td:first-child,
table.dataTable > tfoot > tr:last-child > th:first-child {
border-bottom-left-radius: .52rem;
}
table.dataTable > tbody > tr:last-child > td:last-child,
table.dataTable > tbody > tr:last-child > th:last-child,
table.dataTable > tfoot > tr:last-child > td:last-child,
table.dataTable > tfoot > tr:last-child > th:last-child {
border-bottom-right-radius: .52rem;
}
table.dataTable > tbody > tr:last-child > td,
table.dataTable > tbody > tr:last-child > th,
table.dataTable > tfoot > tr:last-child > td,
table.dataTable > tfoot > tr:last-child > th {
border-bottom: 0 !important;
}
/* Fejléc: minden DataTables táblán egységes sötétkék háttér. */
table.dataTable > thead,
table.dataTable > thead.table-primary {
--bs-table-color: var(--wfc-white, #fff);
--bs-table-bg: var(--wfc-navy-700, #123c69);
--bs-table-border-color: rgb(255 255 255 / 20%);
color: var(--wfc-white, #fff);
background: var(--wfc-navy-700, #123c69);
}
table.dataTable > thead > tr > th,
table.dataTable > thead > tr > td,
table.dataTable > thead.table-primary > tr > th,
table.dataTable > thead.table-primary > tr > td {
border-color: rgb(255 255 255 / 20%) !important;
color: var(--wfc-white, #fff) !important;
background-color: var(--wfc-navy-700, #123c69) !important;
background-image: none !important;
font-weight: 700;
vertical-align: middle;
}
table.dataTable > thead > tr > th:first-child,
table.dataTable > thead > tr > td:first-child {
border-left-color: var(--wfc-navy-700, #123c69) !important;
}
table.dataTable > thead > tr > th:last-child,
table.dataTable > thead > tr > td:last-child {
border-right-color: var(--wfc-navy-700, #123c69) !important;
}
/* DataTables 2 rendezési nyilak: fehérek és jól láthatók. */
table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order::before,
table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order::before,
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order::before,
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order::before,
table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order::after,
table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order::after,
table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order::after,
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order::after {
color: var(--wfc-white, #fff) !important;
}
table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order::before,
table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order::after,
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order::before,
table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order::after {
opacity: .45 !important;
}
table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order::before,
table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order::after,
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order::before,
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order::after {
opacity: 1 !important;
}
/* Finomabb hover-visszajelzés a rendezhető fejlécen. */
table.dataTable > thead > tr > th.dt-orderable-asc:hover,
table.dataTable > thead > tr > th.dt-orderable-desc:hover,
table.dataTable > thead > tr > td.dt-orderable-asc:hover,
table.dataTable > thead > tr > td.dt-orderable-desc:hover {
background-color: var(--wfc-navy-800, #0e2c4f) !important;
}
/* A táblázat lábléce marad világos, de illeszkedik az arculathoz. */
table.dataTable > tfoot > tr > th,
table.dataTable > tfoot > tr > td {
border-color: var(--wfc-border, #d7e2ee);
color: var(--wfc-navy-900, #0b1f3a);
background: #eef6fc;
font-weight: 650;
}
/* DataTables lapozó – Workflow Check sötétkék arculat. */
div.dt-container .dt-paging .pagination {
gap: .18rem;
}
div.dt-container .dt-paging .page-link,
div.dt-container .dt-paging button.dt-paging-button {
min-width: 38px;
border: 1px solid var(--wfc-border, #d7e2ee) !important;
border-radius: .45rem !important;
color: var(--wfc-navy-700, #123c69) !important;
background: var(--wfc-white, #fff) !important;
box-shadow: none !important;
}
div.dt-container .dt-paging .page-link:hover,
div.dt-container .dt-paging .page-link:focus,
div.dt-container .dt-paging button.dt-paging-button:hover,
div.dt-container .dt-paging button.dt-paging-button:focus {
border-color: var(--wfc-blue-500, #38bdf8) !important;
color: var(--wfc-navy-900, #0b1f3a) !important;
background: var(--wfc-blue-200, #bae6fd) !important;
}
div.dt-container .dt-paging .page-item.active .page-link,
div.dt-container .dt-paging button.dt-paging-button.current,
div.dt-container .dt-paging button.dt-paging-button.current:hover {
border-color: var(--wfc-navy-700, #123c69) !important;
color: var(--wfc-white, #fff) !important;
background: var(--wfc-navy-700, #123c69) !important;
box-shadow: 0 4px 10px rgb(18 60 105 / 18%) !important;
}
div.dt-container .dt-paging .page-item.disabled .page-link,
div.dt-container .dt-paging button.dt-paging-button.disabled,
div.dt-container .dt-paging button.dt-paging-button:disabled {
border-color: #e2e8f0 !important;
color: #94a3b8 !important;
background: #f8fafc !important;
opacity: 1;
}
@@ -0,0 +1,2 @@
body {
}
+16
View File
@@ -0,0 +1,16 @@
@import url("wfc-datatables.css");
@import url("wfc-forms.css");
@import url("wfc-buttons.css");
.sidebar-brand {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 90px;
padding: 12px 16px;
}
.sidebar-brand > a {
display: block;
flex: 1;
}
@@ -0,0 +1,483 @@
@import url("wfc.css");
:root {
--wfc-navy-950: #071629;
--wfc-navy-900: #0b1f3a;
--wfc-navy-800: #0e2c4f;
--wfc-navy-700: #123c69;
--wfc-blue-500: #38bdf8;
--wfc-blue-300: #7dd3fc;
--wfc-blue-200: #bae6fd;
--wfc-white: #fff;
--wfc-surface: #f5f8fc;
--wfc-border: #d7e2ee;
--wfc-text: #10243e;
--wfc-muted: #64748b;
--wfc-sidebar-expanded-width: 292px;
--wfc-sidebar-collapsed-width: 76px;
--wfc-topbar-height: 64px;
--wfc-radius: .625rem;
--wfc-shadow: 0 8px 24px rgb(11 31 58 / 10%);
}
html,
body {
width: 100%;
height: 100%;
}
body {
margin: 0;
overflow: hidden;
color: var(--wfc-text);
background: var(--wfc-surface);
font-family: Inter, "Segoe UI", Arial, sans-serif;
}
body.wfc-sidebar-open {
overflow: hidden;
}
a {
color: var(--wfc-navy-700);
}
.wfc-shell {
display: flex;
height: 100vh;
min-height: 0;
}
.wfc-sidebar {
position: fixed;
inset: 0 auto 0 0;
z-index: 1045;
display: flex;
width: var(--wfc-sidebar-expanded-width);
min-width: 0;
flex-direction: column;
overflow: hidden;
color: var(--wfc-white);
background: linear-gradient(180deg, var(--wfc-navy-900), var(--wfc-navy-950));
box-shadow: 8px 0 24px rgb(7 22 41 / 16%);
transition: width .22s ease, transform .22s ease;
}
.wfc-sidebar-brand {
display: flex;
min-height: 78px;
flex: 0 0 78px;
align-items: center;
justify-content: space-between;
padding: 1rem 1.1rem;
border-bottom: 1px solid rgb(255 255 255 / 12%);
}
.wfc-sidebar-brand > a {
display: flex;
min-width: 0;
align-items: center;
}
.wfc-sidebar-logo {
display: block;
width: 100%;
max-width: 245px;
height: 46px;
object-fit: contain;
object-position: left center;
transition: opacity .14s ease;
}
.wfc-sidebar-close,
.wfc-sidebar-open,
.wfc-sidebar-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
border: 0;
border-radius: .5rem;
}
.wfc-sidebar-close {
width: 38px;
height: 38px;
flex: 0 0 38px;
color: var(--wfc-white);
background: rgb(255 255 255 / 10%);
}
.wfc-sidebar-nav {
flex: 1 1 auto;
padding: .85rem .7rem 1rem;
overflow-x: hidden;
overflow-y: auto;
scrollbar-color: rgb(186 230 253 / 35%) transparent;
scrollbar-width: thin;
}
.wfc-sidebar-section {
margin: 1.1rem .75rem .45rem;
overflow: hidden;
color: var(--wfc-blue-300);
font-size: .69rem;
font-weight: 800;
letter-spacing: .095em;
white-space: nowrap;
}
.wfc-sidebar-link,
.wfc-sidebar-account a {
display: flex;
width: 100%;
min-width: 0;
align-items: center;
gap: .75rem;
margin: .16rem 0;
padding: .7rem .8rem;
overflow: hidden;
border: 0;
border-radius: .52rem;
color: #dcebfa;
background: transparent;
font-size: .92rem;
font-weight: 600;
line-height: 1.25;
text-align: left;
text-decoration: none;
white-space: nowrap;
transition: color .16s ease, background-color .16s ease, box-shadow .16s ease;
}
.wfc-sidebar-link > i:first-child,
.wfc-sidebar-account a > i,
.wfc-sidebar-user > i {
width: 1.35rem;
flex: 0 0 1.35rem;
font-size: 1.1rem;
text-align: center;
}
.wfc-sidebar-link > span,
.wfc-sidebar-account a > span,
.wfc-sidebar-user > span {
overflow: hidden;
text-overflow: ellipsis;
}
.wfc-sidebar-link:hover,
.wfc-sidebar-account a:hover {
color: var(--wfc-white);
background: rgb(56 189 248 / 14%);
}
.wfc-sidebar-link.active {
color: var(--wfc-navy-950);
background: linear-gradient(90deg, var(--wfc-blue-200), #e5f7ff);
box-shadow: inset 4px 0 0 var(--wfc-blue-500), 0 4px 14px rgb(0 0 0 / 12%);
}
.wfc-collapse-toggle[aria-expanded="true"] .wfc-toggle-chevron {
transform: rotate(180deg);
}
.wfc-toggle-chevron {
font-size: .8rem;
transition: transform .18s ease;
}
.wfc-sidebar-submenu {
margin: .12rem 0 .42rem;
padding-left: .7rem;
}
.wfc-sidebar-submenu .wfc-sidebar-link {
padding-top: .58rem;
padding-bottom: .58rem;
padding-left: 1rem;
color: #c9def2;
font-size: .86rem;
}
/* Az almenü általános világos színe nem írhatja felül az aktív elem kontrasztját. */
.wfc-sidebar-submenu .wfc-sidebar-link.active,
.wfc-sidebar-submenu .wfc-sidebar-link.active:hover,
.wfc-sidebar-submenu .wfc-sidebar-link.active:focus {
color: var(--wfc-navy-950);
background: linear-gradient(90deg, var(--wfc-blue-200), #f0faff);
}
.wfc-sidebar-submenu .wfc-sidebar-link.active i {
color: var(--wfc-navy-700);
}
.wfc-sidebar-submenu .wfc-sidebar-link > i:first-child {
font-size: .95rem;
}
.wfc-sidebar-divider {
margin: .45rem .8rem;
border-color: rgb(255 255 255 / 14%);
opacity: 1;
}
.wfc-sidebar-account {
flex: 0 0 auto;
padding: .75rem;
border-top: 1px solid rgb(255 255 255 / 12%);
background: rgb(0 0 0 / 9%);
}
.wfc-sidebar-user {
display: flex;
min-width: 0;
align-items: center;
gap: .75rem;
padding: .55rem .8rem .7rem;
overflow: hidden;
color: var(--wfc-blue-200);
font-size: .88rem;
font-weight: 700;
white-space: nowrap;
}
.wfc-content {
display: flex;
width: calc(100% - var(--wfc-sidebar-expanded-width));
height: 100vh;
min-height: 0;
flex-direction: column;
margin-left: var(--wfc-sidebar-expanded-width);
overflow: hidden;
transition: width .22s ease, margin-left .22s ease;
}
.wfc-topbar {
position: sticky;
top: 0;
z-index: 1030;
display: flex;
min-height: var(--wfc-topbar-height);
align-items: center;
gap: .8rem;
padding: 0 1.35rem;
border-bottom: 1px solid var(--wfc-border);
background: rgb(255 255 255 / 94%);
box-shadow: 0 3px 16px rgb(11 31 58 / 6%);
backdrop-filter: blur(10px);
}
.wfc-sidebar-open,
.wfc-sidebar-toggle {
width: 40px;
height: 40px;
flex: 0 0 40px;
color: var(--wfc-navy-700);
background: #eef6fc;
font-size: 1.25rem;
}
.wfc-sidebar-open:hover,
.wfc-sidebar-toggle:hover {
color: var(--wfc-navy-900);
background: var(--wfc-blue-200);
}
.wfc-topbar-title {
overflow: hidden;
color: var(--wfc-navy-900);
font-size: 1.15rem;
font-weight: 750;
text-overflow: ellipsis;
white-space: nowrap;
}
.wfc-main {
width: 100%;
max-width: 100%;
min-height: 0;
flex: 1 1 auto;
padding: 1.5rem;
overflow-x: hidden;
overflow-y: auto;
scrollbar-color: rgb(18 60 105 / 45%) transparent;
scrollbar-width: thin;
}
.wfc-footer {
position: relative;
z-index: 2;
flex: 0 0 auto;
min-height: 48px;
padding: .78rem 1.5rem;
border-top: 1px solid rgb(186 230 253 / 24%);
color: #dcebfa;
background:
radial-gradient(circle at 88% 10%, rgb(56 189 248 / 16%), transparent 20rem),
linear-gradient(90deg, var(--wfc-navy-900), var(--wfc-navy-950));
box-shadow: 0 -5px 18px rgb(7 22 41 / 12%);
font-size: .82rem;
}
.wfc-footer-content {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .45rem;
}
.wfc-footer-content a {
margin-left: auto;
color: var(--wfc-blue-200);
text-decoration: none;
}
.wfc-footer-content a:hover {
color: var(--wfc-white);
}
.wfc-footer-separator {
color: var(--wfc-blue-500);
}
.wfc-sidebar-backdrop {
position: fixed;
inset: 0;
z-index: 1040;
visibility: hidden;
background: rgb(7 22 41 / 58%);
opacity: 0;
transition: visibility .2s ease, opacity .2s ease;
}
.wfc-sidebar-backdrop.show {
visibility: visible;
opacity: 1;
}
.btn-primary {
border-color: var(--wfc-navy-700);
background-color: var(--wfc-navy-700);
}
.btn-primary:hover,
.btn-primary:focus {
border-color: var(--wfc-navy-900);
background-color: var(--wfc-navy-900);
}
.card {
border-color: var(--wfc-border);
border-radius: var(--wfc-radius);
box-shadow: var(--wfc-shadow);
}
html.wfc-sidebar-collapsed .wfc-sidebar {
width: var(--wfc-sidebar-collapsed-width);
}
html.wfc-sidebar-collapsed .wfc-content {
width: calc(100% - var(--wfc-sidebar-collapsed-width));
margin-left: var(--wfc-sidebar-collapsed-width);
}
html.wfc-sidebar-collapsed .wfc-sidebar-logo,
html.wfc-sidebar-collapsed .wfc-sidebar-section,
html.wfc-sidebar-collapsed .wfc-sidebar-link > span,
html.wfc-sidebar-collapsed .wfc-sidebar-link > .wfc-toggle-chevron,
html.wfc-sidebar-collapsed .wfc-sidebar-account span,
html.wfc-sidebar-collapsed .wfc-sidebar-divider,
html.wfc-sidebar-collapsed .wfc-sidebar-submenu {
display: none;
}
html.wfc-sidebar-collapsed .wfc-sidebar-brand {
justify-content: center;
padding-right: .7rem;
padding-left: .7rem;
}
html.wfc-sidebar-collapsed .wfc-sidebar-brand::before {
width: 42px;
height: 42px;
content: "";
background: url("../images/favicon/favicon.svg") center / contain no-repeat;
}
html.wfc-sidebar-collapsed .wfc-sidebar-link,
html.wfc-sidebar-collapsed .wfc-sidebar-account a,
html.wfc-sidebar-collapsed .wfc-sidebar-user {
justify-content: center;
gap: 0;
padding-right: .75rem;
padding-left: .75rem;
}
html.wfc-sidebar-initializing .wfc-sidebar,
html.wfc-sidebar-initializing .wfc-content {
transition: none !important;
}
@media (max-width: 991.98px) {
html,
body {
height: auto;
min-height: 100%;
}
body {
overflow-x: hidden;
overflow-y: auto;
}
body.wfc-sidebar-open {
overflow: hidden;
}
.wfc-shell {
height: auto;
min-height: 100vh;
}
.wfc-sidebar {
width: min(88vw, 320px);
transform: translateX(-102%);
}
.wfc-sidebar.open {
transform: translateX(0);
}
.wfc-content,
html.wfc-sidebar-collapsed .wfc-content {
width: 100%;
height: auto;
min-height: 100vh;
margin-left: 0;
overflow: visible;
}
.wfc-main {
min-height: auto;
flex: 1 0 auto;
padding: 1rem;
overflow: visible;
}
.wfc-topbar {
padding: 0 .85rem;
}
.wfc-footer {
display: none;
}
}
@media (prefers-reduced-motion: reduce) {
.wfc-sidebar,
.wfc-content,
.wfc-sidebar-backdrop,
.wfc-toggle-chevron {
transition: none !important;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="14" fill="#0B1F3A"/><path d="M13 33h8c4 0 4 7 8 7h7c5 0 5-8 10-8" fill="none" stroke="#BAE6FD" stroke-width="5" stroke-linecap="round"/><circle cx="12" cy="33" r="5" fill="#0B1F3A" stroke="#fff" stroke-width="4"/><circle cx="30" cy="40" r="6" fill="#0B1F3A" stroke="#BAE6FD" stroke-width="4"/><circle cx="47" cy="27" r="8" fill="#0B1F3A" stroke="#38BDF8" stroke-width="4"/><path d="m43 27 3 3 8-9" fill="none" stroke="#38BDF8" stroke-width="4"/></svg>

After

Width:  |  Height:  |  Size: 545 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1080 260" role="img" aria-labelledby="title">
<title id="title">Workflow Check – dark background</title>
<defs><linearGradient id="flow" x1="35" y1="95" x2="410" y2="95" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#FFFFFF"/><stop offset="1" stop-color="#38BDF8"/></linearGradient></defs>
<g transform="translate(5 10) scale(.78)"><path d="M96 128h42c24 0 22 40 46 40h55c28 0 28-40 56-40h63c28 0 28-42 28-68" fill="none" stroke="url(#flow)" stroke-width="22" stroke-linecap="round"/><circle cx="70" cy="128" r="30" fill="#0B1F3A" stroke="#fff" stroke-width="18"/><circle cx="245" cy="168" r="34" fill="#0B1F3A" stroke="#BAE6FD" stroke-width="18"/><circle cx="394" cy="83" r="43" fill="#0B1F3A" stroke="#38BDF8" stroke-width="18"/><path d="m373 82 17 17 43-48" fill="none" stroke="#38BDF8" stroke-width="18"/></g>
<text x="385" y="157" fill="#FFFFFF" font-family="Inter,Segoe UI,Arial,sans-serif" font-size="94" font-weight="750" letter-spacing="-3">Workflow</text><text x="816" y="157" fill="#BAE6FD" font-family="Inter,Segoe UI,Arial,sans-serif" font-size="94" font-weight="450" letter-spacing="-3">Check</text>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+36
View File
@@ -0,0 +1,36 @@
# Workflow Check arculati csomag
Az **A jel** a webes arculat fő eleme: gyártási folyamat, ellenőrzési pontok és sikeres lezárás. A **B jel** a mobilalkalmazás ikonja.
## Színek
| Név | HEX | Használat |
|---|---:|---|
| Deep Navy | `#0B1F3A` | oldalsáv, fő szöveg, elsődleges felület |
| Process Blue | `#123C69` | gombok, linkek, köztes állapot |
| Check Blue | `#38BDF8` | aktív elem, pipa, fókusz |
| Light Blue | `#BAE6FD` | aktív menü, táblázatfejléc |
| White | `#FFFFFF` | tartalom és kontraszt |
## Webes bekötés
Másold az `assets` és `css` könyvtárat a webprojekt `wwwroot` mappájába, majd a layout `<head>` részében:
```html
<link rel="icon" href="~/assets/favicon/favicon.ico" sizes="any">
<link rel="icon" href="~/assets/favicon/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="~/assets/favicon/apple-touch-icon.png">
<link rel="stylesheet" href="~/css/workflow-check.css" asp-append-version="true">
```
A `workflow-check.css` a többi fájlt importálja, ezért ezt kell utolsó saját CSS-ként betölteni. A csomag Bootstrap 5 és DataTables felülírásokat is tartalmaz.
## Fájlhasználat
- `workflow-check-logo.svg`: világos hátterű fejléc, belépőoldal, dokumentum.
- `workflow-check-logo-dark.svg`: sötét oldalsáv vagy sötét fejléc.
- `workflow-check-symbol.svg`: kompakt webes jel.
- `workflow-check-app-icon.svg/png`: mobilalkalmazás forrásikon.
- `favicon.*`: böngésző és kezdőképernyő ikonok.
Az SVG a szerkeszthető mester. A PNG és ICO változatokat ebből kell származtatni.
Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" role="img" aria-labelledby="title">
<title id="title">Workflow Check mobile application icon</title>
<defs><linearGradient id="bg" x1="130" y1="80" x2="900" y2="960" gradientUnits="userSpaceOnUse"><stop stop-color="#0B1F3A"/><stop offset="1" stop-color="#123C69"/></linearGradient><linearGradient id="accent" x1="512" y1="250" x2="512" y2="820" gradientUnits="userSpaceOnUse"><stop stop-color="#BAE6FD"/><stop offset="1" stop-color="#38BDF8"/></linearGradient></defs>
<rect x="32" y="32" width="960" height="960" rx="220" fill="url(#bg)"/>
<rect x="286" y="142" width="452" height="740" rx="72" fill="none" stroke="#fff" stroke-width="38"/>
<path d="M452 211h120M468 816h88" stroke="#fff" stroke-width="30" stroke-linecap="round"/>
<path d="M512 350v132M512 550v72" stroke="url(#accent)" stroke-width="30" stroke-linecap="round"/>
<circle cx="512" cy="314" r="62" fill="#0B1F3A" stroke="#fff" stroke-width="30"/><circle cx="512" cy="514" r="62" fill="#0B1F3A" stroke="#BAE6FD" stroke-width="30"/><circle cx="512" cy="690" r="76" fill="#0B1F3A" stroke="#38BDF8" stroke-width="30"/>
<path d="m476 688 35 35 83-93" fill="none" stroke="#38BDF8" stroke-width="34" stroke-linecap="square"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="14" fill="#0B1F3A"/><path d="M13 33h8c4 0 4 7 8 7h7c5 0 5-8 10-8" fill="none" stroke="#BAE6FD" stroke-width="5" stroke-linecap="round"/><circle cx="12" cy="33" r="5" fill="#0B1F3A" stroke="#fff" stroke-width="4"/><circle cx="30" cy="40" r="6" fill="#0B1F3A" stroke="#BAE6FD" stroke-width="4"/><circle cx="47" cy="27" r="8" fill="#0B1F3A" stroke="#38BDF8" stroke-width="4"/><path d="m43 27 3 3 8-9" fill="none" stroke="#38BDF8" stroke-width="4"/></svg>

After

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1080 260" role="img" aria-labelledby="title">
<title id="title">Workflow Check – dark background</title>
<defs><linearGradient id="flow" x1="35" y1="95" x2="410" y2="95" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#FFFFFF"/><stop offset="1" stop-color="#38BDF8"/></linearGradient></defs>
<g transform="translate(5 10) scale(.78)"><path d="M96 128h42c24 0 22 40 46 40h55c28 0 28-40 56-40h63c28 0 28-42 28-68" fill="none" stroke="url(#flow)" stroke-width="22" stroke-linecap="round"/><circle cx="70" cy="128" r="30" fill="#0B1F3A" stroke="#fff" stroke-width="18"/><circle cx="245" cy="168" r="34" fill="#0B1F3A" stroke="#BAE6FD" stroke-width="18"/><circle cx="394" cy="83" r="43" fill="#0B1F3A" stroke="#38BDF8" stroke-width="18"/><path d="m373 82 17 17 43-48" fill="none" stroke="#38BDF8" stroke-width="18"/></g>
<text x="385" y="157" fill="#FFFFFF" font-family="Inter,Segoe UI,Arial,sans-serif" font-size="94" font-weight="750" letter-spacing="-3">Workflow</text><text x="816" y="157" fill="#BAE6FD" font-family="Inter,Segoe UI,Arial,sans-serif" font-size="94" font-weight="450" letter-spacing="-3">Check</text>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1080 260" role="img" aria-labelledby="title desc">
<title id="title">Workflow Check</title>
<desc id="desc">Workflow Check primary horizontal logo.</desc>
<defs>
<linearGradient id="flow" x1="35" y1="95" x2="410" y2="95" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#0B1F3A"/><stop offset=".52" stop-color="#123C69"/><stop offset="1" stop-color="#38BDF8"/>
</linearGradient>
</defs>
<g transform="translate(5 10) scale(.78)">
<path d="M96 128h42c24 0 22 40 46 40h55c28 0 28-40 56-40h63c28 0 28-42 28-68" fill="none" stroke="url(#flow)" stroke-width="22" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="70" cy="128" r="30" fill="#fff" stroke="#0B1F3A" stroke-width="18"/>
<circle cx="245" cy="168" r="34" fill="#fff" stroke="#123C69" stroke-width="18"/>
<circle cx="394" cy="83" r="43" fill="#fff" stroke="#38BDF8" stroke-width="18"/>
<path d="m373 82 17 17 43-48" fill="none" stroke="#38BDF8" stroke-width="18"/>
</g>
<text x="385" y="157" fill="#0B1F3A" font-family="Inter,Segoe UI,Arial,sans-serif" font-size="94" font-weight="750" letter-spacing="-3">Workflow</text>
<text x="816" y="157" fill="#123C69" font-family="Inter,Segoe UI,Arial,sans-serif" font-size="94" font-weight="450" letter-spacing="-3">Check</text>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

@@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 256" role="img" aria-labelledby="title desc">
<title id="title">Workflow Check web symbol</title>
<desc id="desc">A three-stage manufacturing workflow ending in a check mark.</desc>
<defs>
<linearGradient id="flow" x1="55" y1="128" x2="452" y2="128" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#0B1F3A"/><stop offset=".52" stop-color="#123C69"/><stop offset="1" stop-color="#38BDF8"/>
</linearGradient>
</defs>
<path d="M96 128h42c24 0 22 40 46 40h55c28 0 28-40 56-40h63c28 0 28-42 28-68" fill="none" stroke="url(#flow)" stroke-width="22" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="70" cy="128" r="30" fill="#fff" stroke="#0B1F3A" stroke-width="18"/>
<circle cx="245" cy="168" r="34" fill="#fff" stroke="#123C69" stroke-width="18"/>
<circle cx="394" cy="83" r="43" fill="#fff" stroke="#38BDF8" stroke-width="18"/>
<path d="m373 82 17 17 43-48" fill="none" stroke="#38BDF8" stroke-width="18" stroke-linecap="square" stroke-linejoin="miter"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,10 @@
@import url("workflow-check-variables.css");
html { font-size: 16px; }
body { margin: 0; background: var(--wfc-surface); color: var(--wfc-text); font-family: Inter, "Segoe UI", Arial, sans-serif; }
a { color: var(--wfc-navy-700); text-decoration: none; }
a:hover { color: var(--wfc-blue-500); }
.wfc-page-title { margin: 0 0 1.25rem; color: var(--wfc-navy-900); font-size: 1.75rem; font-weight: 700; }
.wfc-card, .card { border: 1px solid var(--wfc-border); border-radius: var(--wfc-radius); background: var(--wfc-white); box-shadow: var(--wfc-shadow); }
.wfc-card-header, .card-header { border-bottom: 1px solid var(--wfc-border); background: var(--wfc-white); color: var(--wfc-navy-900); font-weight: 700; }
.badge.bg-primary { background-color: var(--wfc-navy-700) !important; }
.text-primary { color: var(--wfc-navy-700) !important; }
@@ -0,0 +1,11 @@
.btn { border-radius: .5rem; font-weight: 650; transition: transform .12s ease,box-shadow .12s ease,background-color .12s ease; }
.btn:focus-visible { box-shadow: var(--wfc-focus); }
.btn-primary { border-color: var(--wfc-navy-700); background: var(--wfc-navy-700); color: #fff; }
.btn-primary:hover,.btn-primary:focus { border-color: var(--wfc-navy-900); background: var(--wfc-navy-900); color: #fff; }
.btn-info { border-color: var(--wfc-blue-500); background: var(--wfc-blue-500); color: var(--wfc-navy-950); }
.btn-info:hover { border-color: #159FDC; background: #159FDC; color: var(--wfc-navy-950); }
.btn-outline-primary { border-color: var(--wfc-navy-700); color: var(--wfc-navy-700); }
.btn-outline-primary:hover { background: var(--wfc-navy-700); color: #fff; }
.btn-success { border-color: var(--wfc-success); background: var(--wfc-success); }
.btn-danger { border-color: var(--wfc-danger); background: var(--wfc-danger); }
.btn:active { transform: translateY(1px); }
@@ -0,0 +1,9 @@
table.dataTable { border-color: var(--wfc-border); background: #fff; }
table.dataTable thead th,.table-primary th { border-color: #8ED4F5 !important; background: var(--wfc-blue-200) !important; color: var(--wfc-navy-900) !important; font-weight: 700; }
table.dataTable tbody tr:hover { background: #EFF9FE !important; }
.dataTables_wrapper .dataTables_filter input,.dataTables_wrapper .dataTables_length select { border: 1px solid var(--wfc-border); border-radius: .45rem; }
.dataTables_wrapper .dataTables_filter input:focus,.dataTables_wrapper .dataTables_length select:focus { border-color: var(--wfc-blue-500); outline: 0; box-shadow: var(--wfc-focus); }
.page-item.active .page-link { border-color: var(--wfc-navy-700); background: var(--wfc-navy-700); }
.page-link { color: var(--wfc-navy-700); }
.page-link:focus { box-shadow: var(--wfc-focus); }
div.dt-processing,.dataTables_processing { border: 1px solid var(--wfc-blue-200) !important; border-radius: .6rem !important; background: var(--wfc-navy-900) !important; color: var(--wfc-white) !important; box-shadow: var(--wfc-shadow); }
@@ -0,0 +1,7 @@
.form-label { color: var(--wfc-navy-900); font-weight: 650; }
.form-control,.form-select { min-height: 42px; border-color: var(--wfc-border); border-radius: .5rem; color: var(--wfc-text); }
.form-control:focus,.form-select:focus { border-color: var(--wfc-blue-500); box-shadow: var(--wfc-focus); }
.form-check-input:checked { border-color: var(--wfc-navy-700); background-color: var(--wfc-navy-700); }
.form-check-input:focus { border-color: var(--wfc-blue-500); box-shadow: var(--wfc-focus); }
.input-group-text { border-color: var(--wfc-border); background: #EDF4FA; color: var(--wfc-navy-700); }
.validation-summary-errors,.field-validation-error { color: var(--wfc-danger); }
@@ -0,0 +1,12 @@
.wfc-shell { min-height: 100vh; }
.wfc-sidebar { position: fixed; inset: 0 auto 0 0; z-index: 1040; width: var(--wfc-sidebar-width); overflow-y: auto; color: var(--wfc-white); background: linear-gradient(180deg,var(--wfc-navy-900),var(--wfc-navy-950)); transition: transform .22s ease; }
.wfc-brand { display: flex; align-items: center; min-height: 78px; padding: 1rem 1.25rem; border-bottom: 1px solid rgb(255 255 255 / 12%); }
.wfc-brand img { width: 100%; max-height: 46px; object-fit: contain; object-position: left center; }
.wfc-nav { padding: 1rem .75rem; }
.wfc-nav-link { display: flex; gap: .75rem; align-items: center; margin: .2rem 0; padding: .72rem .85rem; border-radius: .5rem; color: #DCEBFA; font-weight: 550; }
.wfc-nav-link:hover { color: var(--wfc-white); background: rgb(56 189 248 / 13%); }
.wfc-nav-link.active { color: var(--wfc-navy-900); background: var(--wfc-blue-200); box-shadow: inset 4px 0 0 var(--wfc-blue-500); }
.wfc-content { min-height: 100vh; margin-left: var(--wfc-sidebar-width); }
.wfc-topbar { position: sticky; top: 0; z-index: 1030; min-height: 64px; border-bottom: 1px solid var(--wfc-border); background: rgb(255 255 255 / 94%); backdrop-filter: blur(10px); }
.wfc-main { padding: 1.5rem; }
@media (max-width: 991.98px) { .wfc-sidebar { transform: translateX(-100%); } .wfc-sidebar.is-open { transform: translateX(0); } .wfc-content { margin-left: 0; } .wfc-main { padding: 1rem; } }
@@ -0,0 +1,19 @@
:root {
--wfc-navy-950: #071629;
--wfc-navy-900: #0B1F3A;
--wfc-navy-700: #123C69;
--wfc-blue-500: #38BDF8;
--wfc-blue-200: #BAE6FD;
--wfc-white: #FFFFFF;
--wfc-surface: #F5F8FC;
--wfc-border: #D7E2EE;
--wfc-text: #10243E;
--wfc-muted: #64748B;
--wfc-success: #16875B;
--wfc-warning: #E28A17;
--wfc-danger: #D64545;
--wfc-sidebar-width: 264px;
--wfc-radius: .625rem;
--wfc-shadow: 0 8px 24px rgb(11 31 58 / 10%);
--wfc-focus: 0 0 0 .2rem rgb(56 189 248 / 25%);
}
@@ -0,0 +1,6 @@
@import url("workflow-check-variables.css");
@import url("workflow-check-base.css");
@import url("workflow-check-layout.css");
@import url("workflow-check-buttons.css");
@import url("workflow-check-forms.css");
@import url("workflow-check-datatables.css");