853 lines
42 KiB
Text
853 lines
42 KiB
Text
@model ResponseQuestionnaireViewModel
|
|
@{
|
|
ViewData["Title"] = "DisplayQuestionnaire";
|
|
Layout = "~/Views/Shared/_QuestionnaireResponse.cshtml";
|
|
}
|
|
|
|
<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;
|
|
--bg-hover: #243044;
|
|
--text-primary: #e8edf2;
|
|
--text-secondary: #9ba8b9;
|
|
--text-muted: #5e6e82;
|
|
--text-faint: #3d4e63;
|
|
--teal: #33b3ae;
|
|
--teal-soft: rgba(51, 179, 174, 0.1);
|
|
--teal-medium: rgba(51, 179, 174, 0.2);
|
|
--teal-glow: rgba(51, 179, 174, 0.12);
|
|
--teal-dark: #2a9490;
|
|
--amber: #f59e0b;
|
|
--amber-soft: rgba(245, 158, 11, 0.08);
|
|
--amber-border: rgba(245, 158, 11, 0.35);
|
|
--purple: #7c3aed;
|
|
--purple-soft: rgba(124, 58, 237, 0.1);
|
|
--red: #ef4444;
|
|
--red-soft: rgba(239, 68, 68, 0.08);
|
|
--green: #10b981;
|
|
--green-soft: rgba(16, 185, 129, 0.1);
|
|
--green-border: rgba(16, 185, 129, 0.35);
|
|
--border: rgba(255, 255, 255, 0.06);
|
|
--border-focus: rgba(51, 179, 174, 0.4);
|
|
--shadow-xs: 0 1px 3px rgba(0,0,0,0.2);
|
|
--shadow-sm: 0 2px 8px rgba(0,0,0,0.25);
|
|
--shadow-md: 0 4px 16px rgba(0,0,0,0.3);
|
|
--shadow-lg: 0 8px 32px rgba(0,0,0,0.35);
|
|
--shadow-teal: 0 4px 20px rgba(51,179,174,0.2);
|
|
--radius-sm: 8px;
|
|
--radius-md: 12px;
|
|
--radius-lg: 16px;
|
|
--transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
--transition-spring: all 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
}
|
|
|
|
@@keyframes slideUp { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }
|
|
@@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
@@keyframes pulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.08); } }
|
|
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
html, body {
|
|
height: 100%;
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
background: var(--bg);
|
|
color: var(--text-primary);
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
/* ===== PAGE SHELL ===== */
|
|
.survey-page { padding: 40px 20px 100px; min-height: 100vh; }
|
|
|
|
.survey-shell {
|
|
max-width: 960px;
|
|
margin: 0 auto;
|
|
animation: slideUp 0.5s ease both;
|
|
}
|
|
|
|
/* ===== HEADER CARD ===== */
|
|
.survey-header-card {
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
padding: 32px 36px;
|
|
margin-bottom: 20px;
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.survey-title {
|
|
font-size: 26px;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
letter-spacing: -0.4px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.survey-title-dot {
|
|
display: inline-block; width: 8px; height: 8px;
|
|
background: var(--teal); border-radius: 50%;
|
|
margin-left: 6px; vertical-align: super;
|
|
animation: pulse 2s ease infinite;
|
|
}
|
|
|
|
.survey-desc { font-size: 14px; color: var(--text-secondary); line-height: 1.6; }
|
|
|
|
/* ===== USER INFO ===== */
|
|
.user-info-card {
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
padding: 24px 36px;
|
|
margin-bottom: 20px;
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 20px;
|
|
box-shadow: var(--shadow-xs);
|
|
}
|
|
|
|
.field-label {
|
|
display: block; font-size: 12px; font-weight: 600;
|
|
color: var(--text-muted); text-transform: uppercase;
|
|
letter-spacing: 0.4px; margin-bottom: 6px;
|
|
}
|
|
|
|
.field-input {
|
|
width: 100%; background: var(--bg-input);
|
|
border: 1.5px solid var(--border); border-radius: var(--radius-sm);
|
|
padding: 11px 14px; font-size: 14px; font-family: inherit;
|
|
color: var(--text-primary); outline: none; transition: var(--transition);
|
|
}
|
|
|
|
.field-input::placeholder { color: var(--text-faint); }
|
|
.field-input:focus { border-color: var(--border-focus); box-shadow: 0 0 0 3px var(--teal-glow); background: var(--bg-elevated); }
|
|
|
|
/* ===== BUILDER LAYOUT (stepper + content) ===== */
|
|
.survey-body {
|
|
display: flex;
|
|
gap: 20px;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
/* Stepper sidebar */
|
|
.stepper-sidebar {
|
|
width: 220px;
|
|
min-width: 220px;
|
|
position: sticky;
|
|
top: 24px;
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
padding: 18px 14px;
|
|
box-shadow: var(--shadow-xs);
|
|
max-height: calc(100vh - 48px);
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.stepper-title {
|
|
font-size: 11px; font-weight: 700; color: var(--text-muted);
|
|
text-transform: uppercase; letter-spacing: 1px;
|
|
margin-bottom: 12px; padding: 0 6px;
|
|
}
|
|
|
|
.step-dot {
|
|
display: flex; align-items: center; gap: 10px;
|
|
padding: 10px 10px; margin-bottom: 4px;
|
|
border-radius: var(--radius-sm); cursor: default;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.step-dot .dot-num {
|
|
width: 26px; height: 26px; border-radius: 8px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 11px; font-weight: 700; flex-shrink: 0;
|
|
background: var(--bg-elevated); color: var(--text-muted);
|
|
border: 1.5px solid var(--border); transition: var(--transition);
|
|
}
|
|
|
|
.step-dot .dot-label {
|
|
font-size: 12px; color: var(--text-muted); font-weight: 500;
|
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
}
|
|
|
|
.step-dot.active { background: var(--teal-soft); }
|
|
.step-dot.active .dot-num { background: var(--teal); color: #fff; border-color: var(--teal); }
|
|
.step-dot.active .dot-label { color: var(--teal); font-weight: 600; }
|
|
|
|
.stepper-sidebar::-webkit-scrollbar { width: 4px; }
|
|
.stepper-sidebar::-webkit-scrollbar-track { background: transparent; }
|
|
.stepper-sidebar::-webkit-scrollbar-thumb { background: var(--text-faint); border-radius: 4px; }
|
|
|
|
/* Content area */
|
|
.content-area { flex: 1; min-width: 0; }
|
|
|
|
/* ===== QUESTION STEP ===== */
|
|
.q-step {
|
|
display: none;
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
padding: 32px 36px;
|
|
box-shadow: var(--shadow-xs);
|
|
animation: fadeIn 0.3s ease;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.q-step:hover { box-shadow: var(--shadow-sm); }
|
|
.q-step.active { display: block; }
|
|
|
|
.q-text {
|
|
font-size: 17px; font-weight: 600; color: var(--text-primary);
|
|
margin-bottom: 24px; letter-spacing: -0.2px; line-height: 1.5;
|
|
}
|
|
|
|
.q-num-badge {
|
|
display: inline-flex; align-items: center; justify-content: center;
|
|
width: 28px; height: 28px; border-radius: 8px;
|
|
background: var(--teal); color: #fff;
|
|
font-size: 12px; font-weight: 700;
|
|
margin-right: 10px; vertical-align: middle;
|
|
}
|
|
|
|
/* Shared input */
|
|
.q-input {
|
|
width: 100%; background: var(--bg-input); border: 1.5px solid var(--border);
|
|
border-radius: var(--radius-sm); padding: 12px 16px; font-size: 14px;
|
|
font-family: inherit; color: var(--text-primary); outline: none;
|
|
transition: var(--transition); resize: vertical;
|
|
}
|
|
|
|
.q-input::placeholder { color: var(--text-faint); }
|
|
.q-input:focus { border-color: var(--border-focus); box-shadow: 0 0 0 3px var(--teal-glow); background: var(--bg-elevated); }
|
|
|
|
/* ===== OPTION LIST (Checkbox / Radio) ===== */
|
|
.option-list { display: flex; flex-direction: column; gap: 8px; }
|
|
|
|
.option-item {
|
|
display: flex; align-items: center; gap: 12px;
|
|
padding: 14px 16px; background: var(--bg-elevated);
|
|
border: 1.5px solid var(--border); border-radius: var(--radius-sm);
|
|
cursor: pointer; transition: var(--transition); position: relative;
|
|
}
|
|
|
|
.option-item:hover { border-color: rgba(255,255,255,0.12); background: var(--bg-hover); }
|
|
|
|
.option-item input[type="checkbox"],
|
|
.option-item input[type="radio"] {
|
|
accent-color: var(--teal); width: 18px; height: 18px;
|
|
flex-shrink: 0; cursor: pointer;
|
|
}
|
|
|
|
.option-item label {
|
|
font-size: 14px; color: var(--text-primary); cursor: pointer;
|
|
flex: 1; font-weight: 400; line-height: 1.4; margin: 0;
|
|
}
|
|
|
|
/* Other options look identical to regular options — no special styling */
|
|
|
|
/* Other text box */
|
|
.other-text-container { display: none; margin-top: 8px; padding-left: 30px; animation: slideUp 0.2s ease both; }
|
|
.other-text-container.show { display: block; }
|
|
|
|
.other-text-input {
|
|
width: 100%; background: var(--bg-input); border: 1.5px solid var(--border);
|
|
border-radius: var(--radius-sm); padding: 10px 14px; font-size: 13px;
|
|
font-family: inherit; color: var(--text-primary); outline: none;
|
|
resize: vertical; min-height: 70px; transition: var(--transition);
|
|
}
|
|
|
|
.other-text-input:focus { border-color: var(--border-focus); box-shadow: 0 0 0 2px var(--teal-glow); }
|
|
|
|
/* ===== RATING ===== */
|
|
.rating-row { display: flex; flex-wrap: wrap; gap: 10px; }
|
|
|
|
.rating-card {
|
|
display: flex; flex-direction: column; align-items: center; gap: 6px;
|
|
padding: 14px 18px; background: var(--bg-elevated);
|
|
border: 1.5px solid var(--border); border-radius: var(--radius-md);
|
|
cursor: pointer; transition: var(--transition-spring);
|
|
}
|
|
|
|
.rating-card:hover { border-color: rgba(251,191,36,0.4); transform: translateY(-2px); }
|
|
.rating-card .r-label { font-size: 12px; color: var(--text-secondary); font-weight: 500; }
|
|
.rating-card .r-star { font-size: 28px; color: var(--text-faint); transition: var(--transition); cursor: pointer; }
|
|
.rating-card:hover .r-star { color: #fbbf24; }
|
|
.rating-card .r-star.selected { color: #f59e0b; filter: drop-shadow(0 2px 8px rgba(245,158,11,0.4)); }
|
|
.rating-card.rating-hover .r-star { color: #fbbf24; }
|
|
|
|
/* ===== LIKERT ===== */
|
|
.likert-row { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
|
|
.likert-chip {
|
|
flex: 1; min-width: 120px; text-align: center;
|
|
padding: 14px 12px; background: var(--bg-elevated);
|
|
border: 1.5px solid var(--border); border-radius: var(--radius-sm);
|
|
cursor: pointer; font-size: 13px; font-weight: 500;
|
|
color: var(--text-secondary); transition: var(--transition);
|
|
}
|
|
|
|
.likert-chip:hover { border-color: var(--teal-medium); color: var(--text-primary); }
|
|
.likert-chip input[type="radio"] { display: none; }
|
|
.likert-chip.selected { background: var(--teal-soft); border-color: var(--teal); color: var(--teal); }
|
|
.likert-chip.is-other-option { /* same as regular */ }
|
|
.likert-chip.is-other-option.selected { background: var(--teal-soft); border-color: var(--teal); color: var(--teal); }
|
|
|
|
/* ===== MATRIX ===== */
|
|
.matrix-wrap { overflow-x: auto; border-radius: var(--radius-md); border: 1px solid var(--border); }
|
|
.matrix-wrap table { width: 100%; border-collapse: collapse; }
|
|
.matrix-wrap th, .matrix-wrap td { padding: 14px 16px; border: 1px solid var(--border); text-align: center; font-size: 13px; }
|
|
.matrix-wrap th { background: var(--bg-elevated); color: var(--text-secondary); font-weight: 600; }
|
|
.matrix-wrap td { background: var(--bg-card); }
|
|
|
|
.mx-radio { display: none; }
|
|
|
|
.mx-label {
|
|
display: block; padding: 8px 12px; border-radius: 6px;
|
|
border: 1.5px solid var(--border); background: var(--bg-elevated);
|
|
cursor: pointer; font-weight: 500; color: var(--text-secondary);
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.mx-label:hover { border-color: var(--teal); }
|
|
.mx-radio:checked + .mx-label { background: var(--teal); border-color: var(--teal); color: #fff; }
|
|
|
|
/* ===== IMAGE ===== */
|
|
.image-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 14px; }
|
|
|
|
.img-card {
|
|
border: 1.5px solid var(--border); border-radius: var(--radius-md);
|
|
overflow: hidden; cursor: pointer; transition: var(--transition-spring);
|
|
background: var(--bg-elevated);
|
|
}
|
|
|
|
.img-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-sm); border-color: rgba(255,255,255,0.1); }
|
|
.img-card.selected { border-color: var(--teal); box-shadow: var(--shadow-teal); }
|
|
.img-card img { width: 100%; height: 140px; object-fit: cover; display: block; }
|
|
|
|
/* ===== RANKING ===== */
|
|
.rank-list { list-style: none; display: flex; flex-direction: column; gap: 8px; padding: 0; }
|
|
|
|
.rank-item {
|
|
display: flex; align-items: center; gap: 12px;
|
|
padding: 14px 16px; background: var(--bg-elevated);
|
|
border: 1.5px solid var(--border); border-radius: var(--radius-sm);
|
|
cursor: grab; transition: var(--transition);
|
|
}
|
|
|
|
.rank-item:hover { border-color: rgba(255,255,255,0.1); background: var(--bg-hover); }
|
|
.rank-item .rank-num { color: var(--teal); font-weight: 700; font-size: 14px; min-width: 24px; }
|
|
.rank-item .rank-text { flex: 1; font-size: 14px; color: var(--text-primary); }
|
|
|
|
.rank-btns { display: flex; gap: 6px; }
|
|
|
|
.rank-btn {
|
|
background: var(--bg-input); border: 1px solid var(--border);
|
|
color: var(--text-secondary); border-radius: 6px; padding: 5px 10px;
|
|
font-size: 12px; cursor: pointer; transition: var(--transition); font-family: inherit;
|
|
}
|
|
|
|
.rank-btn:hover { border-color: var(--teal); color: var(--teal); }
|
|
|
|
/* ===== SLIDER ===== */
|
|
.slider-wrap { padding: 8px 0; }
|
|
|
|
.slider-range {
|
|
width: 100%; height: 6px; background: var(--bg-elevated);
|
|
border-radius: 100px; outline: none;
|
|
-webkit-appearance: none; appearance: none;
|
|
}
|
|
|
|
.slider-range::-webkit-slider-thumb {
|
|
-webkit-appearance: none; width: 22px; height: 22px;
|
|
background: var(--teal); border-radius: 50%;
|
|
cursor: pointer; box-shadow: var(--shadow-teal);
|
|
}
|
|
|
|
.slider-range::-moz-range-thumb {
|
|
width: 22px; height: 22px; background: var(--teal);
|
|
border-radius: 50%; cursor: pointer; border: none;
|
|
}
|
|
|
|
.slider-val {
|
|
display: inline-block; margin-top: 10px; padding: 6px 14px;
|
|
background: var(--bg-elevated); border: 1px solid var(--border);
|
|
border-radius: 100px; font-size: 15px; font-weight: 600; color: var(--teal);
|
|
}
|
|
|
|
/* ===== NAVIGATION ===== */
|
|
.q-nav {
|
|
display: flex; gap: 10px; margin-top: 28px;
|
|
padding-top: 20px; border-top: 1px solid var(--border);
|
|
}
|
|
|
|
.nav-btn {
|
|
display: inline-flex; align-items: center; gap: 6px;
|
|
padding: 10px 22px; border-radius: var(--radius-sm);
|
|
font-size: 13px; font-weight: 600; font-family: inherit;
|
|
cursor: pointer; transition: var(--transition-spring);
|
|
border: 1.5px solid var(--border); background: var(--bg-elevated);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.nav-btn:hover { border-color: rgba(255,255,255,0.15); color: var(--text-primary); transform: translateY(-1px); }
|
|
|
|
.nav-btn.primary { background: var(--teal); border-color: var(--teal); color: #fff; }
|
|
.nav-btn.primary:hover { background: var(--teal-dark); box-shadow: var(--shadow-teal); }
|
|
|
|
.submit-btn {
|
|
display: none; align-items: center; gap: 8px;
|
|
padding: 12px 28px; background: var(--teal); color: #fff;
|
|
border: none; border-radius: var(--radius-sm);
|
|
font-size: 14px; font-weight: 600; font-family: inherit;
|
|
cursor: pointer; transition: var(--transition-spring);
|
|
box-shadow: var(--shadow-teal); margin-top: 20px;
|
|
}
|
|
|
|
.submit-btn:hover { background: var(--teal-dark); transform: translateY(-1px); }
|
|
|
|
.hidden { display: none !important; }
|
|
|
|
.rank-hint { font-size: 12px; color: var(--text-muted); margin-bottom: 12px; }
|
|
|
|
/* End message */
|
|
.end-message-card {
|
|
background: var(--bg-card); border: 1px solid var(--green-border);
|
|
border-radius: var(--radius-lg); padding: 48px 36px; text-align: center;
|
|
}
|
|
|
|
.end-message-card h3 { color: var(--teal); margin-bottom: 12px; font-size: 22px; }
|
|
.end-message-card p { color: var(--text-secondary); font-size: 15px; }
|
|
|
|
@@media (max-width: 768px) {
|
|
.survey-page { padding: 12px 0 60px; }
|
|
.stepper-sidebar { display: none; }
|
|
.survey-body { flex-direction: column; gap: 0; }
|
|
.user-info-card { grid-template-columns: 1fr; border-radius: 0; margin-bottom: 0; border-left: none; border-right: none; }
|
|
.survey-header-card { border-radius: 0; border-left: none; border-right: none; padding: 24px 20px; margin-bottom: 0; }
|
|
.content-area { width: 100%; }
|
|
.q-step { border-radius: 0; border-left: none; border-right: none; padding: 24px 20px; margin: 0; }
|
|
.q-step.active { display: block; }
|
|
.q-text { font-size: 15px; margin-bottom: 18px; }
|
|
.q-num-badge { width: 24px; height: 24px; font-size: 11px; margin-right: 8px; }
|
|
.option-item { padding: 12px 14px; gap: 10px; }
|
|
.option-item label { font-size: 13px; }
|
|
.likert-row { flex-direction: column; }
|
|
.image-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
|
|
.rating-row { gap: 8px; }
|
|
.rating-card { padding: 10px 14px; }
|
|
.rating-card .r-star { font-size: 22px; }
|
|
.q-nav { margin-top: 20px; padding-top: 16px; }
|
|
.nav-btn { padding: 10px 18px; font-size: 13px; }
|
|
.submit-btn { margin: 16px 20px; }
|
|
.survey-shell { border-radius: 0; }
|
|
}
|
|
</style>
|
|
|
|
<div class="survey-page">
|
|
<div class="survey-shell">
|
|
<form id="questionnaireForm" method="post" asp-action="DisplayQuestionnaire">
|
|
<input type="hidden" name="Id" value="@Model.Id" />
|
|
<input type="hidden" name="Title" value="@Model.Title" />
|
|
<input type="hidden" name="Description" value="@Model.Description" />
|
|
<input type="hidden" id="questionsShown" name="QuestionsShown" value="" />
|
|
<input type="hidden" id="questionsSkipped" name="QuestionsSkipped" value="" />
|
|
|
|
<!-- Header -->
|
|
<div class="survey-header-card">
|
|
<h1 class="survey-title">@Model.Title<span class="survey-title-dot"></span></h1>
|
|
<div class="survey-desc">@Html.Raw(Model.Description)</div>
|
|
</div>
|
|
|
|
<!-- User Info -->
|
|
<div class="user-info-card">
|
|
<div>
|
|
<span class="field-label">Your Name</span>
|
|
<input type="text" class="field-input" name="UserName" placeholder="Enter your name" required autocomplete="name" />
|
|
</div>
|
|
<div>
|
|
<span class="field-label">Email Address</span>
|
|
<input type="email" class="field-input" name="Email" placeholder="Enter your email" required autocomplete="email" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Body: Stepper + Content -->
|
|
<div class="survey-body">
|
|
<div class="stepper-sidebar">
|
|
<div class="stepper-title">Questions</div>
|
|
@for (int i = 0; i < Model.Questions.Count; i++)
|
|
{
|
|
<div class="step-dot @(i == 0 ? "active" : "")" data-step="@i">
|
|
<span class="dot-num">@(i + 1)</span>
|
|
<span class="dot-label">@Model.Questions[i].Type.ToString().Replace("_"," ")</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<div class="content-area">
|
|
@for (int i = 0; i < Model.Questions.Count; i++)
|
|
{
|
|
var question = Model.Questions[i];
|
|
|
|
<input type="hidden" name="Questions[@i].Text" value="@question.Text" />
|
|
<input type="hidden" name="Questions[@i].Id" value="@question.Id" />
|
|
<input type="hidden" name="Questions[@i].Type" value="@((int)question.Type)" />
|
|
@for (int j = 0; j < question.Answers.Count; j++)
|
|
{
|
|
<input type="hidden" name="Questions[@i].Answers[@j].Id" value="@question.Answers[j].Id" />
|
|
<input type="hidden" name="Questions[@i].Answers[@j].Text" value="@question.Answers[j].Text" />
|
|
}
|
|
|
|
<div class="q-step @(i == 0 ? "active" : "")" data-step="@i" data-question-id="@question.Id">
|
|
<div class="q-text"><span class="q-num-badge">@(i + 1)</span> @question.Text</div>
|
|
|
|
@switch (question.Type)
|
|
{
|
|
case QuestionType.Text:
|
|
<input type="text" class="q-input" name="Questions[@i].SelectedText" placeholder="Type your answer..." />
|
|
@if (question.Answers.Any())
|
|
{
|
|
<input type="hidden" name="Questions[@i].SelectedAnswerIds" value="@question.Answers[0].Id" data-condition="@question.Answers[0].ConditionJson" />
|
|
}
|
|
break;
|
|
|
|
case QuestionType.Open_ended:
|
|
<textarea class="q-input" name="Questions[@i].SelectedText" rows="4" placeholder="Type your answer..."></textarea>
|
|
@if (question.Answers.Any())
|
|
{
|
|
<input type="hidden" name="Questions[@i].SelectedAnswerIds" value="@question.Answers[0].Id" data-condition="@question.Answers[0].ConditionJson" />
|
|
}
|
|
break;
|
|
|
|
case QuestionType.CheckBox:
|
|
case QuestionType.Multiple_choice:
|
|
case QuestionType.Demographic:
|
|
<div class="option-list">
|
|
@foreach (var answer in question.Answers)
|
|
{
|
|
<div class="option-item @(answer.IsOtherOption ? "is-other-option" : "")">
|
|
<input type="checkbox" id="q@(i)_a@(answer.Id)" name="Questions[@i].SelectedAnswerIds" value="@answer.Id"
|
|
data-condition="@answer.ConditionJson" data-qi="@i" data-aid="@answer.Id"
|
|
data-is-other="@answer.IsOtherOption.ToString().ToLower()" />
|
|
<label for="q@(i)_a@(answer.Id)">@answer.Text</label>
|
|
</div>
|
|
@if (answer.IsOtherOption)
|
|
{
|
|
<div class="other-text-container" id="otherText_@(i)_@(answer.Id)">
|
|
<textarea class="other-text-input" name="Questions[@i].OtherTexts[@answer.Id]" id="otherArea_@(i)_@(answer.Id)" placeholder="Please specify..."></textarea>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
break;
|
|
|
|
case QuestionType.TrueFalse:
|
|
<div class="option-list">
|
|
@foreach (var answer in question.Answers)
|
|
{
|
|
<div class="option-item @(answer.IsOtherOption ? "is-other-option" : "")">
|
|
<input type="radio" id="q@(i)_a@(answer.Id)" name="Questions[@i].SelectedAnswerIds" value="@answer.Id"
|
|
data-condition="@answer.ConditionJson" data-qi="@i" data-aid="@answer.Id"
|
|
data-is-other="@answer.IsOtherOption.ToString().ToLower()" />
|
|
<label for="q@(i)_a@(answer.Id)">@answer.Text</label>
|
|
</div>
|
|
@if (answer.IsOtherOption)
|
|
{
|
|
<div class="other-text-container" id="otherText_@(i)_@(answer.Id)">
|
|
<textarea class="other-text-input" name="Questions[@i].OtherTexts[@answer.Id]" id="otherArea_@(i)_@(answer.Id)" placeholder="Please specify..."></textarea>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
break;
|
|
|
|
case QuestionType.Rating:
|
|
<div class="rating-row" data-question="@i">
|
|
@foreach (var answer in question.Answers)
|
|
{
|
|
<div class="rating-card" data-aid="@answer.Id">
|
|
<span class="r-label">@answer.Text</span>
|
|
<input type="radio" id="q@(i)_r@(answer.Id)" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" class="hidden" data-condition="@answer.ConditionJson" />
|
|
<label for="q@(i)_r@(answer.Id)" class="bi bi-star-fill r-star"></label>
|
|
</div>
|
|
}
|
|
</div>
|
|
break;
|
|
|
|
case QuestionType.Likert:
|
|
<div class="likert-row">
|
|
@foreach (var answer in question.Answers)
|
|
{
|
|
<label class="likert-chip @(answer.IsOtherOption ? "is-other-option" : "")" data-aid="@answer.Id">
|
|
<input type="radio" name="Questions[@i].SelectedAnswerIds" value="@answer.Id"
|
|
data-condition="@answer.ConditionJson" data-qi="@i" data-aid="@answer.Id"
|
|
data-is-other="@answer.IsOtherOption.ToString().ToLower()" />
|
|
@answer.Text
|
|
</label>
|
|
@if (answer.IsOtherOption)
|
|
{
|
|
<div class="other-text-container" id="otherText_@(i)_@(answer.Id)">
|
|
<textarea class="other-text-input" name="Questions[@i].OtherTexts[@answer.Id]" id="otherArea_@(i)_@(answer.Id)" placeholder="Please specify..."></textarea>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
break;
|
|
|
|
case QuestionType.Matrix:
|
|
<div class="matrix-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th style="text-align:left;">Statement</th>
|
|
@foreach (var opt in question.Answers) { <th>@opt.Text</th> }
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td style="text-align:left; font-weight:500; color:var(--text-primary);">@question.Text</td>
|
|
@foreach (var opt in question.Answers)
|
|
{
|
|
<td>
|
|
<input type="radio" class="mx-radio" id="q@(i)_mx@(opt.Id)" name="Questions[@i].SelectedAnswerIds" value="@opt.Id" data-condition="@opt.ConditionJson" data-qi="@i" data-aid="@opt.Id" data-is-other="@opt.IsOtherOption.ToString().ToLower()" />
|
|
<label for="q@(i)_mx@(opt.Id)" class="mx-label">@opt.Text</label>
|
|
</td>
|
|
}
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
break;
|
|
|
|
case QuestionType.Image:
|
|
<div class="image-grid" data-question-id="@question.Id">
|
|
@foreach (var answer in question.Answers)
|
|
{
|
|
<div class="img-card @(answer.IsOtherOption ? "is-other-option" : "")" id="imgCard_@answer.Id" onclick="pickImage('@answer.Id','@question.Id')">
|
|
@if (answer.IsOtherOption)
|
|
{
|
|
<div style="height:140px;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:6px;background:var(--bg-elevated);">
|
|
<i class="bi bi-plus-circle" style="font-size:1.8rem;color:var(--teal);"></i>
|
|
<span style="font-size:13px;font-weight:600;color:var(--text-primary);">@answer.Text</span>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<img src="@answer.Text" alt="Option" />
|
|
}
|
|
<input type="radio" id="imgR_@answer.Id" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" class="hidden"
|
|
data-condition="@answer.ConditionJson" data-qi="@i" data-aid="@answer.Id"
|
|
data-is-other="@answer.IsOtherOption.ToString().ToLower()" />
|
|
</div>
|
|
@if (answer.IsOtherOption)
|
|
{
|
|
<div class="other-text-container" id="otherText_@(i)_@(answer.Id)" style="grid-column:1/-1;">
|
|
<textarea class="other-text-input" name="Questions[@i].OtherTexts[@answer.Id]" id="otherArea_@(i)_@(answer.Id)" placeholder="Describe your choice..."></textarea>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
break;
|
|
|
|
case QuestionType.Ranking:
|
|
<p class="rank-hint">Drag to reorder or use the buttons</p>
|
|
<ul class="rank-list" id="rank_@question.Id">
|
|
@{ int ri = 1; }
|
|
@foreach (var answer in question.Answers)
|
|
{
|
|
<li class="rank-item" draggable="true" id="ri_@answer.Id">
|
|
<span class="rank-num">@ri.</span>
|
|
<span class="rank-text">@answer.Text</span>
|
|
<input type="hidden" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" data-condition="@answer.ConditionJson" />
|
|
<div class="rank-btns">
|
|
<button type="button" class="rank-btn" onclick="rankUp(this)"><i class="bi bi-arrow-up"></i></button>
|
|
<button type="button" class="rank-btn" onclick="rankDown(this)"><i class="bi bi-arrow-down"></i></button>
|
|
</div>
|
|
</li>
|
|
ri++;
|
|
}
|
|
</ul>
|
|
break;
|
|
|
|
case QuestionType.Slider:
|
|
@if (question.Answers.Any())
|
|
{
|
|
<div class="slider-wrap">
|
|
<input type="range" class="slider-range" name="Questions[@i].SelectedText" min="0" max="100" step="1" value="50"
|
|
oninput="this.parentElement.querySelector('.slider-val').textContent=this.value" />
|
|
<input type="hidden" name="Questions[@i].SelectedAnswerIds" value="@question.Answers[0].Id" data-condition="@question.Answers[0].ConditionJson" />
|
|
<span class="slider-val">50</span>
|
|
</div>
|
|
}
|
|
break;
|
|
|
|
default:
|
|
<div style="padding:16px;background:var(--red-soft);border:1px solid var(--red);border-radius:8px;color:var(--red);font-weight:500;">Unsupported question type.</div>
|
|
break;
|
|
}
|
|
|
|
<div class="q-nav">
|
|
@if (i > 0) { <button type="button" class="nav-btn prev-btn"><i class="bi bi-arrow-left"></i> Previous</button> }
|
|
@if (i < Model.Questions.Count - 1) { <button type="button" class="nav-btn primary next-btn">Next <i class="bi bi-arrow-right"></i></button> }
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<button type="submit" class="submit-btn"><i class="bi bi-check-lg"></i> Submit</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
@section Scripts {
|
|
<partial name="_ValidationScriptsPartial" />
|
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
<script>
|
|
$(function () {
|
|
const form = $('#questionnaireForm');
|
|
const steps = $('.q-step');
|
|
const dots = $('.step-dot');
|
|
const submitBtn = $('.submit-btn');
|
|
let cur = 0, navPath = [0], shown = [1], skipped = [];
|
|
|
|
// ===== STEP NAVIGATION =====
|
|
function showStep(i) {
|
|
steps.removeClass('active').eq(i).addClass('active');
|
|
dots.removeClass('active').eq(i).addClass('active');
|
|
submitBtn.css('display', i === steps.length - 1 ? 'inline-flex' : 'none');
|
|
try { dots.eq(i)[0].scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } catch(e){}
|
|
}
|
|
|
|
function track(n) { if (!shown.includes(n)) shown.push(n); }
|
|
function trackSkip(n, r) { skipped.push({ questionNumber: n, reason: r }); }
|
|
function updateTracking() { $('#questionsShown').val(JSON.stringify(shown)); $('#questionsSkipped').val(JSON.stringify(skipped)); }
|
|
|
|
// ===== CONDITION LOGIC =====
|
|
function getConditions() {
|
|
const el = steps.eq(cur)[0];
|
|
const conds = [];
|
|
el.querySelectorAll('input:checked, input[type="text"][name*="SelectedText"], textarea[name*="SelectedText"]').forEach(inp => {
|
|
const c = inp.getAttribute('data-condition');
|
|
if (c && c !== '' && c !== 'null') { try { conds.push(JSON.parse(c)); } catch(e){} }
|
|
});
|
|
// Also check hidden inputs with conditions (for Text/Open_ended/Slider)
|
|
el.querySelectorAll('input[type="hidden"][data-condition]').forEach(inp => {
|
|
const c = inp.getAttribute('data-condition');
|
|
if (c && c !== '' && c !== 'null') { try { conds.push(JSON.parse(c)); } catch(e){} }
|
|
});
|
|
return conds;
|
|
}
|
|
|
|
function evalCondition() {
|
|
for (let c of getConditions()) {
|
|
if (c.ActionType === 1 && c.TargetQuestionNumber) return { a: 'jump', t: c.TargetQuestionNumber };
|
|
if (c.ActionType === 2 && c.SkipCount) return { a: 'skip', c: c.SkipCount };
|
|
if (c.ActionType === 3) return { a: 'end', m: c.EndMessage };
|
|
}
|
|
return { a: 'next' };
|
|
}
|
|
|
|
function goNext() {
|
|
const r = evalCondition();
|
|
switch (r.a) {
|
|
case 'jump':
|
|
const ti = r.t - 1;
|
|
for (let x = cur + 2; x < r.t; x++) trackSkip(x, 'Jump from Q' + (cur+1));
|
|
cur = ti; navPath.push(cur); track(cur+1); showStep(cur); break;
|
|
case 'skip':
|
|
const ni = cur + r.c + 1;
|
|
for (let x = 1; x <= r.c; x++) { if (cur+x+1 <= steps.length) trackSkip(cur+x+1, 'Skip from Q'+(cur+1)); }
|
|
cur = Math.min(ni, steps.length-1); navPath.push(cur); track(cur+1); showStep(cur); break;
|
|
case 'end':
|
|
for (let x = cur+2; x <= steps.length; x++) trackSkip(x, 'Ended at Q'+(cur+1));
|
|
steps.hide();
|
|
$('.content-area').append(`<div class="end-message-card"><h3>Survey Complete</h3><p>${r.m||'Thank you for participating!'}</p></div>`);
|
|
submitBtn.hide(); break;
|
|
default:
|
|
if (cur < steps.length-1) { cur++; navPath.push(cur); track(cur+1); showStep(cur); }
|
|
}
|
|
}
|
|
|
|
function goPrev() { if (navPath.length > 1) { navPath.pop(); cur = navPath[navPath.length-1]; showStep(cur); } }
|
|
|
|
$(document).on('click', '.next-btn', goNext);
|
|
$(document).on('click', '.prev-btn', goPrev);
|
|
form.on('submit', updateTracking);
|
|
|
|
// ===== OTHER OPTION LOGIC =====
|
|
$(document).on('change', 'input[type="checkbox"]', function () {
|
|
if ($(this).attr('data-is-other') === 'true') {
|
|
const qi = $(this).data('qi'), aid = $(this).data('aid');
|
|
const box = $(`#otherText_${qi}_${aid}`);
|
|
if ($(this).is(':checked')) { box.addClass('show'); box.find('textarea').focus(); }
|
|
else { box.removeClass('show').find('textarea').val(''); }
|
|
}
|
|
});
|
|
|
|
$(document).on('change', 'input[type="radio"]', function () {
|
|
const qi = $(this).data('qi');
|
|
if (qi !== undefined && qi !== '') {
|
|
// Hide all other boxes for this question
|
|
$(`[id^="otherText_${qi}_"]`).removeClass('show').find('textarea').val('');
|
|
// Show if this is the other option
|
|
if ($(this).attr('data-is-other') === 'true') {
|
|
const aid = $(this).data('aid');
|
|
$(`#otherText_${qi}_${aid}`).addClass('show').find('textarea').focus();
|
|
}
|
|
}
|
|
});
|
|
|
|
// ===== RATING =====
|
|
$(document).on('click', '.rating-card', function () {
|
|
const row = $(this).closest('.rating-row');
|
|
row.find('.r-star').removeClass('selected');
|
|
$(this).prevAll('.rating-card').addBack().find('.r-star').addClass('selected');
|
|
$(this).find('input[type="radio"]').prop('checked', true);
|
|
});
|
|
$(document).on('mouseenter', '.rating-card', function () {
|
|
$(this).prevAll('.rating-card').addBack().addClass('rating-hover');
|
|
});
|
|
$(document).on('mouseleave', '.rating-card', function () {
|
|
$(this).closest('.rating-row').find('.rating-card').removeClass('rating-hover');
|
|
});
|
|
|
|
// ===== LIKERT =====
|
|
$(document).on('change', '.likert-chip input', function () {
|
|
$(this).closest('.likert-row').find('.likert-chip').removeClass('selected');
|
|
$(this).closest('.likert-chip').addClass('selected');
|
|
});
|
|
|
|
// ===== IMAGE =====
|
|
window.pickImage = function (aid, qid) {
|
|
$(`.image-grid[data-question-id="${qid}"] .img-card`).removeClass('selected');
|
|
$(`#imgCard_${aid}`).addClass('selected');
|
|
$(`#imgR_${aid}`).prop('checked', true).trigger('change');
|
|
};
|
|
|
|
// ===== RANKING =====
|
|
window.rankUp = function (btn) { const li = $(btn).closest('.rank-item'); if (li.prev().length) li.insertBefore(li.prev()); updateRankNums(li.closest('.rank-list')); };
|
|
window.rankDown = function (btn) { const li = $(btn).closest('.rank-item'); if (li.next().length) li.insertAfter(li.next()); updateRankNums(li.closest('.rank-list')); };
|
|
function updateRankNums(list) { list.find('.rank-item').each(function(i){ $(this).find('.rank-num').text((i+1)+'.'); }); }
|
|
|
|
// Drag ranking
|
|
let dragEl = null;
|
|
$(document).on('dragstart', '.rank-item', function(e) { dragEl = this; e.originalEvent.dataTransfer.effectAllowed = 'move'; });
|
|
$(document).on('dragover', '.rank-item', function(e) { e.preventDefault(); });
|
|
$(document).on('drop', '.rank-item', function(e) {
|
|
e.preventDefault();
|
|
if (dragEl !== this) {
|
|
const rect = this.getBoundingClientRect();
|
|
const mid = rect.top + rect.height / 2;
|
|
if (e.originalEvent.clientY < mid) $(this).before(dragEl);
|
|
else $(this).after(dragEl);
|
|
updateRankNums($(this).closest('.rank-list'));
|
|
}
|
|
});
|
|
|
|
showStep(0);
|
|
});
|
|
</script>
|
|
}
|