2463 lines
135 KiB
HTML
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> |