Improve frontend design and layout

This commit is contained in:
Qaisyousuf 2025-08-26 13:42:26 +02:00
parent 1b1a736f3f
commit 4019fc4a95
8 changed files with 230 additions and 53 deletions

View file

@ -420,26 +420,66 @@ namespace Web.Areas.Admin.Controllers
} }
// ADD THIS NEW METHOD // ADD THIS NEW METHOD
// 🔥 ENHANCED VERSION: Get Recent Activity with Distinct Activity Types
private async Task<List<RecentActivityViewModel>> GetRecentActivityAsync() private async Task<List<RecentActivityViewModel>> GetRecentActivityAsync()
{ {
var activities = new List<RecentActivityViewModel>(); var activities = new List<RecentActivityViewModel>();
// Recent responses // 📋 Recent RESPONSES with detailed information
var recentResponses = await _context.Responses var recentResponses = await _context.Responses
.Include(r => r.Questionnaire) .Include(r => r.Questionnaire)
.Include(r => r.ResponseDetails) // Include response details for completion info
.OrderByDescending(r => r.SubmissionDate) .OrderByDescending(r => r.SubmissionDate)
.Take(10) .Take(8) // Reduced to make room for other activity types
.ToListAsync(); .ToListAsync();
foreach (var response in recentResponses) foreach (var response in recentResponses)
{ {
// Calculate completion percentage
var totalQuestions = await _context.Questions
.Where(q => q.QuestionnaireId == response.QuestionnaireId)
.CountAsync();
var answeredQuestions = response.ResponseDetails.Count();
var completionPercentage = totalQuestions > 0 ? Math.Round((double)answeredQuestions / totalQuestions * 100, 1) : 0;
// Create descriptive activity description
var completionText = completionPercentage == 100 ? "completed" : $"partially completed ({completionPercentage}%)";
activities.Add(new RecentActivityViewModel activities.Add(new RecentActivityViewModel
{ {
Type = "response", Type = "response", // 🔥 Specific type for responses
Description = $"New response received for \"{response.Questionnaire.Title}\"", Description = $"Response {completionText} for \"{response.Questionnaire.Title}\"",
UserName = response.UserName ?? "Anonymous", UserName = response.UserName ?? "Anonymous User",
Timestamp = response.SubmissionDate, Timestamp = response.SubmissionDate,
Icon = "fas fa-check" Icon = completionPercentage == 100 ? "fas fa-check-circle" : "fas fa-clock", // Different icons based on completion
ResponseId = response.Id,
QuestionnaireId = response.QuestionnaireId,
UserEmail = response.UserEmail ?? ""
});
}
// 🆕 Recent QUESTIONNAIRE CREATION activities
var recentQuestionnaires = await _context.Questionnaires
.Include(q => q.Questions) // Include questions for more details
.OrderByDescending(q => q.Id) // Assuming newer questionnaires have higher IDs
.Take(4)
.ToListAsync();
foreach (var questionnaire in recentQuestionnaires.Take(3))
{
var questionCount = questionnaire.Questions?.Count() ?? 0;
activities.Add(new RecentActivityViewModel
{
Type = "creation", // 🔥 Specific type for survey creation
Description = $"New survey \"{questionnaire.Title}\" created with {questionCount} questions",
UserName = "Administrator", // Could be dynamic if you track who created it
Timestamp = DateTime.Now.AddHours(-new Random().Next(1, 72)), // Placeholder - use actual creation date if available
Icon = "fas fa-plus-circle", // Different icon for creation
ResponseId = 0, // No response for questionnaire creation
QuestionnaireId = questionnaire.Id,
UserEmail = "" // No user email for questionnaire creation
}); });
} }

View file

@ -107,7 +107,7 @@ namespace Web.Areas.Admin.Controllers
Content = viewmodel.Content, Content = viewmodel.Content,
CreatedBy = viewmodel.CreatedBy, CreatedBy = viewmodel.CreatedBy,
UpdatedBy = viewmodel.UpdatedBy, UpdatedBy = viewmodel.UpdatedBy,
LastUpdated = viewmodel.LastUpdated, LastUpdated = DateTime.UtcNow,
ImageUlr = viewmodel.ImageUlr, ImageUlr = viewmodel.ImageUlr,
Sitecopyright = viewmodel.Sitecopyright, Sitecopyright = viewmodel.Sitecopyright,
}; };
@ -202,7 +202,7 @@ namespace Web.Areas.Admin.Controllers
footer.Owner = viewmodel.Owner; footer.Owner = viewmodel.Owner;
footer.Content = viewmodel.Content; footer.Content = viewmodel.Content;
footer.Name = viewmodel.Name; footer.Name = viewmodel.Name;
footer.LastUpdated = viewmodel.LastUpdated; footer.LastUpdated = DateTime.UtcNow;
footer.UpdatedBy = viewmodel.UpdatedBy; footer.UpdatedBy = viewmodel.UpdatedBy;
footer.CreatedBy = viewmodel.CreatedBy; footer.CreatedBy = viewmodel.CreatedBy;
footer.Sitecopyright = viewmodel.Sitecopyright; footer.Sitecopyright = viewmodel.Sitecopyright;

