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