floGlobals.taskCategories = { c00: 'Creative Writing', c01: 'Marketing', c02: 'Design', c03: 'Development', c04: 'Social Media Management', c05: 'Video Making', } const render = { displayTaskCard(projectCode, branch, task) { projectCode = projectCode const taskDetails = { title, description, category, maxSlots, duration, durationType, reward } = RIBC.getTaskDetails(projectCode, branch, task) return html`
  • ${floGlobals.taskCategories[category]} View details

    ${title}

    ${duration ? html`
    Duration: ${duration} ${durationType}
    `: ''} ${maxSlots ? html`
    Slots: ${maxSlots}
    `: ''} ${reward ? html`
    Reward: ₹${reward}
    `: ''}
  • `; }, displayTasks(category, searchQuery) { // render tasks const allTasks = RIBC.getAllTasks() const filterCategory = category === 'all' ? false : category; const filtered = [] const availableCategories = new Set(); for (const taskId in allTasks) { const [projectCode, branch, task] = taskId.split('_') if (filterCategory && allTasks[taskId].category !== filterCategory) continue; if (RIBC.getTaskStatus(projectCode, branch, task) !== 'incomplete') continue; if (searchQuery && searchQuery !== '' && !allTasks[taskId].title.toLowerCase().includes(searchQuery.toLowerCase())) continue; if (RIBC.getAssignedInterns(projectCode, branch, task).length >= allTasks[taskId].maxSlots) continue; if (typeOfUser && typeOfUser === 'intern' && floDapps.user.id && RIBC.getAssignedInterns(projectCode, branch, task).includes(floDapps.user.id)) continue; filtered.push(render.displayTaskCard(projectCode, branch, task)) availableCategories.add(allTasks[taskId].category) } let renderedTasks = filtered.reverse() if (searchQuery && filtered.length === 0) { renderedTasks = html`

    No tasks related to ${searchQuery}

    ` } // render categories let renderedCategories = [] if (availableCategories.size > 1) { renderedCategories = [html`All`]; availableCategories.forEach(categoryID => { categories.push(html`${floGlobals.taskCategories[categoryID]}`) }) } setTimeout(() => { if (document.getElementById('task_search_input') && document.getElementById('task_search_input').value.trim() !== searchQuery) document.getElementById('task_search_input').value = searchQuery || '' }, 0); return html`

    Available Tasks

    ${(filtered.length > 0 || searchQuery) ? html` `: ''}
    ${availableCategories.size > 1 ? html`${renderedCategories}` : ''}

    Nothing to see here

    `; }, projectCard(projectCode, isAdmin = false, ref) { const projectName = RIBC.getProjectDetails(projectCode).projectName const page = isAdmin ? 'admin_page' : 'project_explorer' return html.for(ref, projectCode)`${projectName}` }, taskCard(task) { const taskDetails = { title, description, category, maxSlots, duration, durationType, reward } = RIBC.getTaskDetails(appState.params.id, appState.params.branch, task) const branches = getAllBranches(appState.params.id) const branchesButtons = filterMap(branches, (branch) => { const { branchName, parentBranch, startPoint, endPoint } = branch if (parentBranch === appState.params.branch && startPoint === task) { return render.branchButton({ projectCode: appState.params.id, branch: branchName, page: 'project_explorer' }) } }) const assignedInterns = RIBC.getAssignedInterns(appState.params.id, appState.params.branch, task) || [] const assignedInternsCards = filterMap(assignedInterns, (internFloId) => render.assignedInternCard(internFloId)); const status = RIBC.getTaskStatus(appState.params.id, appState.params.branch, task) let applyButton if (!assignedInterns.includes(myFloID) && typeOfUser !== 'admin') { const hasApplied = [...RIBC.getTaskRequests(false), ...sessionTaskRequests].find(({ details }) => { return `${appState.params.id}_${appState.params.branch}_${task}` === details.taskId }) applyButton = html` `; } const linkifyDescription = createElement('p', { innerHTML: DOMPurify.sanitize(linkify(description)), className: `timeline-task__description ws-pre-line wrap-around` }) return html`

    ${title}

    ${applyButton}
    ${assignedInternsCards.length ? html`
    ${assignedInternsCards}
    ` : ''}
    Task details ${linkifyDescription}
    ${duration ? html`
    Duration: ${duration} ${durationType}
    `: ''} ${maxSlots ? html`
    Slots: ${maxSlots}
    `: ''} ${reward ? html`
    Reward: ₹${reward}
    `: ''} ${branchesButtons.length ? html`
    ${branchesButtons}
    ` : ''}
    `; }, internCard(internFloId, { selectable = false } = {}) { const internName = RIBC.getInternList()[internFloId] const internPoints = RIBC.getInternRating(internFloId) const initials = internName.split(' ').map(v => v.charAt(0)).join(''); return html` `; }, internUpdateCard(update) { const { floID, time, note, update: { projectCode, branch, task, description, link } } = update let topic = `${RIBC.getProjectDetails(projectCode).projectName} / ${RIBC.getTaskDetails(projectCode, branch, task).title}` const internName = RIBC.getInternList()[floID] let replyButton if (typeOfUser === 'admin' && !note) { replyButton = html`` } let providedLink if (link) { providedLink = html`${link} ` } let adminReply if (note) { adminReply = html`

    Admin

    ${note}

    ` } return html.node`
  • ${internName} ${getFormattedTime(time)}

    ${topic}

    ${description}

    ${providedLink} ${replyButton} ${adminReply}
  • `; }, branchButton(obj = {}) { const { projectCode, branch, page, active = false } = obj return html` ${branch} `; }, assignedInternCard(internFloId, options) { let optionsButton if (options) { optionsButton = html` `; } return html` ${RIBC.getInternList()[internFloId]} ${optionsButton} ` }, taskListItem(task, ref) { const assignedInterns = RIBC.getAssignedInterns(appState.params.id, appState.params.branch, task) const taskDetails = { title, description, category, maxSlots, duration, durationType, reward } = RIBC.getTaskDetails(appState.params.id, appState.params.branch, task) const status = RIBC.getTaskStatus(appState.params.id, appState.params.branch, task) let assignedInternsCards if (assignedInterns) { assignedInternsCards = filterMap(assignedInterns, (internFloId) => render.assignedInternCard(internFloId, true)) } const branches = getAllBranches(appState.params.id) const branchesButtons = filterMap(branches, (branch) => { const { branchName, parentBranch, startPoint, endPoint } = branch if (parentBranch === appState.params.branch && startPoint === task) { return render.branchButton({ projectCode: appState.params.id, branch: branchName, page: 'admin_page' }) } }) const categories = []; for (const categoryID in floGlobals.taskCategories) { categories.push(html`${floGlobals.taskCategories[categoryID]}`) } const taskDescription = createElement('p', { className: 'task-description ws-pre-line wrap-around', attributes: { 'data-editable': '', 'data-edit-field': 'description', }, innerHTML: DOMPurify.sanitize(description) }) return html.for(ref, `${appState.params.id}_${appState.params.branch}_${task}`)`
  • Mark as complete

    ID: ${task}

    ${title}

    ${assignedInternsCards}
    ${taskDescription}
    ${categories}
    Days Months
    ${branchesButtons.length ? html`
    ${branchesButtons}
    ` : ''}
  • `; }, taskRequestCard(request) { const { details: { taskId, name, brief, contact, portfolioLink }, floID, vectorClock } = request const internName = RIBC.getInternList()[floID]; const [projectCode, branch, task] = taskId.split('_'); const { category } = RIBC.getTaskDetails(projectCode, branch, task); return html`
  • ${floGlobals.taskCategories[category]}

    ${internName || name} applied for ${RIBC.getTaskDetails(projectCode, branch, task).title}

    ${!internName ? html`
    ${brief ? html`
    Educational background

    ${brief}

    ` : ''} ${contact ? html`
    Contact
    ` : ''} ${portfolioLink ? html`
    Portfolio link
    ${portfolioLink}
    ` : ''}
    ` : ''}
  • `; }, internTaskCard(uniqueId) { const [projectCode, branch, task] = uniqueId.split('_'); const { title, description } = RIBC.getTaskDetails(projectCode, branch, task) const projectName = RIBC.getProjectDetails(projectCode).projectName const linkifyDescription = createElement('p', { innerHTML: DOMPurify.sanitize(linkify(description)), className: `timeline-task__description ws-pre-line wrap-around` }) return html`
  • ${projectName}

    ${title}

    ${linkifyDescription}
  • `; }, dashProject(projectCode, ref) { const { projectName } = RIBC.getProjectDetails(projectCode) const projectMap = RIBC.getProjectMap(projectCode) const projectTasks = [] RIBC.getProjectBranches(projectCode).forEach(branch => { projectMap[branch].slice(4).forEach((task) => { projectTasks.push(RIBC.getTaskStatus(projectCode, branch, task)) }) }) const completedTasks = projectTasks.filter(task => task === 'completed').length let completePercent = parseFloat(((completedTasks / (projectTasks.length || 1)) * 100).toFixed(2)) const isPinned = pinnedProjects.includes(projectCode); let pinIcon = '' if (isPinned) { pinIcon = html` `; } else { pinIcon = html` `; } return html.for(ref, projectCode)`

    ${projectName}

    ${completePercent}% complete
    ` }, dashProjects(where, projects) { renderElem(where, html`${projects.map(project => render.dashProject(project, where))} `) }, internRequests() { const requestCategories = new Set() const requestProjects = new Set() const shouldFilterByProject = getRef('filter_requests_by_project').value !== 'all' ? getRef('filter_requests_by_project').value : false; const shouldFilterByCategory = getRef('filter_requests_by_category').value !== 'all' ? getRef('filter_requests_by_category').value : false; let requestCards = filterMap(RIBC.getTaskRequests().reverse(), (request) => { if (Array.isArray(request.details) || !request.details.taskId) return; const [projectCode, branch, task] = request.details.taskId.split('_') const taskDetails = RIBC.getTaskDetails(projectCode, branch, task) if (!taskDetails) return; requestCategories.add(RIBC.getTaskDetails(projectCode, branch, task).category) requestProjects.add(projectCode) if (shouldFilterByCategory && taskDetails.category !== shouldFilterByCategory) return; if (shouldFilterByProject && projectCode !== shouldFilterByProject) return; return render.taskRequestCard(request) }) renderElem(getRef('requests_list'), html`${requestCards}`) if (requestCategories.size) { const categoryOptions = [...requestCategories].map(cat => html`${floGlobals.taskCategories[cat]}`); renderElem(getRef('filter_requests_by_category'), html`${[html`All`, ...categoryOptions]}`) } if (requestProjects.size) { const projectOptions = [...requestProjects].map(project => html`${RIBC.getProjectDetails(project).projectName}`); renderElem(getRef('filter_requests_by_project'), html`${[html`All`, ...projectOptions]}`) } if (requestCategories.size || requestProjects.size) { getRef('requests_container__filters').classList.remove('hidden') } else { getRef('requests_container__filters').classList.add('hidden') } }, projectList(container, projects, isAdminList = false) { renderElem(container, html`${projects.map(projectCode => render.projectCard(projectCode, isAdminList, container))}`) }, requestStatus(request) { if (Array.isArray(request.details) || !request.details.taskId) return const { details: { taskId }, status, vectorClock } = request; const [projectCode, branch, task] = taskId.split('_'); if (!RIBC.getTaskDetails(projectCode, branch, task)) return const timestamp = parseInt(vectorClock.split('_')[0]) let icon = '' if (status === 'Accepted') { icon = html`` } else if (status === 'Rejected') { icon = html`` } else { icon = html`` } return html`
  • You applied for ${RIBC.getTaskDetails(projectCode, branch, task).title}

    ${icon} ${status || 'Under review'}
  • `; }, taskApplications() { const taskRequests = RIBC.getTaskRequests(false) taskRequests.sort((a, b) => { return parseInt(b.vectorClock.split('_')[0]) - parseInt(a.vectorClock.split('_')[0]) }) const taskCards = filterMap(taskRequests, request => render.requestStatus(request)) renderElem(getRef('task_requests_list'), html`${taskCards}`) } } const selectedColors = [ '--dark-red', '--red', '--kinda-pink', '--purple', '--shady-blue', '--nice-blue', '--maybe-cyan', '--teal', '--mint-green', '--greenish-yellow', '--yellowish-green', '--dark-teal', '--orange', '--tangerine', '--redish-orange', ] function randomColor() { return selectedColors[Math.floor(Math.random() * selectedColors.length)]; } const renderedIntensColor = {} function getInternColor(floId) { if (!renderedIntensColor[floId]) { renderedIntensColor[floId] = randomColor() } return renderedIntensColor[floId] } const filterTasks = debounce((e) => { const searchQuery = getRef('task_search_input').value.trim(); const category = getRef('task_category_selector')?.value || 'all'; window.location.hash = `#/${appState.currentPage}?category=${category}${searchQuery !== '' ? `&search=${searchQuery}` : ''}`; }, 100) function showTaskDetails(taskId) { const [projectCode, branch, task] = taskId.split('_') const { title, description, category, maxSlots, duration, durationType, reward } = RIBC.getTaskDetails(projectCode, branch, task) let hasApplied = false try { floDapps.user.id hasApplied = [...RIBC.getTaskRequests(false), ...sessionTaskRequests].find(({ details }) => { return taskId === details.taskId }) } catch (e) { } const descriptionTag = createElement('p', { innerHTML: DOMPurify.sanitize(linkify(description)), className: 'ws-pre-line wrap-around' }) descriptionTag.id = 'task_description' renderElem(getRef('task_details_wrapper'), html`
    ${floGlobals.taskCategories[category]}

    ${title}

    ${duration ? html`
    Duration: ${duration} ${durationType}
    `: ''} ${maxSlots ? html`
    Slots: ${maxSlots}
    `: ''} ${reward ? html`
    Reward: ₹${reward}
    `: ''}
    ${descriptionTag}
    ${!hasApplied ? html` `: ''} `); getRef('task_details').classList.remove('hidden') const animOptions = { duration: floGlobals.prefersReducedMotion ? 0 : 300, easing: 'ease', fill: 'forwards' } getRef('task_details__backdrop').animate([ { opacity: 0 }, { opacity: 1 } ], animOptions) getRef('task_details_wrapper').animate([ { transform: 'translateX(100%)' }, { transform: 'translateX(0)' } ], animOptions) if (appState.currentPage === 'landing') { getRef('landing').animate([ { transform: 'translateX(0)' }, { transform: 'translateX(-10%)' } ], animOptions) } } function hideTaskDetails() { if (getRef('task_details').classList.contains('hidden')) return; history.replaceState(null, null, `#/${appState.currentPage}`); const animOptions = { duration: floGlobals.prefersReducedMotion ? 0 : 300, easing: 'ease', fill: 'forwards' } getRef('task_details__backdrop').animate([ { opacity: 1 }, { opacity: 0 } ], animOptions).onfinish = () => { getRef('task_details').classList.add('hidden') renderElem(getRef('task_details_wrapper'), html``) } getRef('task_details_wrapper').animate([ { transform: 'translateX(0)' }, { transform: 'translateX(100%)' } ], animOptions) if (appState.currentPage === 'landing') { getRef('landing').animate([ { transform: 'translateX(-10%)' }, { transform: 'translateX(0)' }, ], animOptions) } } let pinnedProjects = []; let currentIntern; let typeOfUser = 'general'; function handleDashboardViewChange(e) { document.querySelectorAll('.dashboard-view__item').forEach(item => { if (item.id === 'best_interns_container') item.classList.add('hide-on-mobile') else item.classList.add('hidden') }) document.querySelector(`#${e.target.value}`).classList.remove('hide-on-mobile', 'hidden') } // 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/project?id=${projectCode}&branch=mainLine"]`)?.click() closePopup(); } function makeEditable(elem) { floGlobals.tempEditableContent = DOMPurify.sanitize(elem.innerHTML.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.innerHTML.trim() !== '' && floGlobals.tempEditableContent !== DOMPurify.sanitize(e.target.innerHTML.trim())) { const newTitle = DOMPurify.sanitize(getRef('editing_panel__title').innerHTML.trim()) const newDescription = DOMPurify.sanitize(getRef('editing_panel__description').innerHTML.trim()) RIBC.admin.addProjectDetails(appState.params.id, { 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.innerHTML = 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.setProperty('--color', `var(${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(projectCode) { const { projectName, projectDescription } = RIBC.getProjectDetails(projectCode); getRef('project_explorer__project_title').textContent = projectName; // project name getRef('project_explorer__project_description').textContent = projectDescription; getRef('project_explorer__project_updates').href = `#/updates_page?projectCode=${projectCode}&internId=all`; renderBranches(); } let currentTask = ''; function renderAdminProjectView(projectCode) { const allProjects = getRef('admin_page__project_list').querySelectorAll('.project-card'); allProjects.forEach(project => project.classList.remove('project-card--active')) const targetProject = Array.from(allProjects).find(project => project.getAttribute('href').includes(projectCode)) if (targetProject) targetProject.classList.add('project-card--active') const { projectName, projectDescription } = RIBC.getProjectDetails(projectCode); getRef('editing_panel__title').textContent = projectName; getRef('editing_panel__description').textContent = projectDescription; renderBranches() } function renderBranches() { const { id: projectCode, branch } = appState.params const taskListContainer = appState.currentPage === 'admin_page' ? 'branch_container' : 'explorer_branch_container'; const branchList = filterMap(RIBC.getProjectBranches(appState.params.id), (branch) => { return render.branchButton({ projectCode, branch, page: appState.currentPage, active: branch === appState.params.branch }) }) if (branchList.length > 1) { renderElem(getRef(taskListContainer), html`${branchList}`) getRef(taskListContainer).classList.remove('hidden') } else { getRef(taskListContainer).classList.add('hidden') } } function renderBranchTasks() { const { id: projectCode, branch } = appState.params const taskListContainer = appState.currentPage === 'admin_page' ? 'task_list' : 'explorer_task_list'; let branchTasks = RIBC.getProjectMap(appState.params.id)[appState.params.branch]; if (branchTasks[1] && !taskListContainer === 'task_list') { getRef(taskListContainer).textContent = "No tasks added yet, Please explore other projects" } else { let tasks = [] if (branch !== 'mainLine') { const { startPoint, parentBranch } = getAllBranches(projectCode).find(({ branchName }) => branchName === branch) tasks.push(html`

    Branched off from ${parentBranch}

    `) } if (taskListContainer === 'task_list') { branchTasks.slice(4).forEach((task) => tasks.push(render.taskListItem(task, getRef(taskListContainer)))) } else { branchTasks.slice(4).forEach((task) => tasks.push(render.taskCard(task))) } renderElem(getRef(taskListContainer), html`${tasks}`) } } function getAllBranches(projectCode) { const projectMap = RIBC.getProjectMap(projectCode) const projectBranches = RIBC.getProjectBranches(projectCode) return projectBranches.slice(1).map((branchName, index) => { const [parentBranch, , startPoint, endPoint] = projectMap[branchName] return { branchName, parentBranch, startPoint, endPoint } }) } let currentViewIndex = 0; getRef('admin_view_selector').addEventListener('change', (e) => { const newViewIndex = parseInt(e.target.value); showChildElement(getRef('admin_views'), newViewIndex, { entry: newViewIndex > currentViewIndex ? slideInLeft : slideInRight, exit: newViewIndex > currentViewIndex ? slideOutLeft : slideOutRight }); currentViewIndex = parseInt(e.target.value); }) function toggleEditing(target) { if (target === 'title') { makeEditable(currentTask.querySelector('.task-title')) } else { makeEditable(currentTask.querySelector('.task-description')) } } function formatAmount(amount = 0, currency = 'inr') { if (!amount) return '₹0'; return amount.toLocaleString(currency === 'inr' ? `en-IN` : 'en-US', { style: 'currency', currency, maximumFractionDigits: 0 }) } delegate(getRef('task_list'), 'change', 'sm-checkbox', (e) => { currentTask = e.target.closest('.task-list-item'); const taskStatus = e.target.checked ? 'completed' : 'incomplete' RIBC.admin.putTaskStatus(taskStatus, appState.params.id, appState.params.branch, currentTask.dataset.taskId) }) delegate(getRef('task_list'), 'change', 'sm-select', (e) => { currentTask = e.target.closest('.task-list-item'); const taskDetails = { [e.target.dataset.editField]: e.target.value } RIBC.admin.editTaskDetails(taskDetails, appState.params.id, appState.params.branch, currentTask.dataset.taskId) notify('Changes saved locally, commit the changes to make them permanent', 'success') }) getRef('task_list').addEventListener('focusout', (e) => { currentTask = e.target.closest('.task-list-item'); if (!currentTask) return; const ogTaskDetails = RIBC.getTaskDetails(appState.params.id, appState.params.branch, currentTask.dataset.taskId) const newTaskDetails = {} let valid = false; if (e.target.isContentEditable) { e.target.contentEditable = false newTaskDetails[e.target.dataset.editField] = DOMPurify.sanitize(e.target.innerHTML.trim()) valid = true; } else if (e.target.closest('sm-input')) { newTaskDetails[e.target.dataset.editField] = parseInt(e.target.value) valid = true; } if (!valid) return; if (ogTaskDetails[e.target.dataset.editField] !== newTaskDetails[e.target.dataset.editField]) { RIBC.admin.editTaskDetails(newTaskDetails, appState.params.id, appState.params.branch, currentTask.dataset.taskId) notify('Changes saved locally, commit the changes to make them permanent', 'success') } }) 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: floGlobals.prefersReducedMotion ? 0 : 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: floGlobals.prefersReducedMotion ? 0 : 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?', { confirmText: 'Unassign' }).then((result) => { if (result) { RIBC.admin.unassignInternFromTask(e.target.closest('.assigned-intern').dataset.floId, appState.params.id, appState.params.branch, currentTask.dataset.taskId) notify('Intern removed from the task') renderBranchTasks() } }) } else if (e.target.closest('.cancel-task-button')) { const card = e.target.closest('.temp-task') card.remove(); getRef('add_task').classList.remove('hidden') } else if (e.target.closest('.add-task-button')) { const card = e.target.closest('.temp-task') const title = card.querySelector('.temp-task__title').value.trim(); const description = card.querySelector('.temp-task__description').value.trim(); const category = card.querySelector('.temp-task__category').value.trim(); const maxSlots = parseInt(card.querySelector('.temp-task__max-slots').value.trim()); const duration = parseInt(card.querySelector('.temp-task__duration').value.trim()); const durationType = card.querySelector('.temp-task__duration-type').value.trim(); const reward = parseInt(card.querySelector('.temp-task__reward').value.trim()); if (title === '') { return notify('Please enter task title', 'error') } if (description === '') { return notify('Please enter description of the task', 'error') } const taskDetails = { title, description, category, maxSlots, duration, durationType, reward } const task = RIBC.admin.addTaskInMap(appState.params.id, appState.params.branch) RIBC.admin.editTaskDetails(taskDetails, appState.params.id, appState.params.branch, task) RIBC.admin.putTaskStatus('incomplete', appState.params.id, appState.params.branch, task) card.remove() renderBranchTasks() getRef('add_task').classList.remove('hidden') notify('Task added to current branch', 'success') } }) function addPlaceholderTask() { const categories = []; let first = true; for (const categoryID in floGlobals.taskCategories) { categories.push(html`${floGlobals.taskCategories[categoryID]}`) first = false; } const placeholderTask = html.node`
    ${categories}
    Days Months
    `; getRef('task_list').append(placeholderTask) getRef('task_list').querySelector('.temp-task__title').focusIn() getRef('add_task').classList.add('hidden') getRef('task_list').lastElementChild.scrollIntoView({ behavior: "smooth" }); } 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?", { confirmText: 'Delete' }).then((result) => { if (result) { RIBC.admin.deleteTaskInMap(appState.params.id, appState.params.branch, currentTask.dataset.taskId) renderBranchTasks() } }) } floGlobals.selectedInterns = new Set() delegate(getRef('intern_list_container'), 'change', '.intern-card', (e) => { const floId = e.target.closest('.intern-card').dataset.internFloId; if (e.target.checked) { floGlobals.selectedInterns.add(floId) } else { floGlobals.selectedInterns.delete(floId) } getRef('assign_interns_button').disabled = !floGlobals.selectedInterns.size }) function assignSelectedInterns() { floGlobals.selectedInterns.forEach(floId => { RIBC.admin.assignInternToTask(floId, appState.params.id, appState.params.branch, currentTask.dataset.taskId) renderBranchTasks() }) notify(`Assigned task`, 'success') closePopup() } 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(appState.params.id, appState.params.branch, startPoint, mergePoint); notify(`Branch added ${branchName}`, 'success') renderBranches() closePopup() } function clearRequestFilters() { getRef('filter_requests_by_category').reset() getRef('filter_requests_by_project').reset() } function renderProjectSelectorOptions() { const options = [html`All`]; RIBC.getProjectList().reverse().forEach(project => { options.push(html`${RIBC.getProjectDetails(project).projectName}`); }) renderElem(getRef('updates_page__project_selector'), html`${options}`) } function renderInternSelectorOptions() { const options = [html`All`]; const allInterns = Object.entries(RIBC.getInternList()).sort((a, b) => a[1].toLowerCase().localeCompare(b[1].toLowerCase())); allInterns.forEach(intern => { options.push(html`${intern[1]}`); }) renderElem(getRef('updates_page__intern_selector'), html`${options}`) } function getUpdatesByProject(projectCode) { const projectName = RIBC.getProjectDetails(projectCode).projectName const allUpdates = RIBC.getInternUpdates() const filteredUpdates = allUpdates.filter(({ update: { projectCode: updateProjectCode } }) => { return projectCode === updateProjectCode }) 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.internUpdateCard) } 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`
    `) 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`

    Admin

    ${replyText}

    `) }).catch(err => { notify(err, 'error') buttonLoader(replayBox.querySelector('.update-replay__submit'), false) }) } } function setUpdateFilters(filters) { const { projectCode, internId, date } = filters || getUpdateFilters() if (filters) { getRef('updates_page__project_selector').value = projectCode getRef('updates_page__intern_selector').value = internId getRef('updates_page__date_selector').value = date || '' } else { const dateParam = date !== '' ? `&date=${date}` : '' location.hash = `/updates_page?projectCode=${projectCode}&internId=${internId}${dateParam}` } } function getUpdateFilters() { const projectCode = 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 { projectCode, internId, date } } function clearUpdatesFilter() { 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()) function pinProject(thisBtn) { const projectCode = thisBtn.closest('.pinned-card').dataset.id; pinnedProjects = localStorage.getItem(`${myFloID}_pinned_projects`) ? localStorage.getItem(`${myFloID}_pinned_projects`).split(',') : [] if (pinnedProjects.includes(projectCode)) { pinnedProjects = pinnedProjects.filter(project => project !== projectCode) } else { pinnedProjects.push(projectCode) } localStorage.setItem(`${myFloID}_pinned_projects`, pinnedProjects.join()) render.dashProjects(getRef('pinned_projects'), pinnedProjects) const unpinnedProjects = RIBC.getProjectList().filter(project => !pinnedProjects.includes(project)).reverse() if (unpinnedProjects.length > 0) { getRef('project_list_container').classList.remove('hidden') } else { getRef('project_list_container').classList.add('hidden') } render.dashProjects(getRef('project_list'), unpinnedProjects) } let sessionTaskRequests = new Set(); function requestForTask(btn) { hideTaskDetails() try { floDapps.user.id const taskId = btn ? btn.dataset.taskId : floGlobals.tempUserTaskRequest floGlobals.tempUserTaskRequest = taskId if (typeOfUser === 'general') { getRef('intern_apply__task').textContent = RIBC.getAllTasks()[taskId].title openPopup('apply_for_task_popup', true) } else if (typeOfUser === 'intern') { const hasApplied = [...RIBC.getTaskRequests(false), ...sessionTaskRequests].find(({ details }) => { return taskId === details.taskId }) if (hasApplied) { notify('You have already applied for this task', 'error') } else { if (floGlobals.assignedTasks.has(taskId)) return notify('You have already been assigned this task', 'error'); const [projectCode, branch, task] = taskId.split('_') const { title } = RIBC.getTaskDetails(projectCode, branch, task) getConfirmation(`Do you want to apply for "${title}"`, { confirmText: 'Apply' }).then((result) => { if (result) { if (btn) { btn.textContent = 'Applying...' btn.disabled = true } RIBC.applyForTask({ taskId }).then((result) => { notify('Applied successfully.', 'success') sessionTaskRequests.add({ details: { taskId } }) floGlobals.tempUserTaskRequest = null btn.textContent = 'Applied' }).catch((err) => { if (btn) { btn.textContent = 'Apply' btn.disabled = false } notify(err, 'error') }) } }).catch((error) => { notify(error, 'error') }) } } } catch (err) { floGlobals.tempUserTaskRequest = btn.dataset.taskId; location.hash = '#/sign_in' floGlobals.signInNotification = notify('Please login to apply for task.') } } function toggleUpdatesFilter() { getRef('update_filters_wrapper').classList.toggle('hide-on-mobile') } // Event listeners delegate(getRef('all_interns_page'), 'click', '.intern-card', e => { showInternInfo(e.delegateTarget.dataset.internFloId) }) delegate(getRef('admin_page__intern_list'), 'click', '.intern-card', e => { showInternInfo(e.delegateTarget.dataset.internFloId) }) document.addEventListener('popupopened', e => { getRef('main_page').setAttribute('inert', '') 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 = '' floGlobals.selectedInterns.clear() getRef('assign_interns_button').disabled = true break; } if (popupStack.items.length === 0) { getRef('main_page').removeAttribute('inert') } }) floGlobals.assignedTasks = new Set() function renderAllElements() { let sortedProjectList = getSortedProjectList() document.querySelectorAll('.open-first-project').forEach(link => { link.href = `${link.href}/project?id=${sortedProjectList[0]}&branch=mainLine` }) pinnedProjects = localStorage.getItem(`${myFloID}_pinned_projects`) ? localStorage.getItem(`${myFloID}_pinned_projects`).split(',') : [] // Intern's view if (RIBC.getInternList()[myFloID] && !floGlobals.subAdmins.includes(myFloID)) { typeOfUser = 'intern'; document.querySelectorAll('.intern-option').forEach((option) => { option.classList.remove('hidden') }) floGlobals.assignedProjectsList = new Set(); // store all the projects assigned to interns in array const allTasks = RIBC.getAllTasks() for (const taskKey in allTasks) { const [projectCode, branch, task] = taskKey.split('_') const assignedInterns = RIBC.getAssignedInterns(projectCode, branch, task) if (Array.isArray(assignedInterns) && assignedInterns.includes(myFloID)) { floGlobals.assignedProjectsList.add(projectCode) if (RIBC.getTaskStatus(projectCode, branch, task) === 'incomplete') { floGlobals.assignedTasks.add(taskKey); } } } } else { document.querySelectorAll('.intern-option').forEach((option) => { option.classList.add('hidden') }) } // 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: floGlobals.prefersReducedMotion ? 0 : 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 let result if (RIBC.getInternList()) 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')) } } }) }) // 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 (!RIBC.getInternList()[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 === 'intern') { render.projectList(getRef('my_projects'), [...floGlobals.assignedProjectsList]) sortedProjectList = sortedProjectList.filter(val => !floGlobals.assignedProjectsList.has(val)); } if (sortedProjectList.length > 0) { getRef('other_projects').previousElementSibling.classList.remove('hidden') render.projectList(getRef('other_projects'), sortedProjectList) } else { getRef('other_projects').previousElementSibling.classList.add('hidden') } delegate(getRef('explorer_task_list'), 'click', '.apply-button', e => { requestForTask(e.delegateTarget) }) getRef('user_flo_id').value = myFloID; } let currentTaskId; function initTaskUpdate(e) { const taskCard = e.target.closest('.task-card') currentTaskId = taskCard.dataset.uniqueId const [projectCode, branch, task] = currentTaskId.split('_') getRef('update_of_project').textContent = RIBC.getProjectDetails(projectCode).projectName getRef('update_of_task').textContent = RIBC.getTaskDetails(projectCode, branch, task).title openPopup('post_update_popup') } function postUpdate() { const [projectCode, branch, task] = currentTaskId.split('_') const description = getRef('update__brief').value.trim() const linkText = getRef('update__link').value.trim() const link = linkText !== '' ? linkText : null if (description !== '') { RIBC.postInternUpdate({ projectCode, branch, 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(); const highPerformingInterns = Object.keys(allInterns).sort((a, b) => { return RIBC.getInternRating(b) - RIBC.getInternRating(a) }); let arrayOfInterns = Object.keys(allInterns).sort((a, b) => { return allInterns[a].toLowerCase().localeCompare(allInterns[b].toLowerCase()) }) if (availableInternsOnly) { arrayOfInterns = arrayOfInterns.filter(intern => !RIBC.getAssignedInterns(appState.params.id, appState.params.branch, currentTask.dataset.taskId)?.includes(intern)) } if (searchKey === '') { filtered = (sortByRating ? highPerformingInterns : arrayOfInterns).map(floId => { return render.internCard(floId, { selectable: availableInternsOnly }) }) } else { filtered = filterMap(arrayOfInterns, (floId) => { if (allInterns[floId].toLowerCase().includes(searchKey.toLowerCase())) { return render.internCard(floId, { selectable: availableInternsOnly }) } }) } 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 contact = getRef('intern_apply__contact').value.trim(); const brief = getRef('intern_apply__brief').value.trim(); // const resumeLink = getRef('intern_apply__resume_link').value.trim(); const portfolioLink = getRef('intern_apply__portfolio_link').value.trim(); const details = { name, brief, // resumeLink, contact, portfolioLink: portfolioLink !== '' ? portfolioLink : null, taskId: floGlobals.tempUserTaskRequest } RIBC.applyForTask(details) .then((result) => { notify('Application submitted', 'success') closePopup() }) .catch((error) => { notify(error, 'error') }).finally(() => { buttonLoader(getRef('intern_apply__button'), false) floGlobals.tempUserTaskRequest = null }) } function getSortedProjectList() { return RIBC.getProjectList().sort((a, b) => RIBC.getProjectDetails(a).projectName.toLowerCase().localeCompare(RIBC.getProjectDetails(b).projectName.toLowerCase())) } function getSignedIn(passwordType) { return new Promise((resolve, reject) => { try { getPromptInput('Enter password', '', { isPassword: true, }).then(password => { if (password) { resolve(password) } }) } catch (err) { if (passwordType === 'PIN/Password') { floGlobals.isPrivKeySecured = true; getRef('private_key_field').removeAttribute('data-private-key'); getRef('private_key_field').setAttribute('placeholder', 'Password'); getRef('private_key_field').customValidation = null getRef('secure_pwd_button').closest('.card').classList.add('hidden'); } else { floGlobals.isPrivKeySecured = false; getRef('private_key_field').dataset.privateKey = '' getRef('private_key_field').setAttribute('placeholder', 'FLO private key'); getRef('private_key_field').customValidation = floCrypto.getPubKeyHex; getRef('secure_pwd_button').closest('.card').classList.remove('hidden'); } if (!generalPages.find(page => window.location.hash.includes(page))) { location.hash = floGlobals.isPrivKeySecured ? '#/sign_in' : `#/landing`; } getRef('sign_in_button').onclick = () => { resolve(getRef('private_key_field').value.trim()); getRef('private_key_field').value = ''; routeTo('loading'); getRef("notification_drawer").remove(floGlobals.signInNotification) }; getRef('sign_up_button').onclick = () => { resolve(getRef('generated_private_key').value); getRef('generated_private_key').value = ''; routeTo('loading'); getRef("notification_drawer").remove(floGlobals.signInNotification) }; } }); } function setSecurePassword() { if (!floGlobals.isPrivKeySecured) { const password = getRef('secure_pwd_input').value.trim(); floDapps.securePrivKey(password).then(() => { floGlobals.isPrivKeySecured = true; notify('Password set successfully', 'success'); getRef('secure_pwd_button').closest('.card').classList.add('hidden'); closePopup(); }).catch(err => { notify(err, 'error'); }) } } 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(); } }); } // detect url within text and convert to link function linkify(inputText) { let replacedText, replacePattern1, replacePattern2, replacePattern3; //URLs starting with http://, https://, or ftp:// replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; replacedText = inputText.replace(replacePattern1, '$1'); //URLs starting with "www." (without // before it, or it'd re-link the ones done above). replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim; replacedText = replacedText.replace(replacePattern2, '$1$2'); //Change email addresses to mailto:: links. replacePattern3 = /(\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,6})/gim; replacedText = replacedText.replace(replacePattern3, '$1'); return replacedText; }