Feature update

-- added option to handle intern not completing a task
-- added option to view all the completed task by an intern
-- added option to see task updates by an intern
This commit is contained in:
sairaj mote 2022-11-19 01:34:59 +05:30
parent f7c0cc7609
commit 6042efff99
6 changed files with 312 additions and 106 deletions

View File

@ -17,7 +17,7 @@ body {
body {
--accent-color: #3d5afe;
--secondary-color: #ffac2e;
--text-color: 20, 20, 20;
--text-color: 34, 34, 34;
--foreground-color: 252, 253, 255;
--background-color: 241, 243, 248;
--danger-color: rgb(255, 75, 75);
@ -72,11 +72,6 @@ body[data-theme=dark] ::-webkit-calendar-picker-indicator {
filter: invert(1);
}
.overpass {
font-weight: 700;
font-family: "Overpass", sans-serif;
}
h1,
h2,
h3,
@ -85,7 +80,7 @@ h5,
h6 {
letter-spacing: -0.01em;
font-weight: 700;
font-family: "Overpass", sans-serif;
font-family: "Rubik", sans-serif;
}
p,
@ -143,7 +138,7 @@ button,
padding: 0.8rem;
border-radius: 0.3rem;
justify-content: center;
text-transform: capitalize;
flex-shrink: 0;
}
button:focus-visible,
.button:focus-visible {
@ -302,6 +297,9 @@ sm-chip {
collapsed-text {
--button-background: rgba(var(--foreground-color), 1);
}
collapsed-text:not([open]) {
cursor: pointer;
}
ul {
list-style: none;
@ -495,6 +493,10 @@ ul {
margin-right: 0.5rem;
}
.margin-right-1 {
margin-right: 1rem;
}
.margin-left-0-5 {
margin-left: 0.5rem;
}
@ -777,12 +779,14 @@ ul {
padding: 2rem max(1rem, 4vw);
border-radius: 1rem;
align-items: center;
width: min(100%, 56rem);
}
.landing__card h1 {
font-size: max(1.5rem, 2vw);
font-size: max(2rem, 3vw);
}
.landing__card img {
width: min(100%, 16rem);
height: auto;
}
.landing__card:first-of-type {
background-color: #2a2c35;
@ -793,7 +797,7 @@ ul {
}
#landing_tasks_wrapper {
margin: 0 auto;
margin: 1rem auto 0 auto;
width: min(100%, 48rem);
}
@ -996,6 +1000,7 @@ ul {
.display-task__title {
color: rgba(var(--text-color), 0.9);
font-size: 1.2rem;
font-weight: 500;
}
.display-task__description {
display: -webkit-box;
@ -1370,8 +1375,35 @@ ul {
color: var(--nice-blue);
}
#project_info {
flex-direction: column;
#intern_info__wrapper {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
#intern_info__wrapper > * {
flex: 1;
}
.intern_info__task {
display: grid;
align-items: center;
width: 100%;
gap: 0.3rem;
margin-top: 1rem;
padding: 0.5rem;
border-radius: 0.5rem;
background-color: rgba(var(--text-color), 0.06);
grid-template-columns: minmax(0, 1fr) auto;
}
.intern_info__task h4 {
grid-column: 1/-1;
}
.intern_info__task p {
font-weight: 500;
}
.intern_info__task time {
font-size: 0.9rem;
color: rgba(var(--text-color), 0.8);
}
.branch-button {
@ -1478,25 +1510,41 @@ ul {
gap: 0.5rem;
}
.assigned-interns .assigned-intern {
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
display: flex;
font-size: 0.8rem;
padding: 0.4rem;
padding: 0 0.4rem;
border-radius: 0.3rem;
border: 1px solid rgba(var(--text-color), 0.24);
align-items: center;
white-space: nowrap;
text-transform: capitalize;
}
.assigned-interns .assigned-intern button {
padding: 0.2rem;
margin-right: -0.2rem;
.assigned-interns .assigned-intern .unassign-intern-button {
padding: 0.4rem;
margin-right: -0.4rem;
}
.assigned-interns .assigned-intern button .icon {
height: 1rem;
width: 1rem;
.menu {
position: absolute;
z-index: 5;
top: calc(100% + 0.3rem);
background-color: rgba(var(--foreground-color), 1);
border-radius: 0.5rem;
padding: 0.2rem;
box-shadow: 0 0.3rem 1.5rem rgba(0, 0, 0, 0.2);
left: 0;
min-width: 100%;
border: solid thin rgba(var(--text-color), 0.2);
}
.menu__item button {
padding: 0.6rem 0.8rem;
color: rgba(var(--text-color), 0.8);
width: 100%;
justify-content: flex-start;
}
#task_context {
@ -1984,7 +2032,8 @@ input[type=date]:focus {
padding: 1.5rem 8vw;
}
.landing__card {
grid-template-columns: 1fr auto;
grid-template-columns: -webkit-max-content 1fr;
grid-template-columns: max-content 1fr;
}
.landing__card img {
justify-self: flex-end;
@ -2061,6 +2110,9 @@ input[type=date]:focus {
#all_interns_page__header {
grid-template-columns: 1fr auto;
}
#intern_info_popup {
--width: min(64rem, 100%);
}
#admin_page {
padding: 0;
}
@ -2090,6 +2142,9 @@ input[type=date]:focus {
grid-template-columns: 4rem minmax(0, 1fr);
grid-template-areas: "main-header main-header" "main-nav sub-pages";
}
.display-task .display-task__detail:last-of-type {
margin-left: auto;
}
#post_update_popup {
--width: 28rem;
}

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,7 @@ body {
body {
--accent-color: #3d5afe;
--secondary-color: #ffac2e;
--text-color: 20, 20, 20;
--text-color: 34, 34, 34;
--foreground-color: 252, 253, 255;
--background-color: 241, 243, 248;
--danger-color: rgb(255, 75, 75);
@ -73,10 +73,6 @@ body[data-theme="dark"] {
filter: invert(1);
}
}
.overpass {
font-weight: 700;
font-family: "Overpass", sans-serif;
}
h1,
h2,
h3,
@ -85,7 +81,7 @@ h5,
h6 {
letter-spacing: -0.01em;
font-weight: 700;
font-family: "Overpass", sans-serif;
font-family: "Rubik", sans-serif;
}
p,
strong {
@ -131,8 +127,7 @@ button,
padding: 0.8rem;
border-radius: 0.3rem;
justify-content: center;
text-transform: capitalize;
flex-shrink: 0;
&:focus-visible {
outline: var(--accent-color) solid medium;
}
@ -282,6 +277,9 @@ sm-chip {
collapsed-text {
--button-background: rgba(var(--foreground-color), 1);
&:not([open]) {
cursor: pointer;
}
}
ul {
@ -466,6 +464,9 @@ ul {
.margin-right-0-5 {
margin-right: 0.5rem;
}
.margin-right-1 {
margin-right: 1rem;
}
.margin-left-0-5 {
margin-left: 0.5rem;
@ -746,11 +747,13 @@ ul {
padding: 2rem max(1rem, 4vw);
border-radius: 1rem;
align-items: center;
width: min(100%, 56rem);
h1 {
font-size: max(1.5rem, 2vw);
font-size: max(2rem, 3vw);
}
img {
width: min(100%, 16rem);
height: auto;
}
&:first-of-type {
background-color: #2a2c35;
@ -761,7 +764,7 @@ ul {
}
}
#landing_tasks_wrapper {
margin: 0 auto;
margin: 1rem auto 0 auto;
width: min(100%, 48rem);
}
#display_task_search_wrapper {
@ -968,6 +971,7 @@ ul {
&__title {
color: rgba(var(--text-color), 0.9);
font-size: 1.2rem;
font-weight: 500;
}
&__description {
display: -webkit-box;
@ -1340,8 +1344,34 @@ ul {
color: var(--nice-blue);
}
}
#project_info {
flex-direction: column;
#intern_info__wrapper {
display: flex;
flex-wrap: wrap;
gap: 1rem;
& > * {
flex: 1;
}
}
.intern_info__task {
display: grid;
align-items: center;
width: 100%;
gap: 0.3rem;
margin-top: 1rem;
padding: 0.5rem;
border-radius: 0.5rem;
background-color: rgba(var(--text-color), 0.06);
grid-template-columns: minmax(0, 1fr) auto;
h4 {
grid-column: 1/-1;
}
p {
font-weight: 500;
}
time {
font-size: 0.9rem;
color: rgba(var(--text-color), 0.8);
}
}
.branch-button {
@ -1448,22 +1478,39 @@ ul {
flex-wrap: wrap;
gap: 0.5rem;
.assigned-intern {
position: relative;
user-select: none;
display: flex;
font-size: 0.8rem;
padding: 0.4rem;
padding: 0 0.4rem;
border-radius: 0.3rem;
border: 1px solid rgba(var(--text-color), 0.24);
align-items: center;
white-space: nowrap;
text-transform: capitalize;
.unassign-intern-button {
padding: 0.4rem;
margin-right: -0.4rem;
}
}
}
.menu {
position: absolute;
z-index: 5;
top: calc(100% + 0.3rem);
background-color: rgba(var(--foreground-color), 1);
border-radius: 0.5rem;
padding: 0.2rem;
box-shadow: 0 0.3rem 1.5rem rgba(0 0 0 / 0.2);
left: 0;
min-width: 100%;
border: solid thin rgba(var(--text-color), 0.2);
&__item {
button {
padding: 0.2rem;
margin-right: -0.2rem;
.icon {
height: 1rem;
width: 1rem;
}
padding: 0.6rem 0.8rem;
color: rgba(var(--text-color), 0.8);
width: 100%;
justify-content: flex-start;
}
}
}
@ -1937,7 +1984,7 @@ input[type="date"] {
}
}
.landing__card {
grid-template-columns: 1fr auto;
grid-template-columns: max-content 1fr;
img {
justify-self: flex-end;
}
@ -2019,6 +2066,9 @@ input[type="date"] {
#all_interns_page__header {
grid-template-columns: 1fr auto;
}
#intern_info_popup {
--width: min(64rem, 100%);
}
#admin_page {
padding: 0;
@ -2050,6 +2100,11 @@ input[type="date"] {
grid-template-columns: 4rem minmax(0, 1fr);
grid-template-areas: "main-header main-header" "main-nav sub-pages";
}
.display-task {
.display-task__detail:last-of-type {
margin-left: auto;
}
}
#post_update_popup {
--width: 28rem;

View File

@ -11,7 +11,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Overpass:ital,wght@0,700;0,900;1,700;1,900&family=Roboto:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap"
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&family=Rubik:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap"
rel="stylesheet">
<script src="components.js" defer></script>
<script id="floGlobals">
@ -86,15 +86,15 @@
d="M27.14,30.86c-.74-2.48-3-4.36-8.25-6.94a20,20,0,0,1-4.2-2.49,6,6,0,0,1-1.25-1.67,4,4,0,0,1,0-2.26c.37-1.08.79-1.57,3.89-4.55a11.66,11.66,0,0,0,3.34-4.67,6.54,6.54,0,0,0,.05-2.82C20,3.6,18.58,2,16.16.49c-.89-.56-1.29-.64-1.3-.24a3,3,0,0,1-.3.72l-.3.55L13.42.94C13,.62,12.4.26,12.19.15c-.4-.2-.73-.18-.72.05a9.39,9.39,0,0,1-.61,1.33s-.14,0-.27-.13C8.76.09,8-.27,8,.23A11.73,11.73,0,0,1,6.76,2.6C4.81,5.87,2.83,7.49.77,7.49c-.89,0-.88,0-.61,1,.22.85.33.92,1.09.69A5.29,5.29,0,0,0,3,8.33c.23-.17.45-.29.49-.26a2,2,0,0,1,.22.63A1.31,1.31,0,0,0,4,9.34a5.62,5.62,0,0,0,2.27-.87L7,8l.13.55c.19.74.32.82,1,.65a7.06,7.06,0,0,0,3.46-2.47l.6-.71-.06.64c-.17,1.63-1.3,3.42-3.39,5.42L6.73,14c-3.21,3.06-3,5.59.6,8a46.77,46.77,0,0,0,4.6,2.41c.28.13,1,.52,1.59.87,3.31,2,4.95,3.92,4.95,5.93a2.49,2.49,0,0,0,.07.77h0c.09.09,0,.1.9-.14a2.61,2.61,0,0,0,.83-.32,3.69,3.69,0,0,0-.55-1.83A11.14,11.14,0,0,0,17,26.81a35.7,35.7,0,0,0-5.1-2.91C9.37,22.64,8.38,22,7.52,21.17a3.53,3.53,0,0,1-1.18-2.48c0-1.38.71-2.58,2.5-4.23,2.84-2.6,3.92-3.91,4.67-5.65a3.64,3.64,0,0,0,.42-2A3.37,3.37,0,0,0,13.61,5l-.32-.74.29-.48c.17-.27.37-.63.46-.8l.15-.3.44.64a5.92,5.92,0,0,1,1,2.81,5.86,5.86,0,0,1-.42,1.94c0,.12-.12.3-.15.4a9.49,9.49,0,0,1-.67,1.1,28,28,0,0,1-4,4.29C8.62,15.49,8.05,16.44,8,17.78a3.28,3.28,0,0,0,1.11,2.76c.95,1,2.07,1.74,5.25,3.32,3.64,1.82,5.22,2.9,6.41,4.38A4.78,4.78,0,0,1,21.94,31a3.21,3.21,0,0,0,.14.92,1.06,1.06,0,0,0,.43-.05l.83-.22.46-.12-.06-.46c-.21-1.53-1.62-3.25-3.94-4.8a37.57,37.57,0,0,0-5.22-2.82A13.36,13.36,0,0,1,11,21.19a3.36,3.36,0,0,1-.8-4.19c.41-.85.83-1.31,3.77-4.15,2.39-2.31,3.43-4.13,3.43-6a5.85,5.85,0,0,0-2.08-4.29c-.23-.21-.44-.43-.65-.65A2.5,2.5,0,0,1,15.27.69a10.6,10.6,0,0,1,2.91,2.78A4.16,4.16,0,0,1,19,6.16a4.91,4.91,0,0,1-.87,3c-.71,1.22-1.26,1.82-4.27,4.67a9.47,9.47,0,0,0-2.07,2.6,2.76,2.76,0,0,0-.33,1.54,2.76,2.76,0,0,0,.29,1.47c.57,1.21,2.23,2.55,4.65,3.73a32.41,32.41,0,0,1,5.82,3.24c2.16,1.6,3.2,3.16,3.2,4.8a1.94,1.94,0,0,0,.09.76,4.54,4.54,0,0,0,1.66-.4C27.29,31.42,27.29,31.37,27.14,30.86ZM6.1,7h0a3.77,3.77,0,0,1-1.46.45L4,7.51l.68-.83a25.09,25.09,0,0,0,3-4.82A12,12,0,0,1,8.28.76c.11-.12.77.32,1.53,1l.63.58-.57.84A10.34,10.34,0,0,1,6.1,7Zm5.71-1.78A9.77,9.77,0,0,1,9.24,7.18h0a5.25,5.25,0,0,1-1.17.28l-.58,0,.65-.78a21.29,21.29,0,0,0,2.1-3.12c.22-.41.42-.76.44-.79s.5.43.9,1.24L12,5ZM13.41,3a2.84,2.84,0,0,1-.45.64,11,11,0,0,1-.9-.91l-.84-.9.19-.45c.34-.79.39-.8,1-.31A9.4,9.4,0,0,1,13.8,2.33q-.18.34-.39.69Z">
</path>
</svg>
<h4>RanchiMall Internships</h4>
<h4>RanchiMall</h4>
</div>
<theme-toggle></theme-toggle>
</header>
<div id="landing" class="grid inner-page hidden">
<div class="gap-1-5 landing__card">
<div class="grid gap-1-5">
<h1 class="overpass" style="max-width: 26rem;">
Blockchain based <br> Internship Platform
<h1>
Blockchain Internships
</h1>
<div class="flex gap-0-3">
<a href="#/sign_up" class="button"
@ -105,7 +105,8 @@
</div>
<img src="assets/working-intern.svg" alt="">
</div>
<div id="landing_tasks_wrapper" class="flex flex-direction-column justify-content-center"></div>
<div id="landing_tasks_wrapper" class="flex flex-direction-column justify-content-center">
</div>
</div>
<article id="sign_in" class="inner-page hidden">
<section>
@ -842,6 +843,7 @@
if (popupStack.peek() === undefined)
return;
popupStack.peek().popup.hide(options)
zIndex--
}
@ -1166,6 +1168,7 @@
hideTaskDetails()
break;
case 'updates_page': {
closePopup()
if (!getRef('updates_page__project_selector').children.length) {
renderProjectSelectorOptions()
renderInternSelectorOptions()
@ -1746,7 +1749,7 @@
${reward ? html`
<div class="display-task__detail">
<span class="display-task__detail__title">Reward: </span>
<span class="display-task__detail__value" style="color: var(--green)">₹${reward}</span>
<span class="display-task__detail__value">₹${reward}</span>
</div>
`: ''}
</div>
@ -1791,11 +1794,11 @@
return html`
<div id="display_task_search_wrapper" class="flex flex-direction-column gap-1">
<div class="flex align-center gap-1 flex-wrap space-between">
<h3>
Apply for available tasks
</h3>
<h2>
Apply below
</h2>
${(filtered.length > 0 || searchQuery) ? html`
<sm-input id="task_search_input" oninput="filterTasks(event)" placeholder="Search keywords" type="search">
<sm-input id="task_search_input" oninput="filterTasks(event)" placeholder="Find tasks" type="search">
<svg slot="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"> <circle cx="11" cy="11" r="8"></circle> <line x1="21" y1="21" x2="16.65" y2="16.65"></line> </svg>
</sm-input>
`: ''}
@ -1879,7 +1882,7 @@
${reward ? html`
<div class="display-task__detail">
<span class="display-task__detail__title">Reward: </span>
<span class="display-task__detail__value" style="color: var(--green)">₹${reward}</span>
<span class="display-task__detail__value">₹${reward}</span>
</div>
`: ''}
${branchesButtons.length ? html`<div class="task__branch_container">${branchesButtons}</div>` : ''}
@ -1956,7 +1959,7 @@
assignedInternCard(internFloId, options) {
let optionsButton
if (options) {
optionsButton = html` <button>
optionsButton = html` <button class="unassign-intern-button">
<svg class="icon" 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 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"/></svg>
</button> `;
}
@ -1967,6 +1970,22 @@
</span>
`
},
internSpecificTasks(internId) {
const { completedTasks } = RIBC.getInternRecord(internId)
if (getObjLength(completedTasks) === 0) return false
return Object.keys(completedTasks).map(task => {
const { points, completionDate } = completedTasks[task];
const { title } = RIBC.getAllTasks()[task];
return html`
<div class="intern_info__task">
<h4>${title}</h4>
<time>${getFormattedTime(completionDate, 'date-only')}</time>
<p class="flex align-center gap-0-3">
${points}
<svg class="icon icon--star" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"> <path fill="none" d="M0 0h24v24H0z"></path> <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"></path> </svg>
</p>
</div>`})
},
adminTask(task) {
const assignedInterns = RIBC.getAssignedInterns(appState.params.id, appState.params.branch, task)
const status = RIBC.getTaskStatus(appState.params.id, appState.params.branch, task)
@ -2016,7 +2035,7 @@
<h4 class="task-title capitalize" data-editable data-edit-field="title">${title}</h4>
<div class="assigned-interns">
<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" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><path d="M20,9V6h-2v3h-3v2h3v3h2v-3h3V9H20z M9,12c2.21,0,4-1.79,4-4c0-2.21-1.79-4-4-4S5,5.79,5,8C5,10.21,6.79,12,9,12z M9,6 c1.1,0,2,0.9,2,2c0,1.1-0.9,2-2,2S7,9.1,7,8C7,6.9,7.9,6,9,6z M15.39,14.56C13.71,13.7,11.53,13,9,13c-2.53,0-4.71,0.7-6.39,1.56 C1.61,15.07,1,16.1,1,17.22V20h16v-2.78C17,16.1,16.39,15.07,15.39,14.56z M15,18H3v-0.78c0-0.38,0.2-0.72,0.52-0.88 C4.71,15.73,6.63,15,9,15c2.37,0,4.29,0.73,5.48,1.34C14.8,16.5,15,16.84,15,17.22V18z"/></g></svg>
Assign intern
</button>
${assignedInternsCards}
@ -2410,7 +2429,7 @@
${reward ? html`
<div class="display-task__detail">
<span class="display-task__detail__title">Reward: </span>
<span class="display-task__detail__value" style="color: var(--green)">₹${reward}</span>
<span class="display-task__detail__value">₹${reward}</span>
</div>
`: ''}
</div>
@ -2574,38 +2593,51 @@
initials += splitName[splitName.length - 1][0]
}
renderElem(getRef('intern_info__wrapper'), html`
<div class="flex flex-direction-column align-items-center gap-1-5">
<div id="intern_info__initials" class="intern-card__initials" style=${`--color: var(${getInternColor(internFloId)})`}>${initials}</div>
<div class="flex flex-direction-column align-items-center gap-0-5">
<div class="flex align-center gap-0-5">
<h3 id="intern_info__name" class="text-center">${internName}</h3>
${userType === 'admin' ? html`<button id="edit_intern_name" class="button button--small button--colored" onclick=${toggleInternNameEditing}>Edit</button> ` : ''}
<div id="intern_info__left">
<div class="flex flex-direction-column align-items-center gap-1-5">
<div id="intern_info__initials" class="intern-card__initials" style=${`--color: var(${getInternColor(internFloId)})`}>${initials}</div>
<div class="flex flex-direction-column align-items-center gap-0-5">
<div class="flex align-center gap-0-5">
<h3 id="intern_info__name" class="text-center">${internName}</h3>
${userType === 'admin' ? html`<button id="edit_intern_name" class="button button--small button--colored" onclick=${toggleInternNameEditing}>Edit</button> ` : ''}
</div>
<sm-copy id="intern_info__flo_id" value=${internFloId}></sm-copy>
</div>
${joined ? html`<p>Joined on ${getFormattedTime(joined, 'date-only')}</p>` : ''}
</div>
<div id="stats_wrapper">
<div id="intern_rating" 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">${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>
<sm-copy id="intern_info__flo_id" value=${internFloId}></sm-copy>
</div>
${joined ? html`<p>Joined on ${getFormattedTime(joined, 'date-only')}</p>` : ''}
</div>
<div id="stats_wrapper">
<div id="intern_rating" 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">${rating}%</h4>
<div id="intern_info__right">
<div id="intern_info__tasks">
<div class="flex align-center space-between gap-1">
<h3>Tasks completed</h3>
<a href=${`#/updates_page?projectCode=all&internId=${internFloId}`} class="button button--small button--colored">See updates</a>
</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 id="intern_info__tasks_list">
${render.internSpecificTasks(internFloId) || html`<p>No tasks completed yet</p>`}
</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>
`)
@ -2781,7 +2813,7 @@
const subjectivePoints = parseFloat(ratingPart.querySelector('sm-input').value.trim());
const points = completionPoints + quicknessPoints + subjectivePoints;
const completionDate = new Date(ratingPart.querySelector('input').value).getTime();
RIBC.admin.addTaskScore(internId, taskId, points, { completionDate })
RIBC.admin.addCompletedTask(internId, taskId, points, { completionDate })
})
markTaskAsCompleted()
closePopup()
@ -2824,7 +2856,7 @@
}
if (e.target.closest('.task-option')) {
const optionButton = e.target.closest('.task-option')
getRef('task_context').setAttribute('style', `top: ${optionButton.offsetTop}px`)
getRef('task_context').setAttribute('style', `top: ${optionButton.offsetTop}px;`)
getRef('task_context').classList.remove('hidden')
getRef('task_context').animate([
{
@ -2862,14 +2894,41 @@
});
}
}
else if (e.target.closest('.assigned-intern button')) {
getConfirmation('Do you want to unassign this intern from this task?', { confirmText: 'Unassign', danger: true }).then((result) => {
if (result) {
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')
renderBranchTasks()
}
else if (e.target.closest('.unassign-intern-button')) {
const internCard = e.target.closest('.assigned-intern')
const internId = internCard.dataset.floId
const contentMenu = html.node`
<ul class="menu" data-flo-id=${internId}>
<li class="menu__item">
<button onclick=${markAsFailed}>
<svg class="icon margin-right-1" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M14.59 8L12 10.59 9.41 8 8 9.41 10.59 12 8 14.59 9.41 16 12 13.41 14.59 16 16 14.59 13.41 12 16 9.41 14.59 8zM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/></svg>
Mark as failed
</button>
</li>
<li class="menu__item">
<button onclick=${unassignIntern}>
<svg class="icon margin-right-1" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><g><path d="M14,8c0-2.21-1.79-4-4-4C7.79,4,6,5.79,6,8c0,2.21,1.79,4,4,4C12.21,12,14,10.21,14,8z M12,8c0,1.1-0.9,2-2,2 c-1.1,0-2-0.9-2-2s0.9-2,2-2C11.1,6,12,6.9,12,8z"/><path d="M2,18v2h16v-2c0-2.66-5.33-4-8-4C7.33,14,2,15.34,2,18z M4,18c0.2-0.71,3.3-2,6-2c2.69,0,5.77,1.28,6,2H4z"/><rect height="2" width="6" x="17" y="10"/></g></g></svg>
Unassign
</button>
</li>
</ul>
`;
internCard.appendChild(contentMenu)
contentMenu.animate(slideInDown, {
duration: floGlobals.prefersReducedMotion ? 0 : 200,
easing: 'ease'
})
.onfinish = () => {
contentMenu.firstElementChild.focus()
document.addEventListener("click", function (e) {
contentMenu.animate(slideOutUp, {
duration: floGlobals.prefersReducedMotion ? 0 : 100,
easing: 'ease'
}).onfinish = () => {
contentMenu.remove()
}
}, { once: true });
}
}
else if (e.target.closest('.cancel-task-button')) {
const card = e.target.closest('.temp-task')
@ -2910,6 +2969,30 @@
notify('Task added to current branch', 'success')
}
})
function markAsFailed(e) {
getConfirmation('Failed to complete task?', { message: `This will unassign intern and mark this task as failed in their record.`, confirmText: 'Mark as failed', danger: true }).then((result) => {
if (result) {
const internId = e.target.closest('.menu').dataset.floId
const taskId = `${appState.params.id}_${appState.params.branch}_${currentTask.dataset.taskId}`
const done = RIBC.admin.addFailedTask(internId, taskId)
if (done) {
notify('Task marked as failed', 'success')
renderBranchTasks()
} else {
notify('Failed to mark task as failed', 'error')
}
}
})
}
function unassignIntern(e) {
getConfirmation('Unassign intern from task?', { confirmText: 'Unassign', danger: true }).then((result) => {
if (result) {
RIBC.admin.unassignInternFromTask(e.target.closest('.menu').dataset.floId, `${appState.params.id}_${appState.params.branch}_${currentTask.dataset.taskId}`)
notify('Intern removed from the task')
renderBranchTasks()
}
})
}
function addPlaceholderTask() {
const categories = [];
let first = true;

View File

@ -102,8 +102,8 @@
Ribc.getTaskDetails = (project, branch, task) => _.projectTaskDetails[project + "_" + branch + "_" + task];
Ribc.getTaskStatus = (project, branch, task) => _.projectTaskStatus[project + "_" + branch + "_" + task];
Ribc.getInternList = () => _.internList;
Ribc.getInternRating = (floID) => _.internRating[floID];
Ribc.getInternRecord = (floID) => _.internRecord[floID];
Ribc.getInternRating = (floID) => _.internRating[floID] || 0;
Ribc.getInternRecord = (floID) => _.internRecord[floID] || {};
Ribc.getAssignedInterns = (projectCode, branch, taskNumber) => _.internsAssigned[projectCode + "_" + branch + "_" + taskNumber] || {};
Ribc.getAllTasks = () => _.projectTaskDetails
Ribc.getDisplayedTasks = () => floGlobals.appObjects.RIBC.displayedTasks || [];
@ -166,6 +166,7 @@
active: true,
joined: Date.now(),
completedTasks: {},
failedTasks: {},
}
}
const addIntern = Admin.addIntern = function (floID, internName) {
@ -189,25 +190,38 @@
delete _.internList[floID]
delete _.internRating[floID]
delete _.internRecord[floID]
for (const taskId in _.projectTaskDetails) {
if (_.internsAssigned[taskId].hasOwnProperty(floID))
delete _.internsAssigned[taskId][floID]
for (const taskKey in _.projectTaskDetails) {
if (_.internsAssigned[taskKey].hasOwnProperty(floID))
delete _.internsAssigned[taskKey][floID]
}
return true;
}
Admin.addTaskScore = function (floID, taskId, points, details = {}) {
Admin.addCompletedTask = function (floID, taskKey, points, details = {}) {
if (!(floID in _.internList))
return false;
Admin.initInternRecord(floID)
_.internRecord[floID].completedTasks[taskId] = {
_.internRecord[floID].completedTasks[taskKey] = {
points,
...details
};
// calculate rating
let totalScore = 0;
for (const taskId in _.internRecord[floID].completedTasks) {
totalScore += _.internRecord[floID].completedTasks[taskId].points;
for (const taskKey in _.internRecord[floID].completedTasks) {
totalScore += _.internRecord[floID].completedTasks[taskKey].points;
}
_.internRating[floID] = parseInt(totalScore / (Object.keys(_.internRecord[floID].completedTasks).length || 1));
const completedTasks = Object.keys(_.internRecord[floID].completedTasks).length;
const failedTasks = Object.keys(_.internRecord[floID].failedTasks).length;
_.internRating[floID] = parseInt(totalScore / (completedTasks + failedTasks) || 1);
return true;
}
Admin.addFailedTask = function (floID, taskKey, details = {}) {
if (!(floID in _.internList))
return false;
Admin.initInternRecord(floID)
_.internRecord[floID].failedTasks[taskKey] = {
...details
};
Admin.unassignInternFromTask(floID, taskKey);
return true;
}
Admin.setInternStatus = function (floID, active = true) {
@ -265,10 +279,9 @@
return false
}
Admin.unassignInternFromTask = function (floID, projectCode, branch, taskNumber) {
const key = projectCode + "_" + branch + "_" + taskNumber
if (_.internsAssigned[key] && _.internsAssigned[key].hasOwnProperty(floID)) {
delete _.internsAssigned[key][floID]
Admin.unassignInternFromTask = function (floID, taskKey) {
if (_.internsAssigned[taskKey] && _.internsAssigned[taskKey].hasOwnProperty(floID)) {
delete _.internsAssigned[taskKey][floID]
return true
} else
return false

2
scripts/ribc.min.js vendored

File diff suppressed because one or more lines are too long