ribc/index.html
2022-09-13 03:59:34 +05:30

2745 lines
158 KiB
HTML

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>RIBC</title>
<meta name="description" content="Web app for managing interns and projects">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/main.min.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet">
<script src="components.js" defer></script>
<script id="floGlobals">
/* Constants for FLO blockchain operations !!Make sure to add this at begining!! */
const floGlobals = {
blockchain: "FLO",
adminID: "FKAEdnPfjXLHSYwrXQu377ugN4tXU7VGdf", // "FMeiptdJNtYQEtzyYAVNP8fjsDJ1i4EPfE",
application: "TEST_MODE" // "InternManagement"
}
</script>
<script src="scripts/lib.js"></script>
<script src="scripts/floCrypto.js"></script>
<script src="scripts/floBlockchainAPI.js"></script>
<script src="scripts/compactIDB.js"></script>
<script src="scripts/floCloudAPI.js"></script>
<script src="scripts/floDapps.js"></script>
<script src="https://unpkg.com/uhtml@3.0.1/es.js"></script>
<script src="https://cdn.jsdelivr.net/npm/fuse.js@6.4.6" defer></script>
<script src="https://cdn.jsdelivr.net/npm/dompurify@2.3.0/dist/purify.min.js" defer></script>
<script src="scripts/ribc.js"></script>
<script id="onLoadStartUp">
function onLoadStartUp() {
floDapps.setCustomPrivKeyInput(customSignIn);
floDapps.launchStartUp().then(result => {
loader('show')
console.log(result)
console.log(`Welcome FLO_ID: ${myFloID}`)
RIBC.init(floGlobals.subAdmins.includes(myFloID)).then(result => {
console.log(result)
renderAllElements()
showPage(window.location.hash, { firstLoad: true })
loader('hide')
}).catch(error => console.error(error))
}).catch(error => console.error(error))
}
</script>
</head>
<body onload="onLoadStartUp()">
<sm-notifications id="notification_drawer"></sm-notifications>
<sm-popup id="confirmation_popup">
<h4 id="confirm_title"></h4>
<p id="confirm_message"></p>
<div class="flex align-center">
<sm-button variant="no-outline" class="cancel-btn">Cancel</sm-button>
<sm-button variant="no-outline" class="submit-btn">OK</button>
</div>
</sm-popup>
<section id="loading_page">
<sm-spinner></sm-spinner>
<h4 class="loading-message">Loading RIBC</h4>
<p>
A FLO Blockchain App by RanchiMall
</p>
<footer id="loading_page__footer">
<svg class="icon" viewBox="0 0 118.35 118.35">
<title>RanchiMall logo</title>
<style type="text/css">
.outer-layer {
fill: #CB1F1F;
}
.inner-layer {
fill: #FF4E4E;
}
.logo-layer {
fill: #FFFFFF;
}
</style>
<g id="Group_329_1_" transform="translate(-1783.76 -17.542)">
<g id="Group_330_1_" transform="matrix(0.966, 0.259, -0.259, 0.966, 1814.879, 17.541)">
<path id="Path_180_1_" class="outer-layer" d="M63.89-1.96c-10.68-4.42-9.91-8.27-21.47-8.27s-10.8,3.85-21.48,8.27S8.08-0.8-0.09,7.37
S-5,17.73-9.43,28.41s-8.27,9.92-8.27,21.47s3.85,10.8,8.27,21.48s1.16,12.86,9.34,21.03s10.35,4.91,21.03,9.33
S30.86,110,42.42,110s10.79-3.85,21.47-8.27s12.86-1.16,21.04-9.33s4.91-10.36,9.33-21.04s8.27-9.92,8.27-21.47
s-3.85-10.79-8.27-21.47s-1.23-12.71-9.29-21C77.3-0.46,74.57,2.47,63.89-1.96z" />
</g>
<g id="Group_326_1_" transform="translate(1810.58 44.362)">
<path id="Path_180-2_1_" class="inner-layer" d="M49.08-8.03c-8.31-3.44-7.72-6.44-16.72-6.44s-8.41,3-16.72,6.44S5.62-7.13-0.74-0.76
S-4.56,7.3-8.01,15.61s-6.44,7.72-6.44,16.72s3,8.4,6.44,16.72s0.9,10.01,7.27,16.38s8.06,3.82,16.38,7.27s7.72,6.44,16.72,6.44
s8.4-3,16.72-6.44s10.01-0.9,16.38-7.27s3.82-8.06,7.27-16.38s6.44-7.72,6.44-16.72s-3-8.4-6.44-16.72s-0.96-9.9-7.24-16.35
C59.52-6.86,57.39-4.59,49.08-8.03z" />
</g>
<path id="Path_5_2_" class="logo-layer" d="M1864.51,98.2c-1.11-3.75-4.48-6.58-12.45-10.48c-2.26-0.99-4.39-2.26-6.34-3.76
c-0.78-0.72-1.42-1.57-1.9-2.52c-0.32-1.12-0.33-2.3-0.02-3.42c0.57-1.64,1.2-2.37,5.88-6.87c2.81-2.7,4.23-4.69,5.05-7.06
c0.34-1.4,0.37-2.86,0.08-4.27c-1.04-2.81-3.22-5.18-6.88-7.5c-1.35-0.85-1.95-0.97-1.96-0.37c-0.1,0.39-0.25,0.76-0.45,1.1
l-0.45,0.83l-1.27-0.88c-0.7-0.49-1.54-1.02-1.86-1.19c-0.59-0.3-1.09-0.28-1.09,0.07c-0.25,0.7-0.56,1.37-0.92,2.01
c-0.02,0.03-0.2-0.05-0.4-0.2c-2.77-1.98-3.97-2.52-3.97-1.77c-0.47,1.26-1.08,2.46-1.81,3.59c-2.94,4.94-5.94,7.38-9.05,7.38
c-1.35,0-1.33-0.03-0.93,1.53c0.33,1.28,0.5,1.39,1.65,1.04c0.96-0.27,1.85-0.71,2.65-1.31c0.35-0.25,0.69-0.43,0.75-0.39
c0.16,0.3,0.27,0.62,0.32,0.95c0.05,0.36,0.2,0.69,0.43,0.96c1.22-0.18,2.39-0.62,3.42-1.31l1.04-0.66l0.21,0.83
c0.28,1.11,0.48,1.24,1.48,0.98c2.09-0.67,3.92-1.97,5.24-3.73l0.91-1.07l-0.1,0.96c-0.26,2.46-1.95,5.16-5.12,8.18l-2.95,2.81
c-4.85,4.63-4.57,8.45,0.91,12.07c2.25,1.34,4.57,2.56,6.95,3.64c0.43,0.19,1.51,0.79,2.41,1.31c5,2.96,7.48,5.93,7.48,8.96
c-0.03,0.39,0.01,0.78,0.1,1.16l0,0c0.14,0.14,0.05,0.15,1.37-0.21c0.44-0.09,0.87-0.26,1.26-0.49c-0.03-0.98-0.32-1.93-0.84-2.76
c-1.09-1.65-2.46-3.1-4.05-4.29c-2.44-1.68-5.02-3.15-7.71-4.39c-3.88-1.9-5.38-2.83-6.67-4.13c-1.08-0.95-1.72-2.3-1.78-3.73
c-0.01-2.09,1.06-3.91,3.77-6.4c4.29-3.93,5.92-5.9,7.06-8.53c0.5-0.94,0.72-2,0.63-3.06c0.09-0.93-0.07-1.87-0.48-2.71l-0.48-1.11
l0.44-0.73c0.24-0.4,0.55-0.94,0.68-1.2l0.24-0.47l0.66,0.97c0.84,1.27,1.35,2.73,1.47,4.25c-0.05,1-0.26,1.99-0.64,2.93
c-0.08,0.18-0.18,0.45-0.23,0.6c-0.3,0.58-0.64,1.13-1.01,1.67c-0.89,1.36-2.37,2.93-6.09,6.47c-2.59,2.47-3.45,3.9-3.56,5.93
c-0.14,1.58,0.48,3.13,1.68,4.17c1.44,1.55,3.13,2.63,7.93,5.02c5.5,2.75,7.88,4.37,9.68,6.61c1.04,1.18,1.67,2.66,1.79,4.23
c0.01,0.47,0.08,0.93,0.21,1.39c0.22,0.02,0.45,0,0.66-0.08c0.3-0.08,0.86-0.23,1.25-0.33l0.7-0.18l-0.09-0.69
c-0.32-2.32-2.45-4.92-5.96-7.25c-1.99-1.33-2.66-1.69-7.88-4.27c-1.96-0.87-3.77-2.04-5.36-3.47c-1.87-1.55-2.38-4.2-1.21-6.33
c0.62-1.28,1.25-1.98,5.69-6.26c3.62-3.5,5.18-6.24,5.19-9.12c-0.08-2.51-1.22-4.87-3.15-6.49c-0.34-0.31-0.67-0.64-0.98-0.99
c0.12-0.66,0.42-1.27,0.86-1.77c0.55,0,3.56,2.88,4.4,4.2c0.84,1.18,1.25,2.61,1.16,4.06c0.06,1.59-0.4,3.17-1.31,4.48
c-1.08,1.84-1.91,2.75-6.45,7.06c-1.27,1.11-2.33,2.45-3.13,3.93c-0.64,1.44-0.66,3.08-0.06,4.54c0.86,1.83,3.37,3.85,7.02,5.64
c5.68,2.79,6.85,3.44,8.8,4.89c3.26,2.42,4.83,4.78,4.83,7.25c-0.03,0.39,0.01,0.78,0.13,1.16c0.87-0.05,1.72-0.25,2.51-0.6
C1864.74,99.06,1864.74,98.98,1864.51,98.2z M1832.73,62.18L1832.73,62.18c-0.68,0.38-1.43,0.61-2.2,0.68l-0.97,0.07l1.03-1.26
c1.79-2.24,3.32-4.69,4.55-7.28c0.42-0.84,0.82-1.58,0.89-1.66c0.17-0.18,1.17,0.48,2.32,1.53l0.95,0.87l-0.86,1.27
C1836.33,59.53,1834.65,61.23,1832.73,62.18L1832.73,62.18z M1841.36,59.49c-1.12,1.19-2.44,2.18-3.89,2.94l0,0
c-0.57,0.21-1.16,0.35-1.77,0.42l-0.87,0.08l0.97-1.18c1.2-1.47,2.26-3.05,3.18-4.71c0.34-0.62,0.63-1.15,0.66-1.19
c0.15-0.18,0.75,0.64,1.35,1.86l0.67,1.36L1841.36,59.49z M1843.78,56.14c-0.18,0.35-0.41,0.68-0.68,0.96
c-0.49-0.43-0.94-0.88-1.37-1.37l-1.27-1.36l0.29-0.68c0.51-1.19,0.59-1.22,1.55-0.47c0.75,0.55,1.44,1.18,2.06,1.87
C1844.18,55.46,1843.99,55.8,1843.78,56.14L1843.78,56.14z" />
</g>
</svg>
</footer>
</section>
<article id="sign_in_page" class="hidden">
<h2>Sign In</h2>
<p>Welcome to RIBC.</p>
<sm-form id="sign_in_form" class="grid gap-1">
<sm-input id="private_key_input" placeholder="Private key" type="password"
error-text="Private key is invalid" data-private-key required></sm-input>
<sm-button id="sign_in_button" variant="primary" disabled>Sign in</sm-button>
</sm-form>
<p>
Don't have a private key? get it <a href="https://flo-webwallet.duckdns.org/" target="_blank">here</a>
<br> or
</p>
<sm-button id="guest_btn" title="sign in as a guest">
Sign in as guest
</sm-button>
</article>
<main id="main_page" class="grid">
<header id="main_header" class="hide-on-desktop space-between">
<div id="logo" class="logo">
<svg class="icon rm-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M20.46,21.32C20,19.78,18.6,18.59,15.3,17a12.67,12.67,0,0,1-2.64-1.56,4.27,4.27,0,0,1-.79-1,2.6,2.6,0,0,1,0-1.41c.24-.68.49-1,2.43-2.85a7.18,7.18,0,0,0,2.09-2.92,4.25,4.25,0,0,0,0-1.77,6.52,6.52,0,0,0-2.85-3.11c-.56-.36-.81-.4-.81-.15a2.33,2.33,0,0,1-.18.45L12.4,3l-.53-.36c-.28-.21-.64-.41-.77-.49s-.46-.11-.46,0a6.21,6.21,0,0,1-.37.83s-.08,0-.17-.08c-1.15-.83-1.64-1-1.64-.73A7.33,7.33,0,0,1,7.7,3.65C6.48,5.68,5.24,6.7,4,6.7c-.56,0-.54,0-.37.64s.2.58.68.43a3.37,3.37,0,0,0,1.09-.54.86.86,0,0,1,.3-.17,1.34,1.34,0,0,1,.13.39.79.79,0,0,0,.17.4A3.5,3.5,0,0,0,7.37,7.3L7.8,7l.09.34c.12.45.19.51.62.39a4.25,4.25,0,0,0,2.17-1.54l.38-.45,0,.39A5.92,5.92,0,0,1,8.89,9.54L7.67,10.71c-2,1.93-1.89,3.51.37,5a27.41,27.41,0,0,0,2.89,1.51c.17.07.62.32,1,.54C14,19,15,20.23,15,21.48a2,2,0,0,0,0,.49h0c0,.05,0,.05.56-.1a1.89,1.89,0,0,0,.53-.21,2.41,2.41,0,0,0-.34-1.15,7.05,7.05,0,0,0-1.68-1.77,21.91,21.91,0,0,0-3.2-1.83A9.53,9.53,0,0,1,8.16,15.2a2.18,2.18,0,0,1-.74-1.55C7.42,12.79,7.86,12,9,11c1.77-1.64,2.45-2.45,2.92-3.55a2.28,2.28,0,0,0,.26-1.26A2,2,0,0,0,12,5.06l-.2-.45L12,4.3l.28-.49.09-.18L12.6,4a3.69,3.69,0,0,1,.61,1.76A3.47,3.47,0,0,1,12.94,7l-.09.25s-.21.37-.41.69A17.78,17.78,0,0,1,9.91,10.6c-1.07,1-1.43,1.62-1.47,2.47a2.05,2.05,0,0,0,.7,1.73,10.47,10.47,0,0,0,3.28,2.08c2.28,1.13,3.26,1.81,4,2.73a2.94,2.94,0,0,1,.74,1.75,1.26,1.26,0,0,0,.09.57.48.48,0,0,0,.26,0l.51-.13.29-.08,0-.28c-.13-1-1-2-2.47-3a25.52,25.52,0,0,0-3.26-1.77,8.59,8.59,0,0,1-2.23-1.43,2.09,2.09,0,0,1-.5-2.62c.26-.53.5-.83,2.35-2.6,1.51-1.45,2.15-2.58,2.15-3.79A3.67,3.67,0,0,0,13,3.48a3,3,0,0,1-.4-.42A1.85,1.85,0,0,1,13,2.33a6.74,6.74,0,0,1,1.83,1.73,2.62,2.62,0,0,1,.47,1.68,3,3,0,0,1-.55,1.84c-.45.78-.79,1.14-2.67,2.93a5.56,5.56,0,0,0-1.3,1.64,1.77,1.77,0,0,0-.21,1,1.76,1.76,0,0,0,.19.92,6.28,6.28,0,0,0,2.9,2.34,21.6,21.6,0,0,1,3.66,2c1.35,1,2,2,2,3a1.06,1.06,0,0,0,.05.47,2.83,2.83,0,0,0,1-.24C20.56,21.68,20.56,21.66,20.46,21.32ZM7.29,6.4h0a2.23,2.23,0,0,1-.9.28L6,6.72l.43-.53a15.22,15.22,0,0,0,1.89-3,3.52,3.52,0,0,1,.38-.67c.07-.08.49.2,1,.64l.39.35L9.66,4A6.7,6.7,0,0,1,7.29,6.4Zm3.58-1.11A5.8,5.8,0,0,1,9.25,6.51h0a3.3,3.3,0,0,1-.74.17l-.35,0,.39-.49a15.64,15.64,0,0,0,1.32-2,4.63,4.63,0,0,1,.28-.49c.06-.08.33.26.57.77l.28.57Zm1-1.4a1.63,1.63,0,0,1-.28.4A6.63,6.63,0,0,1,11,3.72l-.53-.56.12-.29c.2-.49.24-.51.64-.19a5.57,5.57,0,0,1,.85.78A2.78,2.78,0,0,1,11.87,3.89Z" />
</svg>
<h4>RIBC</h4>
</div>
<theme-toggle></theme-toggle>
</header>
<nav id="main_nav">
<div class="nav-list__item hide-on-mobile">
<svg class="icon rm-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M20.46,21.32C20,19.78,18.6,18.59,15.3,17a12.67,12.67,0,0,1-2.64-1.56,4.27,4.27,0,0,1-.79-1,2.6,2.6,0,0,1,0-1.41c.24-.68.49-1,2.43-2.85a7.18,7.18,0,0,0,2.09-2.92,4.25,4.25,0,0,0,0-1.77,6.52,6.52,0,0,0-2.85-3.11c-.56-.36-.81-.4-.81-.15a2.33,2.33,0,0,1-.18.45L12.4,3l-.53-.36c-.28-.21-.64-.41-.77-.49s-.46-.11-.46,0a6.21,6.21,0,0,1-.37.83s-.08,0-.17-.08c-1.15-.83-1.64-1-1.64-.73A7.33,7.33,0,0,1,7.7,3.65C6.48,5.68,5.24,6.7,4,6.7c-.56,0-.54,0-.37.64s.2.58.68.43a3.37,3.37,0,0,0,1.09-.54.86.86,0,0,1,.3-.17,1.34,1.34,0,0,1,.13.39.79.79,0,0,0,.17.4A3.5,3.5,0,0,0,7.37,7.3L7.8,7l.09.34c.12.45.19.51.62.39a4.25,4.25,0,0,0,2.17-1.54l.38-.45,0,.39A5.92,5.92,0,0,1,8.89,9.54L7.67,10.71c-2,1.93-1.89,3.51.37,5a27.41,27.41,0,0,0,2.89,1.51c.17.07.62.32,1,.54C14,19,15,20.23,15,21.48a2,2,0,0,0,0,.49h0c0,.05,0,.05.56-.1a1.89,1.89,0,0,0,.53-.21,2.41,2.41,0,0,0-.34-1.15,7.05,7.05,0,0,0-1.68-1.77,21.91,21.91,0,0,0-3.2-1.83A9.53,9.53,0,0,1,8.16,15.2a2.18,2.18,0,0,1-.74-1.55C7.42,12.79,7.86,12,9,11c1.77-1.64,2.45-2.45,2.92-3.55a2.28,2.28,0,0,0,.26-1.26A2,2,0,0,0,12,5.06l-.2-.45L12,4.3l.28-.49.09-.18L12.6,4a3.69,3.69,0,0,1,.61,1.76A3.47,3.47,0,0,1,12.94,7l-.09.25s-.21.37-.41.69A17.78,17.78,0,0,1,9.91,10.6c-1.07,1-1.43,1.62-1.47,2.47a2.05,2.05,0,0,0,.7,1.73,10.47,10.47,0,0,0,3.28,2.08c2.28,1.13,3.26,1.81,4,2.73a2.94,2.94,0,0,1,.74,1.75,1.26,1.26,0,0,0,.09.57.48.48,0,0,0,.26,0l.51-.13.29-.08,0-.28c-.13-1-1-2-2.47-3a25.52,25.52,0,0,0-3.26-1.77,8.59,8.59,0,0,1-2.23-1.43,2.09,2.09,0,0,1-.5-2.62c.26-.53.5-.83,2.35-2.6,1.51-1.45,2.15-2.58,2.15-3.79A3.67,3.67,0,0,0,13,3.48a3,3,0,0,1-.4-.42A1.85,1.85,0,0,1,13,2.33a6.74,6.74,0,0,1,1.83,1.73,2.62,2.62,0,0,1,.47,1.68,3,3,0,0,1-.55,1.84c-.45.78-.79,1.14-2.67,2.93a5.56,5.56,0,0,0-1.3,1.64,1.77,1.77,0,0,0-.21,1,1.76,1.76,0,0,0,.19.92,6.28,6.28,0,0,0,2.9,2.34,21.6,21.6,0,0,1,3.66,2c1.35,1,2,2,2,3a1.06,1.06,0,0,0,.05.47,2.83,2.83,0,0,0,1-.24C20.56,21.68,20.56,21.66,20.46,21.32ZM7.29,6.4h0a2.23,2.23,0,0,1-.9.28L6,6.72l.43-.53a15.22,15.22,0,0,0,1.89-3,3.52,3.52,0,0,1,.38-.67c.07-.08.49.2,1,.64l.39.35L9.66,4A6.7,6.7,0,0,1,7.29,6.4Zm3.58-1.11A5.8,5.8,0,0,1,9.25,6.51h0a3.3,3.3,0,0,1-.74.17l-.35,0,.39-.49a15.64,15.64,0,0,0,1.32-2,4.63,4.63,0,0,1,.28-.49c.06-.08.33.26.57.77l.28.57Zm1-1.4a1.63,1.63,0,0,1-.28.4A6.63,6.63,0,0,1,11,3.72l-.53-.56.12-.29c.2-.49.24-.51.64-.19a5.57,5.57,0,0,1,.85.78A2.78,2.78,0,0,1,11.87,3.89Z" />
</svg>
<h4 class="hide-on-mobile nav-list__item_title">RIBC</h4>
</div>
<a id="dashboard_btn" href="#dashboard_page" class="nav-list__item nav-list__item--active interact"
title="open dashboard page">
<svg class="icon icon--outlined" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M13 21V11h8v10h-8zM3 13V3h8v10H3zm6-2V5H5v6h4zM3 21v-6h8v6H3zm2-2h4v-2H5v2zm10 0h4v-6h-4v6zM13 3h8v6h-8V3zm2 2v2h4V5h-4z" />
</svg>
<svg class="icon icon--filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z" />
</svg>
<span class="nav-list__item_title">
Dashboard
</span>
</a>
<a id="update_panel_btn" href="#updates_page" class="nav-list__item interact" title="show updates">
<svg class="icon icon--outlined" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path d="M20 17h2v2H2v-2h2v-7a8 8 0 1 1 16 0v7zm-2 0v-7a6 6 0 1 0-12 0v7h12zm-9 4h6v2H9v-2z" />
</svg>
<svg class="icon icon--filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path d="M20 17h2v2H2v-2h2v-7a8 8 0 1 1 16 0v7zM9 21h6v2H9v-2z" />
</svg>
<span class="nav-list__item_title">
Updates
</span>
</a>
<a href="#applications" class="nav-list__item interact not-for-admin" title="See status of applications">
<svg class="icon icon--outlined" 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>
<path d="M0,0h24v24H0V0z" fill="none" />
</g>
<g>
<g>
<path
d="M15,3H5C3.9,3,3.01,3.9,3.01,5L3,19c0,1.1,0.89,2,1.99,2H19c1.1,0,2-0.9,2-2V9L15,3z M5,19V5h9v5h5v9H5z M9,8 c0,0.55-0.45,1-1,1S7,8.55,7,8s0.45-1,1-1S9,7.45,9,8z M9,12c0,0.55-0.45,1-1,1s-1-0.45-1-1s0.45-1,1-1S9,11.45,9,12z M9,16 c0,0.55-0.45,1-1,1s-1-0.45-1-1s0.45-1,1-1S9,15.45,9,16z" />
</g>
</g>
</svg>
<svg class="icon icon--filled" 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>
<path d="M0,0h24v24H0V0z" fill="none" />
</g>
<g>
<g>
<path
d="M15,3H5C3.9,3,3.01,3.9,3.01,5L3,19c0,1.1,0.89,2,1.99,2H19c1.1,0,2-0.9,2-2V9L15,3z M8,17c-0.55,0-1-0.45-1-1s0.45-1,1-1 s1,0.45,1,1S8.55,17,8,17z M8,13c-0.55,0-1-0.45-1-1s0.45-1,1-1s1,0.45,1,1S8.55,13,8,13z M8,9C7.45,9,7,8.55,7,8s0.45-1,1-1 s1,0.45,1,1S8.55,9,8,9z M14,10V4.5l5.5,5.5H14z" />
</g>
</g>
</svg>
<span class="nav-list__item_title">
Applications
</span>
</a>
<a href="#admin_page" class="admin-option nav-list__item interact open-first-project"
title="open admin panel">
<svg class="icon icon--outlined" 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 14v2a6 6 0 0 0-6 6H4a8 8 0 0 1 8-8zm0-1c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm0-2c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm2.595 7.812a3.51 3.51 0 0 1 0-1.623l-.992-.573 1-1.732.992.573A3.496 3.496 0 0 1 17 14.645V13.5h2v1.145c.532.158 1.012.44 1.405.812l.992-.573 1 1.732-.992.573a3.51 3.51 0 0 1 0 1.622l.992.573-1 1.732-.992-.573a3.496 3.496 0 0 1-1.405.812V22.5h-2v-1.145a3.496 3.496 0 0 1-1.405-.812l-.992.573-1-1.732.992-.572zM18 19.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />
</svg>
<svg class="icon icon--filled" 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 14v8H4a8 8 0 0 1 8-8zm0-1c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm2.595 5.812a3.51 3.51 0 0 1 0-1.623l-.992-.573 1-1.732.992.573A3.496 3.496 0 0 1 17 14.645V13.5h2v1.145c.532.158 1.012.44 1.405.812l.992-.573 1 1.732-.992.573a3.51 3.51 0 0 1 0 1.622l.992.573-1 1.732-.992-.573a3.496 3.496 0 0 1-1.405.812V22.5h-2v-1.145a3.496 3.496 0 0 1-1.405-.812l-.992.573-1-1.732.992-.572zM18 17a1 1 0 1 0 0 2 1 1 0 0 0 0-2z" />
</svg>
<span class="nav-list__item_title">
Manage
</span>
</a>
<a id="settings_btn" href="#settings_page" class="nav-list__item interact" title="open settings page">
<svg class="icon icon--outlined" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M2 12c0-.865.11-1.703.316-2.504A3 3 0 0 0 4.99 4.867a9.99 9.99 0 0 1 4.335-2.505 3 3 0 0 0 5.348 0 9.99 9.99 0 0 1 4.335 2.505 3 3 0 0 0 2.675 4.63c.206.8.316 1.638.316 2.503 0 .865-.11 1.703-.316 2.504a3 3 0 0 0-2.675 4.629 9.99 9.99 0 0 1-4.335 2.505 3 3 0 0 0-5.348 0 9.99 9.99 0 0 1-4.335-2.505 3 3 0 0 0-2.675-4.63C2.11 13.704 2 12.866 2 12zm4.804 3c.63 1.091.81 2.346.564 3.524.408.29.842.541 1.297.75A4.993 4.993 0 0 1 12 18c1.26 0 2.438.471 3.335 1.274.455-.209.889-.46 1.297-.75A4.993 4.993 0 0 1 17.196 15a4.993 4.993 0 0 1 2.77-2.25 8.126 8.126 0 0 0 0-1.5A4.993 4.993 0 0 1 17.195 9a4.993 4.993 0 0 1-.564-3.524 7.989 7.989 0 0 0-1.297-.75A4.993 4.993 0 0 1 12 6a4.993 4.993 0 0 1-3.335-1.274 7.99 7.99 0 0 0-1.297.75A4.993 4.993 0 0 1 6.804 9a4.993 4.993 0 0 1-2.77 2.25 8.126 8.126 0 0 0 0 1.5A4.993 4.993 0 0 1 6.805 15zM12 15a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" />
</svg>
<svg class="icon icon--filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M5.334 4.545a9.99 9.99 0 0 1 3.542-2.048A3.993 3.993 0 0 0 12 3.999a3.993 3.993 0 0 0 3.124-1.502 9.99 9.99 0 0 1 3.542 2.048 3.993 3.993 0 0 0 .262 3.454 3.993 3.993 0 0 0 2.863 1.955 10.043 10.043 0 0 1 0 4.09c-1.16.178-2.23.86-2.863 1.955a3.993 3.993 0 0 0-.262 3.455 9.99 9.99 0 0 1-3.542 2.047A3.993 3.993 0 0 0 12 20a3.993 3.993 0 0 0-3.124 1.502 9.99 9.99 0 0 1-3.542-2.047 3.993 3.993 0 0 0-.262-3.455 3.993 3.993 0 0 0-2.863-1.954 10.043 10.043 0 0 1 0-4.091 3.993 3.993 0 0 0 2.863-1.955 3.993 3.993 0 0 0 .262-3.454zM13.5 14.597a3 3 0 1 0-3-5.196 3 3 0 0 0 3 5.196z" />
</svg>
<span class="nav-list__item_title">
Settings
</span>
</a>
<theme-toggle class="hide-on-mobile"></theme-toggle>
</nav>
<article id="sub_page_container">
<section id="dashboard_page" class="page">
<div class="flex flex-direction-column gap-2">
<div id="application_card" class="card flex align-center space-between general-only">
<div>
<h2 class="margin-bottom-0-5">Looking for an internship?</h2>
<p class="margin-bottom-1">Apply for internships at RanchiMall</p>
<button class="button button--primary"
onclick="openPopup('apply_for_internship_popup')">Apply
Now</button>
</div>
<svg class="illustration" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg"
width="753.87469" height="703.82827" viewBox="0 0 753.87469 703.82827"
xmlns:xlink="http://www.w3.org/1999/xlink">
<path
d="M578.47505,103.95771l-23.06843,12.58664L271.19846,271.61447,248.13,284.2011a48.1793,48.1793,0,0,0-19.1955,65.29607L440.57765,737.39072a48.17922,48.17922,0,0,0,65.296,19.19561l.05958-.03251L836.15907,576.37545l.05958-.03251a48.17924,48.17924,0,0,0,19.19553-65.296L643.77106,123.15338A48.17929,48.17929,0,0,0,578.47505,103.95771Z"
transform="translate(-223.06266 -98.08587)" fill="#f2f2f2" />
<path
d="M585.11115,116.11916l-27.323,14.908L282.08828,281.455,254.7657,296.36278a34.30947,34.30947,0,0,0-13.66965,46.4988L452.73916,730.75513a34.30947,34.30947,0,0,0,46.4988,13.66952l.05958-.0325L829.5234,564.21377l.06-.03274a34.30935,34.30935,0,0,0,13.66926-46.49851L631.60954,129.789A34.30936,34.30936,0,0,0,585.11115,116.11916Z"
transform="translate(-223.06266 -98.08587)" fill="#fff" />
<path
d="M589.66653,236.52147,466.505,303.72109a8.01411,8.01411,0,1,1-7.677-14.07012l123.16157-67.19962a8.01411,8.01411,0,1,1,7.677,14.07012Z"
transform="translate(-223.06266 -98.08587)" fill="#f2f2f2" />
<path
d="M631.641,244.43106,479.45984,327.46442a8.01411,8.01411,0,0,1-7.677-14.07012l152.18119-83.03336a8.01411,8.01411,0,1,1,7.677,14.07012Z"
transform="translate(-223.06266 -98.08587)" fill="#f2f2f2" />
<path
d="M415.87223,275.74837l-113.5479,61.95419a3.84082,3.84082,0,0,0-1.53151,5.21006L349.14436,431.53a3.84075,3.84075,0,0,0,5.21,1.5317l113.5479-61.95419a3.84075,3.84075,0,0,0,1.53153-5.21l-48.35154-88.61735A3.84081,3.84081,0,0,0,415.87223,275.74837Z"
transform="translate(-223.06266 -98.08587)" fill="#f2f2f2" />
<path
d="M650.7763,348.96263,483.723,440.11051a8.01411,8.01411,0,1,1-7.677-14.07012l167.05327-91.14788a8.01411,8.01411,0,1,1,7.677,14.07012Z"
transform="translate(-223.06266 -98.08587)" fill="#f2f2f2" />
<path
d="M692.7508,356.87223,496.67791,463.85384a8.01411,8.01411,0,0,1-7.677-14.07012L685.07384,342.80211a8.01411,8.01411,0,1,1,7.677,14.07012Z"
transform="translate(-223.06266 -98.08587)" fill="#f2f2f2" />
<circle cx="197.03853" cy="382.67177" r="34" fill="#f2f2f2" />
<path
d="M928.81234,263.78816H552.494a48.17927,48.17927,0,0,0-48.125,48.12512V753.78907a48.17922,48.17922,0,0,0,48.125,48.12506H928.81234a48.17922,48.17922,0,0,0,48.125-48.12506V311.91328A48.17927,48.17927,0,0,0,928.81234,263.78816Z"
transform="translate(-223.06266 -98.08587)" fill="#e6e6e6" />
<path
d="M928.81283,277.64235H552.494a34.30947,34.30947,0,0,0-34.271,34.27093V753.78907A34.30947,34.30947,0,0,0,552.494,788.06H928.81283a34.30936,34.30936,0,0,0,34.27051-34.27088V311.91328A34.30937,34.30937,0,0,0,928.81283,277.64235Z"
transform="translate(-223.06266 -98.08587)" fill="#fff" />
<path
d="M875.14319,385.51745H734.84151a8.01411,8.01411,0,0,1,0-16.02823H875.14319a8.01412,8.01412,0,1,1,0,16.02823Z"
transform="translate(-223.06266 -98.08587)" fill="#6c63ff" />
<path
d="M908.20141,412.56508H734.84151a8.01411,8.01411,0,1,1,0-16.02822h173.3599a8.01411,8.01411,0,0,1,0,16.02822Z"
transform="translate(-223.06266 -98.08587)" fill="#6c63ff" />
<path
d="M703.79234,336.71073H574.44224a3.8408,3.8408,0,0,0-3.83984,3.84v100.95a3.84075,3.84075,0,0,0,3.83984,3.84h129.3501a3.84076,3.84076,0,0,0,3.83984-3.84v-100.95A3.84081,3.84081,0,0,0,703.79234,336.71073Z"
transform="translate(-223.06266 -98.08587)" fill="#e6e6e6" />
<path
d="M609.92406,398.70111a34.087,34.087,0,0,1-8.804,23.076c-5.656,6.20712-14.07618,10.3236-22.57327,8.62043-7.82416-1.56829-14.18219-8.4067-13.389-16.6795a12.356,12.356,0,0,1,15.2668-11.09515c7.43265,1.92885,10.39415,12.64095,4.20051,17.669-1.4862,1.2065-3.62136-.90359-2.12132-2.12132,4.0944-3.32385,2.8295-10.5954-2.11244-12.419-5.75371-2.12311-11.84978,2.44324-12.26355,8.32554-.49057,6.97428,4.85221,12.22646,11.40422,13.463,7.08789,1.3377,14.11532-2.29,18.91808-7.29718a30.95507,30.95507,0,0,0,8.474-21.54183,1.5009,1.5009,0,0,1,3,0Z"
transform="translate(-223.06266 -98.08587)" fill="#2f2e41" />
<circle cx="416.15529" cy="266.1673" r="53.51916" fill="#6c63ff" />
<path
d="M636.47981,387.08916l-.05566-2c3.7207-.10352,7.001-.33692,9.46582-2.1377a6.14794,6.14794,0,0,0,2.38134-4.52832,3.51432,3.51432,0,0,0-1.15283-2.89453c-1.63623-1.38184-4.269-.93457-6.188-.05469l-1.65478.75879,3.17334-23.19043,1.98144.27149-2.69922,19.72656c2.60743-.7666,5.02344-.43652,6.67823.96094a5.471,5.471,0,0,1,1.86035,4.49218,8.13264,8.13264,0,0,1-3.2002,6.07325C643.90266,386.88115,639.78694,386.99638,636.47981,387.08916Z"
transform="translate(-223.06266 -98.08587)" fill="#2f2e41" />
<rect x="431.16715" y="256.92907" width="10.77148" height="2" fill="#2f2e41" />
<rect x="397.16715" y="256.92907" width="10.77148" height="2" fill="#2f2e41" />
<path
d="M609.57212,445.34074a53.00636,53.00636,0,0,1,12.89014-5.93,8.56789,8.56789,0,0,1,.02-4.71,9.42609,9.42609,0,0,1,9.12988-6.63h13.04a9.45955,9.45955,0,0,1,9.15039,6.64,8.532,8.532,0,0,1,.01953,4.7,53.16732,53.16732,0,0,1,12.89014,5.93Z"
transform="translate(-223.06266 -98.08587)" fill="#2f2e41" />
<path
d="M700.52232,344.39072a11.57143,11.57143,0,0,0-3.52979-2.87,8.36739,8.36739,0,0,0-3.8501-.95,8.77158,8.77158,0,0,0-5.10986,1.72c-4.07031,2.88-6.89014,9.09-6.89014,16.28,0,9.02,4.43995,16.5,10.21,17.80005a8.25321,8.25321,0,0,0,1.79.2c6.60987,0,12-8.07,12-18C705.14243,352.81077,703.33238,347.68076,700.52232,344.39072Z"
transform="translate(-223.06266 -98.08587)" fill="#3f3d56" />
<path
d="M590.6024,341.86076h-.00977a8.57836,8.57836,0,0,0-4.4502-1.29,8.36738,8.36738,0,0,0-3.85009.95,11.57143,11.57143,0,0,0-3.52979,2.87l-.01025.01c-2.79981,3.29-4.60987,8.42-4.60987,14.17,0,7.76,3.27979,14.38,7.87989,16.91a8.54175,8.54175,0,0,0,4.12011,1.09,7.72431,7.72431,0,0,0,.96-.06h.00976c6.16016-.74,11.03027-8.5,11.03027-17.94C598.14243,351.01072,595.01255,344.52073,590.6024,341.86076Z"
transform="translate(-223.06266 -98.08587)" fill="#3f3d56" />
<path
d="M582.77242,373.76a1.50127,1.50127,0,0,0,1.42151-1.98,58.49864,58.49864,0,1,1,112.68726-6.5747,1.50006,1.50006,0,0,0,2.93554.61914A61.50091,61.50091,0,1,0,581.35116,372.739,1.50077,1.50077,0,0,0,582.77242,373.76Z"
transform="translate(-223.06266 -98.08587)" fill="#3f3d56" />
<path
d="M666.10324,329.57746c2.11924,2.89278,1.07447,6.79121-1.15837,9.28528-2.90548,3.24541-7.53877,3.45016-11.5618,2.8478-4.51431-.67591-9.3026-2.7909-13.87293-1.3656-3.89537,1.2148-6.67418,4.74793-10.7211,5.63537-3.589.787-7.88081-.25477-9.139-4.08016-.60459-1.83823,2.29142-2.6261,2.89284-.79751.81395,2.47478,4.32865,2.42543,6.34145,1.74012,3.22689-1.09867,5.71374-3.77105,8.8854-5.04749,3.73933-1.50491,7.79621-.82549,11.60323.03181,3.58831.808,7.718,2.006,11.29267.49665,2.64515-1.1169,4.74985-4.635,2.84717-7.23211-1.14219-1.5591,1.45985-3.05738,2.59042-1.51416Z"
transform="translate(-223.06266 -98.08587)" fill="#2f2e41" />
<path
d="M874.932,513.49157H684.63034a8.01411,8.01411,0,1,1,0-16.02823H874.932a8.01412,8.01412,0,0,1,0,16.02823Z"
transform="translate(-223.06266 -98.08587)" fill="#e6e6e6" />
<path
d="M907.99023,540.5392H684.63034a8.01412,8.01412,0,1,1,0-16.02823H907.99023a8.01412,8.01412,0,1,1,0,16.02823Z"
transform="translate(-223.06266 -98.08587)" fill="#e6e6e6" />
<path
d="M874.932,610.705H684.63034a8.01411,8.01411,0,1,1,0-16.02822H874.932a8.01411,8.01411,0,1,1,0,16.02822Z"
transform="translate(-223.06266 -98.08587)" fill="#e6e6e6" />
<path
d="M907.99023,637.75267H684.63034a8.01411,8.01411,0,1,1,0-16.02823H907.99023a8.01411,8.01411,0,1,1,0,16.02823Z"
transform="translate(-223.06266 -98.08587)" fill="#e6e6e6" />
<circle cx="386.2497" cy="420.61448" r="34" fill="#e6e6e6" />
<circle cx="386.2497" cy="518.61448" r="34" fill="#e6e6e6" />
<path
d="M874.932,708.705H684.63034a8.01411,8.01411,0,1,1,0-16.02822H874.932a8.01411,8.01411,0,1,1,0,16.02822Z"
transform="translate(-223.06266 -98.08587)" fill="#e6e6e6" />
<path
d="M907.99023,735.75267H684.63034a8.01411,8.01411,0,1,1,0-16.02823H907.99023a8.01411,8.01411,0,1,1,0,16.02823Z"
transform="translate(-223.06266 -98.08587)" fill="#e6e6e6" />
<circle cx="386.2497" cy="616.61448" r="34" fill="#e6e6e6" />
</svg>
</div>
<section id="project_watching_section" class="w-100">
<h4>Projects watchlist</h4>
<div id="project_watchlist" class="observe-empty-state"></div>
<div class="empty-state">
<h4>No project added to watchlist. </h4>
<p class="margin-block-0-5">
You can add projects to watchlist by clicking on the 'Watch' button in opened project.
</p>
<a href="#project_explorer" class="button open-first-project">See all projects</a>
</div>
</section>
<section id="intern_view" class="hidden intern-option">
<h2>My tasks</h2>
<ul id="assigned_task_list"></ul>
</section>
</div>
<section>
<div id="best_interns_container" class="container-card">
<div class="container-header">
<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 7a8 8 0 1 1 0 16 8 8 0 0 1 0-16zm0 3.5l-1.323 2.68-2.957.43 2.14 2.085-.505 2.946L12 17.25l2.645 1.39-.505-2.945 2.14-2.086-2.957-.43L12 10.5zm1-8.501L18 2v3l-1.363 1.138A9.935 9.935 0 0 0 13 5.049L13 2zm-2 0v3.05a9.935 9.935 0 0 0-3.636 1.088L6 5V2l5-.001z" />
</svg>
<h4>Leaderboard</h4>
<a id="all_interns_btn" href="#all_interns_page" class="button">All</a>
</div>
<div id="top_interns"></div>
</div>
<div id="project_list_container" class="container-card">
<div class="container-header">
<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="M6 7V4a1 1 0 0 1 1-1h6.414l2 2H21a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1h-3v3a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1h3zm0 2H4v10h12v-2H6V9z" />
</svg>
<h4>Projects</h4>
<a href="#project_explorer" class="button open-first-project">All</a>
</div>
<div id="project_list"></div>
</div>
</section>
</section>
<section id="settings_page" class="page hidden">
<section class="grid gap-0-5">
<h2 id="username" class="capitalize"></h2>
<div id="user_role"></div>
<sm-copy id="user_flo_id"></sm-copy>
<button id="logout" class="justify-self-start button button--danger button--primary"
onclick="signOut()">
<svg class="icon 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="M4 18h2v2h12V4H6v2H4V3a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-3zm2-7h7v2H6v3l-5-4 5-4v3z" />
</svg>
Sign out
</button>
</section>
</section>
<section id="admin_page" class="page hidden">
<section id="admin_page__left" class="flex flex-direction-column">
<sm-tab-header target="admin_tabs">
<sm-tab>Projects</sm-tab>
<sm-tab>Interns</sm-tab>
<sm-tab>Requests</sm-tab>
</sm-tab-header>
<sm-tab-panels id="admin_tabs">
<section id="projects_container">
<button class="button button--small margin-left-auto margin-block-0-5"
onclick="openPopup('add_project_popup')">
<svg class="icon 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>
<path
d="M12.414 5H21a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7.414l2 2zM4 5v14h16V7h-8.414l-2-2H4zm7 7V9h2v3h3v2h-3v3h-2v-3H8v-2h3z">
</path>
</svg>
Add project
</button>
<div id="admin_page__project_list" class="list-container observe-empty-state"></div>
<h4 class="empty-state">No project added</h4>
</section>
<section id="interns_container">
<button class="button button--small margin-left-auto margin-block-0-5"
onclick="openPopup('add_intern_popup')">
<svg class="icon 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="M14 14.252v2.09A6 6 0 0 0 6 22l-2-.001a8 8 0 0 1 10-7.748zM12 13c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm0-2c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm6 6v-3h2v3h3v2h-3v3h-2v-3h-3v-2h3z" />
</svg>
Add intern
</button>
<ul id="admin_page__intern_list" class="list-container observe-empty-state"></ul>
<h4 class="empty-state">No interns added</h4>
</section>
<section>
<strip-select id="request_type_selector" class="margin-left-auto margin-block-0-5">
<strip-option value="task" selected>Task</strip-option>
<strip-option value="intern">Internship</strip-option>
</strip-select>
<ul id="requests_list" class="list-container observe-empty-state"></ul>
<h4 class="empty-state">No pending requests</h4>
</section>
</sm-tab-panels>
</section>
<section id="project_editing_panel" class="inner-page hide-on-mobile hidden">
<div id="project_details_wrapper" class="grid gap-1 margin-bottom-2">
<div class="flex flex-wrap align-items-start">
<h2 id="editing_panel__title" data-editable></h2>
<button class="button button--small button--transparent admin-option"
title="Edit this title" onclick="makeEditable(this.previousElementSibling)">
<svg class="icon" 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="M14.06 9.02l.92.92L5.92 19H5v-.92l9.06-9.06M17.66 3c-.25 0-.51.1-.7.29l-1.83 1.83 3.75 3.75 1.83-1.83c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.2-.2-.45-.29-.71-.29zm-3.6 3.19L3 17.25V21h3.75L17.81 9.94l-3.75-3.75z" />
</svg>
</button>
</div>
<div class="flex flex-wrap align-items-start">
<p id="editing_panel__description" data-editable></p>
<button class="button button--small button--transparent admin-option"
title="Edit this description" onclick="makeEditable(this.previousElementSibling)">
<svg class="icon" 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="M14.06 9.02l.92.92L5.92 19H5v-.92l9.06-9.06M17.66 3c-.25 0-.51.1-.7.29l-1.83 1.83 3.75 3.75 1.83-1.83c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.2-.2-.45-.29-.71-.29zm-3.6 3.19L3 17.25V21h3.75L17.81 9.94l-3.75-3.75z" />
</svg>
</button>
</div>
</div>
<h4>Tasks</h4>
<div id="branch_container"></div>
<ul id="task_list" class="grid observe-empty-state"></ul>
<h4 class="empty-state margin-bottom-1">No tasks added yet, tasks will appear here after adding
them.</h4>
<sm-button id="add_task" title="show element to add new task" onclick="addPlaceholderTask()">
<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="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z" />
</svg>
Add task
</sm-button>
<ul id="task_context" class="hidden">
<li id="assign_intern" tabindex="0" class="interact" onclick="toggleEditing('title')">
<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="M15.728 9.686l-1.414-1.414L5 17.586V19h1.414l9.314-9.314zm1.414-1.414l1.414-1.414-1.414-1.414-1.414 1.414 1.414 1.414zM7.242 21H3v-4.243L16.435 3.322a1 1 0 0 1 1.414 0l2.829 2.829a1 1 0 0 1 0 1.414L7.243 21z" />
</svg>
Edit title
</li>
<li id="assign_intern" tabindex="0" class="interact" onclick="toggleEditing()">
<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="M15.728 9.686l-1.414-1.414L5 17.586V19h1.414l9.314-9.314zm1.414-1.414l1.414-1.414-1.414-1.414-1.414 1.414 1.414 1.414zM7.242 21H3v-4.243L16.435 3.322a1 1 0 0 1 1.414 0l2.829 2.829a1 1 0 0 1 0 1.414L7.243 21z" />
</svg>
Edit description
</li>
<li id="assign_intern" tabindex="0" class="interact" onclick="openPopup('intern_list_popup')">
<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="M14 14.252v2.09A6 6 0 0 0 6 22l-2-.001a8 8 0 0 1 10-7.748zM12 13c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm0-2c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm6 6v-3h2v3h3v2h-3v3h-2v-3h-3v-2h3z" />
</svg>
Assign an intern
</li>
<li onclick="showNewBranchPopup()" tabindex="0" class="interact">
<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="M7.105 15.21A3.001 3.001 0 1 1 5 15.17V8.83a3.001 3.001 0 1 1 2 0V12c.836-.628 1.874-1 3-1h4a3.001 3.001 0 0 0 2.895-2.21 3.001 3.001 0 1 1 2.032.064A5.001 5.001 0 0 1 14 13h-4a3.001 3.001 0 0 0-2.895 2.21zM6 17a1 1 0 1 0 0 2 1 1 0 0 0 0-2zM6 5a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm12 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2z" />
</svg>
Create new Branch
</li>
<li onclick="removeThisTask()" tabindex="0" class="interact">
<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="M17 6h5v2h-2v13a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V8H2V6h5V3a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v3zm1 2H6v12h12V8zM9 4v2h6V4H9z" />
</svg>
Delete
</li>
</ul>
</section>
<button class="fab admin-option" onclick="commitToChanges()">
<svg class="icon 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="M7 19v-6h10v6h2V7.828L16.172 5H5v14h2zM4 3h13l4 4v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm5 12v4h6v-4H9z" />
</svg>
Commit changes
</button>
</section>
<section id="updates_page" class="page hidden">
<sm-button class="hide-on-desktop justify-self-end" onclick="toggleFilter()">Filter</sm-button>
<section id="update_filters_wrapper" class="grid hide-on-mobile">
<h4>Filter</h4>
<div class="grid gap-0-5">
<h5 class="uppercase">By Projects</h5>
<sm-select id="updates_page__project_selector"></sm-select>
</div>
<div class="grid gap-0-5">
<h5 class="uppercase">By Intern</h5>
<sm-select id="updates_page__intern_selector"></sm-select>
</div>
<label class="grid gap-0-5">
<h5 class="uppercase">By date</h5>
<input type="date" id="updates_page__date_selector" aria-label="Filter updates by date"
min="2020-01-01">
</label>
<sm-button onclick="clearFilter()">Clear</sm-button>
</section>
<section id="updates_wrapper">
<ul id="all_updates_list" class="grid gap-0-5 observe-empty-state"></ul>
<h4 class="empty-state">No related updates</h4>
</section>
</section>
<section id="applications" class="page hidden align-content-start">
<p>Check status of your applications</p>
<div class="hidden">
<ul id="task_requests_list" class="grid gap-0-5 observe-empty-state"></ul>
<h4 class="empty-state">No task requests</h4>
</div>
<div>
<ul id="internship_requests_list" class="grid gap-0-5 observe-empty-state"></ul>
<h4 class="empty-state">No applications</h4>
</div>
</section>
<section id="all_interns_page" class="page hidden flex flex-direction-column align-start">
<div id="all_interns_page__header" class="grid gap-0-5">
<h2>Interns</h2>
<sm-input id="interns_page__search" placeholder="Search">
<svg class="icon search__icon" slot="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="M18.031 16.617l4.283 4.282-1.415 1.415-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9 9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617zm-2.006-.742A6.977 6.977 0 0 0 18 11c0-3.868-3.133-7-7-7-3.868 0-7 3.132-7 7 0 3.867 3.132 7 7 7a6.977 6.977 0 0 0 4.875-1.975l.15-.15z" />
</svg>
</sm-input>
</div>
<ul id="all_interns_list" class="grid observe-empty-state"></ul>
<h4 class="empty-state">No intern found</h4>
</section>
<section id="project_explorer" class="page hidden">
<div id="project_explorer__left" class="list-container">
<h4 class="padding intern-option hidden">My projects</h4>
<div id="my_projects"></div>
<h4 class="padding intern-option hidden">Other projects</h4>
<div id="other_projects"></div>
</div>
<section id="project_explorer__right" class="grid hide-on-mobile">
<header class="flex space-between align-center">
<h2 id="project_explorer__project_title"></h2>
<sm-button id="watch_project_button" title="Add project to your watchlist"
onclick="watchThisProject(this)">
Watch
</sm-button>
</header>
<p id="project_explorer__project_description"></p>
<div id="explorer_branch_container" class="flex align-center flex-wrap gap-0-3"></div>
<h4></h4>
<div id="explorer_task_list" class="observe-empty-state"></div>
<h4 class="empty-state">No tasks are added to this projects</h4>
</section>
</section>
</article>
</main>
<sm-popup id="intern_info_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close" onclick="closePopup()">
<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>
</header>
<section class="grid gap-1">
<div id="intern_info__initials" class="intern-card__initials"></div>
<h3 id="intern_info__name">Intern name</h3>
<sm-copy id="intern_info__flo_id"></sm-copy>
<div id="update_intern_score" class="flex align-center space-between">
<div class="flex">
<svg class="icon gold-fill 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>
<h4 id="intern_info__score"></h4>
</div>
<div class="flex admin-option hidden">
<sm-button id="reduce_score_button" title="increase intern score" onclick="changeScore(-1)">
<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="M5 11h14v2H5z" />
</svg>
</sm-button>
<sm-button id="increase_score_button" title="decrease intern score" onclick="changeScore(1)">
<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="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z" />
</svg>
</sm-button>
</div>
</div>
</section>
</sm-popup>
<sm-popup id="intern_list_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close" onclick="closePopup()">
<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>
<h3 class="medium-top-bottom-margin">Choose an intern</h3>
</header>
<sm-input id="intern_search_field" placeholder="Search for interns" type="search">
<svg slot="icon" 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="M18.031 16.617l4.283 4.282-1.415 1.415-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9 9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617zm-2.006-.742A6.977 6.977 0 0 0 18 11c0-3.868-3.133-7-7-7-3.868 0-7 3.132-7 7 0 3.867 3.132 7 7 7a6.977 6.977 0 0 0 4.875-1.975l.15-.15z" />
</svg>
</sm-input>
<ul id="intern_list_container" class="observe-empty-state"></ul>
<h4 class="empty-state">No intern found</h4>
</sm-popup>
<sm-popup id="add_project_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close" onclick="closePopup()">
<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>
<h3>Add new project</h3>
</header>
<sm-form>
<sm-input id="project_name_field" placeholder="Name" autofocus required></sm-input>
<sm-textarea id="project_description_field" placeholder="Description" rows="4" required></sm-textarea>
<sm-button variant="primary" onclick="addProjectToList()" disabled>
<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="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z" />
</svg>
Add
</sm-button>
</sm-form>
</sm-popup>
<sm-popup id="add_intern_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close" onclick="closePopup()">
<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>
<h3>Add new intern</h3>
</header>
<sm-form>
<sm-input id="intern_name_field" placeholder="Name" required></sm-input>
<sm-input id="intern_flo_id_field" placeholder="FLO address" error-text="Wrong FLO ID" data-flo-id required>
</sm-input>
<sm-button variant="primary" onclick="addInternToList()" disabled>
<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="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z" />
</svg>
Add
</sm-button>
</sm-form>
</sm-popup>
<sm-popup id="create_branch_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close" onclick="closePopup()">
<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>
<h3>Create a new branch</h3>
</header>
<sm-form>
<sm-input id="branch_start_point" placeholder="Start point" type="number" required></sm-input>
<sm-input id="branch_merge_point" placeholder="Merge point" type="number"></sm-input>
<sm-button id="create_branch_btn" variant="primary">
<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="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z" />
</svg>
Create
</sm-button>
</sm-form>
</sm-popup>
<sm-popup id="post_update_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close" onclick="closePopup()">
<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>
<h3>Post an update</h3>
</header>
<h5 id="update_of_project"></h5>
<h3 id="update_of_task"></h3>
<sm-form>
<sm-textarea id="update_description" placeholder="Type the update" rows="4" autofocus required>
</sm-textarea>
<sm-button id="post_update_btn" title="post this update" variant="primary" disabled onclick="postUpdate()">
<svg class="icon 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>
<path
d="M1.946 9.315c-.522-.174-.527-.455.01-.634l19.087-6.362c.529-.176.832.12.684.638l-5.454 19.086c-.15.529-.455.547-.679.045L12 14l6-8-8 6-8.054-2.685z">
</path>
</svg>
Post update
</sm-button>
</sm-form>
</sm-popup>
<sm-popup id="apply_for_internship_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close" onclick="closePopup()">
<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>
<h3>Apply for internship</h3>
</header>
<sm-form>
<sm-input id="intern_apply__name" placeholder="Full name" autofocus required></sm-input>
<sm-textarea id="intern_apply__brief" placeholder="Tell us about yourself" rows="6" required></sm-textarea>
<div class="multi-state-button">
<sm-button id="intern_apply__button" title="post this update" variant="primary" disabled
onclick="applyForInternship()">
<svg class="icon 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="M1.923 9.37c-.51-.205-.504-.51.034-.689l19.086-6.362c.529-.176.832.12.684.638l-5.454 19.086c-.15.529-.475.553-.717.07L11 13 1.923 9.37zm4.89-.2l5.636 2.255 3.04 6.082 3.546-12.41L6.812 9.17z" />
</svg>
Apply
</sm-button>
</div>
</sm-form>
</sm-popup>
<!-- Templates -->
<template id="placeholder_task_card_template">
<div class="temp-task grid gap-0-5">
<sm-input class="placeholder-task__title" placeholder="task title"></sm-input>
<sm-textarea class="placeholder-task__description" placeholder="task description" rows="4"></sm-textarea>
<div class="flex">
<sm-button class="cancel-task-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>
Cancel
</sm-button>
<sm-button class="add-task-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="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z" />
</svg>
Add
</sm-button>
</div>
</div>
</template>
<template id="task_list_item_template">
<li class="task-list-item">
<sm-checkbox title="Mark this task as completed"></sm-checkbox>
<h4 class="task-title capitalize" data-editable></h4>
<button class="task-option">
<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 3c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 14c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-7c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" />
</svg>
</button>
<div class="assigned-interns"></div>
<p class="task-description" data-editable></p>
<div class="task__branch_container"></div>
</li>
</template>
<template id="intern_card_template">
<li class="intern-card grid align-center interact">
<div class="intern-card__initials"></div>
<div class="intern-card__name capitalize"></div>
<div class="intern-card__score-wrapper flex align-center">
<h4 class="intern-card__score"></h4>
<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 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>
</li>
</template>
<template id="timeline_task_card">
<div class="task">
<div class="left">
<div class="circle"></div>
<div class="line"></div>
</div>
<div class="right">
<div class="apply-container flex space-between align-items-start">
<h4 class="timeline-task__title capitalize"></h4>
</div>
<div class="assigned-interns"></div>
<p class="timeline-task__description"></p>
<div class="task__branch_container"></div>
</div>
</div>
</template>
<template id="watchlist_project_template">
<a class="watchlist_project_card interact">
<div class="project-icon">
<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.414 5H21a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7.414l2 2zM4 7v12h16V7H4z" />
</svg>
</div>
<b class="project__title"></b>
<div class="progress-bar">
<div class="progress-value"></div>
</div>
<span class="project__complete-percent"></span>
</a>
</template>
<script>
/*jshint esversion: 8 */
/**
* @yaireo/relative-time - javascript function to transform timestamp or date to local relative-time
*
* @version v1.0.0
* @homepage https://github.com/yairEO/relative-time
*/
!function (e, t) { var o = o || {}; "function" == typeof o && o.amd ? o([], t) : "object" == typeof exports && "object" == typeof module ? module.exports = t() : "object" == typeof exports ? exports.RelativeTime = t() : e.RelativeTime = t() }(this, (function () { const e = { year: 31536e6, month: 2628e6, day: 864e5, hour: 36e5, minute: 6e4, second: 1e3 }, t = "en", o = { numeric: "auto" }; function n(e) { e = { locale: (e = e || {}).locale || t, options: { ...o, ...e.options } }, this.rtf = new Intl.RelativeTimeFormat(e.locale, e.options) } return n.prototype = { from(t, o) { const n = t - (o || new Date); for (let t in e) if (Math.abs(n) > e[t] || "second" == t) return this.rtf.format(Math.round(n / e[t]), t) } }, n }));
const relativeTime = new RelativeTime({ style: 'narrow' });
</script>
<script id="default_ui_library">
// Global variables
const { html, render: renderElem } = uhtml;
const domRefs = {}
//Checks for internet connection status
if (!navigator.onLine)
notify('There seems to be a problem connecting to the internet, Please check you internet connection.', 'error', '', true)
window.addEventListener('offline', () => {
notify('There seems to be a problem connecting to the internet, Please check you internet connection.', 'error', true, true)
})
window.addEventListener('online', () => {
getRef('notification_drawer').clearAll()
notify('We are back online.', 'success')
})
// Use instead of document.getElementById
function getRef(elementId) {
if (!domRefs.hasOwnProperty(elementId)) {
domRefs[elementId] = {
count: 1,
ref: null,
};
return document.getElementById(elementId);
} else {
if (domRefs[elementId].count < 3) {
domRefs[elementId].count = domRefs[elementId].count + 1;
return document.getElementById(elementId);
} else {
if (!domRefs[elementId].ref)
domRefs[elementId].ref = document.getElementById(elementId);
return domRefs[elementId].ref;
}
}
}
// returns dom with specified element
function createElement(tagName, options = {}) {
const { className, textContent, innerHTML, attributes = {} } = options
const elem = document.createElement(tagName)
for (let attribute in attributes) {
elem.setAttribute(attribute, attributes[attribute])
}
if (className)
elem.className = className
if (textContent)
elem.textContent = textContent
if (innerHTML)
elem.innerHTML = innerHTML
return elem
}
// Use when a function needs to be executed after user finishes changes
const debounce = (callback, wait) => {
let timeoutId = null;
return (...args) => {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => {
callback.apply(null, args);
}, wait);
};
}
let zIndex = 50
// function required for popups or modals to appear
function openPopup(popupId, pinned) {
zIndex++
getRef(popupId).setAttribute('style', `z-index: ${zIndex}`)
getRef(popupId).show({ pinned })
return getRef(popupId);
}
// hides the popup or modal
function closePopup() {
if (popupStack.peek() === undefined)
return;
popupStack.peek().popup.hide()
}
// displays a popup for asking permission. Use this instead of JS confirm
const getConfirmation = (title, message, cancelText = 'Cancel', confirmText = 'OK') => {
return new Promise(resolve => {
openPopup('confirmation_popup', true)
getRef('confirm_title').textContent = title;
getRef('confirm_message').textContent = message;
let cancelButton = getRef('confirmation_popup').children[2].children[0],
submitButton = getRef('confirmation_popup').children[2].children[1]
submitButton.textContent = confirmText
cancelButton.textContent = cancelText
submitButton.onclick = () => {
closePopup()
resolve(true);
}
cancelButton.onclick = () => {
closePopup()
resolve(false);
}
})
}
//Function for displaying toast notifications. pass in error for mode param if you want to show an error.
function notify(message, mode, options = {}) {
let icon
switch (mode) {
case 'success':
icon = `<svg class="icon icon--success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z"/></svg>`
break;
case 'error':
icon = `<svg class="icon icon--error" 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 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z"/></svg>`
options.pinned = true
break;
}
getRef("notification_drawer").push(message, { icon, ...options });
if (mode === 'error') {
console.error(message)
}
}
window.addEventListener('hashchange', e => showPage(window.location.hash))
window.addEventListener("load", () => {
document.body.classList.remove('hidden')
document.querySelectorAll('sm-input[data-flo-id]').forEach(input => input.customValidation = floCrypto.validateAddr)
document.querySelectorAll('sm-input[data-private-key]').forEach(input => input.customValidation = floCrypto.getPubKeyHex)
document.addEventListener('keyup', (e) => {
if (e.code === 'Escape') {
closePopup()
}
})
document.addEventListener("pointerdown", (e) => {
if (e.target.closest("button:not([disabled]), sm-button:not([disabled]), .interact")) {
createRipple(e, e.target.closest("button, sm-button, .interact"));
}
});
document.addEventListener('copy', () => {
notify('copied', 'success')
})
});
function createRipple(event, target) {
const circle = document.createElement("span");
const diameter = Math.max(target.clientWidth, target.clientHeight);
const radius = diameter / 2;
const targetDimensions = target.getBoundingClientRect();
circle.style.width = circle.style.height = `${diameter}px`;
circle.style.left = `${event.clientX - (targetDimensions.left + radius)}px`;
circle.style.top = `${event.clientY - (targetDimensions.top + radius)}px`;
circle.classList.add("ripple");
const rippleAnimation = circle.animate(
[
{
transform: "scale(4)",
opacity: 0,
},
],
{
duration: 600,
fill: "forwards",
easing: "ease-out",
}
);
target.append(circle);
rippleAnimation.onfinish = () => {
circle.remove();
};
}
function randomHsl(saturation = 80, lightness = 80) {
let hue = Math.random() * 360;
let color = {
primary: `hsla( ${hue}, ${saturation}%, ${lightness}%, 1)`,
light: `hsla( ${hue}, ${saturation}%, 90%, 0.6)`,
};
return color;
}
const selectedColors = [
"#FF1744",
"#F50057",
"#8E24AA",
"#5E35B1",
"#3F51B5",
"#3D5AFE",
"#00B0FF",
"#00BCD4",
"#16c79a",
"#66BB6A",
"#8BC34A",
"#11698e",
"#FF6F00",
"#FF9100",
"#FF3D00",
];
function randomColor() {
return selectedColors[Math.floor(Math.random() * selectedColors.length)];
}
function getFormattedTime(timestamp, format) {
try {
if (String(timestamp).length < 13)
timestamp *= 1000
let [day, month, date, year] = new Date(timestamp).toString().split(' '),
minutes = new Date(timestamp).getMinutes(),
hours = new Date(timestamp).getHours(),
currentTime = new Date().toString().split(' ')
minutes = minutes < 10 ? `0${minutes}` : minutes
let finalHours = ``;
if (hours > 12)
finalHours = `${hours - 12}:${minutes}`
else if (hours === 0)
finalHours = `12:${minutes}`
else
finalHours = `${hours}:${minutes}`
finalHours = hours >= 12 ? `${finalHours} PM` : `${finalHours} AM`
switch (format) {
case 'date-only':
return `${month} ${date}, ${year}`;
break;
case 'time-only':
return finalHours;
case 'relative':
// check if timestamp is older than a day
if (Date.now() - new Date(timestamp) < 60 * 60 * 24 * 1000)
return `${finalHours}`;
else
return relativeTime.from(timestamp)
default:
return `${month} ${date}, ${year} at ${finalHours}`;
}
} catch (e) {
console.error(e);
return timestamp;
}
}
let lastPage
let lastParams = {
projectId: '',
branch: ''
}
function showPage(targetPage, options = {}) {
const { firstLoad, hashChange } = options
let pageId
let searchParams
let params
if (targetPage === '') {
pageId = 'dashboard_page'
}
else {
if (targetPage.includes('?')) {
const splitAddress = targetPage.split('?')
searchParams = splitAddress.pop()
pageId = splitAddress.pop().split('#').pop()
} else {
pageId = targetPage.split('#').pop()
}
}
if (!document.querySelector(`#${pageId}.page`)) return
if (searchParams) {
const urlSearchParams = new URLSearchParams('?' + searchParams);
params = Object.fromEntries(urlSearchParams.entries());
}
if (pageId !== lastPage && document.querySelector('.page:not(.hidden)')) {
document.querySelector('.page:not(.hidden)').classList.add('hidden')
}
if (document.querySelector('.nav-list__item--active'))
document.querySelector('.nav-list__item--active').classList.remove('nav-list__item--active');
const targetListItem = [...document.querySelectorAll(`a.nav-list__item`)].find(item => item.href.includes(pageId))
if (targetListItem)
targetListItem.classList.add('nav-list__item--active')
switch (pageId) {
case 'dashboard_page':
const frag = document.createDocumentFragment()
getRef('project_watchlist').innerHTML = ''
if (watchList.length) {
watchList.forEach(project => {
frag.append(render.watchlistProject(project))
})
getRef('project_watchlist').append(frag)
}
break;
case 'updates_page':
renderInternUpdates()
if (!getRef('updates_page__project_selector').children.length) {
renderProjectSelectorOptions()
renderInternSelectorOptions()
}
break;
case 'applications':
render.internApplications()
break;
case 'all_interns_page':
renderAllInterns()
break;
case 'project_explorer':
if (params) {
const { projectId, branch } = params
if (lastParams.projectId !== projectId) {
showProjectInfo(projectId)
}
if (branch) {
const branchDetails = {
destination: 'project_explorer',
taskListContainer: 'explorer_task_list',
projectId,
branch,
pageId
}
showTasksOfBranch(branchDetails)
}
getRef('project_explorer__left').classList.add('hide-on-mobile')
getRef('project_explorer__right').classList.remove('hide-on-mobile')
} else {
getRef('project_explorer__left').classList.remove('hide-on-mobile')
getRef('project_explorer__right').classList.add('hide-on-mobile')
}
break;
case 'admin_page':
if (params && RIBC.getProjectList().includes(params.projectId)) {
const { projectId, branch } = params
renderAdminProjectView(projectId)
if (branch) {
const branchDetails = {
destination: 'project_editing_panel',
taskListContainer: 'task_list',
projectId,
branch,
pageId
}
showTasksOfBranch(branchDetails)
}
getRef('admin_page__left').classList.add('hide-on-mobile')
getRef('project_editing_panel').classList.remove('hide-on-mobile')
} else {
getRef('admin_page__left').classList.remove('hide-on-mobile')
getRef('project_editing_panel').classList.add('hide-on-mobile')
history.replaceState(null, '', '#admin_page')
}
break;
}
if (pageId !== 'all_interns_page') {
getRef('all_interns_list').innerHTML = ''
}
if (pageId !== 'dashboard_page') {
getRef('project_watchlist').innerHTML = ''
}
getRef(pageId).classList.remove('hidden')
lastPage = pageId
lastParams
}
// class based lazy loading
class LazyLoader {
constructor(container, elementsToRender, renderFn, options = {}) {
const { batchSize = 10, freshRender, bottomFirst = false, domUpdated } = options
this.elementsToRender = elementsToRender
this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || []
this.renderFn = renderFn
this.intersectionObserver
this.batchSize = batchSize
this.freshRender = freshRender
this.domUpdated = domUpdated
this.bottomFirst = bottomFirst
this.shouldLazyLoad = false
this.lastScrollTop = 0
this.lastScrollHeight = 0
this.lazyContainer = document.querySelector(container)
this.update = this.update.bind(this)
this.render = this.render.bind(this)
this.init = this.init.bind(this)
this.clear = this.clear.bind(this)
}
get elements() {
return this.arrayOfElements
}
init() {
this.intersectionObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
observer.disconnect()
this.render({ lazyLoad: true })
}
})
}, {
root: this.lazyContainer
})
this.mutationObserver = new MutationObserver(mutationList => {
mutationList.forEach(mutation => {
if (mutation.type === 'childList') {
if (mutation.addedNodes.length) {
if (this.bottomFirst) {
if (this.lazyContainer.firstElementChild)
this.intersectionObserver.observe(this.lazyContainer.firstElementChild)
} else {
if (this.lazyContainer.lastElementChild)
this.intersectionObserver.observe(this.lazyContainer.lastElementChild)
}
}
}
})
})
this.mutationObserver.observe(this.lazyContainer, {
childList: true,
})
this.render()
}
update(elementsToRender) {
this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || []
}
render(options = {}) {
let { lazyLoad = false } = options
this.shouldLazyLoad = lazyLoad
const frag = document.createDocumentFragment();
if (lazyLoad) {
if (this.bottomFirst) {
this.updateEndIndex = this.updateStartIndex
this.updateStartIndex = this.updateEndIndex - this.batchSize
} else {
this.updateStartIndex = this.updateEndIndex
this.updateEndIndex = this.updateEndIndex + this.batchSize
}
} else {
this.intersectionObserver.disconnect()
if (this.bottomFirst) {
this.updateEndIndex = this.arrayOfElements.length
this.updateStartIndex = this.updateEndIndex - this.batchSize - 1
} else {
this.updateStartIndex = 0
this.updateEndIndex = this.batchSize
}
this.lazyContainer.innerHTML = ``;
}
this.lastScrollHeight = this.lazyContainer.scrollHeight
this.lastScrollTop = this.lazyContainer.scrollTop
this.arrayOfElements.slice(this.updateStartIndex, this.updateEndIndex).forEach((element, index) => {
frag.append(this.renderFn(element))
})
if (this.bottomFirst) {
this.lazyContainer.prepend(frag)
// scroll anchoring for reverse scrolling
this.lastScrollTop += this.lazyContainer.scrollHeight - this.lastScrollHeight
this.lazyContainer.scrollTo({ top: this.lastScrollTop })
this.lastScrollHeight = this.lazyContainer.scrollHeight
} else {
this.lazyContainer.append(frag)
}
if (!lazyLoad && this.bottomFirst) {
this.lazyContainer.scrollTop = this.lazyContainer.scrollHeight
}
// Callback to be called if elements are updated or rendered for first time
if (!lazyLoad && this.freshRender)
this.freshRender()
}
clear() {
this.intersectionObserver.disconnect()
this.mutationObserver.disconnect()
this.lazyContainer.innerHTML = ``;
}
reset() {
this.arrayOfElements = (typeof this.elementsToRender === 'function') ? this.elementsToRender() : this.elementsToRender || []
this.render()
}
}
function buttonLoader(id, show) {
const button = typeof id === 'string' ? getRef(id) : id;
button.disabled = show;
const animOptions = {
duration: 200,
fill: 'forwards',
easing: 'ease'
}
if (show) {
button.animate([
{
clipPath: 'circle(100%)',
},
{
clipPath: 'circle(0)',
},
], animOptions).onfinish = e => {
e.target.commitStyles()
e.target.cancel()
}
button.parentNode.append(createElement('sm-spinner'))
} else {
button.style = ''
const potentialTarget = button.parentNode.querySelector('sm-spinner')
if (potentialTarget) potentialTarget.remove();
}
}
// implement event delegation
function delegate(el, event, selector, fn) {
el.addEventListener(event, function (e) {
const potentialTarget = e.target.closest(selector)
if (potentialTarget) {
e.delegateTarget = potentialTarget
fn.call(this, e)
}
})
}
</script>
<script id="UI_functions">
// Method object for creating various UI elements
const render = {
projectCard(projectCode, isAdmin = false, ref) { // creates cards containing project information
const projectName = RIBC.getProjectDetails(projectCode).projectName
const page = isAdmin ? 'admin_page' : 'project_explorer'
return html.for(ref, projectCode)`<a class="project-card flex align-center interact" title="Project information" href=${`#${page}?projectId=${projectCode}&branch=mainLine`}>${projectName}</a>`
},
taskCard(currentProject, currentBranch, taskNo) {
const card = getRef('timeline_task_card').content.cloneNode(true),
assignedInterns = RIBC.getAssignedInterns(currentProject, currentBranch, taskNo).filter(v => v)
if (RIBC.getTaskStatus(currentProject, currentBranch, taskNo) === 'completed') {
card.firstElementChild.classList.add('completed-task')
}
if (assignedInterns) {
renderElem(card.querySelector('.assigned-interns'), html`${assignedInterns.map((internFloId) => render.assignedInternCard(internFloId))}`)
}
if (typeOfUser === 'intern' && !assignedInterns.includes(myFloID)) {
const applyButton = createElement('sm-button', {
textContent: 'Apply',
className: 'apply-button',
attributes: { 'data-project-code': currentProject, 'data-branch': currentBranch, 'data-task-no': taskNo }
})
function checkObject(obj) {
return (obj.projectCode === currentProject && obj.branch === currentBranch && obj.taskNo == taskNo)
}
if (localStorage.getItem('requests') !== null) {
if (JSON.parse(localStorage.getItem('requests')).find(checkObject) !== undefined) {
applyButton.textContent = 'Applied';
applyButton.setAttribute('disabled', '')
}
}
card.querySelector('.apply-container').append(applyButton)
}
const { taskTitle, taskDescription } = RIBC.getTaskDetails(currentProject, currentBranch, taskNo)
card.querySelector('.timeline-task__title').textContent = taskTitle;
card.querySelector('.timeline-task__description').textContent = taskDescription;
const branches = getAllBranches(currentProject)
for (const branch of branches) {
const { branchName, parentBranch, startPoint, endPoint } = branch
if (parentBranch === currentBranch && startPoint === taskNo) {
card.querySelector('.task__branch_container').append(
render.branchButton({
projectId: currentProject,
branch: branchName,
page: 'project_explorer'
})
)
}
}
return card;
},
internCard(internName, internFloID, internPoints) {
const initials = internName.split(' ').map(v => v.charAt(0)).join('');
return html`
<li class="intern-card grid align-center interact" data-intern-flo-id=${internFloID} title="Intern Information">
<div class="intern-card__initials" style=${`background-color: ${getInternColor(internFloID)}`}>${initials}</div>
<div class="intern-card__name capitalize">${internName}</div>
<div class="intern-card__score-wrapper flex align-center">
<h4 class="intern-card__score">${internPoints}</h4>
<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 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>
</li>`;
},
// creates cards containing updates provided by interns
updateCard(update) {
const { time, internName, topic, description, floID, note } = update
let replyButton
if (typeOfUser === 'admin' && !note) {
replyButton = html`<button class="button button--small init-update-replay margin-left-auto">Reply</button>`
}
let adminReply
if (note) {
adminReply = html`<div class="admin-reply grid">
<h4 class="admin-reply__title">Admin</h4>
<p class="admin-reply__description">${note}</p>
</div>`
}
return html.node`
<li class="intern-update" data-vector-clock="${`${time}_${floID}`}">
<div class="flex align-center space-between">
<span class="update__sender">${internName}</span>
<span class="update__time">${getFormattedTime(time)}</span>
</div>
<h4 class="update__topic">${topic}</h4>
<p class="update__message">${description}</p>
${replyButton}
${adminReply}
</li>`;
},
branchButton(obj = {}) {
const { projectId, branch, page, innerHTML } = obj
return html.node`
<a class="branch-button" href=${`#${page}?projectId=${projectId}&branch=${branch}`}>
${innerHTML ? html`${innerHTML}` : branch}
</a>
`;
},
assignedInternCard(internFloId, options) {
let optionsButton
if (options) {
optionsButton = html` <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> `;
}
return html`
<span class="assigned-intern" data-flo-id="${internFloId}">
${RIBC.getInternList()[internFloId]}
${optionsButton}
</span>
`
},
taskListItem(taskNo) {
const card = getRef('task_list_item_template').content.cloneNode(true)
const assignedInterns = RIBC.getAssignedInterns(currentProject, currentBranch, taskNo)
const frag = document.createDocumentFragment()
const { taskTitle, taskDescription } = RIBC.getTaskDetails(currentProject, currentBranch, taskNo)
if (RIBC.getTaskStatus(currentProject, currentBranch, taskNo) !== 'incomplete') {
card.querySelector('sm-checkbox').setAttribute('checked', '')
}
card.firstElementChild.dataset.taskId = taskNo
card.querySelector('.task-title').textContent = taskTitle;
card.querySelector('.task-description').textContent = taskDescription;
if (assignedInterns) {
const assignedInternsCards = assignedInterns.filter(v => v).map((internFloId) => {
return render.assignedInternCard(internFloId, true);
})
renderElem(card.querySelector('.assigned-interns'), html`${assignedInternsCards}`)
}
const branches = getAllBranches(currentProject)
for (const branch of branches) {
const { branchName, parentBranch, startPoint, endPoint } = branch
if (parentBranch === currentBranch && startPoint === taskNo) {
card.querySelector('.task__branch_container').append(
render.branchButton({
projectId: currentProject,
branch: branchName,
page: 'admin_page'
})
)
}
}
return card;
},
taskRequestCard(details) {
const { projectCode, branch, task, floID, vectorClock } = details
const internName = RIBC.getInternList()[floID];
return html`
<li class="request-card" .dataset=${{ vectorClock, type: 'task' }}>
<p class="request-card__description">
<b class="capitalize">${internName}</b> applied for
<b class="capitalize">${RIBC.getTaskDetails(projectCode, branch, task).taskTitle}</b> from
<b class="capitalize">${branch}</b> of
<b class="capitalize">${RIBC.getProjectDetails(projectCode).projectName}</b>
</p>
<div class="flex gap-0-3 margin-left-auto">
<button class="button button--small reject-request">
<svg class="icon margin-right-0-3" 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>
Reject
</button>
<button class="button button--small accept-request">
<svg class="icon margin-right-0-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z"/></svg>
Accept
</button>
</div>
</li>
`;
},
internshipRequest(details) {
const { comments, floID, name, vectorClock } = details
return html`
<li class="request-card" .dataset=${{ vectorClock, type: 'internship' }}>
<h4>${name}</h4>
<p class="request-card__description">${comments}</p>
<div class="flex gap-0-3 margin-left-auto">
<button class="button button--small reject-request">
<svg class="icon margin-right-0-3" 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>
Reject
</button>
<button class="button button--small accept-request">
<svg class="icon margin-right-0-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z"/></svg>
Accept
</button>
</div>
</li>
`;
},
internTaskCard(taskObj) {
const { uniqueId, projectId, projectBranch, task } = taskObj
const { taskTitle, taskDescription } = RIBC.getTaskDetails(projectId, projectBranch, task)
const projectName = RIBC.getProjectDetails(projectId).projectName
return html`
<li class="task-card" data-unique-id="${uniqueId}">
<span class="task__project-title">${projectName}</span>
<div>
<h4 class="task__title">${taskTitle}</h4>
<p class="task__description">${taskDescription}</p>
</div>
<button class="send-update-button">
<svg class="icon 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="M1.946 9.315c-.522-.174-.527-.455.01-.634l19.087-6.362c.529-.176.832.12.684.638l-5.454 19.086c-.15.529-.455.547-.679.045L12 14l6-8-8 6-8.054-2.685z"/></svg>
Post an update
</button>
</li>
`;
},
watchlistProject(projectId) {
const projectCard = getRef('watchlist_project_template').content.cloneNode(true)
const { projectName } = RIBC.getProjectDetails(projectId)
projectCard.firstElementChild.setAttribute('href', `#project_explorer?projectId=${projectId}&branch=mainLine`)
projectCard.querySelector('.project__title').textContent = projectName
const projectMap = RIBC.getProjectMap(projectId)
const projectBranches = RIBC.getProjectBranches(projectId)
const projectTasks = []
projectBranches.forEach(branch => {
projectMap[branch].forEach((task, index, array) => {
if (index > 3) {
projectTasks.push({
status: RIBC.getTaskStatus(projectId, branch, array[index])
})
}
})
})
const completedTasks = projectTasks.reduce((count, task) => {
if (task.status === 'completed') {
return count += 1
} else {
return count
}
}, 0)
let completePercent = (completedTasks / projectTasks.length) * 100
completePercent = Number.isInteger(completePercent) ? completePercent : completePercent.toFixed(1)
projectCard.querySelector('.progress-value').style.width = `${completePercent}%`
projectCard.querySelector('.project__complete-percent').textContent = `${completePercent}% complete`
return projectCard
},
internRequests() {
const selectedRequestType = getRef('request_type_selector').value;
let requestCards = [];
if (selectedRequestType === 'task') {
RIBC.getTaskRequests().forEach((request) => {
const { projectCode, branch, task } = request
if (typeof RIBC.getTaskDetails(projectCode, branch, task) !== 'undefined')
requestCards.push(render.taskRequestCard(request))
})
} else {
requestCards = RIBC.getInternRequests().map((request) => render.internshipRequest(request))
}
console.log(requestCards)
renderElem(getRef('requests_list'), html`${requestCards}`)
},
projectList(container, projects, isAdminList = false) {
renderElem(container, html`${projects.map(projectCode => render.projectCard(projectCode, isAdminList, container))}`)
},
requestStatus(request, type) {
const { comments, floID, projectCode, branch, task, name, status, vectorClock } = request
const timestamp = parseInt(vectorClock.split('_')[0])
let details
let icon = ''
if (status === 'Accepted') {
icon = html`<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="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/></svg>`
} else if (status === 'Rejected') {
icon = html`<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="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM4 12c0-4.42 3.58-8 8-8 1.85 0 3.55.63 4.9 1.69L5.69 16.9C4.63 15.55 4 13.85 4 12zm8 8c-1.85 0-3.55-.63-4.9-1.69L18.31 7.1C19.37 8.45 20 10.15 20 12c0 4.42-3.58 8-8 8z"/></svg>`
} else {
icon = html`<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.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z"/></svg>`
}
if (type === 'internship') {
details = html` <h4 class="status-card__details">Internship application</h4> `;
} else {
details = html`
<p class="status-card__details">
You applied for
<b class="capitalize">${RIBC.getTaskDetails(projectCode, branch, task).taskTitle}</b> from
<b class="capitalize">${branch}</b> of
<b class="capitalize">${RIBC.getProjectDetails(projectCode).projectName}</b>
</p>`;
}
return html`
<li class=${`status-card ${status?.toLowerCase() || 'pending'}`}>
<time class="status-card__time">${getFormattedTime(timestamp, 'relative')}</time>
${details}
<div class="flex align-center status-card__status">
${icon}
<span>${status || 'Under review'}</span>
</div>
</li>
`
},
internApplications() {
const internshipRequests = RIBC.getInternRequests(false).reverse()
const taskRequests = RIBC.getTaskRequests(false).reverse()
const requestCards = internshipRequests.map(request => render.requestStatus(request, 'internship'))
renderElem(getRef('internship_requests_list'), html`${requestCards}`)
if (taskRequests.length) {
const taskCards = taskRequests.map(request => render.requestStatus(request, 'task'))
renderElem(getRef('task_requests_list'), html`${taskCards}`)
getRef('task_requests_list').parentElement.classList.remove('hidden')
} else {
getRef('task_requests_list').parentElement.classList.add('hidden')
}
}
}
const renderedIntensColor = {}
function getInternColor(floId) {
if (!renderedIntensColor[floId]) {
renderedIntensColor[floId] = randomColor()
}
return renderedIntensColor[floId]
}
//helper functions
let allInternsList = [];
let highPerformingInterns = [];
let watchList = [];
let currentIntern;
let currentTaskId;
let typeOfUser = 'general';
// Adds interns to the database **Only SubAdmins can add interns
function addInternToList() {
let internName = getRef('intern_name_field').value.trim(),
internFloID = getRef('intern_flo_id_field').value.trim();
if (RIBC.admin.addIntern(internFloID, internName)) {
renderElem(getRef('admin_page__intern_list'), filterInterns(''))
closePopup();
notify(`${internName} added as an intern.`, 'success')
}
}
function addProjectToList() {
let projectName = getRef('project_name_field').value.trim(),
projectDescription = getRef('project_description_field').value.trim();
if (projectName === '') {
return notify('Project name is important!', 'error')
}
if (projectDescription === '') {
return notify('Project description is important!', 'error')
}
const projectCode = `${new Date().getFullYear()}_project_${RIBC.getProjectList() ? (RIBC.getProjectList().length + 1) : '1'}`;
RIBC.admin.createProject(projectCode)
RIBC.admin.addProjectDetails(projectCode, { projectName, projectDescription })
render.projectList(getRef('admin_page__project_list'), getSortedProjectList(), true)
getRef('admin_page__project_list').querySelector(`[href="#admin_page?projectId=${projectCode}&branch=mainLine"]`)?.click()
closePopup();
}
function makeEditable(elem) {
floGlobals.tempEditableContent = DOMPurify.sanitize(elem.innerText.trim())
elem.contentEditable = true
elem.focus()
document.execCommand('selectAll', false, null);
}
getRef('project_details_wrapper').addEventListener('dblclick', e => {
if (e.target.closest('[data-editable]') && !e.target.closest('[data-editable]').isContentEditable) {
makeEditable(e.target.closest('[data-editable]'))
}
})
getRef('project_details_wrapper').addEventListener('focusout', (e) => {
if (e.target.isContentEditable) {
e.target.contentEditable = false
if (e.target.innerText.trim() !== '' && floGlobals.tempEditableContent !== DOMPurify.sanitize(e.target.innerText.trim())) {
const newTitle = DOMPurify.sanitize(getRef('editing_panel__title').innerText.trim())
const newDescription = DOMPurify.sanitize(getRef('editing_panel__description').innerText.trim())
RIBC.admin.addProjectDetails(currentProject, { projectName: newTitle, projectDescription: newDescription })
notify('Changes saved locally, commit the changes to make them permanent', 'success')
render.projectList(getRef('admin_page__project_list'), getSortedProjectList(), true)
} else {
e.target.innerText = floGlobals.tempEditableContent
}
}
})
// opens a popup containing various intern information
function showInternInfo(internFloId) {
const internName = RIBC.getInternList()[internFloId]
getRef('intern_info__initials').textContent = internName.split(' ').map(v => v.charAt(0)).join('');
getRef('intern_info__initials').style.backgroundColor = getInternColor(internFloId)
getRef('intern_info__name').textContent = internName;
getRef('intern_info__flo_id').value = currentIntern = internFloId;
getRef('intern_info__score').textContent = RIBC.getInternRating(internFloId); // points earned by intern
if (RIBC.getInternRating(internFloId) === 1) {
getRef('reduce_score_button').disabled = true;
}
openPopup('intern_info_popup');
}
// opens a popup containing various project information
function showProjectInfo(projectId) {
const frag = document.createDocumentFragment();
const allProjects = getRef('project_explorer__left').querySelectorAll('.project-card');
allProjects.forEach(project => project.classList.remove('project-card--active'))
const targetProject = Array.from(allProjects).find(project => project.getAttribute('href').includes(projectId))
if (targetProject)
targetProject.classList.add('project-card--active')
const { projectName, projectDescription } = RIBC.getProjectDetails(projectId);
getRef('project_explorer__project_title').textContent = projectName; // project name
if (watchList.includes(projectId)) {
getRef('watch_project_button').textContent = 'watching';
} else {
getRef('watch_project_button').textContent = 'Watch';
}
getRef('project_explorer__project_description').textContent = projectDescription;
getRef('explorer_branch_container').innerHTML = ``;
RIBC.getProjectBranches(projectId).forEach((branch) => {
frag.append(render.branchButton({ projectId, branch, page: 'project_explorer' }))
})
getRef('explorer_branch_container').append(frag);
}
let currentBranch = 'mainLine',
currentProject = '',
currentTask = '',
lastProject;
function renderAdminProjectView(projectId) {
const allProjects = getRef('admin_page__project_list').querySelectorAll('.project-card');
const branchList = document.querySelectorAll('.branch-button');
const frag = document.createDocumentFragment();
allProjects.forEach(project => project.classList.remove('project-card--active'))
const targetProject = Array.from(allProjects).find(project => project.getAttribute('href').includes(projectId))
if (targetProject)
targetProject.classList.add('project-card--active')
getRef('branch_container').innerHTML = '';
getRef('task_list').innerHTML = '';
currentProject = projectId;
branchList.forEach((branch) => {
branch.classList.remove('active-branch')
})
const { projectName, projectDescription } = RIBC.getProjectDetails(projectId);
getRef('editing_panel__title').textContent = projectName;
getRef('editing_panel__description').textContent = projectDescription;
RIBC.getProjectBranches(currentProject).forEach((branch) => {
frag.append(render.branchButton({ projectId: currentProject, branch, page: 'admin_page' }))
})
getRef('branch_container').appendChild(frag)
}
function showTasksOfBranch(obj = {}) {
const { destination, taskListContainer, projectId, branch, pageId } = obj
currentProject = projectId;
currentBranch = branch;
const frag = document.createDocumentFragment();
let branchTasks = RIBC.getProjectMap(currentProject)[currentBranch],
taskList = document.getElementById(taskListContainer)
allBranches = document.getElementById(destination).querySelectorAll('.branch-button');
taskList.innerHTML = '';
allBranches.forEach((branchButton) => {
branchButton.classList.remove('active-branch')
})
document.getElementById(destination).querySelector(`.branch-button[href="#${pageId}?projectId=${projectId}&branch=${branch}"]`).classList.add('active-branch')
if (branchTasks[1] && !taskListContainer === 'task_list') {
taskList.textContent = "No tasks added yet, Please explore other projects"
} else {
if (taskListContainer === 'task_list') {
showInnerPage('project_editing_panel')
branchTasks.slice(4).forEach((taskNo) => {
frag.append(render.taskListItem(taskNo))
})
} else {
branchTasks.slice(4).forEach((taskNo) => {
frag.append(render.taskCard(currentProject, currentBranch, taskNo))
})
}
if (branch !== 'mainLine') {
const { startPoint, parentBranch } = getAllBranches(projectId).find(({ branchName }) => branchName === branch)
taskList.append(html.node`<p class="margin-bottom-0-5">
Branched off from <a href=${`#project_explorer?projectId=${projectId}&branch=${parentBranch}`}> ${parentBranch} </a>
</p>`)
}
taskList.appendChild(frag);
}
}
function getAllBranches(projectId) {
const projectMap = RIBC.getProjectMap(projectId)
const projectBranches = RIBC.getProjectBranches(projectId)
const branchPoints = []
projectBranches.forEach((branch, index) => {
if (index > 0) {
const [parentBranch, , startPoint, endPoint] = projectMap[branch]
branchPoints.push({
branchName: branch,
parentBranch,
startPoint,
endPoint
})
}
})
return branchPoints
}
function toggleEditing(target) {
if (target === 'title') {
makeEditable(currentTask.querySelector('.task-title'))
} else {
makeEditable(currentTask.querySelector('.task-description'))
}
}
getRef('task_list').addEventListener('change', (e) => {
if (e.target.closest('sm-checkbox')) {
currentTask = e.target.closest('.task-list-item');
const taskStatus = e.target.closest('sm-checkbox').checked ? 'completed' : 'incomplete'
RIBC.admin.putTaskStatus(taskStatus, currentProject, currentBranch, currentTask.dataset.taskId)
}
})
getRef('task_list').addEventListener('focusout', (e) => {
if (e.target.isContentEditable) {
e.target.contentEditable = false
if (e.target.innerText.trim() !== '' && floGlobals.tempEditableContent !== DOMPurify.sanitize(e.target.innerText.trim())) {
const { taskTitle, taskDescription } = RIBC.getTaskDetails(currentProject, currentBranch, currentTask.dataset.taskId)
const taskDetails = {}
if (e.target.closest('.task-description')) {
taskDetails['taskTitle'] = taskTitle
taskDetails['taskDescription'] = DOMPurify.sanitize(e.target.innerText.trim())
} else if (e.target.closest('.task-title')) {
taskDetails['taskTitle'] = DOMPurify.sanitize(e.target.innerText.trim())
taskDetails['taskDescription'] = taskDescription
}
RIBC.admin.editTaskDetails(taskDetails, currentProject, currentBranch, currentTask.dataset.taskId)
notify('Changes saved locally, commit the changes to make them permanent', 'success')
} else {
e.target.innerText = floGlobals.tempEditableContent
}
}
})
getRef('task_list').addEventListener('dblclick', (e) => {
if (e.target.closest('[data-editable]') && !e.target.closest('[data-editable]').isContentEditable) {
makeEditable(e.target.closest('[data-editable]'))
}
})
getRef('task_list').addEventListener('click', (e) => {
if (e.target.closest('.task-list-item')) {
currentTask = e.target.closest('.task-list-item');
}
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').classList.remove('hidden')
getRef('task_context').animate([
{
transform: 'scaleY(0.95) translateY(-0.5rem)',
opacity: '0'
},
{
transform: 'none',
opacity: '1'
},
], {
duration: 200,
easing: 'ease'
})
.onfinish = () => {
getRef('task_context').firstElementChild.focus()
const y = document.addEventListener("click", function (e) {
if (e.target.closest('#context_menu') || e.target.closest('.task-option')) return;
getRef('task_context').animate([
{
transform: 'none',
opacity: '1'
},
{
transform: 'scaleY(0.95) translateY(-0.5rem)',
opacity: '0'
},
], {
duration: 100,
easing: 'ease'
}).onfinish = () => {
getRef('task_context').classList.add('hidden')
document.removeEventListener('click', y);
}
});
}
}
else if (e.target.closest('.assigned-intern button')) {
getConfirmation('Do you want to unassign this intern from this task?').then((result) => {
if (result) {
RIBC.admin.unassignInternFromTask(e.target.closest('.assigned-intern').dataset.floId, currentProject, currentBranch, currentTask.dataset.taskId)
e.target.closest('.assigned-intern').remove()
notify('Intern removed from the task')
}
})
}
else if (e.target.closest('.cancel-task-button')) {
const card = e.target.closest('.temp-task')
card.remove();
getRef('add_task').disabled = false
}
else if (e.target.closest('.add-task-button')) {
const card = e.target.closest('.temp-task')
const titleContent = card.querySelector('.placeholder-task__title').value.trim(),
descriptionContent = card.querySelector('.placeholder-task__description').value.trim();
if (titleContent === '') {
notify('Please enter task title', 'error')
return
}
if (descriptionContent === '') {
notify('Please enter description of the task', 'error')
return
}
const taskNo = RIBC.admin.addTaskInMap(currentProject, currentBranch)
RIBC.admin.editTaskDetails({ taskTitle: titleContent, taskDescription: descriptionContent }, currentProject, currentBranch, taskNo)
RIBC.admin.putTaskStatus('incomplete', currentProject, currentBranch, taskNo)
card.remove()
getRef('task_list').append(render.taskListItem(taskNo))
getRef('add_task').disabled = false
notify('Task added to current branch', 'success')
}
})
function addPlaceholderTask() {
const placeholderTask = getRef('placeholder_task_card_template').content.cloneNode(true)
getRef('task_list').append(placeholderTask)
getRef('task_list').lastElementChild.scrollIntoView({ behavior: 'smooth' })
getRef('add_task').disabled = true
}
function commitToChanges() {
getConfirmation("Do you want to commit to changes?").then((result) => {
if (result) {
RIBC.admin.updateObjects().then(res => {
notify('Changes committed.', 'success')
}).catch(err => {
console.error(err)
})
}
})
}
function removeThisTask() {
getConfirmation("Are you sure to delete this task?").then((result) => {
if (result) {
RIBC.admin.deleteTaskInMap(currentProject, currentBranch, currentTask.dataset.taskId)
currentTask.remove();
}
})
}
function showInnerPage(page, type) {
let allPages = document.querySelectorAll('.inner-page'),
thisPage = document.getElementById(page)
allPages.forEach(function (pages) {
pages.classList.add('hidden');
});
thisPage.classList.remove('hidden');
}
function signOut() {
getConfirmation('Sign out?', 'You are about to sign out of the app, continue?', 'Stay', 'Leave')
.then(async (res) => {
if (res) {
await floDapps.clearCredentials();
location.reload();
}
});
}
document.getElementById('intern_list_container').addEventListener('click', (event) => {
if (event.target.closest('.intern-card')) {
const floId = event.target.closest('.intern-card').dataset.internFloId;
const internName = RIBC.getInternList()[floId]
const { projectName } = RIBC.getProjectDetails(currentProject)
if (RIBC.admin.assignInternToTask(floId, currentProject, currentBranch, currentTask.dataset.taskId)) {
notify(`${internName} assigned to ${projectName}`)
currentTask.querySelector('.assigned-interns').append(html.node`${render.assignedInternCard(floId, true)}`)
} else {
notify('intern already assigned', 'error')
}
closePopup()
}
})
function unassignIntern() {
RIBC.admin.unassignInternFromTask(currentProject, currentBranch, currentTask)
}
function renderAllInterns() {
renderElem(getRef('all_interns_list'), filterInterns('', { sortByRating: true }))
}
function changeScore(scoreUpdate) {
let score = parseInt(getRef('intern_info__score').textContent)
score += scoreUpdate;
getRef('intern_info__score').textContent = score
document.querySelectorAll(`[data-intern-flo-id="${currentIntern}"]`).forEach(internCard => {
internCard.querySelector('.intern-card__score').textContent = score
})
if (score > 0) {
getRef('reduce_score_button').disabled = false;
RIBC.admin.updateInternRating(currentIntern, scoreUpdate)
}
if (score === 1 && scoreUpdate === -1) {
getRef('reduce_score_button').disabled = true;
}
}
function showNewBranchPopup() {
openPopup('create_branch_popup')
const startPoint = parseInt(currentTask.dataset.taskId)
getRef('branch_start_point').value = startPoint;
}
getRef('create_branch_btn').onclick = () => {
const startPoint = parseInt(currentTask.dataset.taskId)
const userMergePoint = getRef('branch_merge_point').value.trim()
const mergePoint = (userMergePoint === '') ? startPoint : parseInt(userMergePoint)
const branchName = RIBC.admin.addBranch(currentProject, currentBranch, startPoint, mergePoint);
notify(`Branch added ${branchName}`, 'success')
currentTask.querySelector('.task__branch_container').append(render.branchButton({ projectId: currentProject, branch: branchName, page: 'admin_page' }))
closePopup()
}
function renderProjectSelectorOptions() {
const options = [html`<sm-option value="all" active>All</sm-option>`];
RIBC.getProjectList().reverse().forEach(project => {
options.push(html`<sm-option value="${project}">${RIBC.getProjectDetails(project).projectName}</sm-option>`);
})
renderElem(getRef('updates_page__project_selector'), html`${options}`)
}
function renderInternSelectorOptions() {
const options = [html`<sm-option value="all" active>All</sm-option>`];
const allInterns = Object.entries(RIBC.getInternList()).sort((a, b) => a[1].toLowerCase().localeCompare(b[1].toLowerCase()));
allInterns.forEach(intern => {
options.push(html`<sm-option value="${intern[0]}">${intern[1]}</sm-option>`);
})
renderElem(getRef('updates_page__intern_selector'), html`${options}`)
}
async function getUpdatesByProject(projectId, allUpdates) {
const projectName = RIBC.getProjectDetails(projectId).projectName
if (!allUpdates) {
allUpdates = RIBC.getInternUpdates()
}
const options = {
keys: ['update.topic'],
threshold: 0.2
}
const fuse = new Fuse(allUpdates, options)
return fuse.search(projectName).map(v => v.item)
}
function getUpdatesByIntern(floId, allUpdates = RIBC.getInternUpdates()) {
return allUpdates.filter(update => update.floID === floId)
}
async function getUpdatesByDate(date, allUpdates = RIBC.getInternUpdates()) {
const filteredUpdates = []
const dateStart = new Date(`${date} 00:00:00`).getTime()
const dateEnd = new Date(`${date} 23:59:59`).getTime()
let isFromDate = false
for (const update of allUpdates) {
if (update.time > dateStart && update.time < dateEnd) {
filteredUpdates.push(update)
isFromDate = true
} else if (isFromDate) break
}
return filteredUpdates
}
let updateStartIndex = 0
let updateEndIndex = 0
const intersectionObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
observer.disconnect()
renderFilteredUpdates({ lazyLoad: true })
}
})
}, {
threshold: 0.3
})
function renderInternUpdates(options = {}) {
let { updates, lazyLoad = false } = options
const frag = document.createDocumentFragment();
if (!updates) {
updates = RIBC.getInternUpdates();
}
if (lazyLoad) {
updateStartIndex = updateEndIndex
updateEndIndex = updates.length > updateEndIndex + 10 ? updateEndIndex + 10 : updates.length
} else {
intersectionObserver.disconnect()
getRef('all_updates_list').innerHTML = ``;
updateStartIndex = 0
updateEndIndex = updates.length > 10 ? 10 : updates.length
}
for (let index = updateStartIndex; index < updateEndIndex; index++) {
const { floID, time, note, update: { projectId, projectBranch, task, topic, description } } = updates[index]
const updateObj = {
internName: RIBC.getInternList()[floID],
time: parseInt(time),
topic: topic || `${RIBC.getProjectDetails(project).projectName} / ${RIBC.getTaskDetails(projectId, projectBranch, task).taskTitle}`,
description,
floID,
note
}
frag.append(render.updateCard(updateObj))
}
getRef('all_updates_list').append(frag)
if (updateEndIndex !== updates.length && getRef('all_updates_list').lastElementChild) {
intersectionObserver.observe(getRef('all_updates_list').lastElementChild)
}
}
delegate(getRef('all_updates_list'), 'click', '.init-update-replay', (e) => {
const vectorClock = e.delegateTarget.closest('.intern-update').dataset.vectorClock;
e.delegateTarget.after(html.node`
<div class="update-replay grid gap-0-5">
<div class="update-replay__input">
<sm-textarea placeholder="Enter your reply here" id="update_reply_textarea" rows="4"></sm-textarea>
</div>
<div class="flex align-center gap-0-3 margin-left-auto">
<button class="update-replay__cancel button button--small" onclick="cancelUpdateReply(this.closest('.update-replay'))">Cancel</button>
<div class="multi-state-button">
<button class="update-replay__submit button button--small button--primary" onclick="submitUpdateReply(this.closest('.update-replay'))">Submit</button>
</div>
</div>
</div>
`)
e.delegateTarget.classList.add('hidden')
e.target.closest('.intern-update').querySelector('#update_reply_textarea').focusIn()
})
function cancelUpdateReply(replayBox) {
replayBox.previousElementSibling.classList.remove('hidden')
replayBox.remove()
}
function submitUpdateReply(replayBox) {
buttonLoader(replayBox.querySelector('.update-replay__submit'), true)
const vectorClock = replayBox.previousElementSibling.closest('.intern-update').dataset.vectorClock;
const replyText = replayBox.querySelector('#update_reply_textarea').value.trim()
if (replyText !== '') {
RIBC.admin.commentInternUpdate(vectorClock, replyText).then(res => {
replayBox.previousElementSibling.remove()
replayBox.replaceWith(html.node`
<div class="admin-reply grid">
<h4 class="admin-reply__title">Admin</h4>
<p class="admin-reply__description">${replyText}</p>
</div>`)
}).catch(err => {
notify(err, 'error')
buttonLoader(replayBox.querySelector('.update-replay__submit'), false)
})
}
}
const renderFilteredUpdates = debounce(async (options = {}) => {
const { lazyLoad = false } = options
const selectedProject = getRef('updates_page__project_selector').value
const selectedIntern = getRef('updates_page__intern_selector').value
const selectedDate = getRef('updates_page__date_selector').value
let matchedUpdates
if (selectedProject !== 'all') {
matchedUpdates = await getUpdatesByProject(selectedProject)
}
if (selectedIntern !== 'all') {
matchedUpdates = await getUpdatesByIntern(selectedIntern, matchedUpdates)
}
if (selectedDate) {
matchedUpdates = await getUpdatesByDate(selectedDate, matchedUpdates)
}
renderInternUpdates({ updates: matchedUpdates, lazyLoad })
}, 100)
function clearFilter() {
getRef('updates_page__project_selector').reset()
getRef('updates_page__intern_selector').reset()
getRef('updates_page__date_selector').value = ''
renderFilteredUpdates()
}
getRef('updates_page__project_selector').addEventListener('change', renderFilteredUpdates)
getRef('updates_page__intern_selector').addEventListener('change', renderFilteredUpdates)
getRef('updates_page__date_selector').addEventListener('change', renderFilteredUpdates)
getRef('watch_project_button').addEventListener('mouseenter', e => {
if (e.target.textContent === 'watching') {
e.target.textContent = 'unwatch'
e.target.title = 'Remove project from your watchlist'
}
})
getRef('watch_project_button').addEventListener('mouseleave', e => {
if (e.target.textContent === 'unwatch') {
e.target.textContent = 'watching'
e.target.title = 'Add project to your watchlist'
}
})
function watchThisProject(thisBtn) {
watchList = localStorage.getItem(`${myFloID}_watchlist`) ? localStorage.getItem(`${myFloID}_watchlist`).split(',') : []
if (watchList.includes(currentProject)) {
watchList = watchList.filter(project => project !== currentProject)
thisBtn.textContent = 'Watch'
notify(`${RIBC.getProjectDetails(currentProject).projectName} removed from your watch list.`)
} else {
watchList.push(currentProject)
thisBtn.textContent = 'watching'
notify(`${RIBC.getProjectDetails(currentProject).projectName} added to your watch list.`, 'success')
}
localStorage.setItem(`${myFloID}_watchlist`, watchList.join())
}
let allRequests = [];
function requestForTask(btn) {
getConfirmation('Do you want to apply for this task?').then((result) => {
if (result) {
if (localStorage.getItem('requests') === null)
localStorage.setItem('requests', JSON.stringify(allRequests))
btn.textContent = 'Applied'
btn.disabled = true
RIBC.applyForTask(btn.dataset.projectCode, btn.dataset.branch, btn.dataset.taskNo)
.then((result) => {
notify('Applied successfully.', 'success')
allRequests = JSON.parse(localStorage.getItem('requests'))
allRequests.push({ projectCode: btn.dataset.projectCode, branch: btn.dataset.branch, taskNo: btn.dataset.taskNo })
localStorage.setItem('requests', JSON.stringify(allRequests))
})
}
})
.catch((error) => {
notify(error, 'error')
})
}
function loader(mode) {
if (mode === 'show') {
getRef('loading_page').classList.remove('hidden')
}
else {
getRef('loading_page').classList.add('hidden')
}
}
function customSignIn() {
return new Promise((resolve, reject) => {
loader('hide')
getRef('sign_in_page').classList.remove('hidden');
document.getElementById('guest_btn').onclick = function () {
getRef('sign_in_page').classList.add('hidden');
reject(null);
}
document.getElementById('sign_in_button').onclick = function () {
resolve(getRef('private_key_input').value.trim())
getRef('private_key_input').value = ''
loader('show')
window.location.hash = '#dashboard_page'
getRef('sign_in_page').classList.add('hidden');
}
})
}
function toggleFilter() {
getRef('update_filters_wrapper').classList.toggle('hide-on-mobile')
}
// Event listeners
document.getElementById('top_interns').addEventListener('click', (event) => {
if (event.target.closest('.intern-card'))
showInternInfo(event.target.closest('.intern-card').dataset.internFloId)
})
document.getElementById('all_interns_page').addEventListener('click', (event) => {
if (event.target.closest('.intern-card'))
showInternInfo(event.target.closest('.intern-card').dataset.internFloId)
})
document.getElementById('admin_page__intern_list').addEventListener('click', (event) => {
if (event.target.closest('.intern-card'))
showInternInfo(event.target.closest('.intern-card').dataset.internFloId)
})
document.addEventListener('popupopened', e => {
switch (e.detail.popup.id) {
case 'intern_list_popup':
renderElem(getRef('intern_list_container'), filterInterns(''))
break;
}
})
document.addEventListener('popupclosed', e => {
switch (e.detail.popup.id) {
case 'intern_list_popup':
renderElem(getRef('intern_list_container'), html``)
getRef('intern_search_field').value = ''
break;
}
})
floGlobals.assignedTasks = {}
function renderAllElements() {
let sortedProjectList = getSortedProjectList()
document.querySelectorAll('.open-first-project').forEach(link => {
link.href = `${link.href}?projectId=${sortedProjectList[0]}&branch=mainLine`
})
watchList = localStorage.getItem(`${myFloID}_watchlist`) ? localStorage.getItem(`${myFloID}_watchlist`).split(',') : []
const frag = document.createDocumentFragment()
let allProjectsList = RIBC.getProjectList();
//creates cards for highest performing interns
//sort interns earned points
allInternsList = RIBC.getInternList();
highPerformingInterns = [];
for (let intern in allInternsList) {
highPerformingInterns.push({
floId: intern,
internName: allInternsList[intern],
rating: RIBC.getInternRating(intern)
})
}
highPerformingInterns.sort((a, b) => b.rating - a.rating);
getRef('watch_project_button').classList.remove('hidden')
// Intern's view
let assignedProjectsList = [];
let projectTaskNos;
if (allInternsList[myFloID] && !floGlobals.subAdmins.includes(myFloID)) {
typeOfUser = 'intern';
document.querySelectorAll('.intern-option').forEach((option) => {
option.classList.remove('hidden')
})
document.getElementById('project_watching_section').classList.add('hidden')
getRef('watch_project_button').classList.add('hidden')
// store all the projects assigned to interns in array
sortedProjectList.forEach(projectId => {
nextProject:
for (const branch in RIBC.getProjectMap(projectId)) {
projectTaskNos = RIBC.getProjectMap(projectId)[branch].slice(4);
for (let j = 0; j < projectTaskNos.length; j++) {
const assignedInterns = RIBC.getAssignedInterns(projectId, branch, projectTaskNos[j])
if (Array.isArray(assignedInterns) && assignedInterns.includes(myFloID)) {
assignedProjectsList.push(projectId)
break nextProject;
}
}
}
})
// store all the tasks assigned to intern in array
assignedProjectsList.forEach(project => {
for (const branch in RIBC.getProjectMap(project)) {
branchTasks = RIBC.getProjectMap(project)[branch].slice(4);
branchTasks.forEach(task => {
if (RIBC.getTaskStatus(project, branch, task) === 'incomplete') {
floGlobals.assignedTasks[floCrypto.randString(16, true)] = {
projectId: project,
projectBranch: branch,
task
}
}
})
}
})
document.getElementById('project_list_container').firstElementChild.children[1].textContent = 'My projects'
let parent = document.getElementById('assigned_task_list');
if (!Object.keys(floGlobals.assignedTasks).length) {
parent.textContent = 'No task assigned yet.';
} else {
// Render assigned task cards
const taskCards = []
for (const task in floGlobals.assignedTasks) {
taskCards.push(render.internTaskCard({
'uniqueId': task,
...floGlobals.assignedTasks[task]
}))
}
renderElem(getRef('assigned_task_list'), html`${taskCards}`)
}
// Event delegate clicks on intern tasks
getRef('assigned_task_list').addEventListener('click', e => {
if (e.target.closest('.send-update-button')) {
const taskCard = e.target.closest('.task-card')
currentTaskId = taskCard.dataset.uniqueId
const { projectId, projectBranch, task } = floGlobals.assignedTasks[currentTaskId]
getRef('update_of_project').textContent = RIBC.getProjectDetails(projectId).projectName
getRef('update_of_task').textContent = RIBC.getTaskDetails(projectId, projectBranch, task).taskTitle
openPopup('post_update_popup')
}
})
}
else {
document.querySelectorAll('.intern-option').forEach((option) => {
option.classList.add('hidden')
})
document.getElementById('project_watching_section').classList.remove('hidden')
document.getElementById('project_list_container').firstElementChild.children[1].textContent = 'Projects'
}
// admin view
if (floGlobals.subAdmins.includes(myFloID)) {
typeOfUser = 'admin'
function removeRequest(requestCard) {
requestCard.animate([
{
transform: 'translateX(0)',
opacity: 1
},
{
transform: 'translateX(-100%)',
opacity: 0
},
], {
duration: 300,
easing: 'ease'
}).onfinish = () => {
requestCard.remove()
}
}
render.internRequests()
// accept task request
delegate(getRef('requests_list'), 'click', '.accept-request', (e) => {
getConfirmation('Are you sure you want to accept this request?', (result) => {
if (result) {
const vectorClock = e.delegateTarget.closest('.request-card').dataset.vectorClock
const type = e.delegateTarget.closest('.request-card').dataset.type
let result
if (type === 'task') {
result = RIBC.admin.processTaskRequest(vectorClock, true)
if (result === 'Accepted') {
notify('Intern assigned.', 'success')
removeRequest(e.delegateTarget.closest('.request-card'))
}
} else if (type === 'internship') {
result = RIBC.admin.processInternRequest(vectorClock, true)
if (result === 'Accepted') {
notify('Added intern', 'success')
removeRequest(e.delegateTarget.closest('.request-card'))
}
}
}
})
})
// reject task request
delegate(getRef('requests_list'), 'click', '.reject-request', (e) => {
getConfirmation('Are you sure you want to reject this request?').then((result) => {
if (result) {
const vectorClock = e.delegateTarget.closest('.request-card').dataset.vectorClock
const type = e.delegateTarget.closest('.request-card').dataset.type
let result
if (type === 'task') {
result = RIBC.admin.processTaskRequest(vectorClock, false)
if (result === 'Rejected') {
notify('Request rejected', 'success')
removeRequest(e.delegateTarget.closest('.request-card'))
}
} else if (type === 'internship') {
result = RIBC.admin.processInternRequest(vectorClock, false)
if (result === 'Rejected') {
notify('Request rejected', 'success')
removeRequest(e.delegateTarget.closest('.request-card'))
}
}
}
})
})
document.querySelectorAll('.admin-option').forEach((option) => {
option.classList.remove('hidden')
})
//show interns
renderElem(getRef('admin_page__intern_list'), filterInterns(''))
//show projects
render.projectList(getRef('admin_page__project_list'), getSortedProjectList(), true)
} else {
document.querySelectorAll('.admin-option').forEach((option) => {
option.classList.add('hidden')
})
}
// General only view for non admin and non intern
if (!allInternsList[myFloID] && !floGlobals.subAdmins.includes(myFloID)) {
document.querySelectorAll('.general-only').forEach((elem) => {
elem.classList.remove('hidden')
})
}
else {
document.querySelectorAll('.general-only').forEach((elem) => {
elem.classList.add('hidden')
})
}
if (typeOfUser === 'admin') {
document.querySelectorAll('.not-for-admin').forEach((elem) => {
elem.classList.add('hidden')
})
} else {
const internshipRequests = RIBC.getInternRequests(false).reverse()
const hasPendingApplication = internshipRequests.find(request => !request.status)
if (internshipRequests.length === 0 || !hasPendingApplication) {
getRef('application_card').classList.remove('hidden')
} else {
getRef('application_card').classList.add('hidden')
}
document.querySelectorAll('.not-for-admin').forEach((elem) => {
elem.classList.remove('hidden')
})
}
renderElem(getRef('top_interns'), html`${highPerformingInterns.slice(0, 4).map((intern) => {
const { internName, floId, rating } = intern
return render.internCard(internName, floId, rating)
})}`);
// displays recent projects
let recentProjects = []
if (typeOfUser === 'intern') {
render.projectList(getRef('project_list'), assignedProjectsList)
} else {
render.projectList(getRef('project_list'), allProjectsList.reverse().slice(0, 4))
}
if (typeOfUser === 'intern') {
render.projectList(getRef('my_projects'), assignedProjectsList)
sortedProjectList = sortedProjectList.filter(val => !assignedProjectsList.includes(val));
}
render.projectList(getRef('other_projects'), sortedProjectList)
getRef('explorer_task_list').addEventListener('click', (event) => {
if (event.target.closest('.apply-button')) {
requestForTask(event.target.closest('.apply-button'))
}
})
getRef('username').textContent = typeOfUser === 'intern' ? `Hi, ${RIBC.getInternList()[myFloID]}` : `Hi, there!`;
getRef('user_flo_id').value = myFloID;
getRef('user_role').textContent = typeOfUser;
console.log(typeOfUser)
}
getRef('request_type_selector').addEventListener('change', render.internRequests)
function postUpdate() {
const { projectId, projectBranch, task } = floGlobals.assignedTasks[currentTaskId]
const description = getRef('update_description').value.trim()
if (topic !== '' && description !== '') {
RIBC.postInternUpdate({ projectId, projectBranch, task, description })
.then((result) => {
notify('Update posted', 'success')
closePopup()
})
.catch((error) => {
notify(error, 'error')
})
}
else {
notify('Please enter topic and description', 'error')
}
}
function filterInterns(searchKey, options = {}) {
const { sortByRating = false } = options
let filtered = [];
const allInterns = RIBC.getInternList()
const arrayOfInterns = []
for (const intern in allInterns) {
arrayOfInterns.push({
floId: intern,
internName: allInternsList[intern]
})
}
arrayOfInterns.sort((a, b) => a.internName.toLowerCase().localeCompare(b.internName.toLowerCase()))
if (searchKey === '') {
filtered = (sortByRating ? highPerformingInterns : arrayOfInterns).map(({ internName, floId }) => {
return render.internCard(internName, floId, RIBC.getInternRating(floId))
})
} else {
const options = {
keys: ['internName'],
threshold: 0.2
}
const fuse = new Fuse(arrayOfInterns, options)
filtered = fuse.search(searchKey).map(v => v.item).map(({ internName, floId }) => {
return render.internCard(internName, floId, RIBC.getInternRating(floId))
})
}
return html`${filtered}`
}
const searchInternPopup = debounce((e) => {
renderElem(getRef('intern_list_container'), filterInterns(e.target.value.trim()))
}, 150)
const searchInternPage = debounce((e) => {
renderElem(getRef('all_interns_list'), filterInterns(e.target.value.trim(), { sortByRating: true }))
}, 150)
getRef('intern_search_field').addEventListener('input', searchInternPopup)
getRef('interns_page__search').addEventListener('input', searchInternPage)
function applyForInternship() {
buttonLoader(getRef('intern_apply__button'), true)
const name = getRef('intern_apply__name').value.trim();
const brief = getRef('intern_apply__brief').value.trim();
RIBC.applyForIntern(name, brief)
.then((result) => {
notify('Application submitted', 'success')
getRef('application_card').classList.add('hidden')
closePopup()
})
.catch((error) => {
notify(error, 'error')
}).finally(() => {
buttonLoader(getRef('intern_apply__button'), false)
})
}
function getSortedProjectList() {
return RIBC.getProjectList().sort((a, b) => RIBC.getProjectDetails(a).projectName.toLowerCase().localeCompare(RIBC.getProjectDetails(b).projectName.toLowerCase()))
}
</script>
</body>
</html>