ribc/index.html
2022-09-08 17:55:28 +05:30

2463 lines
135 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" // "RIBC"
}
</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 id="admin_page_btn" href="#admin_page" class="admin-option nav-list__item interact"
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">
<section id="project_watching_section">
<h4>Projects watchlist</h4>
<div id="project_watchlist" class="observe-empty-state"></div>
<h4 class="empty-state">No project added to watchlist.</h4>
</section>
<section id="intern_view" class="hidden intern-option">
<h2>My tasks</h2>
<ul id="assigned_task_list"></ul>
</section>
<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="best_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" id="all_projects_btn" class="button">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>
<sm-button id="logout" variant="primary" class="justify-self-start" onclick="logout()">
<svg class="icon button__icon--left" 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
</sm-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">
<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">
<ul id="admin_page__intern_list" class="list-container observe-empty-state"></ul>
<h4 class="empty-state">No interns added</h4>
</section>
<section>
<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 gap-0-5 flex-wrap align-items-start">
<h2 id="editing_panel__title" data-editable></h2>
<button class="button button--small admin-option" title="Edit this title"
onclick="makeEditable(this.previousElementSibling)">
Edit
</button>
</div>
<div class="flex gap-0-5 flex-wrap align-items-start">
<p id="editing_panel__description" data-editable></p>
<button class="button button--small admin-option" title="Edit this description"
onclick="makeEditable(this.previousElementSibling)">
Edit
</button>
</div>
</div>
<h4>Tasks</h4>
<div id="branch_container"></div>
<ul id="task_list" class="grid gap-1 observe-empty-state"></ul>
<h4 class="empty-state">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>
<section id="admin_fab" class="fab-actions">
<ul id="admin_fab__options" class="grid gap-0-5 hidden">
<li class="fab-actions__item interact" onclick="openPopup('add_project_popup')">
<span class="fab-actions__item-name">
Add project
</span>
<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 5v14h16V7h-8.414l-2-2H4zm7 7V9h2v3h3v2h-3v3h-2v-3H8v-2h3z" />
</svg>
</li>
<li class="fab-actions__item interact" onclick="openPopup('add_intern_popup')">
<span class="fab-actions__item-name">
Add intern
</span>
<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>
</li>
<li class="fab-actions__item interact" onclick="commitToChanges()">
<span class="fab-actions__item-name">
Commit changes
</span>
<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 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>
</li>
</ul>
<button class="fab" onclick="toggleFab()">
<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="M16 18v2H5v-2h11zm5-7v2H3v-2h18zm-2-7v2H8V4h11z" />
</svg>
<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>
</section>
<div id="fab_backdrop" onclick="toggleFab()"></div>
</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-1 observe-empty-state"></ul>
<h4 class="empty-state">No related updates</h4>
</section>
</section>
<section id="all_interns_page" class="page hidden">
<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></div>
<h4 class="padding intern-option hidden">Other projects</h4>
<div></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 button__icon--left" 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" required></sm-textarea>
<sm-button id="post_update_btn" title="post this update" variant="primary" disabled>
<svg class="icon button__icon--left" 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>
Post update
</sm-button>
</sm-form>
</sm-popup>
<!-- Templates -->
<template id="update_card_template">
<li class="intern-update">
<div class="flex align-center space-between">
<h5 class="update__sender"></h5>
<span class="update__time"></span>
</div>
<h4 class="update__topic"></h4>
<p class="update__message"></p>
</li>
</template>
<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-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="project_map_template">
<div class="project_map_card">
<h2 class="project-map__title"></h2>
<p class="project-map__description"></p>
<div class="project__branch_container"></div>
<h4>Timeline</h4>
<div class="project-map__timeline"></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>
<h4 class="project__title"></h4>
<div class="progress-bar">
<div class="progress-value"></div>
</div>
<h5 class="project__complete-percent"></h5>
</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 appPages = ['dashboard_page', 'admin_page', 'settings_page', 'project_explorer', 'updates_page', 'all_interns_page'];
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 (!appPages.includes(pageId)) 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.querySelector(`.nav-list__item[href="#${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 'all_interns_page':
renderAllInterns()
break;
case 'project_explorer':
if (params) {
const { projectId, branch } = params
if (lastParams.projectId !== projectId) {
showProjectInfo(projectId)
}
if (params.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) {
const { projectId, branch } = params
editProjectInfo(projectId)
if (params.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')
}
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()
}
}
</script>
<script id="UI_functions">
// Method object for creating various UI elements
const render = {
projectCard(projectName, projectCode, isAdmin = false) { // creates cards containing project information
const page = isAdmin ? 'admin_page' : 'project_explorer'
return createElement('a', {
className: 'project-card flex align-center interact',
attributes: {
'title': "Project information",
href: `#${page}?projectId=${projectCode}&branch=mainLine`
},
textContent: projectName
});
},
taskCard(currentProject, currentBranch, taskNo) {
const card = getRef('timeline_task_card').content.cloneNode(true),
assignedInterns = RIBC.getAssignedInterns(currentProject, currentBranch, taskNo)
if (RIBC.getTaskStatus(currentProject, currentBranch, taskNo) === 'completed') {
card.firstElementChild.classList.add('completed-task')
}
if (assignedInterns || typeOfUser === 'general') {
renderElem(card.querySelector('.assigned-interns'), html`${assignedInterns.map((internFloId) => {
return render.assignedInternCard(internFloId);
})
}`)
}
else {
if (typeOfUser === 'intern') {
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) {
// creates cards containing intern information
const card = getRef('intern_card_template').content.cloneNode(true)
setAttributes(card.firstElementChild, { 'data-intern-flo-id': internFLOID, 'title': 'Intern Information' })
card.querySelector('.intern-card__initials').style.backgroundColor = getInternColor(internFLOID)
card.querySelector('.intern-card__initials').textContent = internName.split(' ').map(v => v.charAt(0)).join('');
card.querySelector('.intern-card__name').textContent = internName;
card.querySelector('.intern-card__score').textContent = internPoints;
return card;
},
// creates cards containing updates provided by interns
updateCard(update) {
const { time, internName, topic, description } = update
const card = getRef('update_card_template').content.cloneNode(true)
card.querySelector('.update__sender').textContent = internName
card.querySelector('.update__time').textContent = getFormattedTime(time)
card.querySelector('.update__topic').textContent = topic
card.querySelector('.update__message').textContent = description
return card;
},
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>
`
return internCard;
},
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);
})
console.log(assignedInterns)
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;
},
projectMapCard(projectCode, page) {
const card = getRef('project_map_template').content.cloneNode(true)
const frag = document.createDocumentFragment()
const { projectName, projectDescription } = RIBC.getProjectDetails(projectCode)
card.firstElementChild.id = `${projectCode}_map`;
card.querySelector('.project-map__title').textContent = projectName
card.querySelector('.project-map__description').textContent = projectDescription
card.querySelector('.project__branch_container').id = `${projectCode}_branch_container`;
RIBC.getProjectBranches(projectCode).forEach((branch) => {
frag.append(render.branchButton({ projectId: projectCode, branch, page }))
})
card.querySelector('.project__branch_container').append(frag);
card.querySelector('.project-map__timeline').id = `${projectCode}_map_body`
currentProject = projectCode;
// mapItems.push(card, card.id, branchContainer.id, mapBody.id)
return card;
},
requestCard(floId, projectCode, branch, taskNo) {
const internName = RIBC.getInternList()[floId];
return createElement('li', {
className: 'request-card',
innerHTML: `<p class="request-card__description">
<b class="capitalize">${internName}</b> applied for
<b class="capitalize">${RIBC.getTaskDetails(projectCode, branch, taskNo).taskTitle}</b> from
<b class="capitalize">${branch}</b> of
<b class="capitalize">${RIBC.getProjectDetails(projectCode).projectName}</b>
</p>
<div class="flex justify-end">
<sm-button class="reject-app">
<svg class="icon button__icon--left" 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
</sm-button>
<sm-button class="accept-app button__icon--left" data-flo-id="${floId}" data-project-code="${projectCode}" data-branch="${branch}" data-task-no="${taskNo}">
<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>
Accept
</sm-button>
</div>
`
})
},
internTaskCard(taskObj) {
const { uniqueId, projectId, projectName, projectBranch, task } = taskObj
const { taskTitle, taskDescription } = RIBC.getTaskDetails(projectId, projectBranch, task)
return createElement('li', {
className: "task-card",
attributes: { 'data-unique-id': uniqueId },
innerHTML: `
<div class="task__header">
<h5 class="task__project-title">${projectName}</h5>
<h5 class="task__title">${taskTitle}</h5>
<sm-button class="send-update-button">
<svg class="icon button__icon--left" 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
</sm-button>
</div>
<p class="task__description">${taskDescription}</p>
`
})
},
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
}
}
const renderedIntensColor = {}
function getInternColor(floId) {
if (!renderedIntensColor[floId]) {
renderedIntensColor[floId] = randomColor()
}
return renderedIntensColor[floId]
}
//helper functions
function setAttributes(el, attrs) {
for (var key in attrs) {
el.setAttribute(key, attrs[key]);
}
}
let allInternsList = [], highPerformingInterns = [],
watchList = [], currentIntern, currentTaskId,
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)) {
getRef('admin_page__intern_list').append(render.internCard(internName, internFLOID, '1'))
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) {
notify('Project name is important!', 'error')
return
}
if (!projectDescription) {
notify('Project description is important!', 'error')
return
}
const projectCode = `${new Date().getFullYear()}_project_${RIBC.getProjectList() ? (RIBC.getProjectList().length + 1) : '1'}`;
RIBC.admin.createProject(projectCode)
RIBC.admin.addProjectDetails(projectCode, { projectName, projectDescription })
getRef('admin_page__project_list').prepend(render.projectCard(projectName, projectCode, true))
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())
const res = RIBC.admin.addProjectDetails(currentProject, { projectName: newTitle, projectDescription: newDescription })
notify('Changes saved locally, commit the changes to make them permanent', 'success')
} 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 editProjectInfo(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 } = getAllBranches(projectId).find(({ branchName }) => branchName === branch)
let branchEntryPoint = html`
<svg class="icon button__icon--left" 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.828 11H20v2H7.828l5.364 5.364-1.414 1.414L4 12l7.778-7.778 1.414 1.414z"/></svg>
${branchTasks[0]}
`
if (startPoint) {
const { taskTitle } = RIBC.getTaskDetails(projectId, branchTasks[0], startPoint)
branchEntryPoint = html`
<svg class="icon button__icon--left" 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.828 11H20v2H7.828l5.364 5.364-1.414 1.414L4 12l7.778-7.778 1.414 1.414z"/></svg>
${branchTasks[0]} - ${taskTitle}
`
}
taskList.append(render.branchButton({ projectId, branch: branchTasks[0], page: pageId, innerHTML: branchEntryPoint }))
}
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 logout() {
getConfirmation("Do you want to sign out?").then(async (result) => {
if (result) {
await floDapps.clearCredentials()
onLoadStartUp()
}
})
}
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() {
getRef('all_interns_list').innerHTML = ''
getRef('all_interns_list').append(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() {
// Render All the projects inside updates page
const frag = document.createDocumentFragment();
getRef('updates_page__project_selector').innerHTML = ``;
const stripOption = createElement('sm-option', {
textContent: 'All',
attributes: { value: 'all', active: '' }
})
frag.append(stripOption)
RIBC.getProjectList().reverse().forEach(project => {
const stripOption = createElement('sm-option', {
textContent: RIBC.getProjectDetails(project).projectName,
attributes: { value: project }
})
frag.append(stripOption)
})
getRef('updates_page__project_selector').append(frag)
}
function renderInternSelectorOptions() {
// Render All the projects inside updates page
const frag = document.createDocumentFragment();
getRef('updates_page__intern_selector').innerHTML = ``;
const stripOption = createElement('sm-option', {
textContent: 'All',
attributes: { value: 'all', active: '' }
})
frag.append(stripOption)
const allInterns = Object.entries(RIBC.getInternList()).sort((a, b) => a[1].toLowerCase().localeCompare(b[1].toLowerCase()));
allInterns.forEach(intern => {
const stripOption = createElement('sm-option', {
textContent: intern[1], // Intern Name
attributes: { value: intern[0] } // Intern FLO ID
})
frag.append(stripOption)
})
getRef('updates_page__intern_selector').append(frag)
}
async function getUpdatesByProject(projectName, allUpdates) {
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)
}
async function getUpdatesByIntern(floId, allUpdates) {
if (!allUpdates) {
allUpdates = RIBC.getInternUpdates()
}
const options = {
keys: ['floID'],
threshold: 0
}
const fuse = new Fuse(allUpdates, options)
return fuse.search(floId).map(v => v.item)
}
async function getUpdatesByDate(date, allUpdates) {
if (!allUpdates) {
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 element = updates[index];
const { floID, time, update: { topic, description } } = element
const updateObj = {
internName: RIBC.getInternList()[floID],
time: parseInt(time),
topic,
description
}
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)
}
}
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') {
const projectName = RIBC.getProjectDetails(selectedProject).projectName
matchedUpdates = await getUpdatesByProject(projectName)
}
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.splice(watchList.indexOf(currentProject), 1)
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.')
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');
}
})
}
getRef('admin_fab__options').addEventListener('click', e => {
if (e.target.closest('.fab-actions__item')) {
toggleFab()
}
})
function toggleFab() {
if (getRef('admin_fab').hasAttribute('open')) {
getRef('admin_fab').removeAttribute('open')
setTimeout(() => {
getRef('admin_fab__options').classList.add('hidden')
}, 200);
} else {
getRef('admin_fab__options').classList.remove('hidden')
setTimeout(() => {
getRef('admin_fab').setAttribute('open', '')
}, 0);
}
}
function toggleFilter() {
getRef('update_filters_wrapper').classList.toggle('hide-on-mobile')
}
// Event listeners
document.getElementById('best_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':
getRef('intern_list_container').innerHTML = ''
getRef('intern_list_container').append(filterInterns(''))
break;
}
})
document.addEventListener('popupclosed', e => {
switch (e.detail.popup.id) {
case 'intern_list_popup':
getRef('intern_list_container').innerHTML = ''
getRef('intern_search_field').value = ''
break;
}
})
const internAssignedTasks = {}
function renderAllElements() {
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 = [], x;
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 assinged to interns in array
for (let i = 0; i < allProjectsList.length; i++) {
nextProject:
for (branch in RIBC.getProjectMap(allProjectsList[i])) {
x = RIBC.getProjectMap(allProjectsList[i])[branch].slice(4);
for (let j = 0; j < x.length; j++)
if (Array.isArray(RIBC.getAssignedInterns(allProjectsList[i], branch, x[j])) && RIBC.getAssignedInterns(allProjectsList[i], branch, x[j]).includes(myFloID)) {
assignedProjectsList.push(allProjectsList[i])
break nextProject;
}
}
}
// sore all the tasks assigned to intern in array
assignedProjectsList.forEach(project => {
for (branch in RIBC.getProjectMap(project)) {
branchTasks = RIBC.getProjectMap(project)[branch].slice(4);
branchTasks.forEach(task => {
if (RIBC.getTaskStatus(project, branch, task) === 'incomplete' && Array.isArray(RIBC.getAssignedInterns(project, branch, task)) && RIBC.getAssignedInterns(project, branch, task).includes(myFloID)) {
internAssignedTasks[floCrypto.randString(16, true)] = {
projectId: project,
projectName: RIBC.getProjectDetails(project).projectName,
projectBranch: branch,
task
}
}
})
}
})
document.getElementById('project_list_container').firstElementChild.children[1].textContent = 'My projects'
let parent = document.getElementById('assigned_task_list');
if (!Object.keys(internAssignedTasks).length) {
parent.textContent = 'No task assigned yet.';
}
else {
// Render assigned task cards
const taskListFrag = document.createDocumentFragment()
getRef('assigned_task_list').innerHTML = ''
for (const task in internAssignedTasks) {
const taskObj = {
'uniqueId': task,
...internAssignedTasks[task]
}
taskListFrag.append(render.internTaskCard(taskObj))
}
getRef('assigned_task_list').append(taskListFrag)
}
// 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, projectName, projectBranch, task } = internAssignedTasks[currentTaskId]
getRef('update_of_project').textContent = 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'
getRef('requests_list').innerHTML = ''
RIBC.admin.getTaskRequests().forEach((app) => {
try {
if (!Array.isArray(RIBC.getAssignedInterns(app.projectCode, app.branch, app.task)) && typeof RIBC.getTaskDetails(app.projectCode, app.branch, app.task) !== 'undefined')
frag.append(render.requestCard(app.floID, app.projectCode, app.branch, app.task))
}
catch (error) {
console.error(error)
}
})
getRef('requests_list').append(frag)
getRef('requests_list').addEventListener('click', (e) => {
function removeRequest(requestCard) {
requestCard.animate([
{
transform: 'translateX(0)',
opacity: 1
},
{
transform: 'translateX(-100%)',
opacity: 0
},
], {
duration: 300,
easing: 'ease'
}).onfinish = () => {
requestCard.remove()
}
}
if (e.target.closest('.accept-app')) {
let btn = e.target.closest('.accept-app'),
requestCard = e.target.closest('.request-card');
if (RIBC.admin.assignInternToTask(btn.dataset.floId, btn.dataset.projectCode, btn.dataset.branch, btn.dataset.taskNo)) {
notify('Intern assinged.', 'success')
removeRequest(requestCard)
}
else {
notify('Intern already assinged.', 'error')
}
} else if (e.target.closest('.reject-app')) {
let requestCard = e.target.closest('.request-card');
removeRequest(requestCard)
}
})
document.querySelectorAll('.admin-option').forEach((option) => {
option.classList.remove('hidden')
})
//show interns
getRef('admin_page__intern_list').innerHTML = ``;
getRef('admin_page__intern_list').append(filterInterns(''))
//show project
getRef('admin_page__project_list').innerHTML = ``;
for (let project in allProjectsList) {
const projectCode = allProjectsList[project]
const projectName = RIBC.getProjectDetails(projectCode).projectName
frag.prepend(render.projectCard(projectName, projectCode, true))
}
getRef('admin_page__project_list').append(frag)
}
else {
document.querySelectorAll('.admin-option').forEach((option) => {
option.classList.add('hidden')
})
}
document.getElementById('best_interns').innerHTML = ``;
let limit = Math.min(4, highPerformingInterns.length);
for (let p = 0; p < limit; p++) {
const { internName, floId, rating } = highPerformingInterns[p]
frag.append(render.internCard(internName, floId, rating))
}
document.getElementById('best_interns').appendChild(frag)
// displays recent projects
document.getElementById('project_list').innerHTML = '';
if (allInternsList.myFloID && !floGlobals.subAdmins.includes(myFloID)) {
assignedProjectsList.forEach((project) => {
frag.append(render.projectCard(RIBC.getProjectDetails(project).projectName, project))
})
}
else {
for (project of allProjectsList.reverse().slice(0, 4)) {
frag.append(render.projectCard(RIBC.getProjectDetails(project).projectName, project))
}
}
document.getElementById('project_list').appendChild(frag)
getRef('post_update_btn').addEventListener('click', () => {
const { projectId, projectName, projectBranch, task } = internAssignedTasks[currentTaskId]
const topic = `${projectName} / ${RIBC.getTaskDetails(projectId, projectBranch, task).taskTitle}`;
const description = getRef('update_description').value.trim()
if (topic !== '' && description !== '') {
RIBC.postInternUpdate({ topic, description })
.then((result) => {
notify('Update posted', 'success')
closePopup()
})
.catch((error) => {
notify(error, 'error')
})
}
else {
notify('Please enter topic and description', 'error')
}
})
getRef('project_explorer').children[0].children[1].innerHTML = ``;
getRef('project_explorer').children[0].children[3].innerHTML = ``;
if (allInternsList[myFloID] && !floGlobals.subAdmins.includes(myFloID)) {
assignedProjectsList.forEach((project) => {
frag.append(render.projectCard(RIBC.getProjectDetails(project).projectName, project))
})
getRef('project_explorer').children[0].children[1].appendChild(frag)
allProjectsList = allProjectsList.filter(val => !assignedProjectsList.includes(val));
}
for (i = 0; i < allProjectsList.length; i++) {
frag.appendChild(render.projectCard(RIBC.getProjectDetails(allProjectsList[i]).projectName, allProjectsList[i]))
}
getRef('project_explorer').children[0].children[3].appendChild(frag)
getRef('explorer_task_list').addEventListener('click', (event) => {
if (event.target.closest('.apply-button')) {
requestForTask(event.target.closest('.apply-button'))
}
})
const greetings = RIBC.getInternList()[myFloID] !== undefined ? `Hi, ${RIBC.getInternList()[myFloID]}` : `Hi, there!`
getRef('username').textContent = greetings;
getRef('user_flo_id').value = myFloID;
getRef('user_role').textContent = typeOfUser;
console.log(typeOfUser)
}
function filterInterns(searchKey, options = {}) {
const { sortByRating = false } = options
const frag = document.createDocumentFragment();
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 === '') {
if (sortByRating) {
highPerformingInterns.forEach(({ internName, floId }) => {
frag.append(render.internCard(internName, floId, RIBC.getInternRating(floId)))
})
} else {
arrayOfInterns.forEach(({ internName, floId }) => {
frag.append(render.internCard(internName, floId, RIBC.getInternRating(floId)))
})
}
} else {
const options = {
keys: ['internName'],
threshold: 0.2
}
const fuse = new Fuse(arrayOfInterns, options)
fuse.search(searchKey).map(v => v.item).forEach(({ internName, floId }) => {
frag.append(render.internCard(internName, floId, RIBC.getInternRating(floId)))
})
}
return frag
}
const searchInternPopup = debounce((e) => {
getRef('intern_list_container').innerHTML = ''
getRef('intern_list_container').append(filterInterns(e.target.value.trim()))
}, 150)
const searchInternPage = debounce((e) => {
getRef('all_interns_list').innerHTML = ''
getRef('all_interns_list').append(filterInterns(e.target.value.trim(), { sortByRating: true }))
}, 150)
getRef('intern_search_field').addEventListener('input', searchInternPopup)
getRef('interns_page__search').addEventListener('input', searchInternPage)
</script>
</body>
</html>