cityssm/attendance-tracking

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

Summary

Maintainability
D
2 days
Test Coverage
// eslint-disable-next-line eslint-comments/disable-enable-pair
/* eslint-disable @typescript-eslint/indent */

// eslint-disable-next-line eslint-comments/disable-enable-pair
/* eslint-disable 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 { DoAddAfterHoursRecordResponse } from '../handlers/attendance-post/doAddAfterHoursRecord.js'
import type { DoDeleteAfterHoursRecordResponse } from '../handlers/attendance-post/doDeleteAfterHoursRecord.js'
import type { Attend as AttendGlobal } from '../types/globalTypes.js'
import type {
  AfterHoursReason,
  AfterHoursRecord,
  Employee
} from '../types/recordTypes.js'

declare const bulmaJS: BulmaJS

declare const cityssm: cityssmGlobal
;(() => {
  const Attend = exports.Attend as AttendGlobal

  const afterHoursReasons = exports.afterHoursReasons as AfterHoursReason[]
  const employees = exports.employees as Employee[]

  let afterHoursRecords = exports.afterHoursRecords as AfterHoursRecord[]

  const employeeNumberRegularExpression =
    exports.employeeNumberRegularExpression as RegExp | undefined

  // const canUpdateAfterHours = exports.afterHoursCanUpdate as boolean

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

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

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

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

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

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

    if (containerElement === null) {
      return
    }

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

      return
    }

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

    let todayCount = 0

    for (const afterHoursRecord of afterHoursRecords) {
      const attendanceDateTime = new Date(afterHoursRecord.attendanceDateTime)

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

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

      panelBlockElement.tabIndex = 0

      panelBlockElement.innerHTML = `<div class="columns is-mobile">
        <div class="column is-narrow">
          <i class="fas fa-clock" aria-hidden="true"></i>
        </div>
        <div class="column is-3">
          <strong data-tooltip="Attendance Date">
            ${attendanceDateTime.toLocaleDateString()}
            </strong><br />
            <span class="is-size-7">${attendanceDateTime.toLocaleTimeString()}</span>
        </div>
        <div class="column is-4">
          <strong>${afterHoursRecord.employeeName}</strong><br />
          <span class="is-size-7">${
            afterHoursRecord.employeeNumber ?? ''
          }</span>
        </div>
        <div class="column">
          <strong data-tooltip="Absence Type">${
            afterHoursRecord.afterHoursReason ?? ''
          }</strong><br />
          <span class="is-size-7">${afterHoursRecord.recordComment ?? ''}</span>
        </div>
        </div>`

      if (afterHoursRecord.canUpdate as boolean) {
        panelBlockElement.querySelector('.columns')?.insertAdjacentHTML(
          'beforeend',
          `<div class="column is-narrow">
              <button class="button is-small is-inverted is-danger is-delete-button" 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', deleteAfterHoursRecord)
      }

      panelElement.append(panelBlockElement)
    }

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

  document
    .querySelector('.is-new-after-hours-button')
    ?.addEventListener('click', () => {
      let afterHoursModalElement: HTMLElement
      let afterHoursCloseModalFunction: () => void

      let previousEmployeeNumberPiece = ''

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

        const employeeNumberPiece = employeeNumberElement.value.toLowerCase()

        if (employeeNumberPiece === previousEmployeeNumberPiece) {
          return
        }

        previousEmployeeNumberPiece = employeeNumberPiece

        const employeeNameElement = afterHoursModalElement.querySelector(
          '#afterHoursAdd--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 recordAfterHours(formEvent: SubmitEvent): void {
        formEvent.preventDefault()

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

            if (responseJSON.success) {
              afterHoursCloseModalFunction()

              bulmaJS.alert({
                message: 'After hours attendance recorded successfully.'
              })

              afterHoursRecords = responseJSON.afterHoursRecords
              renderAfterHoursRecords()
            }
          }
        )
      }

      cityssm.openHtmlModal('afterHours-add', {
        onshow(modalElement) {
          afterHoursModalElement = modalElement

          const afterHoursReasonElement = modalElement.querySelector(
            '#afterHoursAdd--afterHoursReasonId'
          ) as HTMLSelectElement

          for (const reason of afterHoursReasons) {
            const optionElement = document.createElement('option')
            optionElement.value = reason.afterHoursReasonId.toString()
            optionElement.textContent = reason.afterHoursReason
            afterHoursReasonElement.append(optionElement)
          }
        },
        onshown(modalElement, closeModalFunction) {
          afterHoursCloseModalFunction = closeModalFunction

          bulmaJS.toggleHtmlClipped()

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

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

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

          employeeNumberElement.focus()

          employeeNumberElement.addEventListener('keyup', populateEmployeeName)
        },
        onremoved() {
          bulmaJS.toggleHtmlClipped()
        }
      })
    })

  renderAfterHoursRecords()
})()