SurveyVista/Web/Areas/Admin/Views/UserResponse/Index.cshtml
2026-03-07 02:37:33 +01:00

283 lines
17 KiB
Text

@model List<Response>
@{
ViewData["Title"] = "Response Management";
var totalResponses = Model?.Count ?? 0;
var uniqueUsers = Model?.Select(r => r.UserEmail).Distinct().Count() ?? 0;
var uniqueSurveys = Model?.Select(r => r.QuestionnaireId).Distinct().Count() ?? 0;
var todayCount = Model?.Count(r => r.SubmissionDate.Date == DateTime.Today) ?? 0;
}
<style>
@@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
:root{
--bg:#0f1923;--bg-card:#1a2332;--bg-elevated:#1f2b3d;--bg-input:#16202e;
--text-primary:#e8edf2;--text-secondary:#9ba8b9;--text-muted:#5e6e82;--text-faint:#3d4e63;
--teal:#33b3ae;--teal-soft:rgba(51,179,174,0.1);--teal-border:rgba(51,179,174,0.3);--teal-dark:#2a9490;
--blue:#3b82f6;--blue-soft:rgba(59,130,246,0.1);--blue-border:rgba(59,130,246,0.3);
--purple:#7c3aed;--purple-soft:rgba(124,58,237,0.1);--purple-border:rgba(124,58,237,0.3);
--amber:#f59e0b;--amber-soft:rgba(245,158,11,0.1);--amber-border:rgba(245,158,11,0.3);
--red:#ef4444;--red-soft:rgba(239,68,68,0.08);--red-border:rgba(239,68,68,0.3);
--green:#10b981;--green-soft:rgba(16,185,129,0.1);--green-border:rgba(16,185,129,0.3);
--info:#06b6d4;--info-soft:rgba(6,182,212,0.1);--info-border:rgba(6,182,212,0.3);
--border:rgba(255,255,255,0.06);--border-light:rgba(255,255,255,0.04);
--shadow-sm:0 2px 8px rgba(0,0,0,0.25);--shadow-md:0 4px 16px rgba(0,0,0,0.3);
--radius-sm:8px;--radius-md:12px;--radius-lg:16px;
--transition:all 0.2s cubic-bezier(0.4,0,0.2,1);
}
@@keyframes slideUp{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}
@@keyframes fadeIn{from{opacity:0}to{opacity:1}}
@@keyframes gridMove{0%{transform:translate(0,0)}100%{transform:translate(60px,60px)}}
@@keyframes meshShift{0%,100%{filter:hue-rotate(0deg);transform:scale(1)}50%{filter:hue-rotate(30deg);transform:scale(1.05)}}
*{box-sizing:border-box}
.resp-page{font-family:'Inter',-apple-system,sans-serif;background:var(--bg);min-height:100vh;padding:32px 40px 80px;-webkit-font-smoothing:antialiased;color:var(--text-primary);position:relative}
/* ===== DYNAMIC BACKGROUND ===== */
.bg-pattern{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:0;overflow:hidden;pointer-events:none}
.grid-overlay{position:absolute;inset:0;background-image:linear-gradient(rgba(51,179,174,0.07) 1px,transparent 1px),linear-gradient(90deg,rgba(59,130,246,0.07) 1px,transparent 1px);background-size:60px 60px;animation:gridMove 20s linear infinite}
.gradient-mesh{position:absolute;inset:0;background:radial-gradient(circle at 20% 20%,rgba(51,179,174,0.12) 0%,transparent 50%),radial-gradient(circle at 80% 60%,rgba(124,58,237,0.12) 0%,transparent 50%),radial-gradient(circle at 40% 80%,rgba(59,130,246,0.1) 0%,transparent 50%);animation:meshShift 15s ease-in-out infinite}
.resp-page>*:not(.bg-pattern){position:relative;z-index:1}
/* ===== HEADER ===== */
.page-header{max-width:1200px;margin:0 auto 28px;animation:slideUp .5s ease both}
.page-header-inner{background:rgba(255,255,255,0.04);backdrop-filter:blur(20px);border:1px solid rgba(255,255,255,0.08);border-radius:var(--radius-lg);padding:28px 32px;display:flex;align-items:center;justify-content:space-between;gap:20px;flex-wrap:wrap;position:relative;overflow:hidden;box-shadow:0 8px 32px rgba(0,0,0,0.12)}
.page-header-inner::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--teal),var(--blue),var(--purple))}
.header-left{display:flex;align-items:center;gap:16px}
.header-icon{width:48px;height:48px;border-radius:var(--radius-md);background:linear-gradient(135deg,var(--teal),var(--teal-dark));display:flex;align-items:center;justify-content:center;font-size:22px;color:#fff;box-shadow:0 4px 16px rgba(51,179,174,0.25)}
.header-title{font-size:24px;font-weight:700;color:var(--text-primary);margin:0}
.header-subtitle{font-size:13px;color:var(--text-muted);margin:4px 0 0;font-weight:500}
.header-actions{display:flex;gap:10px}
.btn-action{display:inline-flex;align-items:center;gap:8px;padding:10px 20px;border-radius:var(--radius-sm);font-size:13px;font-weight:600;font-family:inherit;cursor:pointer;transition:var(--transition);text-decoration:none;border:none}
.btn-delete-sel{background:var(--red-soft);color:var(--red);border:1.5px solid var(--red-border)}
.btn-delete-sel:hover{background:rgba(239,68,68,0.15);color:#fff;text-decoration:none}
.btn-delete-sel:disabled,.btn-delete-sel.disabled{opacity:0.4;pointer-events:none}
/* ===== STATS ===== */
.stats-bar{max-width:1200px;margin:0 auto 24px;display:grid;grid-template-columns:repeat(4,1fr);gap:16px;animation:slideUp .5s ease .06s both}
.stat-card{background:rgba(255,255,255,0.04);backdrop-filter:blur(20px);border:1px solid rgba(255,255,255,0.08);border-radius:var(--radius-md);padding:20px 24px;text-align:center;transition:var(--transition);position:relative;overflow:hidden;box-shadow:0 8px 32px rgba(0,0,0,0.12)}
.stat-card::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;border-radius:3px 3px 0 0}
.stat-card:nth-child(1)::before{background:var(--teal)}
.stat-card:nth-child(2)::before{background:var(--blue)}
.stat-card:nth-child(3)::before{background:var(--purple)}
.stat-card:nth-child(4)::before{background:var(--amber)}
.stat-card:hover{box-shadow:var(--shadow-sm);border-color:rgba(255,255,255,0.1);transform:translateY(-2px)}
.stat-num{font-size:32px;font-weight:800;line-height:1;margin-bottom:6px}
.stat-card:nth-child(1) .stat-num{background:linear-gradient(135deg,var(--teal),#5eead4);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.stat-card:nth-child(2) .stat-num{background:linear-gradient(135deg,var(--blue),#93c5fd);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.stat-card:nth-child(3) .stat-num{background:linear-gradient(135deg,var(--purple),#a78bfa);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.stat-card:nth-child(4) .stat-num{background:linear-gradient(135deg,var(--amber),#fcd34d);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.stat-lbl{font-size:11px;font-weight:700;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.6px}
/* ===== TOOLBAR ===== */
.toolbar{max-width:1200px;margin:0 auto 20px;display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;animation:slideUp .5s ease .1s both}
.select-all-wrap{display:flex;align-items:center;gap:10px}
.cb-custom{width:18px;height:18px;border-radius:5px;border:2px solid var(--text-faint);background:var(--bg-input);cursor:pointer;appearance:none;-webkit-appearance:none;transition:var(--transition);position:relative}
.cb-custom:checked{background:var(--teal);border-color:var(--teal)}
.cb-custom:checked::after{content:'\2713';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;font-size:11px;font-weight:700}
.select-label{font-size:13px;font-weight:600;color:var(--text-secondary);cursor:pointer}
.sel-count{font-size:13px;font-weight:600;color:var(--teal)}
/* ===== GRID ===== */
.resp-grid{max-width:1200px;margin:0 auto;display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:16px;animation:slideUp .5s ease .14s both}
/* ===== RESPONSE CARD ===== */
.resp-card{background:rgba(255,255,255,0.04);backdrop-filter:blur(20px);border:1.5px solid rgba(255,255,255,0.08);border-radius:var(--radius-md);overflow:hidden;transition:var(--transition);position:relative;box-shadow:0 8px 32px rgba(0,0,0,0.12)}
.resp-card:hover{box-shadow:var(--shadow-md);border-color:rgba(255,255,255,0.1);transform:translateY(-3px)}
.resp-card.selected{border-color:var(--teal);box-shadow:0 0 0 1px var(--teal),var(--shadow-sm)}
.card-top{padding:18px 20px 14px;display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
.card-id{font-size:10px;font-weight:700;color:var(--teal);background:var(--teal-soft);border:1px solid var(--teal-border);padding:3px 10px;border-radius:100px;letter-spacing:0.3px;font-family:'JetBrains Mono',monospace}
.card-cb{width:18px;height:18px;border-radius:5px;border:2px solid var(--text-faint);background:var(--bg-input);cursor:pointer;appearance:none;-webkit-appearance:none;transition:var(--transition);flex-shrink:0}
.card-cb:checked{background:var(--teal);border-color:var(--teal)}
.card-cb:checked::after{content:'\2713';position:absolute;color:#fff;font-size:11px;font-weight:700}
.card-body{padding:0 20px 16px}
.card-title{font-size:16px;font-weight:700;color:var(--text-primary);margin:0 0 14px;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.card-user{display:flex;align-items:center;gap:10px;margin-bottom:12px}
.user-avatar{width:34px;height:34px;border-radius:var(--radius-sm);background:linear-gradient(135deg,var(--blue),var(--purple));display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;color:#fff;flex-shrink:0}
.user-info{min-width:0}
.user-name{font-size:13px;font-weight:600;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.user-email{font-size:11px;color:var(--text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.card-date{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--text-muted);font-weight:500}
.card-date i{color:var(--amber);font-size:13px}
.card-footer{border-top:1px solid var(--border-light);padding:0}
.card-footer a{display:flex;align-items:center;justify-content:center;gap:8px;padding:14px;font-size:13px;font-weight:600;color:var(--teal);text-decoration:none;transition:var(--transition)}
.card-footer a:hover{background:var(--teal-soft);color:#fff}
/* ===== EMPTY STATE ===== */
.empty-state{max-width:1200px;margin:0 auto;text-align:center;padding:80px 24px;animation:slideUp .5s ease .14s both}
.empty-icon{width:80px;height:80px;border-radius:var(--radius-lg);background:rgba(255,255,255,0.04);backdrop-filter:blur(20px);display:flex;align-items:center;justify-content:center;font-size:36px;color:var(--text-faint);margin:0 auto 20px;border:1px solid rgba(255,255,255,0.08);box-shadow:0 8px 32px rgba(0,0,0,0.12)}
.empty-state h3{font-size:20px;font-weight:700;color:var(--text-secondary);margin:0 0 8px}
.empty-state p{font-size:14px;color:var(--text-muted);margin:0}
/* ===== RESPONSIVE ===== */
@@media(max-width:768px){
.resp-page{padding:16px}
.stats-bar{grid-template-columns:repeat(2,1fr)}
.resp-grid{grid-template-columns:1fr}
.page-header-inner{flex-direction:column;align-items:flex-start}
.toolbar{flex-direction:column;align-items:flex-start}
}
@@media(prefers-reduced-motion:reduce){
*,*::before,*::after{animation-duration:0.01ms !important;animation-iteration-count:1 !important;transition-duration:0.01ms !important}
}
@@media print{
.bg-pattern{display:none !important}
.resp-card,.stat-card,.page-header-inner{background:white;color:black;border:1px solid #333;backdrop-filter:none}
}
</style>
<form id="deleteForm" method="post" asp-action="DeleteMultiple" asp-controller="UserResponse">
<div class="resp-page">
<!-- Dynamic Background -->
<div class="bg-pattern">
<div class="grid-overlay"></div>
<div class="gradient-mesh"></div>
</div>
<!-- Header -->
<div class="page-header">
<div class="page-header-inner">
<div class="header-left">
<div class="header-icon"><i class="bi bi-clipboard-data"></i></div>
<div>
<h1 class="header-title">Response Management</h1>
<p class="header-subtitle">Monitor and manage all survey responses</p>
</div>
</div>
<div class="header-actions">
<button type="submit" class="btn-action btn-delete-sel disabled" id="btnDeleteSelected" disabled>
<i class="bi bi-trash3"></i> Delete Selected (<span id="selCountTop">0</span>)
</button>
</div>
</div>
</div>
<!-- Stats -->
<div class="stats-bar">
<div class="stat-card">
<div class="stat-num">@totalResponses</div>
<div class="stat-lbl">Total</div>
</div>
<div class="stat-card">
<div class="stat-num">@uniqueUsers</div>
<div class="stat-lbl">Users</div>
</div>
<div class="stat-card">
<div class="stat-num">@uniqueSurveys</div>
<div class="stat-lbl">Surveys</div>
</div>
<div class="stat-card">
<div class="stat-num">@todayCount</div>
<div class="stat-lbl">Today</div>
</div>
</div>
@if (Model != null && Model.Any())
{
<!-- Toolbar -->
<div class="toolbar">
<div class="select-all-wrap">
<input type="checkbox" class="cb-custom" id="selectAll" />
<label for="selectAll" class="select-label">Select All</label>
</div>
<span class="sel-count"><span id="selCount">0</span> selected</span>
</div>
<!-- Response Grid -->
<div class="resp-grid">
@foreach (var response in Model.OrderByDescending(r => r.SubmissionDate))
{
var initials = !string.IsNullOrEmpty(response.UserName) ? response.UserName.Substring(0, 1).ToUpper() : "?";
<div class="resp-card" data-id="@response.Id">
<div class="card-top">
<span class="card-id">ID: @response.Id</span>
<input type="checkbox" name="ids" value="@response.Id" class="card-cb item-cb" />
</div>
<div class="card-body">
<h3 class="card-title">@(response.Questionnaire?.Title ?? "Untitled Survey")</h3>
<div class="card-user">
<div class="user-avatar">@initials</div>
<div class="user-info">
<div class="user-name">@(response.UserName ?? "Anonymous")</div>
<div class="user-email">@(response.UserEmail ?? "No email")</div>
</div>
</div>
<div class="card-date">
<i class="bi bi-calendar3"></i>
@response.SubmissionDate.ToString("MMM dd, yyyy HH:mm")
</div>
</div>
<div class="card-footer">
<a asp-action="ViewResponse" asp-route-id="@response.Id">
<i class="bi bi-eye"></i> View Details
</a>
</div>
</div>
}
</div>
}
else
{
<div class="empty-state">
<div class="empty-icon"><i class="bi bi-inbox"></i></div>
<h3>No Responses Yet</h3>
<p>Survey responses will appear here once users submit them.</p>
</div>
}
</div>
</form>
@section Scripts {
<script>
$(document).ready(function () {
var $selectAll = $('#selectAll');
var $items = $('.item-cb');
var $btnDel = $('#btnDeleteSelected');
var $selCount = $('#selCount');
var $selCountTop = $('#selCountTop');
function updateCount() {
var count = $items.filter(':checked').length;
$selCount.text(count);
$selCountTop.text(count);
if (count > 0) {
$btnDel.removeClass('disabled').prop('disabled', false);
} else {
$btnDel.addClass('disabled').prop('disabled', true);
}
// Visual selected state on cards
$items.each(function () {
$(this).closest('.resp-card').toggleClass('selected', $(this).is(':checked'));
});
}
$selectAll.on('change', function () {
$items.prop('checked', this.checked);
updateCount();
});
$items.on('change', function () {
$selectAll.prop('checked', $items.length === $items.filter(':checked').length);
updateCount();
});
// Confirm before delete
$('#deleteForm').on('submit', function (e) {
var count = $items.filter(':checked').length;
if (count === 0) { e.preventDefault(); return false; }
if (!confirm('Are you sure you want to delete ' + count + ' response(s)? This cannot be undone.')) {
e.preventDefault();
return false;
}
});
});
</script>
}