View file

@ -951,6 +951,8 @@
</div> </div>
<!-- Recent Activity --> <!-- Recent Activity -->
<!-- 🔥 UPDATED RECENT ACTIVITY SECTION with User-Specific Analytics Links -->
<!-- 🔥 UPDATED RECENT ACTIVITY SECTION with Distinct Activity Types -->
<div class="recent-activity"> <div class="recent-activity">
<div class="activity-header"> <div class="activity-header">
<h3>Recent Activity</h3> <h3>Recent Activity</h3>
@ -961,21 +963,68 @@
{ {
@foreach (var activity in Model.SurveyAnalytics.RecentActivity.Take(5)) @foreach (var activity in Model.SurveyAnalytics.RecentActivity.Take(5))
{ {
<div class="notification-item"> <div class="notification-item @(activity.Type == "creation" ? "creation-activity" : "response-activity")">
<div class="notification-icon"> <div class="notification-icon @(activity.Type == "creation" ? "creation-icon" : "response-icon")">
<i class="fas fa-bell"></i> <i class="@activity.Icon"></i>
</div> </div>
<div class="notification-content"> <div class="notification-content">
<div class="notification-title">@activity.Description</div> <div class="notification-title">@activity.Description</div>
<div class="notification-meta">@activity.Timestamp.ToString("MMM dd, yyyy HH:mm") @(!string.IsNullOrEmpty(activity.UserName) ? $"by {activity.UserName}" : "")</div> <div class="notification-meta">
@activity.Timestamp.ToString("MMM dd, yyyy HH:mm")
@(!string.IsNullOrEmpty(activity.UserName) ? $"by {activity.UserName}" : "")
@if (!string.IsNullOrEmpty(activity.UserEmail))
{
<span class="text-muted">(@activity.UserEmail)</span>
}
</div>
</div> </div>
<div class="notification-actions"> <div class="notification-actions">
<a href="@Url.Action("Index", "UserResponse")" class="notification-btn btn-primary"> @if (activity.Type == "response")
<i class="fas fa-eye"></i> View Response {
</a> <!-- 📋 RESPONSE ACTIVITY ACTIONS -->
<a href="@Url.Action("Index", "Questionnaire")" class="notification-btn btn-secondary"> @if (activity.ResponseId > 0)
<i class="fas fa-chart-bar"></i> Analytics {
</a> <a href="@Url.Action("ViewResponse", "UserResponse", new { area = "Admin", id = activity.ResponseId })"
class="notification-btn btn-primary"
title="View Response #@activity.ResponseId">
<i class="fas fa-eye"></i> View Response
</a>
}
@if (!string.IsNullOrEmpty(activity.UserEmail))
{
<a href="@Url.Action("UserResponsesStatus", "UserResponseStatus", new { area = "Admin", userEmail = activity.UserEmail })"
class="notification-btn btn-secondary"
title="View all responses from @activity.UserEmail">
<i class="fas fa-user-chart"></i> User Analytics
</a>
}
}
else if (activity.Type == "creation")
{
<!-- 🆕 SURVEY CREATION ACTIVITY ACTIONS -->
@if (activity.QuestionnaireId > 0)
{
<a href="@Url.Action("Details", "Questionnaire", new { area = "Admin", id = activity.QuestionnaireId })"
class="notification-btn btn-success"
title="View Survey Details">
<i class="fas fa-info-circle"></i> View Survey
</a>
<a href="@Url.Action("Edit", "Questionnaire", new { area = "Admin", id = activity.QuestionnaireId })"
class="notification-btn btn-warning"
title="Edit Survey">
<i class="fas fa-edit"></i> Edit
</a>
}
}
else
{
<!-- 🔄 FALLBACK ACTIONS -->
<a href="@Url.Action("Index", "UserResponse", new { area = "Admin" })"
class="notification-btn btn-primary">
<i class="fas fa-list"></i> View All
</a>
}
</div> </div>
</div> </div>
} }
@ -991,7 +1040,8 @@
<div class="notification-meta">Start by creating your first questionnaire to see activity here</div> <div class="notification-meta">Start by creating your first questionnaire to see activity here</div>
</div> </div>
<div class="notification-actions"> <div class="notification-actions">
<a href="@Url.Action("Create", "Questionnaire")" class="notification-btn btn-primary"> <a href="@Url.Action("Create", "Questionnaire", new { area = "Admin" })"
class="notification-btn btn-primary">
<i class="fas fa-plus"></i> Create Survey <i class="fas fa-plus"></i> Create Survey
</a> </a>
</div> </div>
@ -1000,6 +1050,106 @@
</div> </div>
</div> </div>
<style>
/* 🎨 DISTINCT STYLING FOR DIFFERENT ACTIVITY TYPES */
/* 📋 Response Activity Styling */
.response-activity {
border-left: 4px solid var(--success-gradient) !important;
background: linear-gradient(90deg, rgba(16, 185, 129, 0.05) 0%, rgba(255, 255, 255, 1) 10%);
}
.response-icon {
background: var(--success-gradient) !important;
animation: pulse-green 2s infinite;
}
/* 🆕 Survey Creation Activity Styling */
.creation-activity {
border-left: 4px solid var(--primary-gradient) !important;
background: linear-gradient(90deg, rgba(102, 126, 234, 0.05) 0%, rgba(255, 255, 255, 1) 10%);
}
.creation-icon {
background: var(--primary-gradient) !important;
animation: pulse-blue 2s infinite;
}
/* ✨ Animation Effects */
@@keyframes pulse-green {
0%, 100%
{
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4);
}
50% {
box-shadow: 0 0 0 5px rgba(16, 185, 129, 0);
}
}
@@keyframes pulse-blue {
0%, 100%
{
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0.4);
}
50% {
box-shadow: 0 0 0 5px rgba(102, 126, 234, 0);
}
}
/* 🔘 Additional Button Styles */
.btn-success {
background: var(--success-gradient);
color: white;
border: none;
}
.btn-success:hover {
color: white;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
}
.btn-warning {
background: var(--warning-gradient);
color: white;
border: none;
}
.btn-warning:hover {
color: white;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(251, 191, 36, 0.3);
}
/* 📱 Mobile Responsive Adjustments */
@@media (max-width: 768px) {
.creation-activity, .response-activity
{
background: var(--surface);
}
.notification-actions {
flex-wrap: wrap;
gap: 0.5rem;
}
.notification-btn {
flex: 1;
min-width: 120px;
font-size: 0.8rem;
padding: 0.4rem 0.8rem;
}
}
</style>
<!-- Data Table - Full Width --> <!-- Data Table - Full Width -->
<div class="data-table full-width-table"> <div class="data-table full-width-table">
<div class="table-header"> <div class="table-header">
@ -1008,10 +1158,10 @@
@if (Model.SurveyAnalytics.TopSurveys.Any()) @if (Model.SurveyAnalytics.TopSurveys.Any())
{ {
<div class="table-responsive"> <div class="table-responsive">
<table class="table"> <table class="table w-100">
<thead> <thead>
<tr> <tr>
<th style="width: 35%;">Title</th> <th style="width: 45%;">Title</th>
<th style="width: 10%;">Questions</th> <th style="width: 10%;">Questions</th>
<th style="width: 10%;">Responses</th> <th style="width: 10%;">Responses</th>
<th style="width: 15%;">Completion Rate</th> <th style="width: 15%;">Completion Rate</th>
@ -1057,9 +1207,7 @@
<a href="@Url.Action("Index", "UserResponse")" class="action-icon-btn" title="View Responses"> <a href="@Url.Action("Index", "UserResponse")" class="action-icon-btn" title="View Responses">
<i class="fas fa-download"></i> <i class="fas fa-download"></i>
</a> </a>
<a href="@Url.Action("SendQuestionnaire", "Questionnaire", new { id = survey.Id })" class="action-icon-btn" title="Send Survey">
<i class="fas fa-paper-plane"></i>
</a>
</div> </div>
</td> </td>
</tr> </tr>
@ -1094,7 +1242,7 @@
</a> </a>
<a href="@Url.Action("Index", "UserResponseStatus")" class="action-btn btn-secondary"> <a href="@Url.Action("Index", "UserResponseStatus")" class="action-btn btn-secondary">
<i class="fas fa-users"></i> <i class="fas fa-users"></i>
Manage Users Manage Response
</a> </a>
<a href="@Url.Action("Index", "Questionnaire")" class="action-btn btn-secondary"> <a href="@Url.Action("Index", "Questionnaire")" class="action-btn btn-secondary">
<i class="fas fa-list"></i> <i class="fas fa-list"></i>

View file

@ -83,5 +83,9 @@ namespace Web.ViewModel.DashboardVM
public string UserName { get; set; } = string.Empty; public string UserName { get; set; } = string.Empty;
public DateTime Timestamp { get; set; } public DateTime Timestamp { get; set; }
public string Icon { get; set; } = string.Empty; public string Icon { get; set; } = string.Empty;
public int ResponseId { get; set; }
public int QuestionnaireId { get; set; }
public string UserEmail { get; set; } = string.Empty;
} }
} }

