import type { BulmaJS } from '@cityssm/bulma-js/types.js' import type { cityssmGlobal } from '@cityssm/bulma-webapp-js/src/types.js' import type { LOS } from '../../types/globalTypes.js' import type { Lot, LotOccupancy, LotStatus, WorkOrderComment, WorkOrderMilestone, WorkOrderMilestoneType } from '../../types/recordTypes.js' declare const cityssm: cityssmGlobal declare const bulmaJS: BulmaJS declare const exports: Record ;(() => { const los = exports.los as LOS const workOrderId = ( document.querySelector('#workOrderEdit--workOrderId') as HTMLInputElement ).value const isCreate = workOrderId === '' const workOrderFormElement = document.querySelector( '#form--workOrderEdit' ) as HTMLFormElement los.initializeDatePickers( workOrderFormElement .querySelector('#workOrderEdit--workOrderOpenDateString') ?.closest('.field') as HTMLElement ) los.initializeUnlockFieldButtons(workOrderFormElement) function setUnsavedChanges(): void { los.setUnsavedChanges() document .querySelector("button[type='submit'][form='form--workOrderEdit']") ?.classList.remove('is-light') } function clearUnsavedChanges(): void { los.clearUnsavedChanges() document .querySelector("button[type='submit'][form='form--workOrderEdit']") ?.classList.add('is-light') } workOrderFormElement.addEventListener('submit', (submitEvent) => { submitEvent.preventDefault() cityssm.postJSON( `${los.urlPrefix}/workOrders/${isCreate ? 'doCreateWorkOrder' : 'doUpdateWorkOrder'}`, submitEvent.currentTarget, (rawResponseJSON) => { const responseJSON = rawResponseJSON as { success: boolean workOrderId?: number errorMessage?: string } if (responseJSON.success) { clearUnsavedChanges() if (isCreate) { globalThis.location.href = los.getWorkOrderURL( responseJSON.workOrderId, true ) } else { bulmaJS.alert({ message: 'Work Order Updated Successfully', contextualColorName: 'success' }) } } else { bulmaJS.alert({ title: 'Error Updating Work Order', message: responseJSON.errorMessage ?? '', contextualColorName: 'danger' }) } } ) }) const inputElements: NodeListOf< HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement > = workOrderFormElement.querySelectorAll('input, select, textarea') for (const inputElement of inputElements) { inputElement.addEventListener('change', setUnsavedChanges) } /* * Work Order Options */ function doClose(): void { cityssm.postJSON( `${los.urlPrefix}/workOrders/doCloseWorkOrder`, { workOrderId }, (rawResponseJSON) => { const responseJSON = rawResponseJSON as { success: boolean errorMessage?: string } if (responseJSON.success) { clearUnsavedChanges() globalThis.location.href = los.getWorkOrderURL(workOrderId) } else { bulmaJS.alert({ title: 'Error Closing Work Order', message: responseJSON.errorMessage ?? '', contextualColorName: 'danger' }) } } ) } function doDelete(): void { cityssm.postJSON( `${los.urlPrefix}/workOrders/doDeleteWorkOrder`, { workOrderId }, (rawResponseJSON) => { const responseJSON = rawResponseJSON as { success: boolean errorMessage?: string } if (responseJSON.success) { clearUnsavedChanges() globalThis.location.href = `${los.urlPrefix}/workOrders` } else { bulmaJS.alert({ title: 'Error Deleting Work Order', message: responseJSON.errorMessage ?? '', contextualColorName: 'danger' }) } } ) } let workOrderMilestones: WorkOrderMilestone[] document .querySelector('#button--closeWorkOrder') ?.addEventListener('click', () => { const hasOpenMilestones = workOrderMilestones.some( (milestone) => !milestone.workOrderMilestoneCompletionDate ) if (hasOpenMilestones) { bulmaJS.alert({ title: 'Outstanding Milestones', message: `You cannot close a work order with outstanding milestones. Either complete the outstanding milestones, or remove them from the work order.`, contextualColorName: 'warning' }) /* // Disable closing work orders with open milestones bulmaJS.confirm({ title: "Close Work Order with Outstanding Milestones", message: "Are you sure you want to close this work order with outstanding milestones?", contextualColorName: "danger", okButton: { text: "Yes, Close Work Order", callbackFunction: doClose } }); */ } else { bulmaJS.confirm({ title: 'Close Work Order', message: los.hasUnsavedChanges() ? 'Are you sure you want to close this work order with unsaved changes?' : 'Are you sure you want to close this work order?', contextualColorName: los.hasUnsavedChanges() ? 'warning' : 'info', okButton: { text: 'Yes, Close Work Order', callbackFunction: doClose } }) } }) document .querySelector('#button--deleteWorkOrder') ?.addEventListener('click', (clickEvent: Event) => { clickEvent.preventDefault() bulmaJS.confirm({ title: 'Delete Work Order', message: 'Are you sure you want to delete this work order?', contextualColorName: 'warning', okButton: { text: 'Yes, Delete Work Order', callbackFunction: doDelete } }) }) /** * Related Lots */ if (!isCreate) { ;(() => { let workOrderLots = exports.workOrderLots as Lot[] delete exports.workOrderLots let workOrderLotOccupancies = exports.workOrderLotOccupancies as LotOccupancy[] delete exports.workOrderLotOccupancies function deleteLotOccupancy(clickEvent: Event): void { const lotOccupancyId = ( (clickEvent.currentTarget as HTMLElement).closest( '.container--lotOccupancy' ) as HTMLElement ).dataset.lotOccupancyId function doDelete(): void { cityssm.postJSON( `${los.urlPrefix}/workOrders/doDeleteWorkOrderLotOccupancy`, { workOrderId, lotOccupancyId }, (rawResponseJSON) => { const responseJSON = rawResponseJSON as { success: boolean errorMessage?: string workOrderLotOccupancies: LotOccupancy[] } if (responseJSON.success) { workOrderLotOccupancies = responseJSON.workOrderLotOccupancies renderRelatedLotsAndOccupancies() } else { bulmaJS.alert({ title: 'Error Deleting Relationship', message: responseJSON.errorMessage ?? '', contextualColorName: 'danger' }) } } ) } bulmaJS.confirm({ title: `Delete ${los.escapedAliases.Occupancy} Relationship`, message: `Are you sure you want to remove the relationship to this ${los.escapedAliases.occupancy} record from this work order? Note that the record will remain.`, contextualColorName: 'warning', okButton: { text: 'Yes, Delete Relationship', callbackFunction: doDelete } }) } function addLot( lotId: number | string, callbackFunction?: (success: boolean) => void ): void { cityssm.postJSON( `${los.urlPrefix}/workOrders/doAddWorkOrderLot`, { workOrderId, lotId }, (rawResponseJSON) => { const responseJSON = rawResponseJSON as { success: boolean errorMessage?: string workOrderLots: Lot[] } if (responseJSON.success) { workOrderLots = responseJSON.workOrderLots renderRelatedLotsAndOccupancies() } else { bulmaJS.alert({ title: `Error Adding ${los.escapedAliases.Lot}`, message: responseJSON.errorMessage ?? '', contextualColorName: 'danger' }) } if (callbackFunction !== undefined) { callbackFunction(responseJSON.success) } } ) } function addLotOccupancy( lotOccupancyId: number | string, callbackFunction?: (success?: boolean) => void ): void { cityssm.postJSON( `${los.urlPrefix}/workOrders/doAddWorkOrderLotOccupancy`, { workOrderId, lotOccupancyId }, (rawResponseJSON) => { const responseJSON = rawResponseJSON as { success: boolean errorMessage?: string workOrderLotOccupancies: LotOccupancy[] } if (responseJSON.success) { workOrderLotOccupancies = responseJSON.workOrderLotOccupancies renderRelatedLotsAndOccupancies() } else { bulmaJS.alert({ title: `Error Adding ${los.escapedAliases.Occupancy}`, message: responseJSON.errorMessage ?? '', contextualColorName: 'danger' }) } if (callbackFunction !== undefined) { callbackFunction(responseJSON.success) } } ) } function addLotFromLotOccupancy(clickEvent: Event): void { const lotId = (clickEvent.currentTarget as HTMLElement).dataset.lotId ?? '' addLot(lotId) } function renderRelatedOccupancies(): void { const occupanciesContainerElement = document.querySelector( '#container--lotOccupancies' ) as HTMLElement ;( document.querySelector( ".tabs a[href='#relatedTab--lotOccupancies'] .tag" ) as HTMLElement ).textContent = workOrderLotOccupancies.length.toString() if (workOrderLotOccupancies.length === 0) { // eslint-disable-next-line no-unsanitized/property occupanciesContainerElement.innerHTML = `

There are no ${los.escapedAliases.occupancies} associated with this work order.

` return } // eslint-disable-next-line no-unsanitized/property occupanciesContainerElement.innerHTML = `
${los.escapedAliases.Occupancy} Type ${los.escapedAliases.Lot} ${los.escapedAliases.OccupancyStartDate} End Date ${los.escapedAliases.Occupants}
` const currentDateString = cityssm.dateToString(new Date()) for (const lotOccupancy of workOrderLotOccupancies) { const rowElement = document.createElement('tr') rowElement.className = 'container--lotOccupancy' rowElement.dataset.lotOccupancyId = lotOccupancy.lotOccupancyId.toString() const isActive = !( lotOccupancy.occupancyEndDate && lotOccupancy.occupancyEndDateString! < currentDateString ) const hasLotRecord = lotOccupancy.lotId && workOrderLots.some((lot) => lotOccupancy.lotId === lot.lotId) // eslint-disable-next-line no-unsanitized/property rowElement.innerHTML = ` ${ isActive ? `` : `` } ${cityssm.escapeHTML(lotOccupancy.occupancyType ?? '')}
#${lotOccupancy.lotOccupancyId} ` if (lotOccupancy.lotId) { // eslint-disable-next-line no-unsanitized/method rowElement.insertAdjacentHTML( 'beforeend', ` ${cityssm.escapeHTML(lotOccupancy.lotName ?? '')} ${ hasLotRecord ? '' : ` ` } ` ) } else { // eslint-disable-next-line no-unsanitized/method rowElement.insertAdjacentHTML( 'beforeend', `(No ${los.escapedAliases.Lot})` ) } let occupantsHTML = '' for (const occupant of lotOccupancy.lotOccupancyOccupants!) { occupantsHTML += `
  • ${cityssm.escapeHTML(occupant.occupantName ?? '')} ${cityssm.escapeHTML(occupant.occupantFamilyName ?? '')}
  • ` } // eslint-disable-next-line no-unsanitized/method rowElement.insertAdjacentHTML( 'beforeend', ` ${lotOccupancy.occupancyStartDateString} ${ lotOccupancy.occupancyEndDate ? lotOccupancy.occupancyEndDateString : '(No End Date)' } ${ lotOccupancy.lotOccupancyOccupants!.length === 0 ? `(No ${los.escapedAliases.Occupants})` : `` } ` ) rowElement .querySelector('.button--addLot') ?.addEventListener('click', addLotFromLotOccupancy) rowElement .querySelector('.button--deleteLotOccupancy') ?.addEventListener('click', deleteLotOccupancy) occupanciesContainerElement.querySelector('tbody')?.append(rowElement) } } function openEditLotStatus(clickEvent: Event): void { const lotId = Number.parseInt( ( (clickEvent.currentTarget as HTMLElement).closest( '.container--lot' ) as HTMLElement ).dataset.lotId ?? '', 10 ) const lot = workOrderLots.find( (possibleLot) => possibleLot.lotId === lotId ) as Lot let editCloseModalFunction: () => void function doUpdateLotStatus(submitEvent: SubmitEvent): void { submitEvent.preventDefault() cityssm.postJSON( `${los.urlPrefix}/workOrders/doUpdateLotStatus`, submitEvent.currentTarget, (rawResponseJSON) => { const responseJSON = rawResponseJSON as { success: boolean errorMessage?: string workOrderLots: Lot[] } if (responseJSON.success) { workOrderLots = responseJSON.workOrderLots renderRelatedLotsAndOccupancies() editCloseModalFunction() } else { bulmaJS.alert({ title: 'Error Deleting Relationship', message: responseJSON.errorMessage ?? '', contextualColorName: 'danger' }) } } ) } cityssm.openHtmlModal('lot-editLotStatus', { onshow(modalElement) { los.populateAliases(modalElement) ;( modalElement.querySelector( '#lotStatusEdit--lotId' ) as HTMLInputElement ).value = lotId.toString() ;( modalElement.querySelector( '#lotStatusEdit--lotName' ) as HTMLInputElement ).value = lot.lotName ?? '' const lotStatusElement = modalElement.querySelector( '#lotStatusEdit--lotStatusId' ) as HTMLSelectElement let lotStatusFound = false for (const lotStatus of exports.lotStatuses as LotStatus[]) { const optionElement = document.createElement('option') optionElement.value = lotStatus.lotStatusId.toString() optionElement.textContent = lotStatus.lotStatus if (lotStatus.lotStatusId === lot.lotStatusId) { lotStatusFound = true } lotStatusElement.append(optionElement) } if (!lotStatusFound && lot.lotStatusId) { const optionElement = document.createElement('option') optionElement.value = lot.lotStatusId.toString() optionElement.textContent = lot.lotStatus ?? '' lotStatusElement.append(optionElement) } if (lot.lotStatusId) { lotStatusElement.value = lot.lotStatusId.toString() } // eslint-disable-next-line no-unsanitized/method modalElement .querySelector('form') ?.insertAdjacentHTML( 'beforeend', `` ) }, onshown(modalElement, closeModalFunction) { editCloseModalFunction = closeModalFunction bulmaJS.toggleHtmlClipped() modalElement .querySelector('form') ?.addEventListener('submit', doUpdateLotStatus) }, onremoved() { bulmaJS.toggleHtmlClipped() } }) } function deleteLot(clickEvent: Event): void { const lotId = ( (clickEvent.currentTarget as HTMLElement).closest( '.container--lot' ) as HTMLElement ).dataset.lotId function doDelete(): void { cityssm.postJSON( `${los.urlPrefix}/workOrders/doDeleteWorkOrderLot`, { workOrderId, lotId }, (rawResponseJSON) => { const responseJSON = rawResponseJSON as { success: boolean errorMessage?: string workOrderLots: Lot[] } if (responseJSON.success) { workOrderLots = responseJSON.workOrderLots renderRelatedLotsAndOccupancies() } else { bulmaJS.alert({ title: 'Error Deleting Relationship', message: responseJSON.errorMessage ?? '', contextualColorName: 'danger' }) } } ) } bulmaJS.confirm({ title: `Delete ${los.escapedAliases.Occupancy} Relationship`, message: `Are you sure you want to remove the relationship to this ${los.escapedAliases.occupancy} record from this work order? Note that the record will remain.`, contextualColorName: 'warning', okButton: { text: 'Yes, Delete Relationship', callbackFunction: doDelete } }) } function renderRelatedLots(): void { const lotsContainerElement = document.querySelector( '#container--lots' ) as HTMLElement ;( document.querySelector( ".tabs a[href='#relatedTab--lots'] .tag" ) as HTMLElement ).textContent = workOrderLots.length.toString() if (workOrderLots.length === 0) { // eslint-disable-next-line no-unsanitized/property lotsContainerElement.innerHTML = `

    There are no ${los.escapedAliases.lots} associated with this work order.

    ` return } // eslint-disable-next-line no-unsanitized/property lotsContainerElement.innerHTML = `
    ${los.escapedAliases.Lot} ${los.escapedAliases.Map} ${los.escapedAliases.Lot} Type Status
    ` for (const lot of workOrderLots) { const rowElement = document.createElement('tr') rowElement.className = 'container--lot' rowElement.dataset.lotId = lot.lotId.toString() // eslint-disable-next-line no-unsanitized/property rowElement.innerHTML = ` ${cityssm.escapeHTML(lot.lotName ?? '')} ${cityssm.escapeHTML(lot.mapName ?? '')} ${cityssm.escapeHTML(lot.lotType ?? '')} ${ lot.lotStatusId ? cityssm.escapeHTML(lot.lotStatus ?? '') : '(No Status)' } ` rowElement .querySelector('.button--editLotStatus') ?.addEventListener('click', openEditLotStatus) rowElement .querySelector('.button--deleteLot') ?.addEventListener('click', deleteLot) lotsContainerElement.querySelector('tbody')?.append(rowElement) } } function renderRelatedLotsAndOccupancies(): void { renderRelatedOccupancies() renderRelatedLots() } renderRelatedLotsAndOccupancies() function doAddLotOccupancy(clickEvent: Event): void { const rowElement = (clickEvent.currentTarget as HTMLElement).closest( 'tr' ) as HTMLTableRowElement const lotOccupancyId = rowElement.dataset.lotOccupancyId ?? '' addLotOccupancy(lotOccupancyId, (success) => { if (success) { rowElement.remove() } }) } document .querySelector('#button--addLotOccupancy') ?.addEventListener('click', () => { let searchFormElement: HTMLFormElement let searchResultsContainerElement: HTMLElement function doSearch(event?: Event): void { if (event) { event.preventDefault() } // eslint-disable-next-line no-unsanitized/property searchResultsContainerElement.innerHTML = los.getLoadingParagraphHTML('Searching...') cityssm.postJSON( `${los.urlPrefix}/lotOccupancies/doSearchLotOccupancies`, searchFormElement, (rawResponseJSON) => { const responseJSON = rawResponseJSON as { lotOccupancies: LotOccupancy[] } if (responseJSON.lotOccupancies.length === 0) { searchResultsContainerElement.innerHTML = `

    There are no records that meet the search criteria.

    ` return } // eslint-disable-next-line no-unsanitized/property searchResultsContainerElement.innerHTML = `
    ${los.escapedAliases.Occupancy} Type ${los.escapedAliases.Lot} ${los.escapedAliases.OccupancyStartDate} End Date ${los.escapedAliases.Occupants}
    ` for (const lotOccupancy of responseJSON.lotOccupancies) { const rowElement = document.createElement('tr') rowElement.className = 'container--lotOccupancy' rowElement.dataset.lotOccupancyId = lotOccupancy.lotOccupancyId.toString() rowElement.innerHTML = ` ${cityssm.escapeHTML(lotOccupancy.occupancyType ?? '')} ` if (lotOccupancy.lotId) { rowElement.insertAdjacentHTML( 'beforeend', `${cityssm.escapeHTML(lotOccupancy.lotName ?? '')}` ) } else { // eslint-disable-next-line no-unsanitized/method rowElement.insertAdjacentHTML( 'beforeend', `(No ${los.escapedAliases.Lot})` ) } // eslint-disable-next-line no-unsanitized/method rowElement.insertAdjacentHTML( 'beforeend', ` ${lotOccupancy.occupancyStartDateString} ${ lotOccupancy.occupancyEndDate ? lotOccupancy.occupancyEndDateString : '(No End Date)' } ${ lotOccupancy.lotOccupancyOccupants!.length === 0 ? ` (No ${cityssm.escapeHTML( los.escapedAliases.Occupants )}) ` : cityssm.escapeHTML( `${lotOccupancy.lotOccupancyOccupants![0].occupantName} ${ lotOccupancy.lotOccupancyOccupants![0] .occupantFamilyName }` ) + (lotOccupancy.lotOccupancyOccupants!.length > 1 ? ` plus ${( lotOccupancy.lotOccupancyOccupants!.length - 1 ).toString()}` : '') }` ) rowElement .querySelector('.button--addLotOccupancy') ?.addEventListener('click', doAddLotOccupancy) searchResultsContainerElement .querySelector('tbody') ?.append(rowElement) } } ) } cityssm.openHtmlModal('workOrder-addLotOccupancy', { onshow(modalElement) { los.populateAliases(modalElement) searchFormElement = modalElement.querySelector( 'form' ) as HTMLFormElement searchResultsContainerElement = modalElement.querySelector( '#resultsContainer--lotOccupancyAdd' ) as HTMLElement ;( modalElement.querySelector( '#lotOccupancySearch--notWorkOrderId' ) as HTMLInputElement ).value = workOrderId ;( modalElement.querySelector( '#lotOccupancySearch--occupancyEffectiveDateString' ) as HTMLInputElement ).value = ( document.querySelector( '#workOrderEdit--workOrderOpenDateString' ) as HTMLInputElement ).value doSearch() }, onshown(modalElement) { bulmaJS.toggleHtmlClipped() const occupantNameElement = modalElement.querySelector( '#lotOccupancySearch--occupantName' ) as HTMLInputElement occupantNameElement.addEventListener('change', doSearch) occupantNameElement.focus() ;( modalElement.querySelector( '#lotOccupancySearch--lotName' ) as HTMLInputElement ).addEventListener('change', doSearch) searchFormElement.addEventListener('submit', doSearch) }, onremoved() { bulmaJS.toggleHtmlClipped() ;( document.querySelector( '#button--addLotOccupancy' ) as HTMLButtonElement ).focus() } }) }) function doAddLot(clickEvent: Event): void { const rowElement = (clickEvent.currentTarget as HTMLElement).closest( 'tr' ) as HTMLTableRowElement const lotId = rowElement.dataset.lotId ?? '' addLot(lotId, (success) => { if (success) { rowElement.remove() } }) } document .querySelector('#button--addLot') ?.addEventListener('click', () => { let searchFormElement: HTMLFormElement let searchResultsContainerElement: HTMLElement function doSearch(event?: Event): void { if (event) { event.preventDefault() } // eslint-disable-next-line no-unsanitized/property searchResultsContainerElement.innerHTML = los.getLoadingParagraphHTML('Searching...') cityssm.postJSON( `${los.urlPrefix}/lots/doSearchLots`, searchFormElement, (rawResponseJSON) => { const responseJSON = rawResponseJSON as { lots: Lot[] } if (responseJSON.lots.length === 0) { searchResultsContainerElement.innerHTML = `

    There are no records that meet the search criteria.

    ` return } // eslint-disable-next-line no-unsanitized/property searchResultsContainerElement.innerHTML = `
    ${los.escapedAliases.Lot} ${los.escapedAliases.Map} ${los.escapedAliases.Lot} Type Status
    ` for (const lot of responseJSON.lots) { const rowElement = document.createElement('tr') rowElement.className = 'container--lot' rowElement.dataset.lotId = lot.lotId.toString() rowElement.innerHTML = ` ${cityssm.escapeHTML(lot.lotName ?? '')} ${cityssm.escapeHTML(lot.mapName ?? '')} ${cityssm.escapeHTML(lot.lotType ?? '')} ${cityssm.escapeHTML(lot.lotStatus ?? '')} ` rowElement .querySelector('.button--addLot') ?.addEventListener('click', doAddLot) searchResultsContainerElement .querySelector('tbody') ?.append(rowElement) } } ) } cityssm.openHtmlModal('workOrder-addLot', { onshow(modalElement) { los.populateAliases(modalElement) searchFormElement = modalElement.querySelector( 'form' ) as HTMLFormElement searchResultsContainerElement = modalElement.querySelector( '#resultsContainer--lotAdd' ) as HTMLElement ;( modalElement.querySelector( '#lotSearch--notWorkOrderId' ) as HTMLInputElement ).value = workOrderId const lotStatusElement = modalElement.querySelector( '#lotSearch--lotStatusId' ) as HTMLSelectElement for (const lotStatus of exports.lotStatuses as LotStatus[]) { const optionElement = document.createElement('option') optionElement.value = lotStatus.lotStatusId.toString() optionElement.textContent = lotStatus.lotStatus lotStatusElement.append(optionElement) } doSearch() }, onshown(modalElement) { bulmaJS.toggleHtmlClipped() const lotNameElement = modalElement.querySelector( '#lotSearch--lotName' ) as HTMLInputElement lotNameElement.addEventListener('change', doSearch) lotNameElement.focus() modalElement .querySelector('#lotSearch--lotStatusId') ?.addEventListener('change', doSearch) searchFormElement.addEventListener('submit', doSearch) }, onremoved() { bulmaJS.toggleHtmlClipped() ;( document.querySelector('#button--addLot') as HTMLButtonElement ).focus() } }) }) })() } /** * Comments */ ;(() => { let workOrderComments = exports.workOrderComments as WorkOrderComment[] delete exports.workOrderComments function openEditWorkOrderComment(clickEvent: Event): void { const workOrderCommentId = Number.parseInt( (clickEvent.currentTarget as HTMLElement).closest('tr')?.dataset .workOrderCommentId ?? '', 10 ) const workOrderComment = workOrderComments.find( (currentComment) => currentComment.workOrderCommentId === workOrderCommentId ) as WorkOrderComment let editFormElement: HTMLFormElement let editCloseModalFunction: () => void function editComment(submitEvent: SubmitEvent): void { submitEvent.preventDefault() cityssm.postJSON( `${los.urlPrefix}/workOrders/doUpdateWorkOrderComment`, editFormElement, (rawResponseJSON) => { const responseJSON = rawResponseJSON as { success: boolean errorMessage?: string workOrderComments: WorkOrderComment[] } if (responseJSON.success) { workOrderComments = responseJSON.workOrderComments editCloseModalFunction() renderWorkOrderComments() } else { bulmaJS.alert({ title: 'Error Updating Comment', message: responseJSON.errorMessage ?? '', contextualColorName: 'danger' }) } } ) } cityssm.openHtmlModal('workOrder-editComment', { onshow(modalElement) { ;( modalElement.querySelector( '#workOrderCommentEdit--workOrderId' ) as HTMLInputElement ).value = workOrderId ;( modalElement.querySelector( '#workOrderCommentEdit--workOrderCommentId' ) as HTMLInputElement ).value = workOrderCommentId.toString() ;( modalElement.querySelector( '#workOrderCommentEdit--workOrderComment' ) as HTMLInputElement ).value = workOrderComment.workOrderComment ?? '' const workOrderCommentDateStringElement = modalElement.querySelector( '#workOrderCommentEdit--workOrderCommentDateString' ) as HTMLInputElement workOrderCommentDateStringElement.value = workOrderComment.workOrderCommentDateString ?? '' const currentDateString = cityssm.dateToString(new Date()) workOrderCommentDateStringElement.max = workOrderComment.workOrderCommentDateString! <= currentDateString ? currentDateString : workOrderComment.workOrderCommentDateString ?? '' ;( modalElement.querySelector( '#workOrderCommentEdit--workOrderCommentTimeString' ) as HTMLInputElement ).value = workOrderComment.workOrderCommentTimeString ?? '' }, onshown(modalElement, closeModalFunction) { bulmaJS.toggleHtmlClipped() los.initializeDatePickers(modalElement) ;( modalElement.querySelector( '#workOrderCommentEdit--workOrderComment' ) as HTMLTextAreaElement ).focus() editFormElement = modalElement.querySelector( 'form' ) as HTMLFormElement editFormElement.addEventListener('submit', editComment) editCloseModalFunction = closeModalFunction }, onremoved() { bulmaJS.toggleHtmlClipped() } }) } function deleteWorkOrderComment(clickEvent: Event): void { const workOrderCommentId = Number.parseInt( (clickEvent.currentTarget as HTMLElement).closest('tr')?.dataset .workOrderCommentId ?? '', 10 ) function doDelete(): void { cityssm.postJSON( `${los.urlPrefix}/workOrders/doDeleteWorkOrderComment`, { workOrderId, workOrderCommentId }, (rawResponseJSON) => { const responseJSON = rawResponseJSON as { success: boolean errorMessage?: string workOrderComments: WorkOrderComment[] } if (responseJSON.success) { workOrderComments = responseJSON.workOrderComments renderWorkOrderComments() } else { bulmaJS.alert({ title: 'Error Removing Comment', message: responseJSON.errorMessage ?? '', contextualColorName: 'danger' }) } } ) } bulmaJS.confirm({ title: 'Remove Comment?', message: 'Are you sure you want to remove this comment?', okButton: { text: 'Yes, Remove Comment', callbackFunction: doDelete }, contextualColorName: 'warning' }) } function renderWorkOrderComments(): void { const containerElement = document.querySelector( '#container--workOrderComments' ) as HTMLElement if (workOrderComments.length === 0) { containerElement.innerHTML = `

    There are no comments to display.

    ` return } const tableElement = document.createElement('table') tableElement.className = 'table is-fullwidth is-striped is-hoverable' tableElement.innerHTML = ` Commentor Comment Date Comment Options ` for (const workOrderComment of workOrderComments) { const tableRowElement = document.createElement('tr') tableRowElement.dataset.workOrderCommentId = workOrderComment.workOrderCommentId?.toString() // eslint-disable-next-line no-unsanitized/property tableRowElement.innerHTML = ` ${cityssm.escapeHTML(workOrderComment.recordCreate_userName ?? '')} ${workOrderComment.workOrderCommentDateString} ${ workOrderComment.workOrderCommentTime === 0 ? '' : workOrderComment.workOrderCommentTimePeriodString } ${cityssm.escapeHTML(workOrderComment.workOrderComment ?? '')}
    ` tableRowElement .querySelector('.button--edit') ?.addEventListener('click', openEditWorkOrderComment) tableRowElement .querySelector('.button--delete') ?.addEventListener('click', deleteWorkOrderComment) tableElement.querySelector('tbody')?.append(tableRowElement) } containerElement.innerHTML = '' containerElement.append(tableElement) } function openAddCommentModal(): void { let addCommentCloseModalFunction: () => void function doAddComment(formEvent: SubmitEvent): void { formEvent.preventDefault() cityssm.postJSON( `${los.urlPrefix}/workOrders/doAddWorkOrderComment`, formEvent.currentTarget, (rawResponseJSON) => { const responseJSON = rawResponseJSON as { success: boolean workOrderComments: WorkOrderComment[] } if (responseJSON.success) { workOrderComments = responseJSON.workOrderComments renderWorkOrderComments() addCommentCloseModalFunction() } } ) } cityssm.openHtmlModal('workOrder-addComment', { onshow(modalElement) { los.populateAliases(modalElement) ;( modalElement.querySelector( '#workOrderCommentAdd--workOrderId' ) as HTMLInputElement ).value = workOrderId modalElement .querySelector('form') ?.addEventListener('submit', doAddComment) }, onshown(modalElement, closeModalFunction) { bulmaJS.toggleHtmlClipped() addCommentCloseModalFunction = closeModalFunction ;( modalElement.querySelector( '#workOrderCommentAdd--workOrderComment' ) as HTMLTextAreaElement ).focus() }, onremoved() { bulmaJS.toggleHtmlClipped() ;( document.querySelector( '#workOrderComments--add' ) as HTMLButtonElement ).focus() } }) } document .querySelector('#workOrderComments--add') ?.addEventListener('click', openAddCommentModal) if (!isCreate) { renderWorkOrderComments() } })() /* * Milestones */ function clearPanelBlockElements(panelElement: HTMLElement): void { for (const panelBlockElement of panelElement.querySelectorAll( '.panel-block' )) { panelBlockElement.remove() } } function refreshConflictingMilestones( workOrderMilestoneDateString: string, targetPanelElement: HTMLElement ): void { // Clear panel-block elements clearPanelBlockElements(targetPanelElement) // eslint-disable-next-line no-unsanitized/method targetPanelElement.insertAdjacentHTML( 'beforeend', `
    ${los.getLoadingParagraphHTML('Loading conflicting milestones...')}
    ` ) cityssm.postJSON( `${los.urlPrefix}/workOrders/doGetWorkOrderMilestones`, { workOrderMilestoneDateFilter: 'date', workOrderMilestoneDateString }, (rawResponseJSON) => { const responseJSON = rawResponseJSON as { workOrderMilestones: WorkOrderMilestone[] } const workOrderMilestones = responseJSON.workOrderMilestones.filter( (possibleMilestone) => possibleMilestone.workOrderId.toString() !== workOrderId ) clearPanelBlockElements(targetPanelElement) for (const milestone of workOrderMilestones) { targetPanelElement.insertAdjacentHTML( 'beforeend', `
    ${cityssm.escapeHTML(milestone.workOrderMilestoneTime === 0 ? 'No Time' : milestone.workOrderMilestoneTimePeriodString ?? '')}
    ${cityssm.escapeHTML(milestone.workOrderMilestoneType ?? '')}
    ${cityssm.escapeHTML(milestone.workOrderNumber ?? '')}
    ${cityssm.escapeHTML(milestone.workOrderDescription ?? '')}
    ` ) } if (workOrderMilestones.length === 0) { targetPanelElement.insertAdjacentHTML( 'beforeend', `

    There are no milestones on other work orders scheduled for ${cityssm.escapeHTML(workOrderMilestoneDateString)}.

    ` ) } } ) } function processMilestoneResponse(rawResponseJSON: unknown): void { const responseJSON = rawResponseJSON as { success: boolean errorMessage?: string workOrderMilestones: WorkOrderMilestone[] } if (responseJSON.success) { workOrderMilestones = responseJSON.workOrderMilestones renderMilestones() } else { bulmaJS.alert({ title: 'Error Reopening Milestone', message: responseJSON.errorMessage ?? '', contextualColorName: 'danger' }) } } function completeMilestone(clickEvent: Event): void { clickEvent.preventDefault() const currentDateString = cityssm.dateToString(new Date()) const workOrderMilestoneId = Number.parseInt( ( (clickEvent.currentTarget as HTMLElement).closest( '.container--milestone' ) as HTMLElement ).dataset.workOrderMilestoneId ?? '', 10 ) const workOrderMilestone = workOrderMilestones.find( (currentMilestone) => currentMilestone.workOrderMilestoneId === workOrderMilestoneId ) as WorkOrderMilestone function doComplete(): void { cityssm.postJSON( `${los.urlPrefix}/workOrders/doCompleteWorkOrderMilestone`, { workOrderId, workOrderMilestoneId }, processMilestoneResponse ) } bulmaJS.confirm({ title: 'Complete Milestone', message: `Are you sure you want to complete this milestone? ${ workOrderMilestone.workOrderMilestoneDateString !== undefined && workOrderMilestone.workOrderMilestoneDateString !== '' && workOrderMilestone.workOrderMilestoneDateString > currentDateString ? '
    Note that this milestone is expected to be completed in the future.' : '' }`, messageIsHtml: true, contextualColorName: 'warning', okButton: { text: 'Yes, Complete Milestone', callbackFunction: doComplete } }) } function reopenMilestone(clickEvent: Event): void { clickEvent.preventDefault() const workOrderMilestoneId = ( (clickEvent.currentTarget as HTMLElement).closest( '.container--milestone' ) as HTMLElement ).dataset.workOrderMilestoneId function doReopen(): void { cityssm.postJSON( `${los.urlPrefix}/workOrders/doReopenWorkOrderMilestone`, { workOrderId, workOrderMilestoneId }, processMilestoneResponse ) } bulmaJS.confirm({ title: 'Reopen Milestone', message: 'Are you sure you want to remove the completion status from this milestone, and reopen it?', contextualColorName: 'warning', okButton: { text: 'Yes, Reopen Milestone', callbackFunction: doReopen } }) } function deleteMilestone(clickEvent: Event): void { clickEvent.preventDefault() const workOrderMilestoneId = ( (clickEvent.currentTarget as HTMLElement).closest( '.container--milestone' ) as HTMLElement ).dataset.workOrderMilestoneId function doDelete(): void { cityssm.postJSON( `${los.urlPrefix}/workOrders/doDeleteWorkOrderMilestone`, { workOrderMilestoneId, workOrderId }, processMilestoneResponse ) } bulmaJS.confirm({ title: 'Delete Milestone', message: 'Are you sure you want to delete this milestone?', contextualColorName: 'warning', okButton: { text: 'Yes, Delete Milestone', callbackFunction: doDelete } }) } function editMilestone(clickEvent: Event): void { clickEvent.preventDefault() const workOrderMilestoneId = Number.parseInt( ( (clickEvent.currentTarget as HTMLElement).closest( '.container--milestone' ) as HTMLElement ).dataset.workOrderMilestoneId ?? '', 10 ) const workOrderMilestone = workOrderMilestones.find( (currentMilestone) => currentMilestone.workOrderMilestoneId === workOrderMilestoneId ) as WorkOrderMilestone let editCloseModalFunction: () => void let workOrderMilestoneDateStringElement: HTMLInputElement function doEdit(submitEvent: SubmitEvent): void { submitEvent.preventDefault() cityssm.postJSON( `${los.urlPrefix}/workOrders/doUpdateWorkOrderMilestone`, submitEvent.currentTarget, (rawResponseJSON) => { const responseJSON = rawResponseJSON as { success: boolean errorMessage?: string workOrderMilestones?: WorkOrderMilestone[] } processMilestoneResponse(responseJSON) if (responseJSON.success) { editCloseModalFunction() } } ) } cityssm.openHtmlModal('workOrder-editMilestone', { onshow(modalElement) { ;( modalElement.querySelector( '#milestoneEdit--workOrderId' ) as HTMLInputElement ).value = workOrderId ;( modalElement.querySelector( '#milestoneEdit--workOrderMilestoneId' ) as HTMLInputElement ).value = workOrderMilestone.workOrderMilestoneId?.toString() ?? '' const milestoneTypeElement = modalElement.querySelector( '#milestoneEdit--workOrderMilestoneTypeId' ) as HTMLSelectElement let milestoneTypeFound = false for (const milestoneType of exports.workOrderMilestoneTypes as WorkOrderMilestoneType[]) { const optionElement = document.createElement('option') optionElement.value = milestoneType.workOrderMilestoneTypeId.toString() optionElement.textContent = milestoneType.workOrderMilestoneType if ( milestoneType.workOrderMilestoneTypeId === workOrderMilestone.workOrderMilestoneTypeId ) { optionElement.selected = true milestoneTypeFound = true } milestoneTypeElement.append(optionElement) } if ( !milestoneTypeFound && workOrderMilestone.workOrderMilestoneTypeId ) { const optionElement = document.createElement('option') optionElement.value = workOrderMilestone.workOrderMilestoneTypeId.toString() optionElement.textContent = workOrderMilestone.workOrderMilestoneType ?? '' optionElement.selected = true milestoneTypeElement.append(optionElement) } workOrderMilestoneDateStringElement = modalElement.querySelector( '#milestoneEdit--workOrderMilestoneDateString' ) as HTMLInputElement workOrderMilestoneDateStringElement.value = workOrderMilestone.workOrderMilestoneDateString ?? '' if (workOrderMilestone.workOrderMilestoneTime) { ;( modalElement.querySelector( '#milestoneEdit--workOrderMilestoneTimeString' ) as HTMLInputElement ).value = workOrderMilestone.workOrderMilestoneTimeString ?? '' } ;( modalElement.querySelector( '#milestoneEdit--workOrderMilestoneDescription' ) as HTMLTextAreaElement ).value = workOrderMilestone.workOrderMilestoneDescription ?? '' }, onshown(modalElement, closeModalFunction) { editCloseModalFunction = closeModalFunction bulmaJS.toggleHtmlClipped() los.initializeDatePickers(modalElement) // los.initializeTimePickers(modalElement); modalElement.querySelector('form')?.addEventListener('submit', doEdit) const conflictingMilestonePanelElement = document.querySelector( '#milestoneEdit--conflictingMilestonesPanel' ) as HTMLElement workOrderMilestoneDateStringElement.addEventListener('change', () => { refreshConflictingMilestones( workOrderMilestoneDateStringElement.value, conflictingMilestonePanelElement ) }) refreshConflictingMilestones( workOrderMilestoneDateStringElement.value, conflictingMilestonePanelElement ) }, onremoved() { bulmaJS.toggleHtmlClipped() } }) } function renderMilestones(): void { // Clear milestones panel const milestonesPanelElement = document.querySelector( '#panel--milestones' ) as HTMLElement const panelBlockElementsToDelete = milestonesPanelElement.querySelectorAll('.panel-block') for (const panelBlockToDelete of panelBlockElementsToDelete) { panelBlockToDelete.remove() } for (const milestone of workOrderMilestones) { const panelBlockElement = document.createElement('div') panelBlockElement.className = 'panel-block is-block container--milestone' panelBlockElement.dataset.workOrderMilestoneId = milestone.workOrderMilestoneId?.toString() // eslint-disable-next-line no-unsanitized/property panelBlockElement.innerHTML = `
    ${ milestone.workOrderMilestoneCompletionDate ? ` ` : `` }
    ${ milestone.workOrderMilestoneTypeId ? `${cityssm.escapeHTML(milestone.workOrderMilestoneType ?? '')}
    ` : '' } ${ milestone.workOrderMilestoneDate === 0 ? '(No Set Date)' : milestone.workOrderMilestoneDateString } ${ milestone.workOrderMilestoneTime ? ` ${milestone.workOrderMilestoneTimePeriodString}` : '' }
    ${cityssm.escapeHTML(milestone.workOrderMilestoneDescription ?? '')}
    ` panelBlockElement .querySelector('.button--reopenMilestone') ?.addEventListener('click', reopenMilestone) panelBlockElement .querySelector('.button--editMilestone') ?.addEventListener('click', editMilestone) panelBlockElement .querySelector('.button--completeMilestone') ?.addEventListener('click', completeMilestone) panelBlockElement .querySelector('.button--deleteMilestone') ?.addEventListener('click', deleteMilestone) milestonesPanelElement.append(panelBlockElement) } bulmaJS.init(milestonesPanelElement) } if (!isCreate) { workOrderMilestones = exports.workOrderMilestones as WorkOrderMilestone[] delete exports.workOrderMilestones renderMilestones() document .querySelector('#button--addMilestone') ?.addEventListener('click', () => { let addFormElement: HTMLFormElement let workOrderMilestoneDateStringElement: HTMLInputElement let addCloseModalFunction: () => void function doAdd(submitEvent: SubmitEvent): void { if (submitEvent) { submitEvent.preventDefault() } const currentDateString = cityssm.dateToString(new Date()) function _doAdd(): void { cityssm.postJSON( `${los.urlPrefix}/workOrders/doAddWorkOrderMilestone`, addFormElement, (rawResponseJSON) => { const responseJSON = rawResponseJSON as { success: boolean errorMessage?: string workOrderMilestones?: WorkOrderMilestone[] } processMilestoneResponse(responseJSON) if (responseJSON.success) { addCloseModalFunction() } } ) } const milestoneDateString = workOrderMilestoneDateStringElement.value if ( milestoneDateString !== '' && milestoneDateString < currentDateString ) { bulmaJS.confirm({ title: 'Milestone Date in the Past', message: 'Are you sure you want to create a milestone with a date in the past?', contextualColorName: 'warning', okButton: { text: 'Yes, Create a Past Milestone', callbackFunction: _doAdd } }) } else { _doAdd() } } cityssm.openHtmlModal('workOrder-addMilestone', { onshow(modalElement) { ;( modalElement.querySelector( '#milestoneAdd--workOrderId' ) as HTMLInputElement ).value = workOrderId const milestoneTypeElement = modalElement.querySelector( '#milestoneAdd--workOrderMilestoneTypeId' ) as HTMLSelectElement for (const milestoneType of exports.workOrderMilestoneTypes as WorkOrderMilestoneType[]) { const optionElement = document.createElement('option') optionElement.value = milestoneType.workOrderMilestoneTypeId.toString() optionElement.textContent = milestoneType.workOrderMilestoneType milestoneTypeElement.append(optionElement) } workOrderMilestoneDateStringElement = modalElement.querySelector( '#milestoneAdd--workOrderMilestoneDateString' ) as HTMLInputElement workOrderMilestoneDateStringElement.valueAsDate = new Date() }, onshown(modalElement, closeModalFunction) { addCloseModalFunction = closeModalFunction los.initializeDatePickers(modalElement) // los.initializeTimePickers(modalElement); bulmaJS.toggleHtmlClipped() ;( modalElement.querySelector( '#milestoneAdd--workOrderMilestoneTypeId' ) as HTMLSelectElement ).focus() addFormElement = modalElement.querySelector( 'form' ) as HTMLFormElement addFormElement.addEventListener('submit', doAdd) const conflictingMilestonePanelElement = document.querySelector( '#milestoneAdd--conflictingMilestonesPanel' ) as HTMLElement workOrderMilestoneDateStringElement.addEventListener( 'change', () => { refreshConflictingMilestones( workOrderMilestoneDateStringElement.value, conflictingMilestonePanelElement ) } ) refreshConflictingMilestones( workOrderMilestoneDateStringElement.value, conflictingMilestonePanelElement ) }, onremoved() { bulmaJS.toggleHtmlClipped() ;( document.querySelector( '#button--addMilestone' ) as HTMLButtonElement ).focus() } }) }) } })()