cityssm/attendance-tracking

View on GitHub
public-typescript/attendance.callIns.ts

Summary

Maintainability
F
4 days
Test Coverage
// eslint-disable-next-line eslint-comments/disable-enable-pair
/* eslint-disable @typescript-eslint/indent, unicorn/prefer-module */

// eslint-disable-next-line n/no-missing-import
import type { BulmaJS } from '@cityssm/bulma-js/types.js'
import type { cityssmGlobal } from '@cityssm/bulma-webapp-js/src/types.js'

import type { DoDeleteAbsenceRecordResponse } from '../handlers/attendance-post/doDeleteAbsenceRecord.js'
import type { DoDeleteReturnToWorkRecordResponse } from '../handlers/attendance-post/doDeleteReturnToWorkRecord.js'
import type { DoRecordCallInResponse } from '../handlers/attendance-post/doRecordCallIn.js'
import type { Attend as AttendGlobal } from '../types/globalTypes.js'
import type {
  AbsenceRecord,
  AbsenceType,
  Employee,
  ReturnToWorkRecord
} from '../types/recordTypes.js'

declare const bulmaJS: BulmaJS

declare const cityssm: cityssmGlobal

  // eslint-disable-next-line sonarjs/cognitive-complexity
;(() => {
  const Attend = exports.Attend as AttendGlobal

  const absenceTypes = exports.absenceTypes as AbsenceType[]
  const employees = exports.employees as Employee[]

  const employeeNumberRegularExpression =
    exports.employeeNumberRegularExpression as RegExp | undefined

  // const updateDays = exports.updateDays as number

  const canUpdateAbsences = exports.absencesCanUpdate as boolean
  // const canManageAbsences = exports.absencesCanManage as boolean

  const canUpdateReturnsToWork = exports.returnsToWorkCanUpdate as boolean
  // const canManageReturnsToWork = exports.returnsToWorkCanManage as boolean

  let absenceRecords = exports.absenceRecords as AbsenceRecord[]
  let returnToWorkRecords = exports.returnToWorkRecords as ReturnToWorkRecord[]

  function deleteAbsenceRecord(clickEvent: Event): void {
    const recordId = (
      (clickEvent.currentTarget as HTMLButtonElement).closest(
        '.panel-block'
      ) as HTMLElement
    ).dataset.recordId

    function doDelete(): void {
      cityssm.postJSON(
        `${Attend.urlPrefix}/attendance/doDeleteAbsenceRecord`,
        {
          recordId
        },
        (rawResponseJSON) => {
          const responseJSON =
            rawResponseJSON as unknown as DoDeleteAbsenceRecordResponse

          if (responseJSON.success) {
            bulmaJS.alert({
              message: 'Absence record deleted successfully.',
              contextualColorName: 'success'
            })

            absenceRecords = responseJSON.absenceRecords
            renderAbsenceRecords()
          } else {
            bulmaJS.alert({
              title: 'Error Deleting Record',
              message: 'Please try again.',
              contextualColorName: 'danger'
            })
          }
        }
      )
    }

    bulmaJS.confirm({
      title: 'Delete Absence Record',
      message: 'Are you sure you want to delete this absence record?',
      contextualColorName: 'warning',
      okButton: {
        text: 'Yes, Delete Record',
        callbackFunction: doDelete
      }
    })
  }

  function renderAbsenceRecords(): void {
    const containerElement = document.querySelector(
      '#container--absences'
    ) as HTMLElement

    if (containerElement === null) {
      return
    }

    if (absenceRecords.length === 0) {
      containerElement.innerHTML = `<div class="message is-info">
        <p class="message-body">There are no recent absence records to show.</p>
        </div>`

      return
    }

    const panelElement = document.createElement('div')
    panelElement.className = 'panel'

    let todayCount = 0
    let previousDateString = ''

    for (const absenceRecord of absenceRecords) {
      const absenceDate = new Date(absenceRecord.absenceDateTime)
      const currentDateString = absenceDate.toLocaleDateString()

      const panelBlockElement = document.createElement('div')
      panelBlockElement.className = 'panel-block is-block'
      panelBlockElement.dataset.recordId = absenceRecord.recordId
      panelBlockElement.tabIndex = 0

      if (Date.now() - absenceDate.getTime() <= 86_400 * 1000) {
        panelBlockElement.classList.add('has-background-success-light')
        todayCount += 1
      }

      panelBlockElement.innerHTML = `<div class="columns is-mobile">
        <div class="column is-narrow">
          <i class="fas fa-sign-out-alt" aria-hidden="true"></i>
        </div>
        <div class="column is-3">
          <strong class="${
            currentDateString === previousDateString
              ? 'has-text-grey-light'
              : ''
          }" data-tooltip="Absence Date">
            ${currentDateString}
          </strong>
        </div>
        <div class="column is-4">
          <strong>${absenceRecord.employeeName}</strong><br />
          <span class="is-size-7">${absenceRecord.employeeNumber ?? ''}</span>
        </div>
        <div class="column">
          <strong data-tooltip="Absence Type">${
            absenceRecord.absenceType ?? absenceRecord.absenceTypeKey
          }</strong><br />
          <span class="is-size-7">${absenceRecord.recordComment ?? ''}</span>
        </div>
        </div>`

      if (absenceRecord.canUpdate as boolean) {
        panelBlockElement.querySelector('.columns')?.insertAdjacentHTML(
          'beforeend',
          `<div class="column is-narrow">
            <button class="button is-small is-inverted is-danger has-tooltip-left is-delete-button" data-tooltip="Delete Record" data-cy="delete" type="button" aria-label="Delete Record">
              <i class="fas fa-trash" aria-hidden="true"></i>
            </button>
          </div>`
        )

        panelBlockElement
          .querySelector('.is-delete-button')
          ?.addEventListener('click', deleteAbsenceRecord)
      }

      panelElement.append(panelBlockElement)

      previousDateString = currentDateString
    }

    containerElement.innerHTML = ''
    containerElement.append(panelElement)
    ;(
      document.querySelector(
        '#menu--attendance a[href="#tab--absences"] .tag'
      ) as HTMLElement
    ).textContent = todayCount.toString()
  }

  function deleteReturnToWorkRecord(clickEvent: Event): void {
    const recordId = (
      (clickEvent.currentTarget as HTMLButtonElement).closest(
        '.panel-block'
      ) as HTMLElement
    ).dataset.recordId

    function doDelete(): void {
      cityssm.postJSON(
        `${Attend.urlPrefix}/attendance/doDeleteReturnToWorkRecord`,
        {
          recordId
        },
        (rawResponseJSON) => {
          const responseJSON =
            rawResponseJSON as unknown as DoDeleteReturnToWorkRecordResponse

          if (responseJSON.success) {
            bulmaJS.alert({
              message: 'Return to work record deleted successfully.',
              contextualColorName: 'success'
            })

            returnToWorkRecords = responseJSON.returnToWorkRecords
            renderReturnToWorkRecords()
          } else {
            bulmaJS.alert({
              title: 'Error Deleting Record',
              message: 'Please try again.',
              contextualColorName: 'danger'
            })
          }
        }
      )
    }

    bulmaJS.confirm({
      title: 'Delete Return to Work Record',
      message: 'Are you sure you want to delete this return to work record?',
      contextualColorName: 'warning',
      okButton: {
        text: 'Yes, Delete Record',
        callbackFunction: doDelete
      }
    })
  }

  function renderReturnToWorkRecords(): void {
    const containerElement = document.querySelector(
      '#container--returnsToWork'
    ) as HTMLElement

    if (containerElement === null) {
      return
    }

    if (returnToWorkRecords.length === 0) {
      containerElement.innerHTML = `<div class="message is-info">
        <p class="message-body">There are no recent return to work records to show.</p>
        </div>`

      return
    }

    const panelElement = document.createElement('div')
    panelElement.className = 'panel'

    let todayCount = 0
    let previousDateString = ''

    for (const returnToWorkRecord of returnToWorkRecords) {
      const returnDate = new Date(returnToWorkRecord.returnDateTime)
      const currentDateString = returnDate.toLocaleDateString()

      const panelBlockElement = document.createElement('div')
      panelBlockElement.className = 'panel-block is-block'
      panelBlockElement.dataset.recordId = returnToWorkRecord.recordId
      panelBlockElement.tabIndex = 0

      if (Date.now() - returnDate.getTime() <= 86_400 * 1000) {
        panelBlockElement.classList.add('has-background-success-light')
        todayCount += 1
      }

      panelBlockElement.innerHTML = `<div class="columns is-mobile">
        <div class="column is-narrow">
          <i class="fas fa-sign-in-alt" aria-hidden="true"></i>
        </div>
        <div class="column is-3">
          <strong class="${
            currentDateString === previousDateString
              ? 'has-text-grey-light'
              : ''
          }" data-tooltip="Return Date">
            ${currentDateString}
          </strong>
        </div>
        <div class="column is-4">
          <strong>${returnToWorkRecord.employeeName}</strong><br />
          <span class="is-size-7">${
            returnToWorkRecord.employeeNumber ?? ''
          }</span>
        </div>
        <div class="column">
          <strong data-tooltip="Return Shift">${
            returnToWorkRecord.returnShift ?? '(No Shift)'
          }</strong><br />
          <span class="is-size-7">${
            returnToWorkRecord.recordComment ?? ''
          }</span>
        </div>
        </div>`

      if (returnToWorkRecord.canUpdate as boolean) {
        panelBlockElement.querySelector('.columns')?.insertAdjacentHTML(
          'beforeend',
          `<div class="column is-narrow">
              <button class="button is-small is-inverted is-danger has-tooltip-left is-delete-button" data-tooltip="Delete Record" data-cy="delete" type="button" aria-label="Delete Record">
                <i class="fas fa-trash" aria-hidden="true"></i>
              </button>
            </div>`
        )

        panelBlockElement
          .querySelector('.is-delete-button')
          ?.addEventListener('click', deleteReturnToWorkRecord)
      }

      panelElement.append(panelBlockElement)

      previousDateString = currentDateString
    }

    containerElement.innerHTML = ''
    containerElement.append(panelElement)
    ;(
      document.querySelector(
        '#menu--attendance a[href="#tab--returnsToWork"] .tag'
      ) as HTMLElement
    ).textContent = todayCount.toString()
  }

  function openCallInModal(clickEvent: Event): void {
    let callInModalElement: HTMLElement
    let callInCloseModalFunction: () => void

    const callInType =
      (clickEvent.currentTarget as HTMLElement).dataset.callInType ?? ''

    let previousEmployeeNumberPiece = ''

    function populateEmployeeName(): void {
      const employeeNumberElement = callInModalElement.querySelector(
        '#callInAdd--employeeNumber'
      ) as HTMLInputElement

      const employeeNumberPiece = employeeNumberElement.value.toLowerCase()

      if (employeeNumberPiece === previousEmployeeNumberPiece) {
        return
      }

      previousEmployeeNumberPiece = employeeNumberPiece

      const employeeNameElement = callInModalElement.querySelector(
        '#callInAdd--employeeName'
      ) as HTMLInputElement
      employeeNameElement.value = ''

      const matchingEmployees = employees.filter((possibleEmployee) => {
        return (
          employeeNumberPiece.length >=
            possibleEmployee.employeeNumber.length / 2 &&
          possibleEmployee.employeeNumber
            .toLowerCase()
            .endsWith(employeeNumberPiece)
        )
      })

      if (matchingEmployees.length === 1) {
        employeeNumberElement.value = matchingEmployees[0].employeeNumber
        previousEmployeeNumberPiece =
          matchingEmployees[0].employeeNumber.toLowerCase()

        employeeNameElement.value =
          `${matchingEmployees[0].employeeGivenName} ${matchingEmployees[0].employeeSurname}`.trim()
      }
    }

    function toggleCallInType(): void {
      const callInTypeRadioElements: NodeListOf<HTMLInputElement> =
        callInModalElement.querySelectorAll('input[name="callInType"]')

      for (const radioElement of callInTypeRadioElements) {
        const labelButtonElement = radioElement.closest(
          'label'
        ) as HTMLLabelElement
        const fieldsetElement = callInModalElement.querySelector(
          `fieldset[data-call-in-type="${radioElement.value}"]`
        ) as HTMLFieldSetElement

        if (radioElement.checked) {
          labelButtonElement.classList.add('is-link')
          ;(
            labelButtonElement.querySelector('.icon') as HTMLElement
          ).innerHTML = '<i class="fas fa-check" aria-hidden="true"></i>'

          fieldsetElement.disabled = false
          fieldsetElement.classList.remove('is-hidden')
        } else {
          labelButtonElement.classList.remove('is-link')
          ;(
            labelButtonElement.querySelector('.icon') as HTMLElement
          ).innerHTML = '<i class="fas fa-minus" aria-hidden="true"></i>'

          fieldsetElement.classList.add('is-hidden')
          fieldsetElement.disabled = true
        }
      }
    }

    function recordCallIn(formEvent: Event): void {
      formEvent.preventDefault()

      cityssm.postJSON(
        `${Attend.urlPrefix}/attendance/doRecordCallIn`,
        formEvent.currentTarget,
        (rawResponseJSON) => {
          const responseJSON =
            rawResponseJSON as unknown as DoRecordCallInResponse

          if (responseJSON.success) {
            callInCloseModalFunction()

            if (responseJSON.callInType === 'absence') {
              absenceRecords = responseJSON.absenceRecords
              renderAbsenceRecords()
            } else if (responseJSON.callInType === 'returnToWork') {
              returnToWorkRecords = responseJSON.returnToWorkRecords
              renderReturnToWorkRecords()
            }
          }
        }
      )
    }

    cityssm.openHtmlModal('callIn-add', {
      onshow(modalElement) {
        callInModalElement = modalElement

        if (canUpdateAbsences) {
          // eslint-disable-next-line no-extra-semi
          ;(
            modalElement.querySelector(
              '#callInAdd--absenceDateString-absence'
            ) as HTMLInputElement
          ).valueAsDate = new Date()

          const absenceTypeElement = modalElement.querySelector(
            '#callInAdd--absenceTypeKey-absence'
          ) as HTMLSelectElement

          for (const absenceType of absenceTypes) {
            const optionElement = document.createElement('option')
            optionElement.value = absenceType.absenceTypeKey
            optionElement.textContent = absenceType.absenceType
            absenceTypeElement.append(optionElement)
          }
        } else {
          modalElement.querySelector('#callInAdd--callInType_absence')?.remove()
        }

        if (canUpdateReturnsToWork) {
          // eslint-disable-next-line no-extra-semi
          ;(
            modalElement.querySelector(
              '#callInAdd--returnDateString-returnToWork'
            ) as HTMLInputElement
          ).valueAsDate = new Date()
        } else {
          modalElement
            .querySelector('#callInAdd--callInType_returnToWork')
            ?.remove()
        }

        if (callInType !== '') {
          // eslint-disable-next-line no-extra-semi
          ;(
            modalElement.querySelector(
              `#callInAdd--callInType_${callInType}`
            ) as HTMLInputElement
          ).checked = true

          toggleCallInType()
        }
      },
      onshown(modalElement, closeModalFunction) {
        callInCloseModalFunction = closeModalFunction

        bulmaJS.toggleHtmlClipped()

        modalElement
          .querySelector('form')
          ?.addEventListener('submit', recordCallIn)

        const employeeNumberElement = modalElement.querySelector(
          '#callInAdd--employeeNumber'
        ) as HTMLInputElement

        if (employeeNumberRegularExpression !== undefined) {
          employeeNumberElement.pattern = employeeNumberRegularExpression.source
        }

        employeeNumberElement.focus()

        employeeNumberElement.addEventListener('keyup', populateEmployeeName)

        modalElement
          .querySelector('#callInAdd--callInType_absence')
          ?.addEventListener('change', toggleCallInType)

        modalElement
          .querySelector('#callInAdd--callInType_returnToWork')
          ?.addEventListener('change', toggleCallInType)
      },
      onremoved() {
        bulmaJS.toggleHtmlClipped()
      }
    })
  }

  if (canUpdateAbsences || canUpdateReturnsToWork) {
    const addCallInButtonElements = document.querySelectorAll(
      '.is-new-call-in-button'
    )

    for (const buttonElement of addCallInButtonElements) {
      buttonElement.addEventListener('click', openCallInModal)
    }
  }

  renderAbsenceRecords()
  renderReturnToWorkRecords()
})()