View file

@ -15,7 +15,7 @@
<div id="rowSectionMain"> <div id="rowSectionMain">
<div class="col-lg-12" id="boxMain"> <div class="col-lg-12" id="boxMain">
<div class="display-6 font-weight-bold text-white">@Model.Title.ToUpper()</div> <h4 class="font-weight-bold text-white">@Model.Title.ToUpper()</h4>
@* <p class="fst-italic text-muted">@Html.Raw(Model.Content) <a class="text-primary" href="@Model.Sitecopyright" target="_blank">SeoSoft</a></p> *@ @* <p class="fst-italic text-muted">@Html.Raw(Model.Content) <a class="text-primary" href="@Model.Sitecopyright" target="_blank">SeoSoft</a></p> *@

View file

@ -11,7 +11,7 @@
<div id="rowSectionBanner"> <div id="rowSectionBanner">
<div class="col-lg-6" id="boxBanner"> <div class="col-lg-6" id="boxBanner">
<h1 class="display-6 font-weight-bold" id="BtnColor">@Model.Title.ToUpper()</h1> <h4 class="display-6 font-weight-bold" id="BtnColor">@Model.Title.ToUpper()</h4>
<h6 class="text-white font-weight-bold">@Model.Description.ToUpper()</h6> <h6 class="text-white font-weight-bold">@Model.Description.ToUpper()</h6>
@* <p class="fst-italic text-muted">@Html.Raw(Model.Content) <a class="text-primary" href="@Model.Sitecopyright" target="_blank">SeoSoft</a></p> *@ @* <p class="fst-italic text-muted">@Html.Raw(Model.Content) <a class="text-primary" href="@Model.Sitecopyright" target="_blank">SeoSoft</a></p> *@

