New scoring system display UI

This commit is contained in:
sairaj mote 2022-11-09 16:53:15 +05:30
parent 0ecde83eb1
commit 75a55b2252
6 changed files with 242 additions and 111 deletions

View File

@ -158,26 +158,25 @@ button:not(:disabled),
border: solid thin rgba(var(--text-color), 0.06); border: solid thin rgba(var(--text-color), 0.06);
} }
.button--primary { .button--primary {
color: rgba(var(--background-color), 1) !important; color: rgba(var(--background-color), 1);
background-color: var(--accent-color);
} }
.button--primary .icon { .button--primary .icon {
fill: rgba(var(--background-color), 1); fill: rgba(var(--background-color), 1);
} }
.button--danger {
color: var(--danger-color);
}
.button--danger .icon {
fill: var(--danger-color);
}
.button--primary {
background-color: var(--accent-color);
}
.button--colored { .button--colored {
color: var(--accent-color); color: var(--accent-color);
} }
.button--colored .icon { .button--colored .icon {
fill: var(--accent-color); fill: var(--accent-color);
} }
.button--danger {
background-color: rgba(255, 115, 115, 0.062745098);
color: var(--danger-color);
}
.button--danger .icon {
fill: var(--danger-color);
}
.button--small { .button--small {
padding: 0.4rem 0.6rem; padding: 0.4rem 0.6rem;
} }
@ -246,18 +245,6 @@ details[open] > summary .down-arrow {
transform: rotate(180deg); transform: rotate(180deg);
} }
fieldset {
border: none;
}
input {
accent-color: var(--accent-color);
}
input[type=range]:active {
cursor: -webkit-grab;
cursor: grab;
}
sm-input, sm-input,
sm-textarea, sm-textarea,
tags-input { tags-input {
@ -422,6 +409,10 @@ ul {
align-items: flex-start; align-items: flex-start;
} }
.align-items-center {
align-items: center;
}
.align-content-start { .align-content-start {
align-content: flex-start; align-content: flex-start;
} }
@ -450,7 +441,7 @@ ul {
justify-content: start; justify-content: start;
} }
.justify-center { .justify-content-center {
justify-content: center; justify-content: center;
} }
@ -486,6 +477,10 @@ ul {
justify-content: space-between; justify-content: space-between;
} }
.space-evenly {
justify-content: space-evenly;
}
.w-100 { .w-100 {
width: 100%; width: 100%;
} }
@ -1291,14 +1286,6 @@ ul {
flex: 1; flex: 1;
} }
#intern_info_popup .grid > * {
justify-self: center;
}
#intern_info_popup #update_intern_score {
width: 100%;
margin-top: 1rem;
}
#intern_info__initials { #intern_info__initials {
position: relative; position: relative;
height: 4rem; height: 4rem;
@ -1309,15 +1296,81 @@ ul {
#intern_info__name { #intern_info__name {
font-size: 1.5rem; font-size: 1.5rem;
margin-bottom: 0.5rem;
} }
.icon--star { .icon--star {
fill: var(--orange); fill: var(--orange);
} }
#intern_info__score { #stats_wrapper {
font-size: 1.5rem; display: flex;
gap: 1rem;
justify-content: space-evenly;
margin-top: 1.5rem;
}
#stats_wrapper > * {
flex: 1;
}
.stat {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 0.5rem;
}
.stat__display {
display: grid;
justify-items: center;
align-items: center;
height: 4rem;
}
.stat__display > * {
grid-area: 1/1;
}
.stat__circle {
height: 100%;
stroke-width: 10;
overflow: visible;
stroke-dasharray: 400;
stroke-dashoffset: var(--progress, 400);
transform: rotate(-90deg);
stroke-linecap: round;
transition: stroke-dashoffset 1s;
}
.stat__count {
font-size: 1.2rem;
font-weight: 700;
}
.stat p {
color: rgba(var(--text-color), 0.8);
font-size: 0.9rem;
max-width: 8ch;
line-height: 1.2;
}
#intern_rating .stat__circle {
fill: rgba(var(--text-color), 0.06);
stroke: var(--rating-color, --green);
}
#intern_rating .stat__count {
color: var(--rating-color, --green);
}
#intern_complete_tasks .stat__circle {
fill: rgba(255, 61, 0, 0.062745098);
stroke-dashoffset: 0;
}
#intern_complete_tasks .stat__count {
color: var(--orange);
}
#intern_points .stat__circle {
fill: rgba(109, 131, 255, 0.062745098);
stroke-dashoffset: 0;
}
#intern_points .stat__count {
color: var(--nice-blue);
} }
#project_info { #project_info {
@ -1762,8 +1815,8 @@ input[type=date] {
display: flex; display: flex;
width: 100%; width: 100%;
padding: 0.5rem; padding: 0.5rem;
border: rgba(var(--text-color), 0.2) solid thin; border: none;
border-radius: 0.3rem; border-radius: 0.5rem;
font-family: inherit; font-family: inherit;
font-size: inherit; font-size: inherit;
color: inherit; color: inherit;

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -145,28 +145,26 @@ button,
background-color: rgba(var(--text-color), 0.02); background-color: rgba(var(--text-color), 0.02);
border: solid thin rgba(var(--text-color), 0.06); border: solid thin rgba(var(--text-color), 0.06);
&--primary { &--primary {
color: rgba(var(--background-color), 1) !important; color: rgba(var(--background-color), 1);
background-color: var(--accent-color);
.icon { .icon {
fill: rgba(var(--background-color), 1); fill: rgba(var(--background-color), 1);
} }
} }
&--danger {
color: var(--danger-color);
.icon {
fill: var(--danger-color);
}
}
&--primary {
background-color: var(--accent-color);
}
&--colored { &--colored {
color: var(--accent-color); color: var(--accent-color);
.icon { .icon {
fill: var(--accent-color); fill: var(--accent-color);
} }
} }
&--danger {
background-color: #ff737310;
color: var(--danger-color);
.icon {
fill: var(--danger-color);
}
}
&--small { &--small {
padding: 0.4rem 0.6rem; padding: 0.4rem 0.6rem;
@ -230,20 +228,6 @@ details[open] {
} }
} }
fieldset {
border: none;
}
input {
accent-color: var(--accent-color);
&[type="range"] {
&:active {
cursor: grab;
}
}
}
sm-input, sm-input,
sm-textarea, sm-textarea,
tags-input { tags-input {
@ -411,6 +395,9 @@ ul {
.align-items-start { .align-items-start {
align-items: flex-start; align-items: flex-start;
} }
.align-items-center {
align-items: center;
}
.align-content-start { .align-content-start {
align-content: flex-start; align-content: flex-start;
} }
@ -438,7 +425,7 @@ ul {
justify-content: start; justify-content: start;
} }
.justify-center { .justify-content-center {
justify-content: center; justify-content: center;
} }
@ -473,6 +460,9 @@ ul {
.space-between { .space-between {
justify-content: space-between; justify-content: space-between;
} }
.space-evenly {
justify-content: space-evenly;
}
.w-100 { .w-100 {
width: 100%; width: 100%;
@ -1283,15 +1273,6 @@ ul {
} }
#intern_info_popup { #intern_info_popup {
.grid {
& > * {
justify-self: center;
}
}
#update_intern_score {
width: 100%;
margin-top: 1rem;
}
} }
#intern_info__initials { #intern_info__initials {
position: relative; position: relative;
@ -1302,15 +1283,82 @@ ul {
} }
#intern_info__name { #intern_info__name {
font-size: 1.5rem; font-size: 1.5rem;
margin-bottom: 0.5rem;
} }
.icon--star { .icon--star {
fill: var(--orange); fill: var(--orange);
} }
#intern_info__score { #stats_wrapper {
font-size: 1.5rem; display: flex;
gap: 1rem;
justify-content: space-evenly;
margin-top: 1.5rem;
& > * {
flex: 1;
}
}
.stat {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 0.5rem;
&__display {
display: grid;
justify-items: center;
align-items: center;
height: 4rem;
& > * {
grid-area: 1/1;
}
}
&__circle {
height: 100%;
stroke-width: 10;
overflow: visible;
stroke-dasharray: 400;
stroke-dashoffset: var(--progress, 400);
transform: rotate(-90deg);
stroke-linecap: round;
transition: stroke-dashoffset 1s;
}
&__count {
font-size: 1.2rem;
font-weight: 700;
}
p {
color: rgba(var(--text-color), 0.8);
font-size: 0.9rem;
max-width: 8ch;
line-height: 1.2;
}
}
#intern_rating {
.stat__circle {
fill: rgba(var(--text-color), 0.06);
stroke: var(--rating-color, --green);
}
.stat__count {
color: var(--rating-color, --green);
}
}
#intern_complete_tasks {
.stat__circle {
fill: #ff3d0010;
stroke-dashoffset: 0;
}
.stat__count {
color: var(--orange);
}
}
#intern_points {
.stat__circle {
fill: #6d83ff10;
stroke-dashoffset: 0;
}
.stat__count {
color: var(--nice-blue);
}
} }
#project_info { #project_info {
flex-direction: column; flex-direction: column;
} }
@ -1739,8 +1787,8 @@ input[type="date"] {
display: flex; display: flex;
width: 100%; width: 100%;
padding: 0.5rem; padding: 0.5rem;
border: rgba(var(--text-color), 0.2) solid thin; border: none;
border-radius: 0.3rem; border-radius: 0.5rem;
font-family: inherit; font-family: inherit;
font-size: inherit; font-size: inherit;
color: inherit; color: inherit;

View File

@ -105,7 +105,7 @@
</div> </div>
<img src="assets/working-intern.svg" alt=""> <img src="assets/working-intern.svg" alt="">
</div> </div>
<div id="landing_tasks_wrapper" class="flex flex-direction-column justify-center"></div> <div id="landing_tasks_wrapper" class="flex flex-direction-column justify-content-center"></div>
</div> </div>
<article id="sign_in" class="inner-page hidden"> <article id="sign_in" class="inner-page hidden">
<section> <section>
@ -878,7 +878,7 @@
// displays a popup for asking permission. Use this instead of JS confirm // displays a popup for asking permission. Use this instead of JS confirm
const getConfirmation = (title, options = {}) => { const getConfirmation = (title, options = {}) => {
return new Promise(resolve => { return new Promise(resolve => {
const { message = '', cancelText = 'Cancel', confirmText = 'OK' } = options const { message = '', cancelText = 'Cancel', confirmText = 'OK', danger = false } = options
openPopup('confirmation_popup', true) openPopup('confirmation_popup', true)
getRef('confirm_title').innerText = title; getRef('confirm_title').innerText = title;
getRef('confirm_message').innerText = message; getRef('confirm_message').innerText = message;
@ -886,6 +886,10 @@
const confirmButton = getRef('confirmation_popup').querySelector('.confirm-button') const confirmButton = getRef('confirmation_popup').querySelector('.confirm-button')
confirmButton.textContent = confirmText confirmButton.textContent = confirmText
cancelButton.textContent = cancelText cancelButton.textContent = cancelText
if (danger)
confirmButton.classList.add('button--danger')
else
confirmButton.classList.remove('button--danger')
confirmButton.onclick = () => { confirmButton.onclick = () => {
closePopup() closePopup()
resolve(true); resolve(true);
@ -1142,7 +1146,7 @@
<ul id="assigned_task_list">${renderedAssignedTasks}</ul> <ul id="assigned_task_list">${renderedAssignedTasks}</ul>
</section> </section>
` : ''} ` : ''}
<div id="dashboard_tasks_wrapper" class=${`flex flex-direction-column justify-center dashboard-view__item ${typeOfUser === 'intern' ? 'hidden' : ''}`}>${render.displayTasks(params?.category, params?.search)}</div> <div id="dashboard_tasks_wrapper" class=${`flex flex-direction-column justify-content-center dashboard-view__item ${typeOfUser === 'intern' ? 'hidden' : ''}`}>${render.displayTasks(params?.category, params?.search)}</div>
<div id="projects_wrapper" class="grid gap-2 align-items-start dashboard-view__item hidden">${render.dashProjects()}</div> <div id="projects_wrapper" class="grid gap-2 align-items-start dashboard-view__item hidden">${render.dashProjects()}</div>
<div id="intern_leaderboard_container" class="container-card dashboard-view__item hide-on-mobile"> <div id="intern_leaderboard_container" class="container-card dashboard-view__item hide-on-mobile">
<div class="container-header"> <div class="container-header">
@ -1975,7 +1979,7 @@
</div> </div>
<h4 class="task-title capitalize" data-editable data-edit-field="title">${title}</h4> <h4 class="task-title capitalize" data-editable data-edit-field="title">${title}</h4>
<div class="assigned-interns"> <div class="assigned-interns">
<button class="button--outlined button--small button--colored" onclick="currentTask=this.closest('.admin-task');openPopup('intern_list_popup')"> <button class="button button--small button--colored" onclick="currentTask=this.closest('.admin-task');openPopup('intern_list_popup')">
<svg class="icon margin-right-0-3" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M11 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0-6c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zM5 18c.2-.63 2.57-1.68 4.96-1.94l2.04-2c-.39-.04-.68-.06-1-.06-2.67 0-8 1.34-8 4v2h9l-2-2H5zm15.6-5.5l-5.13 5.17-2.07-2.08L12 17l3.47 3.5L22 13.91z"/></svg> <svg class="icon margin-right-0-3" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M11 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0-6c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zM5 18c.2-.63 2.57-1.68 4.96-1.94l2.04-2c-.39-.04-.68-.06-1-.06-2.67 0-8 1.34-8 4v2h9l-2-2H5zm15.6-5.5l-5.13 5.17-2.07-2.08L12 17l3.47 3.5L22 13.91z"/></svg>
Assign intern Assign intern
</button> </button>
@ -2509,31 +2513,57 @@
// opens a popup containing various intern information // opens a popup containing various intern information
function showInternInfo(e) { function showInternInfo(e) {
const internFloId = e.target.closest('.intern-card').dataset.internFloId; const internFloId = e.target.closest('.intern-card').dataset.internFloId;
const { completedTasks = {}, active = true } = RIBC.getInternRecord(internFloId) || {}
const internName = RIBC.getInternList()[internFloId] const internName = RIBC.getInternList()[internFloId]
const { completedTasks = [], active = true } = RIBC.getInternRecord(internFloId) || {} const rating = RIBC.getInternRating(internFloId)
let completedTasksCount = 0;
let totalPoints = 0
for (const task in completedTasks) {
completedTasksCount++
totalPoints += completedTasks[task].points
}
renderElem(getRef('intern_info__wrapper'), html` renderElem(getRef('intern_info__wrapper'), html`
<div id="intern_info__initials" class="intern-card__initials" style=${`--color: var(${getInternColor(internFloId)})`}>${internName.split(' ').map(v => v.charAt(0)).join('')}</div> <div class="flex flex-direction-column align-items-center gap-1 text-center">
<h3 id="intern_info__name">${internName}</h3> <div id="intern_info__initials" class="intern-card__initials" style=${`--color: var(${getInternColor(internFloId)})`}>${internName.split(' ').map(v => v.charAt(0)).join('')}</div>
<h3 id="intern_info__name">${internName}</h3>
</div>
<sm-copy id="intern_info__flo_id" value=${internFloId}></sm-copy> <sm-copy id="intern_info__flo_id" value=${internFloId}></sm-copy>
<div id="update_intern_score" class="flex align-center"> <div id="stats_wrapper">
<svg class="icon icon--star margin-right-0-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"> <path fill="none" d="M0 0h24v24H0z" /> <path d="M12 18.26l-7.053 3.948 1.575-7.928L.587 8.792l8.027-.952L12 .5l3.386 7.34 8.027.952-5.935 5.488 1.575 7.928z" /> </svg> <div id="intern_rating" class="stat">
<h4 id="intern_info__score">0</h4> <div class="stat__display">
<svg class="stat__circle" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="64" cy="64" r="63.5"/> </svg>
<span class="stat__count">${rating}%</h4>
</div>
<p>Rating</p>
</div>
<div id="intern_complete_tasks" class="stat">
<div class="stat__display">
<svg class="stat__circle" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="64" cy="64" r="63.5"/> </svg>
<span class="stat__count">${completedTasksCount}</h4>
</div>
<p>Task completed</p>
</div>
<div id="intern_points" class="stat">
<div class="stat__display">
<svg class="stat__circle" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="64" cy="64" r="63.5"/> </svg>
<span class="stat__count">${totalPoints}</h4>
</div>
<p>Points earned</p>
</div>
</div> </div>
`) `)
openPopup('intern_info_popup'); openPopup('intern_info_popup');
// animate number counting up within 1s from 0 to the intern's score // TODO: popup opening should return a promise
const scoreElem = getRef('intern_info__score') let color = '--green';
const finalScore = RIBC.getInternRating(internFloId) if (rating < 50) {
let score = 0; color = '--danger-color'
const scoreInterval = setInterval(incrementNumber, 600 / finalScore) } else if (rating < 80) {
function incrementNumber() { color = '--orange'
scoreElem.textContent = score;
score++;
if (score > finalScore) {
clearInterval(scoreInterval)
}
} }
incrementNumber() setTimeout(() => {
document.getElementById('intern_rating').style.setProperty('--progress', `${400 - (rating * 4)}`)
document.getElementById('intern_rating').style.setProperty('--rating-color', `var(${color})`)
}, 0);
} }
// opens a popup containing various project information // opens a popup containing various project information
@ -2634,7 +2664,7 @@
<div class="flex flex-direction-column gap-0-5 rating-part" data-intern-id=${intern}> <div class="flex flex-direction-column gap-0-5 rating-part" data-intern-id=${intern}>
<h4>${RIBC.getInternList()[intern]}</h4> <h4>${RIBC.getInternList()[intern]}</h4>
<div class="flex gap-0-5"> <div class="flex gap-0-5">
<sm-input type="number" min="0" max="50" class="flex-1" placeholder="Rate out of 50" error-text="Points must be between 0-50" autofocus required></sm-input> <sm-input type="number" min="0" max="50" class="flex-1" placeholder="Rate out of 50" error-text="Points must be between 0-50" ?autofocus=${index === 0} required></sm-input>
<input class="flex-1" type="date" value=${formatDate(new Date())} placeholder="Completion date" aria-label="Set date of completion" required> <input class="flex-1" type="date" value=${formatDate(new Date())} placeholder="Completion date" aria-label="Set date of completion" required>
</div> </div>
</div> </div>
@ -2646,7 +2676,7 @@
} }
function markTaskAsIncomplete(e) { function markTaskAsIncomplete(e) {
currentTask = e.target.closest('.admin-task'); currentTask = e.target.closest('.admin-task');
getConfirmation('Mark this task as incomplete?', { message: 'Score given to participants regarding this task will also be removed', confirmText: 'Mark as incomplete' }).then(res => { getConfirmation('Mark this task as incomplete?', { message: 'Score given to participants regarding this task will also be removed', confirmText: 'Mark as incomplete', danger: true }).then(res => {
if (res) { if (res) {
RIBC.admin.putTaskStatus('incomplete', appState.params.id, appState.params.branch, currentTask.dataset.taskId) RIBC.admin.putTaskStatus('incomplete', appState.params.id, appState.params.branch, currentTask.dataset.taskId)
// TODO: remove task scores from intern rating // TODO: remove task scores from intern rating
@ -2674,9 +2704,9 @@
document.querySelectorAll('.rating-part').forEach((ratingPart) => { document.querySelectorAll('.rating-part').forEach((ratingPart) => {
const taskId = `${appState.params.id}_${appState.params.branch}_${currentTask.dataset.taskId}`; const taskId = `${appState.params.id}_${appState.params.branch}_${currentTask.dataset.taskId}`;
const internId = ratingPart.dataset.internId; const internId = ratingPart.dataset.internId;
const score = 50 + parseInt(ratingPart.querySelector('sm-input').value.trim()); const points = 50 + parseInt(ratingPart.querySelector('sm-input').value.trim());
const completionDate = ratingPart.querySelector('input').value; const completionDate = ratingPart.querySelector('input').value;
RIBC.admin.addTaskScore(internId, taskId, score, { completionDate }) RIBC.admin.addTaskScore(internId, taskId, points, { completionDate })
}) })
markTaskAsCompleted() markTaskAsCompleted()
closePopup() closePopup()
@ -2758,7 +2788,7 @@
} }
} }
else if (e.target.closest('.assigned-intern button')) { else if (e.target.closest('.assigned-intern button')) {
getConfirmation('Do you want to unassign this intern from this task?', { confirmText: 'Unassign' }).then((result) => { getConfirmation('Do you want to unassign this intern from this task?', { confirmText: 'Unassign', danger: true }).then((result) => {
if (result) { if (result) {
RIBC.admin.unassignInternFromTask(e.target.closest('.assigned-intern').dataset.floId, appState.params.id, appState.params.branch, currentTask.dataset.taskId) RIBC.admin.unassignInternFromTask(e.target.closest('.assigned-intern').dataset.floId, appState.params.id, appState.params.branch, currentTask.dataset.taskId)
notify('Intern removed from the task') notify('Intern removed from the task')
@ -2863,7 +2893,7 @@
}) })
} }
function removeThisTask() { function removeThisTask() {
getConfirmation("Are you sure to delete this task?", { confirmText: 'Delete' }).then((result) => { getConfirmation("Are you sure to delete this task?", { confirmText: 'Delete', danger: true }).then((result) => {
if (result) { if (result) {
RIBC.admin.deleteTaskInMap(appState.params.id, appState.params.branch, currentTask.dataset.taskId) RIBC.admin.deleteTaskInMap(appState.params.id, appState.params.branch, currentTask.dataset.taskId)
const taskId = `${appState.params.id}_${appState.params.branch}_${currentTask.dataset.taskId}`; const taskId = `${appState.params.id}_${appState.params.branch}_${currentTask.dataset.taskId}`;

View File

@ -191,7 +191,7 @@
} }
return true; return true;
} }
Admin.addTaskScore = function (floID, taskId, score, details = {}) { Admin.addTaskScore = function (floID, taskId, points, details = {}) {
if (!(floID in _.internList)) if (!(floID in _.internList))
return false; return false;
if (!_.internRecord[floID]) if (!_.internRecord[floID])
@ -200,14 +200,14 @@
completedTasks: {}, completedTasks: {},
} }
_.internRecord[floID].completedTasks[taskId] = { _.internRecord[floID].completedTasks[taskId] = {
score, points,
...details ...details
}; };
let totalScore = 0; let totalScore = 0;
for (const taskId in _.internRecord[floID].completedTasks) { for (const taskId in _.internRecord[floID].completedTasks) {
totalScore += _.internRecord[floID].completedTasks[taskId].score; totalScore += _.internRecord[floID].completedTasks[taskId].points;
} }
_.internRating[floID] = totalScore; _.internRating[floID] = parseInt(totalScore / (Object.keys(_.internRecord[floID].completedTasks).length || 1));
return true; return true;
} }
Admin.setInternStatus = function (floID, active = true) { Admin.setInternStatus = function (floID, active = true) {

2
scripts/ribc.min.js vendored

File diff suppressed because one or more lines are too long