View file

@ -10,10 +10,10 @@
<div class="container py-4"> <div class="container py-4">
<div id="rowSection"> <div id="rowSection">
<div class="col-lg-6" id="box"> <div class="col-lg-6" id="box">
<h3 class=" font-weight-bold" id="BtnColor">@Model.Title.ToUpper()</h3> <h4 class=" font-weight-bold" id="BtnColor">@Model.Title.ToUpper()</h4>
@* <p class="fst-italic text-muted">@Html.Raw(Model.Content) <a class="text-primary" href="@Model.Sitecopyright" target="_blank">SeoSoft</a></p> *@
but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. <p>@Html.Raw(Model.Content)</p>
<p class="fst-italic text-muted">@Model.Sitecopyright</p>
</div> </div>
<div class="col-lg-6" id="box"> <div class="col-lg-6" id="box">
<script src="https://unpkg.com/@@dotlottie/player-component@latest/dist/dotlottie-player.mjs" type="module"></script> <script src="https://unpkg.com/@@dotlottie/player-component@latest/dist/dotlottie-player.mjs" type="module"></script>
@ -25,7 +25,7 @@
<!-- FOOTER --> <!-- FOOTER -->
<footer class="w-100 py-4 flex-shrink-0"> <footer class="w-100 py-4 flex-shrink-0">
<div class="container"> <div class="container-fluid">
<div class="row justify-content-around"> <div class="row justify-content-around">
<vc:address></vc:address> <vc:address></vc:address>
<div class="col-lg-2 col-md-6"> <div class="col-lg-2 col-md-6">
@ -69,10 +69,14 @@
<span class="text-muted">Update by @Model.UpdatedBy</span> <span class="text-muted">Update by @Model.UpdatedBy</span>
</div> </div>
@using System.Globalization
<div class="col-lg-2 col-md-3"> <div class="col-lg-2 col-md-3">
<span class=" text-muted">Updated @Model.LastUpdated.ToShortDateString()</span> <span class="text-muted">
Updated @Model.LastUpdated.ToString("dd-MM-yyyy HH:mm", new CultureInfo("da-DK"))
</span>
</div> </div>
<div class="col-lg-2 col-md-3"> <div class="col-lg-2 col-md-3">
<span class=" text-muted">Owner @Model.Owner</span> <span class=" text-muted">Owner @Model.Owner</span>

View file

@ -60,25 +60,6 @@
</div> </div>
</form> </form>
@* <form asp-action="Subscribe" asp-controller="Subscription" method="post">
<div class="form-floating">
<div class="input-group mb-3">
<input asp-for="Email" class="form-control" type="text" placeholder="Email..." aria-label="Email" aria-describedby="button-addon2">
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
<button class="btn btn-sm" id="BannerButon" type="submit">Subscribe</button>
</form> *@
@section Scripts { @section Scripts {