From 0bdc76247f2c98db051822e8ed02c65923d35837 Mon Sep 17 00:00:00 2001 From: Dan Gowans Date: Tue, 25 Feb 2025 15:25:38 -0500 Subject: [PATCH] major refactoring, but the application starts! --- data/config.defaultValues.js | 2 +- data/config.defaultValues.ts | 2 +- database/addBurialSite.d.ts | 2 +- database/addBurialSite.js | 2 +- database/addBurialSite.ts | 2 +- database/addBurialSiteContract.js | 26 +- database/addBurialSiteContract.ts | 44 +- database/addCemetery.d.ts | 1 + database/addCemetery.js | 6 +- database/addCemetery.ts | 6 +- database/addWorkOrderBurialSite.d.ts | 2 +- database/addWorkOrderBurialSite.js | 2 +- database/addWorkOrderBurialSite.ts | 2 +- database/getCemetery.d.ts | 2 +- database/getCemetery.js | 4 +- database/getCemetery.ts | 4 +- database/initializeDatabase.js | 10 +- database/initializeDatabase.ts | 10 +- database/updateCemetery.d.ts | 1 + database/updateCemetery.js | 29 +- database/updateCemetery.ts | 33 +- helpers/burialSites.helpers.js | 4 +- helpers/burialSites.helpers.ts | 4 +- helpers/functions.authentication.js | 16 +- helpers/functions.authentication.ts | 16 +- helpers/functions.cache.d.ts | 2 +- helpers/functions.cache.js | 1 - helpers/functions.cache.ts | 2 +- helpers/functions.print.js | 2 + helpers/functions.print.ts | 3 + public/images/cemetery-logo.svg | 1 - public/images/sunrise-cms.png | Bin 0 -> 73729 bytes public/javascripts/burialSite.edit.d.ts | 1 + public/javascripts/burialSite.edit.js | 367 ++++ public/javascripts/burialSite.edit.ts | 4 +- public/javascripts/burialSite.search.d.ts | 1 + public/javascripts/burialSite.search.js | 22 +- public/javascripts/burialSite.search.ts | 4 +- public/javascripts/burialSite.view.d.ts | 1 + public/javascripts/burialSite.view.js | 9 + .../javascripts/burialSiteContract.edit.d.ts | 1 + public/javascripts/burialSiteContract.edit.js | 1735 +++++++++++++++++ .../burialSiteContract.search.d.ts | 1 + .../javascripts/burialSiteContract.search.js | 152 ++ .../javascripts/burialSiteContract.search.ts | 2 +- public/javascripts/burialSiteTypes.admin.d.ts | 1 + public/javascripts/burialSiteTypes.admin.js | 409 ++++ public/javascripts/burialSiteTypes.admin.ts | 10 +- public/javascripts/cemetery.edit.d.ts | 1 + public/javascripts/cemetery.edit.js | 81 + public/javascripts/cemetery.edit.ts | 48 +- public/javascripts/cemetery.search.d.ts | 1 + public/javascripts/cemetery.search.js | 102 + public/javascripts/cemetery.search.ts | 64 +- public/javascripts/cemetery.view.d.ts | 1 + public/javascripts/cemetery.view.js | 20 + public/javascripts/contractTypes.admin.d.ts | 1 + public/javascripts/contractTypes.admin.js | 602 ++++++ public/javascripts/database.admin.d.ts | 1 + public/javascripts/database.admin.js | 70 + public/javascripts/fees.admin.d.ts | 1 + public/javascripts/fees.admin.js | 659 +++++++ public/javascripts/main.js | 74 +- public/javascripts/main.ts | 83 +- public/javascripts/report.search.d.ts | 0 public/javascripts/report.search.js | 28 + public/javascripts/tables.admin.d.ts | 1 + public/javascripts/tables.admin.js | 716 +++++++ public/javascripts/tables.admin.ts | 4 +- public/javascripts/types.d.ts | 28 + public/javascripts/types.js | 2 + public/javascripts/types.ts | 6 +- public/javascripts/workOrder.edit.d.ts | 1 + public/javascripts/workOrder.edit.js | 1243 ++++++++++++ public/javascripts/workOrder.edit.ts | 26 +- .../workOrder.milestoneCalendar.d.ts | 1 + .../workOrder.milestoneCalendar.js | 118 ++ public/javascripts/workOrder.outlook.d.ts | 1 + public/javascripts/workOrder.outlook.js | 46 + public/javascripts/workOrder.search.d.ts | 1 + public/javascripts/workOrder.search.js | 180 ++ public/javascripts/workOrder.view.d.ts | 1 + public/javascripts/workOrder.view.js | 37 + temp/legacy.importFromCSV.js | 193 +- temp/legacy.importFromCSV.ts | 301 +-- temp/legacy.importFromCsv.data.d.ts | 11 - temp/legacy.importFromCsv.data.js | 16 +- temp/legacy.importFromCsv.data.ts | 35 +- temp/legacy.importFromCsv.ids.js | 14 +- temp/legacy.importFromCsv.ids.ts | 14 +- test/functions.js | 12 +- test/functions.ts | 12 +- views/_footerA.ejs | 9 - views/_header.ejs | 12 +- views/cemetery-edit.ejs | 337 ++-- views/cemetery-search.ejs | 20 +- views/cemetery-view.ejs | 237 ++- views/dashboard.ejs | 228 ++- 98 files changed, 7548 insertions(+), 1115 deletions(-) delete mode 100644 public/images/cemetery-logo.svg create mode 100644 public/images/sunrise-cms.png create mode 100644 public/javascripts/burialSite.edit.d.ts create mode 100644 public/javascripts/burialSite.edit.js create mode 100644 public/javascripts/burialSite.search.d.ts create mode 100644 public/javascripts/burialSite.view.d.ts create mode 100644 public/javascripts/burialSite.view.js create mode 100644 public/javascripts/burialSiteContract.edit.d.ts create mode 100644 public/javascripts/burialSiteContract.edit.js create mode 100644 public/javascripts/burialSiteContract.search.d.ts create mode 100644 public/javascripts/burialSiteContract.search.js create mode 100644 public/javascripts/burialSiteTypes.admin.d.ts create mode 100644 public/javascripts/burialSiteTypes.admin.js create mode 100644 public/javascripts/cemetery.edit.d.ts create mode 100644 public/javascripts/cemetery.edit.js create mode 100644 public/javascripts/cemetery.search.d.ts create mode 100644 public/javascripts/cemetery.search.js create mode 100644 public/javascripts/cemetery.view.d.ts create mode 100644 public/javascripts/cemetery.view.js create mode 100644 public/javascripts/contractTypes.admin.d.ts create mode 100644 public/javascripts/contractTypes.admin.js create mode 100644 public/javascripts/database.admin.d.ts create mode 100644 public/javascripts/database.admin.js create mode 100644 public/javascripts/fees.admin.d.ts create mode 100644 public/javascripts/fees.admin.js create mode 100644 public/javascripts/report.search.d.ts create mode 100644 public/javascripts/report.search.js create mode 100644 public/javascripts/tables.admin.d.ts create mode 100644 public/javascripts/tables.admin.js create mode 100644 public/javascripts/types.d.ts create mode 100644 public/javascripts/types.js create mode 100644 public/javascripts/workOrder.edit.d.ts create mode 100644 public/javascripts/workOrder.edit.js create mode 100644 public/javascripts/workOrder.milestoneCalendar.d.ts create mode 100644 public/javascripts/workOrder.milestoneCalendar.js create mode 100644 public/javascripts/workOrder.outlook.d.ts create mode 100644 public/javascripts/workOrder.outlook.js create mode 100644 public/javascripts/workOrder.search.d.ts create mode 100644 public/javascripts/workOrder.search.js create mode 100644 public/javascripts/workOrder.view.d.ts create mode 100644 public/javascripts/workOrder.view.js diff --git a/data/config.defaultValues.js b/data/config.defaultValues.js index 1cf77d7b..c176db9a 100644 --- a/data/config.defaultValues.js +++ b/data/config.defaultValues.js @@ -3,7 +3,7 @@ export const configDefaultValues = { activeDirectory: undefined, 'application.applicationName': 'Sunrise CMS', 'application.backgroundURL': '/images/cemetery-background.jpg', - 'application.logoURL': '/images/cemetery-logo.png', + 'application.logoURL': '/images/sunrise-cms.png', 'application.httpPort': 7000, 'application.userDomain': '', 'application.useTestDatabases': false, diff --git a/data/config.defaultValues.ts b/data/config.defaultValues.ts index b990b70e..a4f83559 100644 --- a/data/config.defaultValues.ts +++ b/data/config.defaultValues.ts @@ -13,7 +13,7 @@ export const configDefaultValues = { 'application.applicationName': 'Sunrise CMS', 'application.backgroundURL': '/images/cemetery-background.jpg', - 'application.logoURL': '/images/cemetery-logo.png', + 'application.logoURL': '/images/sunrise-cms.png', 'application.httpPort': 7000, 'application.userDomain': '', 'application.useTestDatabases': false, diff --git a/database/addBurialSite.d.ts b/database/addBurialSite.d.ts index e01f6910..3e434f12 100644 --- a/database/addBurialSite.d.ts +++ b/database/addBurialSite.d.ts @@ -13,4 +13,4 @@ export interface AddBurialSiteForm { burialSiteTypeFieldIds?: string; [fieldValue_burialSiteTypeFieldId: string]: unknown; } -export default function addLot(burialSiteForm: AddBurialSiteForm, user: User): Promise; +export default function addBurialSite(burialSiteForm: AddBurialSiteForm, user: User): Promise; diff --git a/database/addBurialSite.js b/database/addBurialSite.js index 28f3eb62..11acfb84 100644 --- a/database/addBurialSite.js +++ b/database/addBurialSite.js @@ -1,7 +1,7 @@ import { buildBurialSiteName } from '../helpers/burialSites.helpers.js'; import addOrUpdateBurialSiteField from './addOrUpdateBurialSiteField.js'; import { acquireConnection } from './pool.js'; -export default async function addLot(burialSiteForm, user) { +export default async function addBurialSite(burialSiteForm, user) { const database = await acquireConnection(); const rightNowMillis = Date.now(); const burialSiteName = buildBurialSiteName(burialSiteForm); diff --git a/database/addBurialSite.ts b/database/addBurialSite.ts index ed85c4c4..d66f29ec 100644 --- a/database/addBurialSite.ts +++ b/database/addBurialSite.ts @@ -23,7 +23,7 @@ export interface AddBurialSiteForm { [fieldValue_burialSiteTypeFieldId: string]: unknown } -export default async function addLot( +export default async function addBurialSite( burialSiteForm: AddBurialSiteForm, user: User ): Promise { diff --git a/database/addBurialSiteContract.js b/database/addBurialSiteContract.js index 1ff5f145..079c8219 100644 --- a/database/addBurialSiteContract.js +++ b/database/addBurialSiteContract.js @@ -1,14 +1,10 @@ import { dateStringToInteger } from '@cityssm/utils-datetime'; -import addBurialSiteContractOccupant from './addBurialSiteContractOccupant.js'; import addOrUpdateBurialSiteContractField from './addOrUpdateBurialSiteContractField.js'; import { acquireConnection } from './pool.js'; export default async function addBurialSiteContract(addForm, user, connectedDatabase) { const database = connectedDatabase ?? (await acquireConnection()); const rightNowMillis = Date.now(); const contractStartDate = dateStringToInteger(addForm.contractStartDateString); - if (contractStartDate <= 0) { - console.error(addForm); - } const result = database .prepare(`insert into BurialSiteContracts ( contractTypeId, lotId, @@ -22,31 +18,15 @@ export default async function addBurialSiteContract(addForm, user, connectedData const burialSiteContractId = result.lastInsertRowid; const contractTypeFieldIds = (addForm.contractTypeFieldIds ?? '').split(','); for (const contractTypeFieldId of contractTypeFieldIds) { - const burialSiteContractFieldValue = addForm[`burialSiteContractFieldValue_${contractTypeFieldId}`]; - if ((burialSiteContractFieldValue ?? '') !== '') { + const fieldValue = addForm[`fieldValue_${contractTypeFieldId}`]; + if ((fieldValue ?? '') !== '') { await addOrUpdateBurialSiteContractField({ burialSiteContractId, contractTypeFieldId, - burialSiteContractFieldValue: burialSiteContractFieldValue ?? '' + fieldValue: fieldValue ?? '' }, user, database); } } - if ((addForm.lotOccupantTypeId ?? '') !== '') { - await addBurialSiteContractOccupant({ - burialSiteContractId, - lotOccupantTypeId: addForm.lotOccupantTypeId ?? '', - occupantName: addForm.occupantName ?? '', - occupantFamilyName: addForm.occupantFamilyName ?? '', - occupantAddress1: addForm.occupantAddress1 ?? '', - occupantAddress2: addForm.occupantAddress2 ?? '', - occupantCity: addForm.occupantCity ?? '', - occupantProvince: addForm.occupantProvince ?? '', - occupantPostalCode: addForm.occupantPostalCode ?? '', - occupantPhoneNumber: addForm.occupantPhoneNumber ?? '', - occupantEmailAddress: addForm.occupantEmailAddress ?? '', - occupantComment: addForm.occupantComment ?? '' - }, user, database); - } if (connectedDatabase === undefined) { database.release(); } diff --git a/database/addBurialSiteContract.ts b/database/addBurialSiteContract.ts index 1c85e497..73d0c5c5 100644 --- a/database/addBurialSiteContract.ts +++ b/database/addBurialSiteContract.ts @@ -1,7 +1,6 @@ import { type DateString, dateStringToInteger } from '@cityssm/utils-datetime' import type { PoolConnection } from 'better-sqlite-pool' -import addBurialSiteContractOccupant from './addBurialSiteContractOccupant.js' import addOrUpdateBurialSiteContractField from './addOrUpdateBurialSiteContractField.js' import { acquireConnection } from './pool.js' @@ -41,10 +40,6 @@ export default async function addBurialSiteContract( addForm.contractStartDateString as DateString ) - if (contractStartDate <= 0) { - console.error(addForm) - } - const result = database .prepare( `insert into BurialSiteContracts ( @@ -60,9 +55,7 @@ export default async function addBurialSiteContract( contractStartDate, addForm.contractEndDateString === '' ? undefined - : dateStringToInteger( - addForm.contractEndDateString as DateString - ), + : dateStringToInteger(addForm.contractEndDateString as DateString), user.userName, rightNowMillis, user.userName, @@ -71,21 +64,19 @@ export default async function addBurialSiteContract( const burialSiteContractId = result.lastInsertRowid as number - const contractTypeFieldIds = ( - addForm.contractTypeFieldIds ?? '' - ).split(',') + const contractTypeFieldIds = (addForm.contractTypeFieldIds ?? '').split(',') for (const contractTypeFieldId of contractTypeFieldIds) { - const burialSiteContractFieldValue = addForm[ - `burialSiteContractFieldValue_${contractTypeFieldId}` - ] as string | undefined + const fieldValue = addForm[`fieldValue_${contractTypeFieldId}`] as + | string + | undefined - if ((burialSiteContractFieldValue ?? '') !== '') { + if ((fieldValue ?? '') !== '') { await addOrUpdateBurialSiteContractField( { burialSiteContractId, contractTypeFieldId, - burialSiteContractFieldValue: burialSiteContractFieldValue ?? '' + fieldValue: fieldValue ?? '' }, user, database @@ -93,27 +84,6 @@ export default async function addBurialSiteContract( } } - if ((addForm.lotOccupantTypeId ?? '') !== '') { - await addBurialSiteContractOccupant( - { - burialSiteContractId, - lotOccupantTypeId: addForm.lotOccupantTypeId ?? '', - occupantName: addForm.occupantName ?? '', - occupantFamilyName: addForm.occupantFamilyName ?? '', - occupantAddress1: addForm.occupantAddress1 ?? '', - occupantAddress2: addForm.occupantAddress2 ?? '', - occupantCity: addForm.occupantCity ?? '', - occupantProvince: addForm.occupantProvince ?? '', - occupantPostalCode: addForm.occupantPostalCode ?? '', - occupantPhoneNumber: addForm.occupantPhoneNumber ?? '', - occupantEmailAddress: addForm.occupantEmailAddress ?? '', - occupantComment: addForm.occupantComment ?? '' - }, - user, - database - ) - } - if (connectedDatabase === undefined) { database.release() } diff --git a/database/addCemetery.d.ts b/database/addCemetery.d.ts index 50178e6c..008a545a 100644 --- a/database/addCemetery.d.ts +++ b/database/addCemetery.d.ts @@ -1,5 +1,6 @@ export interface AddCemeteryForm { cemeteryName: string; + cemeteryKey: string; cemeteryDescription: string; cemeterySvg: string; cemeteryLatitude: string; diff --git a/database/addCemetery.js b/database/addCemetery.js index 93ab4baa..315fd088 100644 --- a/database/addCemetery.js +++ b/database/addCemetery.js @@ -4,15 +4,15 @@ export default async function addCemetery(addForm, user) { const rightNowMillis = Date.now(); const result = database .prepare(`insert into Cemeteries ( - cemeteryName, cemeteryDescription, + cemeteryName, cemeteryKey, cemeteryDescription, cemeterySvg, cemeteryLatitude, cemeteryLongitude, cemeteryAddress1, cemeteryAddress2, cemeteryCity, cemeteryProvince, cemeteryPostalCode, cemeteryPhoneNumber, recordCreate_userName, recordCreate_timeMillis, recordUpdate_userName, recordUpdate_timeMillis) - values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`) - .run(addForm.cemeteryName, addForm.cemeteryDescription, addForm.cemeterySvg, addForm.cemeteryLatitude === '' ? undefined : addForm.cemeteryLatitude, addForm.cemeteryLongitude === '' ? undefined : addForm.cemeteryLongitude, addForm.cemeteryAddress1, addForm.cemeteryAddress2, addForm.cemeteryCity, addForm.cemeteryProvince, addForm.cemeteryPostalCode, addForm.cemeteryPhoneNumber, user.userName, rightNowMillis, user.userName, rightNowMillis); + values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`) + .run(addForm.cemeteryName, addForm.cemeteryKey, addForm.cemeteryDescription, addForm.cemeterySvg, addForm.cemeteryLatitude === '' ? undefined : addForm.cemeteryLatitude, addForm.cemeteryLongitude === '' ? undefined : addForm.cemeteryLongitude, addForm.cemeteryAddress1, addForm.cemeteryAddress2, addForm.cemeteryCity, addForm.cemeteryProvince, addForm.cemeteryPostalCode, addForm.cemeteryPhoneNumber, user.userName, rightNowMillis, user.userName, rightNowMillis); database.release(); return result.lastInsertRowid; } diff --git a/database/addCemetery.ts b/database/addCemetery.ts index 8d17271a..9388407b 100644 --- a/database/addCemetery.ts +++ b/database/addCemetery.ts @@ -2,6 +2,7 @@ import { acquireConnection } from './pool.js' export interface AddCemeteryForm { cemeteryName: string + cemeteryKey: string cemeteryDescription: string cemeterySvg: string @@ -27,17 +28,18 @@ export default async function addCemetery( const result = database .prepare( `insert into Cemeteries ( - cemeteryName, cemeteryDescription, + cemeteryName, cemeteryKey, cemeteryDescription, cemeterySvg, cemeteryLatitude, cemeteryLongitude, cemeteryAddress1, cemeteryAddress2, cemeteryCity, cemeteryProvince, cemeteryPostalCode, cemeteryPhoneNumber, recordCreate_userName, recordCreate_timeMillis, recordUpdate_userName, recordUpdate_timeMillis) - values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` ) .run( addForm.cemeteryName, + addForm.cemeteryKey, addForm.cemeteryDescription, addForm.cemeterySvg, addForm.cemeteryLatitude === '' ? undefined : addForm.cemeteryLatitude, diff --git a/database/addWorkOrderBurialSite.d.ts b/database/addWorkOrderBurialSite.d.ts index f48ba043..2f84e34b 100644 --- a/database/addWorkOrderBurialSite.d.ts +++ b/database/addWorkOrderBurialSite.d.ts @@ -2,4 +2,4 @@ export interface AddWorkOrderLotForm { workOrderId: number | string; burialSiteId: number | string; } -export default function addWorkOrderLot(workOrderLotForm: AddWorkOrderLotForm, user: User): Promise; +export default function addWorkOrderBurialSite(workOrderLotForm: AddWorkOrderLotForm, user: User): Promise; diff --git a/database/addWorkOrderBurialSite.js b/database/addWorkOrderBurialSite.js index 0d1c1796..c7ed6ec2 100644 --- a/database/addWorkOrderBurialSite.js +++ b/database/addWorkOrderBurialSite.js @@ -1,5 +1,5 @@ import { acquireConnection } from './pool.js'; -export default async function addWorkOrderLot(workOrderLotForm, user) { +export default async function addWorkOrderBurialSite(workOrderLotForm, user) { const database = await acquireConnection(); const rightNowMillis = Date.now(); const row = database diff --git a/database/addWorkOrderBurialSite.ts b/database/addWorkOrderBurialSite.ts index 72e11e10..72682563 100644 --- a/database/addWorkOrderBurialSite.ts +++ b/database/addWorkOrderBurialSite.ts @@ -5,7 +5,7 @@ export interface AddWorkOrderLotForm { burialSiteId: number | string } -export default async function addWorkOrderLot( +export default async function addWorkOrderBurialSite( workOrderLotForm: AddWorkOrderLotForm, user: User ): Promise { diff --git a/database/getCemetery.d.ts b/database/getCemetery.d.ts index 91c36727..83cfca22 100644 --- a/database/getCemetery.d.ts +++ b/database/getCemetery.d.ts @@ -1,2 +1,2 @@ import type { Cemetery } from '../types/recordTypes.js'; -export default function getMap(cemeteryId: number | string): Promise; +export default function getCemetery(cemeteryId: number | string): Promise; diff --git a/database/getCemetery.js b/database/getCemetery.js index 41c556b1..6712be9b 100644 --- a/database/getCemetery.js +++ b/database/getCemetery.js @@ -1,8 +1,8 @@ import { acquireConnection } from './pool.js'; -export default async function getMap(cemeteryId) { +export default async function getCemetery(cemeteryId) { const database = await acquireConnection(); const map = database - .prepare(`select m.cemeteryId, m.cemeteryName, m.cemeteryDescription, + .prepare(`select m.cemeteryId, m.cemeteryName, m.cemeteryKey, m.cemeteryDescription, m.cemeteryLatitude, m.cemeteryLongitude, m.cemeterySvg, m.cemeteryAddress1, m.cemeteryAddress2, m.cemeteryCity, m.cemeteryProvince, m.cemeteryPostalCode, m.cemeteryPhoneNumber, diff --git a/database/getCemetery.ts b/database/getCemetery.ts index 8790abec..1f7e0622 100644 --- a/database/getCemetery.ts +++ b/database/getCemetery.ts @@ -2,14 +2,14 @@ import type { Cemetery } from '../types/recordTypes.js' import { acquireConnection } from './pool.js' -export default async function getMap( +export default async function getCemetery( cemeteryId: number | string ): Promise { const database = await acquireConnection() const map = database .prepare( - `select m.cemeteryId, m.cemeteryName, m.cemeteryDescription, + `select m.cemeteryId, m.cemeteryName, m.cemeteryKey, m.cemeteryDescription, m.cemeteryLatitude, m.cemeteryLongitude, m.cemeterySvg, m.cemeteryAddress1, m.cemeteryAddress2, m.cemeteryCity, m.cemeteryProvince, m.cemeteryPostalCode, m.cemeteryPhoneNumber, diff --git a/database/initializeDatabase.js b/database/initializeDatabase.js index a9e4afd2..28a9c152 100644 --- a/database/initializeDatabase.js +++ b/database/initializeDatabase.js @@ -23,7 +23,7 @@ const createStatements = [ orderNumber smallint not null default 0, ${recordColumns})`, `create index if not exists idx_BurialSiteTypes_orderNumber - on LotTypes (orderNumber, burialSiteType)`, + on BurialSiteTypes (orderNumber, burialSiteType)`, `create table if not exists BurialSiteTypeFields ( burialSiteTypeFieldId integer not null primary key autoincrement, burialSiteTypeId integer not null, @@ -110,7 +110,7 @@ const createStatements = [ commentTime integer not null check (commentTime >= 0), comment text not null, ${recordColumns}, - foreign key (lotId) references BurialSites (burialSiteId))`, + foreign key (burialSiteId) references BurialSites (burialSiteId))`, `create index if not exists idx_BurialSiteComments_datetime on BurialSiteComments (burialSiteId, commentDate, commentTime)`, /* @@ -221,9 +221,9 @@ const createStatements = [ `create table if not exists BurialSiteContractInterments ( burialSiteContractId integer not null, intermentNumber integer not null, - isCremated bit not null default 0, - + deceasedName varchar(50) not null, + isCremated bit not null default 0, birthDate integer, birthPlace varchar(100), @@ -239,7 +239,7 @@ const createStatements = [ ${recordColumns}, primary key (burialSiteContractId, intermentNumber), - foreign key (burialSiteId) references BurialSites (burialSiteId), + foreign key (burialSiteContractId) references BurialSiteContracts (burialSiteContractId), foreign key (intermentContainerTypeId) references IntermentContainerTypes (intermentContainerTypeId), foreign key (intermentCommittalTypeId) references IntermentCommittalTypes (intermentCommittalTypeId)) without rowid`, /* diff --git a/database/initializeDatabase.ts b/database/initializeDatabase.ts index 3c0a6fa7..741fdb5b 100644 --- a/database/initializeDatabase.ts +++ b/database/initializeDatabase.ts @@ -31,7 +31,7 @@ const createStatements = [ ${recordColumns})`, `create index if not exists idx_BurialSiteTypes_orderNumber - on LotTypes (orderNumber, burialSiteType)`, + on BurialSiteTypes (orderNumber, burialSiteType)`, `create table if not exists BurialSiteTypeFields ( burialSiteTypeFieldId integer not null primary key autoincrement, @@ -129,7 +129,7 @@ const createStatements = [ commentTime integer not null check (commentTime >= 0), comment text not null, ${recordColumns}, - foreign key (lotId) references BurialSites (burialSiteId))`, + foreign key (burialSiteId) references BurialSites (burialSiteId))`, `create index if not exists idx_BurialSiteComments_datetime on BurialSiteComments (burialSiteId, commentDate, commentTime)`, @@ -260,9 +260,9 @@ const createStatements = [ `create table if not exists BurialSiteContractInterments ( burialSiteContractId integer not null, intermentNumber integer not null, - isCremated bit not null default 0, - + deceasedName varchar(50) not null, + isCremated bit not null default 0, birthDate integer, birthPlace varchar(100), @@ -278,7 +278,7 @@ const createStatements = [ ${recordColumns}, primary key (burialSiteContractId, intermentNumber), - foreign key (burialSiteId) references BurialSites (burialSiteId), + foreign key (burialSiteContractId) references BurialSiteContracts (burialSiteContractId), foreign key (intermentContainerTypeId) references IntermentContainerTypes (intermentContainerTypeId), foreign key (intermentCommittalTypeId) references IntermentCommittalTypes (intermentCommittalTypeId)) without rowid`, diff --git a/database/updateCemetery.d.ts b/database/updateCemetery.d.ts index 0b67f27f..c4c03f9b 100644 --- a/database/updateCemetery.d.ts +++ b/database/updateCemetery.d.ts @@ -1,6 +1,7 @@ export interface UpdateCemeteryForm { cemeteryId: string; cemeteryName: string; + cemeteryKey: string; cemeteryDescription: string; cemeterySvg: string; cemeteryLatitude: string; diff --git a/database/updateCemetery.js b/database/updateCemetery.js index 41845aec..b309a02e 100644 --- a/database/updateCemetery.js +++ b/database/updateCemetery.js @@ -2,23 +2,28 @@ import { acquireConnection } from './pool.js'; export default async function updateCemetery(updateForm, user) { const database = await acquireConnection(); const result = database - .prepare(`update Maps + .prepare(`update Cemeteries set cemeteryName = ?, - mapDescription = ?, - mapSVG = ?, - mapLatitude = ?, - mapLongitude = ?, - mapAddress1 = ?, - mapAddress2 = ?, - mapCity = ?, - mapProvince = ?, - mapPostalCode = ?, - mapPhoneNumber = ?, + cemeteryKey = ?, + cemeteryDescription = ?, + cemeterySvg = ?, + cemeteryLatitude = ?, + cemeteryLongitude = ?, + cemeteryAddress1 = ?, + cemeteryAddress2 = ?, + cemeteryCity = ?, + cemeteryProvince = ?, + cemeteryPostalCode = ?, + cemeteryPhoneNumber = ?, recordUpdate_userName = ?, recordUpdate_timeMillis = ? where cemeteryId = ? and recordDelete_timeMillis is null`) - .run(updateForm.cemeteryName, updateForm.cemeteryDescription, updateForm.cemeterySvg, updateForm.cemeteryLatitude === '' ? undefined : updateForm.cemeteryLatitude, updateForm.cemeteryLongitude === '' ? undefined : updateForm.cemeteryLongitude, updateForm.cemeteryAddress1, updateForm.cemeteryAddress2, updateForm.cemeteryCity, updateForm.cemeteryProvince, updateForm.cemeteryPostalCode, updateForm.cemeteryPhoneNumber, user.userName, Date.now(), updateForm.cemeteryId); + .run(updateForm.cemeteryName, updateForm.cemeteryKey, updateForm.cemeteryDescription, updateForm.cemeterySvg, updateForm.cemeteryLatitude === '' + ? undefined + : updateForm.cemeteryLatitude, updateForm.cemeteryLongitude === '' + ? undefined + : updateForm.cemeteryLongitude, updateForm.cemeteryAddress1, updateForm.cemeteryAddress2, updateForm.cemeteryCity, updateForm.cemeteryProvince, updateForm.cemeteryPostalCode, updateForm.cemeteryPhoneNumber, user.userName, Date.now(), updateForm.cemeteryId); database.release(); return result.changes > 0; } diff --git a/database/updateCemetery.ts b/database/updateCemetery.ts index a1ca213a..424b76d9 100644 --- a/database/updateCemetery.ts +++ b/database/updateCemetery.ts @@ -3,6 +3,7 @@ import { acquireConnection } from './pool.js' export interface UpdateCemeteryForm { cemeteryId: string cemeteryName: string + cemeteryKey: string cemeteryDescription: string cemeterySvg: string cemeteryLatitude: string @@ -23,18 +24,19 @@ export default async function updateCemetery( const result = database .prepare( - `update Maps + `update Cemeteries set cemeteryName = ?, - mapDescription = ?, - mapSVG = ?, - mapLatitude = ?, - mapLongitude = ?, - mapAddress1 = ?, - mapAddress2 = ?, - mapCity = ?, - mapProvince = ?, - mapPostalCode = ?, - mapPhoneNumber = ?, + cemeteryKey = ?, + cemeteryDescription = ?, + cemeterySvg = ?, + cemeteryLatitude = ?, + cemeteryLongitude = ?, + cemeteryAddress1 = ?, + cemeteryAddress2 = ?, + cemeteryCity = ?, + cemeteryProvince = ?, + cemeteryPostalCode = ?, + cemeteryPhoneNumber = ?, recordUpdate_userName = ?, recordUpdate_timeMillis = ? where cemeteryId = ? @@ -42,10 +44,15 @@ export default async function updateCemetery( ) .run( updateForm.cemeteryName, + updateForm.cemeteryKey, updateForm.cemeteryDescription, updateForm.cemeterySvg, - updateForm.cemeteryLatitude === '' ? undefined : updateForm.cemeteryLatitude, - updateForm.cemeteryLongitude === '' ? undefined : updateForm.cemeteryLongitude, + updateForm.cemeteryLatitude === '' + ? undefined + : updateForm.cemeteryLatitude, + updateForm.cemeteryLongitude === '' + ? undefined + : updateForm.cemeteryLongitude, updateForm.cemeteryAddress1, updateForm.cemeteryAddress2, updateForm.cemeteryCity, diff --git a/helpers/burialSites.helpers.js b/helpers/burialSites.helpers.js index e0cd50a8..c770ce01 100644 --- a/helpers/burialSites.helpers.js +++ b/helpers/burialSites.helpers.js @@ -5,7 +5,7 @@ import { minutesToSeconds } from '@cityssm/to-millis'; import Debug from 'debug'; import NodeCache from 'node-cache'; import getNextBurialSiteIdFromDatabase from '../database/getNextBurialSiteId.js'; -import getPreviousLotIdFromDatabase from '../database/getPreviousLotId.js'; +import getPreviousBurialSiteIdFromDatabase from '../database/getPreviousBurialSiteId.js'; import { DEBUG_NAMESPACE } from '../debug.config.js'; import { getConfigProperty } from './config.helpers.js'; const debug = Debug(`${DEBUG_NAMESPACE}:burialSites.helpers:${process.pid}`); @@ -46,7 +46,7 @@ export async function getNextBurialSiteId(burialSiteId) { export async function getPreviousBurialSiteId(burialSiteId) { let previousBurialSiteId = previousBurialSiteIdCache.get(burialSiteId); if (previousBurialSiteId === undefined) { - previousBurialSiteId = await getPreviousLotIdFromDatabase(burialSiteId); + previousBurialSiteId = await getPreviousBurialSiteIdFromDatabase(burialSiteId); if (previousBurialSiteId !== undefined) { cacheBurialSiteIds(previousBurialSiteId, burialSiteId); } diff --git a/helpers/burialSites.helpers.ts b/helpers/burialSites.helpers.ts index 7bedf1a5..9ad438fc 100644 --- a/helpers/burialSites.helpers.ts +++ b/helpers/burialSites.helpers.ts @@ -8,7 +8,7 @@ import Debug from 'debug' import NodeCache from 'node-cache' import getNextBurialSiteIdFromDatabase from '../database/getNextBurialSiteId.js' -import getPreviousLotIdFromDatabase from '../database/getPreviousLotId.js' +import getPreviousBurialSiteIdFromDatabase from '../database/getPreviousBurialSiteId.js' import { DEBUG_NAMESPACE } from '../debug.config.js' import type { CacheBurialSiteIdsWorkerMessage, @@ -79,7 +79,7 @@ export async function getPreviousBurialSiteId( previousBurialSiteIdCache.get(burialSiteId) if (previousBurialSiteId === undefined) { - previousBurialSiteId = await getPreviousLotIdFromDatabase(burialSiteId) + previousBurialSiteId = await getPreviousBurialSiteIdFromDatabase(burialSiteId) if (previousBurialSiteId !== undefined) { cacheBurialSiteIds(previousBurialSiteId, burialSiteId) diff --git a/helpers/functions.authentication.js b/helpers/functions.authentication.js index f3d6ae14..62bde639 100644 --- a/helpers/functions.authentication.js +++ b/helpers/functions.authentication.js @@ -29,15 +29,15 @@ export async function authenticate(userName, password) { const safeRedirects = new Set([ '/admin/cleanup', '/admin/fees', - '/admin/lottypes', - '/admin/occupancytypes', + '/admin/burialsitetypes', + '/admin/contracttypes', '/admin/tables', - '/lotoccupancies', + '/contracts', '/contracts/new', - '/lots', - '/lots/new', - '/maps', - '/maps/new', + '/burialSites', + '/burialSites/new', + '/cemeteries', + '/cemeteries/new', '/workorders', '/workorders/new', '/workorders/milestonecalendar', @@ -45,7 +45,7 @@ const safeRedirects = new Set([ '/reports' ]); /* eslint-enable @cspell/spellchecker */ -const recordUrl = /^\/(?:maps|lots|lotoccupancies|workorders)\/\d+(?:\/edit)?$/; +const recordUrl = /^\/(?:cemeteries|burialSites|contracts|workorders)\/\d+(?:\/edit)?$/; const printUrl = /^\/print\/(?:pdf|screen)\/[\d/=?A-Za-z-]+$/; export function getSafeRedirectURL(possibleRedirectURL = '') { const urlPrefix = getConfigProperty('reverseProxy.urlPrefix'); diff --git a/helpers/functions.authentication.ts b/helpers/functions.authentication.ts index 24ceb9c7..6e82974d 100644 --- a/helpers/functions.authentication.ts +++ b/helpers/functions.authentication.ts @@ -45,15 +45,15 @@ export async function authenticate( const safeRedirects = new Set([ '/admin/cleanup', '/admin/fees', - '/admin/lottypes', - '/admin/occupancytypes', + '/admin/burialsitetypes', + '/admin/contracttypes', '/admin/tables', - '/lotoccupancies', + '/contracts', '/contracts/new', - '/lots', - '/lots/new', - '/maps', - '/maps/new', + '/burialSites', + '/burialSites/new', + '/cemeteries', + '/cemeteries/new', '/workorders', '/workorders/new', '/workorders/milestonecalendar', @@ -63,7 +63,7 @@ const safeRedirects = new Set([ /* eslint-enable @cspell/spellchecker */ -const recordUrl = /^\/(?:maps|lots|lotoccupancies|workorders)\/\d+(?:\/edit)?$/ +const recordUrl = /^\/(?:cemeteries|burialSites|contracts|workorders)\/\d+(?:\/edit)?$/ const printUrl = /^\/print\/(?:pdf|screen)\/[\d/=?A-Za-z-]+$/ diff --git a/helpers/functions.cache.d.ts b/helpers/functions.cache.d.ts index e03d2586..46667cb3 100644 --- a/helpers/functions.cache.d.ts +++ b/helpers/functions.cache.d.ts @@ -17,6 +17,6 @@ export declare function getWorkOrderMilestoneTypeById(workOrderMilestoneTypeId: export declare function getWorkOrderMilestoneTypeByWorkOrderMilestoneType(workOrderMilestoneTypeString: string): Promise; export declare function preloadCaches(): Promise; export declare function clearCaches(): void; -type CacheTableNames = 'BurialSiteStatuses' | 'BurialSiteTypes' | 'BurialSiteTypeFields' | 'ContractTypes' | 'ContractTypeFields' | 'ContractTypePrints' | 'WorkOrderMilestoneTypes' | 'WorkOrderTypes'; +type CacheTableNames = 'BurialSiteStatuses' | 'BurialSiteTypes' | 'BurialSiteTypeFields' | 'ContractTypes' | 'ContractTypeFields' | 'ContractTypePrints' | 'WorkOrderMilestoneTypes' | 'WorkOrderTypes' | 'FeeCategories'; export declare function clearCacheByTableName(tableName: CacheTableNames, relayMessage?: boolean): void; export {}; diff --git a/helpers/functions.cache.js b/helpers/functions.cache.js index 4683897d..bab645eb 100644 --- a/helpers/functions.cache.js +++ b/helpers/functions.cache.js @@ -177,7 +177,6 @@ export function clearCacheByTableName(tableName, relayMessage = true) { clearWorkOrderTypesCache(); break; } - // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check default: { return; } diff --git a/helpers/functions.cache.ts b/helpers/functions.cache.ts index 9ff0c8da..08900004 100644 --- a/helpers/functions.cache.ts +++ b/helpers/functions.cache.ts @@ -280,6 +280,7 @@ type CacheTableNames = | 'ContractTypePrints' | 'WorkOrderMilestoneTypes' | 'WorkOrderTypes' + | 'FeeCategories' export function clearCacheByTableName( tableName: CacheTableNames, @@ -314,7 +315,6 @@ export function clearCacheByTableName( break } - // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check default: { return } diff --git a/helpers/functions.print.js b/helpers/functions.print.js index 0108044e..d7300f07 100644 --- a/helpers/functions.print.js +++ b/helpers/functions.print.js @@ -1,3 +1,5 @@ +// eslint-disable-next-line @eslint-community/eslint-comments/disable-enable-pair +/* eslint-disable security/detect-object-injection */ import * as dateTimeFunctions from '@cityssm/utils-datetime'; import getBurialSite from '../database/getBurialSite.js'; import getBurialSiteContract from '../database/getBurialSiteContract.js'; diff --git a/helpers/functions.print.ts b/helpers/functions.print.ts index bbce6ea2..d2baf2d7 100644 --- a/helpers/functions.print.ts +++ b/helpers/functions.print.ts @@ -1,3 +1,6 @@ +// eslint-disable-next-line @eslint-community/eslint-comments/disable-enable-pair +/* eslint-disable security/detect-object-injection */ + import * as dateTimeFunctions from '@cityssm/utils-datetime' import getBurialSite from '../database/getBurialSite.js' diff --git a/public/images/cemetery-logo.svg b/public/images/cemetery-logo.svg deleted file mode 100644 index a398f291..00000000 --- a/public/images/cemetery-logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/images/sunrise-cms.png b/public/images/sunrise-cms.png new file mode 100644 index 0000000000000000000000000000000000000000..c2325259b3d92641f61cadb248b75cc89712873e GIT binary patch literal 73729 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�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

vR1=wTWN<&g?YmffxLs)hBSTGBd3hN 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 HY1sb&JA(5l literal 0 HcmV?d00001 diff --git a/public/javascripts/burialSite.edit.d.ts b/public/javascripts/burialSite.edit.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/burialSite.edit.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/burialSite.edit.js b/public/javascripts/burialSite.edit.js new file mode 100644 index 00000000..c82a997a --- /dev/null +++ b/public/javascripts/burialSite.edit.js @@ -0,0 +1,367 @@ +"use strict"; +// eslint-disable-next-line @eslint-community/eslint-comments/disable-enable-pair +/* eslint-disable unicorn/prefer-module */ +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const los = exports.los; + const lotId = document.querySelector('#lot--lotId') + .value; + const isCreate = lotId === ''; + // Main form + let refreshAfterSave = isCreate; + function setUnsavedChanges() { + los.setUnsavedChanges(); + document + .querySelector("button[type='submit'][form='form--lot']") + ?.classList.remove('is-light'); + } + function clearUnsavedChanges() { + los.clearUnsavedChanges(); + document + .querySelector("button[type='submit'][form='form--lot']") + ?.classList.add('is-light'); + } + const formElement = document.querySelector('#form--lot'); + function updateBurialSite(formEvent) { + formEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/lots/${isCreate ? 'doCreateBurialSite' : 'doUpdateBurialSite'}`, formElement, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + clearUnsavedChanges(); + if (isCreate || refreshAfterSave) { + window.location.href = los.getBurialSiteURL(responseJSON.lotId, true, true); + } + else { + bulmaJS.alert({ + message: `${los.escapedAliases.Lot} Updated Successfully`, + contextualColorName: 'success' + }); + } + } + else { + bulmaJS.alert({ + title: `Error Updating ${los.escapedAliases.Lot}`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + formElement.addEventListener('submit', updateBurialSite); + const formInputElements = formElement.querySelectorAll('input, select'); + for (const formInputElement of formInputElements) { + formInputElement.addEventListener('change', setUnsavedChanges); + } + los.initializeUnlockFieldButtons(formElement); + document + .querySelector('#button--deleteLot') + ?.addEventListener('click', (clickEvent) => { + clickEvent.preventDefault(); + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/lots/doDeleteBurialSite`, { + lotId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + clearUnsavedChanges(); + window.location.href = los.getBurialSiteURL(); + } + else { + bulmaJS.alert({ + title: `Error Deleting ${los.escapedAliases.Lot}`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + bulmaJS.confirm({ + title: `Delete ${los.escapedAliases.Lot}`, + message: `Are you sure you want to delete this ${los.escapedAliases.lot}?`, + contextualColorName: 'warning', + okButton: { + text: `Yes, Delete ${los.escapedAliases.Lot}`, + callbackFunction: doDelete + } + }); + }); + // Lot Type + const burialSiteTypeIdElement = document.querySelector('#lot--burialSiteTypeId'); + if (isCreate) { + const lotFieldsContainerElement = document.querySelector('#container--lotFields'); + burialSiteTypeIdElement.addEventListener('change', () => { + if (burialSiteTypeIdElement.value === '') { + // eslint-disable-next-line no-unsanitized/property + lotFieldsContainerElement.innerHTML = `

`; + return; + } + cityssm.postJSON(`${los.urlPrefix}/lots/doGetBurialSiteTypeFields`, { + burialSiteTypeId: burialSiteTypeIdElement.value + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.BurialSiteTypeFields.length === 0) { + // eslint-disable-next-line no-unsanitized/property + lotFieldsContainerElement.innerHTML = `
+

+ There are no additional fields for this ${los.escapedAliases.lot} type. +

+
`; + return; + } + lotFieldsContainerElement.innerHTML = ''; + let lotTypeFieldIds = ''; + for (const lotTypeField of responseJSON.BurialSiteTypeFields) { + lotTypeFieldIds += `,${lotTypeField.lotTypeFieldId.toString()}`; + const fieldName = `lotFieldValue_${lotTypeField.lotTypeFieldId.toString()}`; + const fieldId = `lot--${fieldName}`; + const fieldElement = document.createElement('div'); + fieldElement.className = 'field'; + // eslint-disable-next-line no-unsanitized/property + fieldElement.innerHTML = ` +
`; + fieldElement.querySelector('label').textContent = lotTypeField.lotTypeField; + if (lotTypeField.lotTypeFieldValues === '') { + const inputElement = document.createElement('input'); + inputElement.className = 'input'; + inputElement.id = fieldId; + inputElement.name = fieldName; + inputElement.type = 'text'; + inputElement.required = lotTypeField.isRequired; + inputElement.minLength = lotTypeField.minimumLength; + inputElement.maxLength = lotTypeField.maximumLength; + if ((lotTypeField.pattern ?? '') !== '') { + inputElement.pattern = lotTypeField.pattern ?? ''; + } + fieldElement.querySelector('.control')?.append(inputElement); + } + else { + // eslint-disable-next-line no-unsanitized/property + ; + fieldElement.querySelector('.control').innerHTML = `
+ +
`; + const selectElement = fieldElement.querySelector('select'); + selectElement.required = lotTypeField.isRequired; + const optionValues = lotTypeField.lotTypeFieldValues.split('\n'); + for (const optionValue of optionValues) { + const optionElement = document.createElement('option'); + optionElement.value = optionValue; + optionElement.textContent = optionValue; + selectElement.append(optionElement); + } + } + lotFieldsContainerElement.append(fieldElement); + } + lotFieldsContainerElement.insertAdjacentHTML('beforeend', ``); + }); + }); + } + else { + const originalburialSiteTypeId = burialSiteTypeIdElement.value; + burialSiteTypeIdElement.addEventListener('change', () => { + if (burialSiteTypeIdElement.value !== originalburialSiteTypeId) { + bulmaJS.confirm({ + title: 'Confirm Change', + message: `Are you sure you want to change the ${los.escapedAliases.lot} type?\n + This change affects the additional fields associated with this record.`, + contextualColorName: 'warning', + okButton: { + text: 'Yes, Keep the Change', + callbackFunction() { + refreshAfterSave = true; + } + }, + cancelButton: { + text: 'Revert the Change', + callbackFunction() { + burialSiteTypeIdElement.value = originalburialSiteTypeId; + } + } + }); + } + }); + } + // Comments + let lotComments = exports.lotComments; + delete exports.lotComments; + function openEditLotComment(clickEvent) { + const lotCommentId = Number.parseInt(clickEvent.currentTarget.closest('tr')?.dataset + .lotCommentId ?? '', 10); + const lotComment = lotComments.find((currentLotComment) => { + return currentLotComment.lotCommentId === lotCommentId; + }); + let editFormElement; + let editCloseModalFunction; + function editComment(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/lots/doUpdateBurialSiteComment`, editFormElement, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + lotComments = responseJSON.lotComments; + editCloseModalFunction(); + renderLotComments(); + } + else { + bulmaJS.alert({ + title: 'Error Updating Comment', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + cityssm.openHtmlModal('lot-editComment', { + onshow(modalElement) { + los.populateAliases(modalElement); + modalElement.querySelector('#lotCommentEdit--lotId').value = lotId; + modalElement.querySelector('#lotCommentEdit--lotCommentId').value = lotCommentId.toString(); + modalElement.querySelector('#lotCommentEdit--lotComment').value = lotComment.lotComment ?? ''; + const lotCommentDateStringElement = modalElement.querySelector('#lotCommentEdit--lotCommentDateString'); + lotCommentDateStringElement.value = + lotComment.lotCommentDateString ?? ''; + const currentDateString = cityssm.dateToString(new Date()); + lotCommentDateStringElement.max = + lotComment.lotCommentDateString <= currentDateString + ? currentDateString + : lotComment.lotCommentDateString ?? ''; + modalElement.querySelector('#lotCommentEdit--lotCommentTimeString').value = lotComment.lotCommentTimeString ?? ''; + }, + onshown(modalElement, closeModalFunction) { + bulmaJS.toggleHtmlClipped(); + los.initializeDatePickers(modalElement); + modalElement.querySelector('#lotCommentEdit--lotComment').focus(); + editFormElement = modalElement.querySelector('form'); + editFormElement.addEventListener('submit', editComment); + editCloseModalFunction = closeModalFunction; + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + } + }); + } + function deleteLotComment(clickEvent) { + const lotCommentId = Number.parseInt(clickEvent.currentTarget.closest('tr')?.dataset + .lotCommentId ?? '', 10); + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/lots/doDeleteBurialSiteComment`, { + lotId, + lotCommentId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + lotComments = responseJSON.lotComments; + renderLotComments(); + } + 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 renderLotComments() { + const containerElement = document.querySelector('#container--lotComments'); + if (lotComments.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 lotComment of lotComments) { + const tableRowElement = document.createElement('tr'); + tableRowElement.dataset.lotCommentId = lotComment.lotCommentId?.toString(); + // eslint-disable-next-line no-unsanitized/property + tableRowElement.innerHTML = ` + ${cityssm.escapeHTML(lotComment.recordCreate_userName ?? '')} + + ${lotComment.lotCommentDateString} + ${lotComment.lotCommentTime === 0 + ? '' + : ` ${lotComment.lotCommentTimePeriodString}`} + + ${cityssm.escapeHTML(lotComment.lotComment ?? '')} + +
+ + +
+ `; + tableRowElement + .querySelector('.button--edit') + ?.addEventListener('click', openEditLotComment); + tableRowElement + .querySelector('.button--delete') + ?.addEventListener('click', deleteLotComment); + tableElement.querySelector('tbody')?.append(tableRowElement); + } + containerElement.innerHTML = ''; + containerElement.append(tableElement); + } + function openAddCommentModal() { + let addCommentCloseModalFunction; + function doAddComment(formEvent) { + formEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/lots/doAddBurialSiteComment`, formEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + lotComments = responseJSON.lotComments; + renderLotComments(); + addCommentCloseModalFunction(); + } + }); + } + cityssm.openHtmlModal('lot-addComment', { + onshow(modalElement) { + los.populateAliases(modalElement); + modalElement.querySelector('#lotCommentAdd--lotId').value = lotId; + modalElement + .querySelector('form') + ?.addEventListener('submit', doAddComment); + }, + onshown(modalElement, closeModalFunction) { + bulmaJS.toggleHtmlClipped(); + addCommentCloseModalFunction = closeModalFunction; + modalElement.querySelector('#lotCommentAdd--lotComment').focus(); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + document.querySelector('#lotComments--add').focus(); + } + }); + } + if (!isCreate) { + document + .querySelector('#lotComments--add') + ?.addEventListener('click', openAddCommentModal); + renderLotComments(); + } +})(); diff --git a/public/javascripts/burialSite.edit.ts b/public/javascripts/burialSite.edit.ts index c8561d86..f2ca2763 100644 --- a/public/javascripts/burialSite.edit.ts +++ b/public/javascripts/burialSite.edit.ts @@ -55,7 +55,7 @@ declare const exports: Record clearUnsavedChanges() if (isCreate || refreshAfterSave) { - window.location.href = los.getLotURL(responseJSON.lotId, true, true) + window.location.href = los.getBurialSiteURL(responseJSON.lotId, true, true) } else { bulmaJS.alert({ message: `${los.escapedAliases.Lot} Updated Successfully`, @@ -102,7 +102,7 @@ declare const exports: Record if (responseJSON.success) { clearUnsavedChanges() - window.location.href = los.getLotURL() + window.location.href = los.getBurialSiteURL() } else { bulmaJS.alert({ title: `Error Deleting ${los.escapedAliases.Lot}`, diff --git a/public/javascripts/burialSite.search.d.ts b/public/javascripts/burialSite.search.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/burialSite.search.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/burialSite.search.js b/public/javascripts/burialSite.search.js index ecbc4be6..c1390029 100644 --- a/public/javascripts/burialSite.search.js +++ b/public/javascripts/burialSite.search.js @@ -20,13 +20,13 @@ Object.defineProperty(exports, "__esModule", { value: true }); // eslint-disable-next-line no-unsanitized/method resultsTbodyElement.insertAdjacentHTML('beforeend', ` -
+ ${cityssm.escapeHTML(lot.lotName ?? '')} - - ${lot.mapName - ? cityssm.escapeHTML(lot.mapName) + + ${lot.cemeteryName + ? cityssm.escapeHTML(lot.cemeteryName) : '(No Name)'} @@ -35,7 +35,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); ${lot.burialSiteStatusId ? cityssm.escapeHTML(lot.lotStatus ?? '') : '(No Status)'}
- ${(lot.lotOccupancyCount ?? 0) > 0 + ${(lot.burialSiteContractCount ?? 0) > 0 ? 'Currently Occupied' : ''} @@ -62,22 +62,22 @@ Object.defineProperty(exports, "__esModule", { value: true }); .querySelector("button[data-page='next']") ?.addEventListener('click', nextAndGetLots); } - function getLots() { + function getBurialSites() { // eslint-disable-next-line no-unsanitized/property searchResultsContainerElement.innerHTML = los.getLoadingParagraphHTML(`Loading ${los.escapedAliases.Lots}...`); - cityssm.postJSON(`${los.urlPrefix}/lots/doSearchLots`, searchFilterFormElement, renderLots); + cityssm.postJSON(`${los.urlPrefix}/lots/doSearchBurialSites`, searchFilterFormElement, renderLots); } function resetOffsetAndGetLots() { offsetElement.value = '0'; - getLots(); + getBurialSites(); } function previousAndGetLots() { offsetElement.value = Math.max(Number.parseInt(offsetElement.value, 10) - limit, 0).toString(); - getLots(); + getBurialSites(); } function nextAndGetLots() { offsetElement.value = (Number.parseInt(offsetElement.value, 10) + limit).toString(); - getLots(); + getBurialSites(); } const filterElements = searchFilterFormElement.querySelectorAll('input, select'); for (const filterElement of filterElements) { @@ -86,5 +86,5 @@ Object.defineProperty(exports, "__esModule", { value: true }); searchFilterFormElement.addEventListener('submit', (formEvent) => { formEvent.preventDefault(); }); - getLots(); + getBurialSites(); })(); diff --git a/public/javascripts/burialSite.search.ts b/public/javascripts/burialSite.search.ts index 323db3b0..c13bee25 100644 --- a/public/javascripts/burialSite.search.ts +++ b/public/javascripts/burialSite.search.ts @@ -49,11 +49,11 @@ declare const exports: Record 'beforeend', ` - + ${cityssm.escapeHTML(lot.lotName ?? '')} - + ${ lot.cemeteryName ? cityssm.escapeHTML(lot.cemeteryName) diff --git a/public/javascripts/burialSite.view.d.ts b/public/javascripts/burialSite.view.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/burialSite.view.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/burialSite.view.js b/public/javascripts/burialSite.view.js new file mode 100644 index 00000000..b5a2727d --- /dev/null +++ b/public/javascripts/burialSite.view.js @@ -0,0 +1,9 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const mapContainerElement = document.querySelector('#lot--map'); + if (mapContainerElement !== null) { + ; + exports.los.highlightMap(mapContainerElement, mapContainerElement.dataset.mapKey ?? '', 'success'); + } +})(); diff --git a/public/javascripts/burialSiteContract.edit.d.ts b/public/javascripts/burialSiteContract.edit.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/burialSiteContract.edit.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/burialSiteContract.edit.js b/public/javascripts/burialSiteContract.edit.js new file mode 100644 index 00000000..c321572c --- /dev/null +++ b/public/javascripts/burialSiteContract.edit.js @@ -0,0 +1,1735 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const los = exports.los; + const burialSiteContractId = document.querySelector('#burialSiteContract--burialSiteContractId').value; + const isCreate = burialSiteContractId === ''; + /* + * Main form + */ + let refreshAfterSave = isCreate; + function setUnsavedChanges() { + los.setUnsavedChanges(); + document + .querySelector("button[type='submit'][form='form--burialSiteContract']") + ?.classList.remove('is-light'); + } + function clearUnsavedChanges() { + los.clearUnsavedChanges(); + document + .querySelector("button[type='submit'][form='form--burialSiteContract']") + ?.classList.add('is-light'); + } + const formElement = document.querySelector('#form--burialSiteContract'); + formElement.addEventListener('submit', (formEvent) => { + formEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/contracts/${isCreate ? 'doCreateBurialSiteOccupancy' : 'doUpdateBurialSiteContract'}`, formElement, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + clearUnsavedChanges(); + if (isCreate || refreshAfterSave) { + globalThis.location.href = los.getBurialSiteContractURL(responseJSON.burialSiteContractId, true, true); + } + else { + bulmaJS.alert({ + message: `${los.escapedAliases.Occupancy} Updated Successfully`, + contextualColorName: 'success' + }); + } + } + else { + bulmaJS.alert({ + title: `Error Saving ${los.escapedAliases.Occupancy}`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + }); + const formInputElements = formElement.querySelectorAll('input, select'); + for (const formInputElement of formInputElements) { + formInputElement.addEventListener('change', setUnsavedChanges); + } + function doCopy() { + cityssm.postJSON(`${los.urlPrefix}/contracts/doCopyBurialSiteContract`, { + burialSiteContractId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + clearUnsavedChanges(); + globalThis.location.href = los.getBurialSiteContractURL(responseJSON.burialSiteContractId, true); + } + else { + bulmaJS.alert({ + title: 'Error Copying Record', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + document + .querySelector('#button--copyBurialSiteContract') + ?.addEventListener('click', (clickEvent) => { + clickEvent.preventDefault(); + if (los.hasUnsavedChanges()) { + bulmaJS.alert({ + title: 'Unsaved Changes', + message: 'Please save all unsaved changes before continuing.', + contextualColorName: 'warning' + }); + } + else { + bulmaJS.confirm({ + title: `Copy ${los.escapedAliases.Occupancy} Record as New`, + message: 'Are you sure you want to copy this record to a new record?', + contextualColorName: 'info', + okButton: { + text: 'Yes, Copy', + callbackFunction: doCopy + } + }); + } + }); + document + .querySelector('#button--deleteLotOccupancy') + ?.addEventListener('click', (clickEvent) => { + clickEvent.preventDefault(); + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/contracts/doDeleteBurialSiteContract`, { + burialSiteContractId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + clearUnsavedChanges(); + globalThis.location.href = los.getBurialSiteContractURL(); + } + else { + bulmaJS.alert({ + title: 'Error Deleting Record', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + bulmaJS.confirm({ + title: `Delete ${los.escapedAliases.Occupancy} Record`, + message: 'Are you sure you want to delete this record?', + contextualColorName: 'warning', + okButton: { + text: 'Yes, Delete', + callbackFunction: doDelete + } + }); + }); + document + .querySelector('#button--createWorkOrder') + ?.addEventListener('click', (clickEvent) => { + clickEvent.preventDefault(); + let createCloseModalFunction; + function doCreate(formEvent) { + formEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/workOrders/doCreateWorkOrder`, formEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + createCloseModalFunction(); + bulmaJS.confirm({ + title: 'Work Order Created Successfully', + message: 'Would you like to open the work order now?', + contextualColorName: 'success', + okButton: { + text: 'Yes, Open the Work Order', + callbackFunction: () => { + globalThis.location.href = los.getWorkOrderURL(responseJSON.workOrderId, true); + } + } + }); + } + else { + bulmaJS.alert({ + title: 'Error Creating Work Order', + message: responseJSON.errorMessage, + contextualColorName: 'danger' + }); + } + }); + } + cityssm.openHtmlModal('burialSiteContract-createWorkOrder', { + onshow(modalElement) { + ; + modalElement.querySelector('#workOrderCreate--burialSiteContractId').value = burialSiteContractId; + modalElement.querySelector('#workOrderCreate--workOrderOpenDateString').value = cityssm.dateToString(new Date()); + const workOrderTypeSelectElement = modalElement.querySelector('#workOrderCreate--workOrderTypeId'); + const workOrderTypes = exports + .workOrderTypes; + if (workOrderTypes.length === 1) { + workOrderTypeSelectElement.innerHTML = ''; + } + for (const workOrderType of workOrderTypes) { + const optionElement = document.createElement('option'); + optionElement.value = workOrderType.workOrderTypeId.toString(); + optionElement.textContent = workOrderType.workOrderType ?? ''; + workOrderTypeSelectElement.append(optionElement); + } + }, + onshown(modalElement, closeModalFunction) { + createCloseModalFunction = closeModalFunction; + bulmaJS.toggleHtmlClipped(); + modalElement.querySelector('#workOrderCreate--workOrderTypeId').focus(); + modalElement + .querySelector('form') + ?.addEventListener('submit', doCreate); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + document.querySelector('#button--createWorkOrder').focus(); + } + }); + }); + // Occupancy Type + const contractTypeIdElement = document.querySelector('#burialSiteContract--contractTypeId'); + if (isCreate) { + const burialSiteContractFieldsContainerElement = document.querySelector('#container--burialSiteContractFields'); + contractTypeIdElement.addEventListener('change', () => { + if (contractTypeIdElement.value === '') { + // eslint-disable-next-line no-unsanitized/property + burialSiteContractFieldsContainerElement.innerHTML = `
+

Select the ${los.escapedAliases.occupancy} type to load the available fields.

+
`; + return; + } + cityssm.postJSON(`${los.urlPrefix}/contracts/doGetContractTypeFields`, { + contractTypeId: contractTypeIdElement.value + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.ContractTypeFields.length === 0) { + // eslint-disable-next-line no-unsanitized/property + burialSiteContractFieldsContainerElement.innerHTML = `
+

There are no additional fields for this ${los.escapedAliases.occupancy} type.

+
`; + return; + } + burialSiteContractFieldsContainerElement.innerHTML = ''; + let contractTypeFieldIds = ''; + for (const occupancyTypeField of responseJSON.ContractTypeFields) { + contractTypeFieldIds += `,${occupancyTypeField.contractTypeFieldId.toString()}`; + const fieldName = `burialSiteContractFieldValue_${occupancyTypeField.contractTypeFieldId.toString()}`; + const fieldId = `burialSiteContract--${fieldName}`; + const fieldElement = document.createElement('div'); + fieldElement.className = 'field'; + fieldElement.innerHTML = `
`; + fieldElement.querySelector('label').textContent = occupancyTypeField.occupancyTypeField; + if (occupancyTypeField.fieldType === 'select' || + (occupancyTypeField.occupancyTypeFieldValues ?? '') !== '') { + ; + fieldElement.querySelector('.control').innerHTML = `
+ +
`; + const selectElement = fieldElement.querySelector('select'); + selectElement.required = occupancyTypeField.isRequired; + const optionValues = occupancyTypeField.occupancyTypeFieldValues.split('\n'); + for (const optionValue of optionValues) { + const optionElement = document.createElement('option'); + optionElement.value = optionValue; + optionElement.textContent = optionValue; + selectElement.append(optionElement); + } + } + else { + const inputElement = document.createElement('input'); + inputElement.className = 'input'; + inputElement.id = fieldId; + inputElement.name = fieldName; + inputElement.type = occupancyTypeField.fieldType; + inputElement.required = occupancyTypeField.isRequired; + inputElement.minLength = + occupancyTypeField.minimumLength; + inputElement.maxLength = + occupancyTypeField.maximumLength; + if ((occupancyTypeField.pattern ?? '') !== '') { + inputElement.pattern = occupancyTypeField.pattern; + } + ; + fieldElement.querySelector('.control').append(inputElement); + } + console.log(fieldElement); + burialSiteContractFieldsContainerElement.append(fieldElement); + } + burialSiteContractFieldsContainerElement.insertAdjacentHTML('beforeend', + // eslint-disable-next-line no-secrets/no-secrets + ``); + }); + }); + } + else { + const originalcontractTypeId = contractTypeIdElement.value; + contractTypeIdElement.addEventListener('change', () => { + if (contractTypeIdElement.value !== originalcontractTypeId) { + bulmaJS.confirm({ + title: 'Confirm Change', + message: `Are you sure you want to change the ${los.escapedAliases.occupancy} type?\n + This change affects the additional fields associated with this record, and may also affect the available fees.`, + contextualColorName: 'warning', + okButton: { + text: 'Yes, Keep the Change', + callbackFunction: () => { + refreshAfterSave = true; + } + }, + cancelButton: { + text: 'Revert the Change', + callbackFunction: () => { + contractTypeIdElement.value = originalcontractTypeId; + } + } + }); + } + }); + } + // Lot Selector + const lotNameElement = document.querySelector('#burialSiteContract--lotName'); + lotNameElement.addEventListener('click', (clickEvent) => { + const currentLotName = clickEvent.currentTarget.value; + let lotSelectCloseModalFunction; + let lotSelectModalElement; + let lotSelectFormElement; + let lotSelectResultsElement; + function renderSelectedLotAndClose(lotId, lotName) { + ; + document.querySelector('#burialSiteContract--lotId').value = lotId.toString(); + document.querySelector('#burialSiteContract--lotName').value = lotName; + setUnsavedChanges(); + lotSelectCloseModalFunction(); + } + function selectExistingLot(clickEvent) { + clickEvent.preventDefault(); + const selectedLotElement = clickEvent.currentTarget; + renderSelectedLotAndClose(selectedLotElement.dataset.lotId ?? '', selectedLotElement.dataset.lotName ?? ''); + } + function searchLots() { + // eslint-disable-next-line no-unsanitized/property + lotSelectResultsElement.innerHTML = + los.getLoadingParagraphHTML('Searching...'); + cityssm.postJSON(`${los.urlPrefix}/lots/doSearchBurialSites`, lotSelectFormElement, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.count === 0) { + lotSelectResultsElement.innerHTML = `
+

No results.

+
`; + return; + } + const panelElement = document.createElement('div'); + panelElement.className = 'panel'; + for (const lot of responseJSON.lots) { + const panelBlockElement = document.createElement('a'); + panelBlockElement.className = 'panel-block is-block'; + panelBlockElement.href = '#'; + panelBlockElement.dataset.lotId = lot.lotId.toString(); + panelBlockElement.dataset.lotName = lot.lotName; + // eslint-disable-next-line no-unsanitized/property + panelBlockElement.innerHTML = `
+
+ ${cityssm.escapeHTML(lot.lotName ?? '')}
+ ${cityssm.escapeHTML(lot.cemeteryName ?? '')} +
+
+ ${cityssm.escapeHTML(lot.lotStatus)}
+ + ${lot.burialSiteContractCount > 0 ? 'Currently Occupied' : ''} + +
+
`; + panelBlockElement.addEventListener('click', selectExistingLot); + panelElement.append(panelBlockElement); + } + lotSelectResultsElement.innerHTML = ''; + lotSelectResultsElement.append(panelElement); + }); + } + function createLotAndSelect(submitEvent) { + submitEvent.preventDefault(); + const lotName = lotSelectModalElement.querySelector('#lotCreate--lotName').value; + cityssm.postJSON(`${los.urlPrefix}/lots/doCreateBurialSite`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + renderSelectedLotAndClose(responseJSON.lotId ?? '', lotName); + } + else { + bulmaJS.alert({ + title: `Error Creating ${los.escapedAliases.Lot}`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + cityssm.openHtmlModal('burialSiteContract-selectLot', { + onshow(modalElement) { + los.populateAliases(modalElement); + }, + onshown(modalElement, closeModalFunction) { + bulmaJS.toggleHtmlClipped(); + lotSelectModalElement = modalElement; + lotSelectCloseModalFunction = closeModalFunction; + bulmaJS.init(modalElement); + // search Tab + const lotNameFilterElement = modalElement.querySelector('#lotSelect--lotName'); + if (document.querySelector('#burialSiteContract--lotId') + .value !== '') { + lotNameFilterElement.value = currentLotName; + } + lotNameFilterElement.focus(); + lotNameFilterElement.addEventListener('change', searchLots); + const occupancyStatusFilterElement = modalElement.querySelector('#lotSelect--occupancyStatus'); + occupancyStatusFilterElement.addEventListener('change', searchLots); + if (currentLotName !== '') { + occupancyStatusFilterElement.value = ''; + } + lotSelectFormElement = modalElement.querySelector('#form--lotSelect'); + lotSelectResultsElement = modalElement.querySelector('#resultsContainer--lotSelect'); + lotSelectFormElement.addEventListener('submit', (submitEvent) => { + submitEvent.preventDefault(); + }); + searchLots(); + // Create Tab + if (exports.lotNamePattern) { + const regex = exports.lotNamePattern; + modalElement.querySelector('#lotCreate--lotName').pattern = regex.source; + } + const lotTypeElement = modalElement.querySelector('#lotCreate--burialSiteTypeId'); + for (const lotType of exports.lotTypes) { + const optionElement = document.createElement('option'); + optionElement.value = lotType.burialSiteTypeId.toString(); + optionElement.textContent = lotType.lotType; + lotTypeElement.append(optionElement); + } + const lotStatusElement = modalElement.querySelector('#lotCreate--burialSiteStatusId'); + for (const lotStatus of exports.lotStatuses) { + const optionElement = document.createElement('option'); + optionElement.value = lotStatus.burialSiteStatusId.toString(); + optionElement.textContent = lotStatus.lotStatus; + lotStatusElement.append(optionElement); + } + const mapElement = modalElement.querySelector('#lotCreate--cemeteryId'); + for (const map of exports.maps) { + const optionElement = document.createElement('option'); + optionElement.value = map.cemeteryId.toString(); + optionElement.textContent = + (map.cemeteryName ?? '') === '' ? '(No Name)' : map.cemeteryName ?? ''; + mapElement.append(optionElement); + } + ; + modalElement.querySelector('#form--lotCreate').addEventListener('submit', createLotAndSelect); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + } + }); + }); + document + .querySelector('.is-lot-view-button') + ?.addEventListener('click', () => { + const lotId = document.querySelector('#burialSiteContract--lotId').value; + if (lotId === '') { + bulmaJS.alert({ + message: `No ${los.escapedAliases.lot} selected.`, + contextualColorName: 'info' + }); + } + else { + window.open(`${los.urlPrefix}/lots/${lotId}`); + } + }); + document + .querySelector('.is-clear-lot-button') + ?.addEventListener('click', () => { + if (lotNameElement.disabled) { + bulmaJS.alert({ + message: 'You need to unlock the field before clearing it.', + contextualColorName: 'info' + }); + } + else { + lotNameElement.value = `(No ${los.escapedAliases.Lot})`; + document.querySelector('#burialSiteContract--lotId').value = ''; + setUnsavedChanges(); + } + }); + // Start Date + los.initializeDatePickers(formElement); + document + .querySelector('#burialSiteContract--contractStartDateString') + ?.addEventListener('change', () => { + const endDatePicker = document.querySelector('#burialSiteContract--contractEndDateString').bulmaCalendar.datePicker; + endDatePicker.min = document.querySelector('#burialSiteContract--contractStartDateString').value; + endDatePicker.refresh(); + }); + los.initializeUnlockFieldButtons(formElement); + (() => { + let burialSiteContractOccupants = exports.burialSiteContractOccupants; + delete exports.burialSiteContractOccupants; + function openEditLotOccupancyOccupant(clickEvent) { + const lotOccupantIndex = Number.parseInt(clickEvent.currentTarget.closest('tr')?.dataset + .lotOccupantIndex ?? '', 10); + const burialSiteContractOccupant = burialSiteContractOccupants.find((currentLotOccupancyOccupant) => { + return (currentLotOccupancyOccupant.lotOccupantIndex === lotOccupantIndex); + }); + let editFormElement; + let editCloseModalFunction; + function editOccupant(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/contracts/doUpdateBurialSiteContractOccupant`, editFormElement, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + burialSiteContractOccupants = responseJSON.burialSiteContractOccupants; + editCloseModalFunction(); + renderLotOccupancyOccupants(); + } + else { + bulmaJS.alert({ + title: `Error Updating ${los.escapedAliases.Occupant}`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + cityssm.openHtmlModal('burialSiteContract-editOccupant', { + onshow(modalElement) { + los.populateAliases(modalElement); + modalElement.querySelector('#burialSiteContractOccupantEdit--burialSiteContractId').value = burialSiteContractId; + modalElement.querySelector('#burialSiteContractOccupantEdit--lotOccupantIndex').value = lotOccupantIndex.toString(); + const lotOccupantTypeSelectElement = modalElement.querySelector('#burialSiteContractOccupantEdit--lotOccupantTypeId'); + let lotOccupantTypeSelected = false; + for (const lotOccupantType of exports.lotOccupantTypes) { + const optionElement = document.createElement('option'); + optionElement.value = lotOccupantType.lotOccupantTypeId.toString(); + optionElement.textContent = lotOccupantType.lotOccupantType; + optionElement.dataset.occupantCommentTitle = + lotOccupantType.occupantCommentTitle; + optionElement.dataset.fontAwesomeIconClass = + lotOccupantType.fontAwesomeIconClass; + if (lotOccupantType.lotOccupantTypeId === + burialSiteContractOccupant.lotOccupantTypeId) { + optionElement.selected = true; + lotOccupantTypeSelected = true; + } + lotOccupantTypeSelectElement.append(optionElement); + } + if (!lotOccupantTypeSelected) { + const optionElement = document.createElement('option'); + optionElement.value = + burialSiteContractOccupant.lotOccupantTypeId?.toString() ?? ''; + optionElement.textContent = + burialSiteContractOccupant.lotOccupantType ?? ''; + optionElement.dataset.occupantCommentTitle = + burialSiteContractOccupant.occupantCommentTitle; + optionElement.dataset.fontAwesomeIconClass = + burialSiteContractOccupant.fontAwesomeIconClass; + optionElement.selected = true; + lotOccupantTypeSelectElement.append(optionElement); + } + ; + modalElement.querySelector('#burialSiteContractOccupantEdit--fontAwesomeIconClass').innerHTML = + ``; + modalElement.querySelector('#burialSiteContractOccupantEdit--occupantName').value = burialSiteContractOccupant.occupantName ?? ''; + modalElement.querySelector('#burialSiteContractOccupantEdit--occupantFamilyName').value = burialSiteContractOccupant.occupantFamilyName ?? ''; + modalElement.querySelector('#burialSiteContractOccupantEdit--occupantAddress1').value = burialSiteContractOccupant.occupantAddress1 ?? ''; + modalElement.querySelector('#burialSiteContractOccupantEdit--occupantAddress2').value = burialSiteContractOccupant.occupantAddress2 ?? ''; + modalElement.querySelector('#burialSiteContractOccupantEdit--occupantCity').value = burialSiteContractOccupant.occupantCity ?? ''; + modalElement.querySelector('#burialSiteContractOccupantEdit--occupantProvince').value = burialSiteContractOccupant.occupantProvince ?? ''; + modalElement.querySelector('#burialSiteContractOccupantEdit--occupantPostalCode').value = burialSiteContractOccupant.occupantPostalCode ?? ''; + modalElement.querySelector('#burialSiteContractOccupantEdit--occupantPhoneNumber').value = burialSiteContractOccupant.occupantPhoneNumber ?? ''; + modalElement.querySelector('#burialSiteContractOccupantEdit--occupantEmailAddress').value = burialSiteContractOccupant.occupantEmailAddress ?? ''; + modalElement.querySelector('#burialSiteContractOccupantEdit--occupantCommentTitle').textContent = + (burialSiteContractOccupant.occupantCommentTitle ?? '') === '' + ? 'Comment' + : burialSiteContractOccupant.occupantCommentTitle ?? ''; + modalElement.querySelector('#burialSiteContractOccupantEdit--occupantComment').value = burialSiteContractOccupant.occupantComment ?? ''; + }, + onshown(modalElement, closeModalFunction) { + bulmaJS.toggleHtmlClipped(); + const lotOccupantTypeIdElement = modalElement.querySelector('#burialSiteContractOccupantEdit--lotOccupantTypeId'); + lotOccupantTypeIdElement.focus(); + lotOccupantTypeIdElement.addEventListener('change', () => { + const fontAwesomeIconClass = lotOccupantTypeIdElement.selectedOptions[0].dataset + .fontAwesomeIconClass ?? 'user'; + modalElement.querySelector('#burialSiteContractOccupantEdit--fontAwesomeIconClass').innerHTML = + ``; + let occupantCommentTitle = lotOccupantTypeIdElement.selectedOptions[0].dataset + .occupantCommentTitle ?? ''; + if (occupantCommentTitle === '') { + occupantCommentTitle = 'Comment'; + } + ; + modalElement.querySelector('#burialSiteContractOccupantEdit--occupantCommentTitle').textContent = occupantCommentTitle; + }); + editFormElement = modalElement.querySelector('form'); + editFormElement.addEventListener('submit', editOccupant); + editCloseModalFunction = closeModalFunction; + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + } + }); + } + function deleteLotOccupancyOccupant(clickEvent) { + const lotOccupantIndex = clickEvent.currentTarget.closest('tr')?.dataset.lotOccupantIndex; + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/contracts/doDeleteBurialSiteContractOccupant`, { + burialSiteContractId, + lotOccupantIndex + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + burialSiteContractOccupants = responseJSON.burialSiteContractOccupants; + renderLotOccupancyOccupants(); + } + else { + bulmaJS.alert({ + title: `Error Removing ${los.escapedAliases.Occupant}`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + bulmaJS.confirm({ + title: `Remove ${los.escapedAliases.Occupant}?`, + message: `Are you sure you want to remove this ${los.escapedAliases.occupant}?`, + okButton: { + text: `Yes, Remove ${los.escapedAliases.Occupant}`, + callbackFunction: doDelete + }, + contextualColorName: 'warning' + }); + } + function renderLotOccupancyOccupants() { + const occupantsContainer = document.querySelector('#container--burialSiteContractOccupants'); + cityssm.clearElement(occupantsContainer); + if (burialSiteContractOccupants.length === 0) { + // eslint-disable-next-line no-unsanitized/property + occupantsContainer.innerHTML = `
+

There are no ${los.escapedAliases.occupants} associated with this record.

+
`; + return; + } + const tableElement = document.createElement('table'); + tableElement.className = 'table is-fullwidth is-striped is-hoverable'; + // eslint-disable-next-line no-unsanitized/property + tableElement.innerHTML = ` + ${los.escapedAliases.Occupant} + Address + Other Contact + Comment + Options + + `; + for (const burialSiteContractOccupant of burialSiteContractOccupants) { + const tableRowElement = document.createElement('tr'); + tableRowElement.dataset.lotOccupantIndex = + burialSiteContractOccupant.lotOccupantIndex?.toString(); + // eslint-disable-next-line no-unsanitized/property + tableRowElement.innerHTML = ` + ${cityssm.escapeHTML((burialSiteContractOccupant.occupantName ?? '') === '' && + (burialSiteContractOccupant.occupantFamilyName ?? '') === '' + ? '(No Name)' + : `${burialSiteContractOccupant.occupantName} ${burialSiteContractOccupant.occupantFamilyName}`)}
+ + + ${cityssm.escapeHTML(burialSiteContractOccupant.lotOccupantType ?? '')} + + + ${(burialSiteContractOccupant.occupantAddress1 ?? '') === '' + ? '' + : `${cityssm.escapeHTML(burialSiteContractOccupant.occupantAddress1 ?? '')}
`} + ${(burialSiteContractOccupant.occupantAddress2 ?? '') === '' + ? '' + : `${cityssm.escapeHTML(burialSiteContractOccupant.occupantAddress2 ?? '')}
`} + ${(burialSiteContractOccupant.occupantCity ?? '') === '' + ? '' + : `${cityssm.escapeHTML(burialSiteContractOccupant.occupantCity ?? '')}, `} + ${cityssm.escapeHTML(burialSiteContractOccupant.occupantProvince ?? '')}
+ ${cityssm.escapeHTML(burialSiteContractOccupant.occupantPostalCode ?? '')} + + ${(burialSiteContractOccupant.occupantPhoneNumber ?? '') === '' + ? '' + : `${cityssm.escapeHTML(burialSiteContractOccupant.occupantPhoneNumber ?? '')}
`} + ${(burialSiteContractOccupant.occupantEmailAddress ?? '') === '' + ? '' + : cityssm.escapeHTML(burialSiteContractOccupant.occupantEmailAddress ?? '')} + + + ${cityssm.escapeHTML(burialSiteContractOccupant.occupantComment ?? '')} + + +
+ + +
+ `; + tableRowElement + .querySelector('.button--edit') + ?.addEventListener('click', openEditLotOccupancyOccupant); + tableRowElement + .querySelector('.button--delete') + ?.addEventListener('click', deleteLotOccupancyOccupant); + tableElement.querySelector('tbody')?.append(tableRowElement); + } + occupantsContainer.append(tableElement); + } + if (isCreate) { + const lotOccupantTypeIdElement = document.querySelector('#burialSiteContract--lotOccupantTypeId'); + lotOccupantTypeIdElement.addEventListener('change', () => { + const occupantFields = formElement.querySelectorAll("[data-table='LotOccupancyOccupant']"); + for (const occupantField of occupantFields) { + occupantField.disabled = lotOccupantTypeIdElement.value === ''; + } + let occupantCommentTitle = lotOccupantTypeIdElement.selectedOptions[0].dataset + .occupantCommentTitle ?? ''; + if (occupantCommentTitle === '') { + occupantCommentTitle = 'Comment'; + } + ; + formElement.querySelector('#burialSiteContract--occupantCommentTitle').textContent = occupantCommentTitle; + }); + } + else { + renderLotOccupancyOccupants(); + } + document + .querySelector('#button--addOccupant') + ?.addEventListener('click', () => { + let addCloseModalFunction; + let addFormElement; + let searchFormElement; + let searchResultsElement; + function addOccupant(formOrObject) { + cityssm.postJSON(`${los.urlPrefix}/contracts/doAddLotOccupancyOccupant`, formOrObject, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + burialSiteContractOccupants = responseJSON.burialSiteContractOccupants; + addCloseModalFunction(); + renderLotOccupancyOccupants(); + } + else { + bulmaJS.alert({ + title: `Error Adding ${los.escapedAliases.Occupant}`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + function addOccupantFromForm(submitEvent) { + submitEvent.preventDefault(); + addOccupant(addFormElement); + } + let pastOccupantSearchResults = []; + function addOccupantFromCopy(clickEvent) { + clickEvent.preventDefault(); + const panelBlockElement = clickEvent.currentTarget; + const occupant = pastOccupantSearchResults[Number.parseInt(panelBlockElement.dataset.index ?? '', 10)]; + const lotOccupantTypeId = (panelBlockElement + .closest('.modal') + ?.querySelector('#burialSiteContractOccupantCopy--lotOccupantTypeId')).value; + if (lotOccupantTypeId === '') { + bulmaJS.alert({ + title: `No ${los.escapedAliases.Occupant} Type Selected`, + message: `Select a type to apply to the newly added ${los.escapedAliases.occupant}.`, + contextualColorName: 'warning' + }); + } + else { + occupant.lotOccupantTypeId = Number.parseInt(lotOccupantTypeId, 10); + occupant.burialSiteContractId = Number.parseInt(burialSiteContractId, 10); + addOccupant(occupant); + } + } + function searchOccupants(event) { + event.preventDefault(); + if (searchFormElement.querySelector('#burialSiteContractOccupantCopy--searchFilter').value === '') { + searchResultsElement.innerHTML = `
+

Enter a partial name or address in the search field above.

+
`; + return; + } + // eslint-disable-next-line no-unsanitized/property + searchResultsElement.innerHTML = + los.getLoadingParagraphHTML('Searching...'); + cityssm.postJSON(`${los.urlPrefix}/contracts/doSearchPastOccupants`, searchFormElement, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + pastOccupantSearchResults = responseJSON.occupants; + const panelElement = document.createElement('div'); + panelElement.className = 'panel'; + for (const [index, occupant] of pastOccupantSearchResults.entries()) { + const panelBlockElement = document.createElement('a'); + panelBlockElement.className = 'panel-block is-block'; + panelBlockElement.href = '#'; + panelBlockElement.dataset.index = index.toString(); + // eslint-disable-next-line no-unsanitized/property + panelBlockElement.innerHTML = ` + ${cityssm.escapeHTML(occupant.occupantName ?? '')} ${cityssm.escapeHTML(occupant.occupantFamilyName ?? '')} +
+
+
+ ${cityssm.escapeHTML(occupant.occupantAddress1 ?? '')}
+ ${(occupant.occupantAddress2 ?? '') === '' + ? '' + : `${cityssm.escapeHTML(occupant.occupantAddress2 ?? '')}
`}${cityssm.escapeHTML(occupant.occupantCity ?? '')}, ${cityssm.escapeHTML(occupant.occupantProvince ?? '')}
+ ${cityssm.escapeHTML(occupant.occupantPostalCode ?? '')} +
+
+ ${(occupant.occupantPhoneNumber ?? '') === '' + ? '' + : `${cityssm.escapeHTML(occupant.occupantPhoneNumber ?? '')}
`} + ${cityssm.escapeHTML(occupant.occupantEmailAddress ?? '')}
+
+
`; + panelBlockElement.addEventListener('click', addOccupantFromCopy); + panelElement.append(panelBlockElement); + } + searchResultsElement.innerHTML = ''; + searchResultsElement.append(panelElement); + }); + } + cityssm.openHtmlModal('burialSiteContract-addOccupant', { + onshow(modalElement) { + los.populateAliases(modalElement); + modalElement.querySelector('#burialSiteContractOccupantAdd--burialSiteContractId').value = burialSiteContractId; + const lotOccupantTypeSelectElement = modalElement.querySelector('#burialSiteContractOccupantAdd--lotOccupantTypeId'); + const lotOccupantTypeCopySelectElement = modalElement.querySelector('#burialSiteContractOccupantCopy--lotOccupantTypeId'); + for (const lotOccupantType of exports.lotOccupantTypes) { + const optionElement = document.createElement('option'); + optionElement.value = lotOccupantType.lotOccupantTypeId.toString(); + optionElement.textContent = lotOccupantType.lotOccupantType; + optionElement.dataset.occupantCommentTitle = + lotOccupantType.occupantCommentTitle; + optionElement.dataset.fontAwesomeIconClass = + lotOccupantType.fontAwesomeIconClass; + lotOccupantTypeSelectElement.append(optionElement); + lotOccupantTypeCopySelectElement.append(optionElement.cloneNode(true)); + } + ; + modalElement.querySelector('#burialSiteContractOccupantAdd--occupantCity').value = exports.occupantCityDefault; + modalElement.querySelector('#burialSiteContractOccupantAdd--occupantProvince').value = exports.occupantProvinceDefault; + }, + onshown(modalElement, closeModalFunction) { + bulmaJS.toggleHtmlClipped(); + bulmaJS.init(modalElement); + const lotOccupantTypeIdElement = modalElement.querySelector('#burialSiteContractOccupantAdd--lotOccupantTypeId'); + lotOccupantTypeIdElement.focus(); + lotOccupantTypeIdElement.addEventListener('change', () => { + const fontAwesomeIconClass = lotOccupantTypeIdElement.selectedOptions[0].dataset + .fontAwesomeIconClass ?? 'user'; + modalElement.querySelector('#burialSiteContractOccupantAdd--fontAwesomeIconClass').innerHTML = + ``; + let occupantCommentTitle = lotOccupantTypeIdElement.selectedOptions[0].dataset + .occupantCommentTitle ?? ''; + if (occupantCommentTitle === '') { + occupantCommentTitle = 'Comment'; + } + ; + modalElement.querySelector('#burialSiteContractOccupantAdd--occupantCommentTitle').textContent = occupantCommentTitle; + }); + addFormElement = modalElement.querySelector('#form--burialSiteContractOccupantAdd'); + addFormElement.addEventListener('submit', addOccupantFromForm); + searchResultsElement = modalElement.querySelector('#burialSiteContractOccupantCopy--searchResults'); + searchFormElement = modalElement.querySelector('#form--burialSiteContractOccupantCopy'); + searchFormElement.addEventListener('submit', (formEvent) => { + formEvent.preventDefault(); + }); + modalElement.querySelector('#burialSiteContractOccupantCopy--searchFilter').addEventListener('change', searchOccupants); + addCloseModalFunction = closeModalFunction; + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + document.querySelector('#button--addOccupant').focus(); + } + }); + }); + })(); + if (!isCreate) { + /** + * Comments + */ + ; + (() => { + let burialSiteContractComments = exports.burialSiteContractComments; + delete exports.burialSiteContractComments; + function openEditLotOccupancyComment(clickEvent) { + const burialSiteContractCommentId = Number.parseInt(clickEvent.currentTarget.closest('tr')?.dataset + .burialSiteContractCommentId ?? '', 10); + const burialSiteContractComment = burialSiteContractComments.find((currentLotOccupancyComment) => { + return (currentLotOccupancyComment.burialSiteContractCommentId === + burialSiteContractCommentId); + }); + let editFormElement; + let editCloseModalFunction; + function editComment(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/contracts/doUpdateBurialSiteContractComment`, editFormElement, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + burialSiteContractComments = responseJSON.burialSiteContractComments ?? []; + editCloseModalFunction(); + renderLotOccupancyComments(); + } + else { + bulmaJS.alert({ + title: 'Error Updating Comment', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + cityssm.openHtmlModal('burialSiteContract-editComment', { + onshow(modalElement) { + los.populateAliases(modalElement); + modalElement.querySelector('#burialSiteContractCommentEdit--burialSiteContractId').value = burialSiteContractId; + modalElement.querySelector('#burialSiteContractCommentEdit--burialSiteContractCommentId').value = burialSiteContractCommentId.toString(); + modalElement.querySelector('#burialSiteContractCommentEdit--burialSiteContractComment').value = burialSiteContractComment.burialSiteContractComment ?? ''; + const burialSiteContractCommentDateStringElement = modalElement.querySelector('#burialSiteContractCommentEdit--burialSiteContractCommentDateString'); + burialSiteContractCommentDateStringElement.value = + burialSiteContractComment.burialSiteContractCommentDateString ?? ''; + const currentDateString = cityssm.dateToString(new Date()); + burialSiteContractCommentDateStringElement.max = + burialSiteContractComment.burialSiteContractCommentDateString <= + currentDateString + ? currentDateString + : burialSiteContractComment.burialSiteContractCommentDateString ?? ''; + modalElement.querySelector('#burialSiteContractCommentEdit--burialSiteContractCommentTimeString').value = burialSiteContractComment.burialSiteContractCommentTimeString ?? ''; + }, + onshown(modalElement, closeModalFunction) { + bulmaJS.toggleHtmlClipped(); + los.initializeDatePickers(modalElement); + modalElement.querySelector('#burialSiteContractCommentEdit--burialSiteContractComment').focus(); + editFormElement = modalElement.querySelector('form'); + editFormElement.addEventListener('submit', editComment); + editCloseModalFunction = closeModalFunction; + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + } + }); + } + function deleteLotOccupancyComment(clickEvent) { + const burialSiteContractCommentId = Number.parseInt(clickEvent.currentTarget.closest('tr')?.dataset + .burialSiteContractCommentId ?? '', 10); + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/contracts/doDeleteBurialSiteContractComment`, { + burialSiteContractId, + burialSiteContractCommentId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + burialSiteContractComments = responseJSON.burialSiteContractComments; + renderLotOccupancyComments(); + } + 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 renderLotOccupancyComments() { + const containerElement = document.querySelector('#container--burialSiteContractComments'); + if (burialSiteContractComments.length === 0) { + containerElement.innerHTML = `
+

There are no comments associated with this record.

+
`; + return; + } + const tableElement = document.createElement('table'); + tableElement.className = 'table is-fullwidth is-striped is-hoverable'; + tableElement.innerHTML = ` + Commentor + Comment Date + Comment + Options + + `; + for (const burialSiteContractComment of burialSiteContractComments) { + const tableRowElement = document.createElement('tr'); + tableRowElement.dataset.burialSiteContractCommentId = + burialSiteContractComment.burialSiteContractCommentId?.toString(); + tableRowElement.innerHTML = `${cityssm.escapeHTML(burialSiteContractComment.recordCreate_userName ?? '')} + + ${cityssm.escapeHTML(burialSiteContractComment.burialSiteContractCommentDateString ?? '')} + ${cityssm.escapeHTML(burialSiteContractComment.burialSiteContractCommentTime === 0 + ? '' + : burialSiteContractComment.burialSiteContractCommentTimePeriodString ?? '')} + + ${cityssm.escapeHTML(burialSiteContractComment.burialSiteContractComment ?? '')} + +
+ + +
+ `; + tableRowElement + .querySelector('.button--edit') + ?.addEventListener('click', openEditLotOccupancyComment); + tableRowElement + .querySelector('.button--delete') + ?.addEventListener('click', deleteLotOccupancyComment); + tableElement.querySelector('tbody')?.append(tableRowElement); + } + containerElement.innerHTML = ''; + containerElement.append(tableElement); + } + document + .querySelector('#button--addComment') + ?.addEventListener('click', () => { + let addFormElement; + let addCloseModalFunction; + function addComment(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/contracts/doAddBurialSiteContractComment`, addFormElement, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + burialSiteContractComments = responseJSON.burialSiteContractComments; + addCloseModalFunction(); + renderLotOccupancyComments(); + } + else { + bulmaJS.alert({ + title: 'Error Adding Comment', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + cityssm.openHtmlModal('burialSiteContract-addComment', { + onshow(modalElement) { + los.populateAliases(modalElement); + modalElement.querySelector('#burialSiteContractCommentAdd--burialSiteContractId').value = burialSiteContractId; + }, + onshown(modalElement, closeModalFunction) { + bulmaJS.toggleHtmlClipped(); + modalElement.querySelector('#burialSiteContractCommentAdd--burialSiteContractComment').focus(); + addFormElement = modalElement.querySelector('form'); + addFormElement.addEventListener('submit', addComment); + addCloseModalFunction = closeModalFunction; + }, + onremoved: () => { + bulmaJS.toggleHtmlClipped(); + document.querySelector('#button--addComment').focus(); + } + }); + }); + renderLotOccupancyComments(); + })(); + (() => { + let burialSiteContractFees = exports.burialSiteContractFees; + delete exports.burialSiteContractFees; + const burialSiteContractFeesContainerElement = document.querySelector('#container--burialSiteContractFees'); + function getFeeGrandTotal() { + let feeGrandTotal = 0; + for (const burialSiteContractFee of burialSiteContractFees) { + feeGrandTotal += + ((burialSiteContractFee.feeAmount ?? 0) + + (burialSiteContractFee.taxAmount ?? 0)) * + (burialSiteContractFee.quantity ?? 0); + } + return feeGrandTotal; + } + function editLotOccupancyFeeQuantity(clickEvent) { + const feeId = Number.parseInt(clickEvent.currentTarget.closest('tr')?.dataset + .feeId ?? '', 10); + const fee = burialSiteContractFees.find((possibleFee) => { + return possibleFee.feeId === feeId; + }); + let updateCloseModalFunction; + function doUpdateQuantity(formEvent) { + formEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/contracts/doUpdateBurialSiteContractFeeQuantity`, formEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + burialSiteContractFees = responseJSON.burialSiteContractFees; + renderLotOccupancyFees(); + updateCloseModalFunction(); + } + else { + bulmaJS.alert({ + title: 'Error Updating Quantity', + message: 'Please try again.', + contextualColorName: 'danger' + }); + } + }); + } + cityssm.openHtmlModal('burialSiteContract-editFeeQuantity', { + onshow(modalElement) { + ; + modalElement.querySelector('#burialSiteContractFeeQuantity--burialSiteContractId').value = burialSiteContractId; + modalElement.querySelector('#burialSiteContractFeeQuantity--feeId').value = fee.feeId.toString(); + modalElement.querySelector('#burialSiteContractFeeQuantity--quantity').valueAsNumber = fee.quantity ?? 0; + modalElement.querySelector('#burialSiteContractFeeQuantity--quantityUnit').textContent = fee.quantityUnit ?? ''; + }, + onshown(modalElement, closeModalFunction) { + bulmaJS.toggleHtmlClipped(); + updateCloseModalFunction = closeModalFunction; + modalElement.querySelector('#burialSiteContractFeeQuantity--quantity').focus(); + modalElement + .querySelector('form') + ?.addEventListener('submit', doUpdateQuantity); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + } + }); + } + function deleteBurialSiteContractFee(clickEvent) { + const feeId = clickEvent.currentTarget.closest('.container--burialSiteContractFee').dataset.feeId; + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/contracts/doDeleteBurialSiteContractFee`, { + burialSiteContractId, + feeId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + burialSiteContractFees = responseJSON.burialSiteContractFees; + renderLotOccupancyFees(); + } + else { + bulmaJS.alert({ + title: 'Error Deleting Fee', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + bulmaJS.confirm({ + title: 'Delete Fee', + message: 'Are you sure you want to delete this fee?', + contextualColorName: 'warning', + okButton: { + text: 'Yes, Delete Fee', + callbackFunction: doDelete + } + }); + } + function renderLotOccupancyFees() { + if (burialSiteContractFees.length === 0) { + burialSiteContractFeesContainerElement.innerHTML = `
+

There are no fees associated with this record.

+
`; + renderLotOccupancyTransactions(); + return; + } + // eslint-disable-next-line no-secrets/no-secrets + burialSiteContractFeesContainerElement.innerHTML = ` + + + + + + + + + + + + + + + + + + + + + + +
FeeUnit Cost×QuantityequalsTotalOptions
Subtotal
Tax
Grand Total
`; + let feeAmountTotal = 0; + let taxAmountTotal = 0; + for (const burialSiteContractFee of burialSiteContractFees) { + const tableRowElement = document.createElement('tr'); + tableRowElement.className = 'container--burialSiteContractFee'; + tableRowElement.dataset.feeId = burialSiteContractFee.feeId.toString(); + tableRowElement.dataset.includeQuantity = + burialSiteContractFee.includeQuantity ?? false ? '1' : '0'; + // eslint-disable-next-line no-unsanitized/property + tableRowElement.innerHTML = ` + ${cityssm.escapeHTML(burialSiteContractFee.feeName ?? '')}
+ ${cityssm.escapeHTML(burialSiteContractFee.feeCategory ?? '')} + + ${burialSiteContractFee.quantity === 1 + ? '' + : ` + $${burialSiteContractFee.feeAmount?.toFixed(2)} + + × + ${burialSiteContractFee.quantity?.toString()} + =`} + + $${((burialSiteContractFee.feeAmount ?? 0) * (burialSiteContractFee.quantity ?? 0)).toFixed(2)} + + +
+ ${burialSiteContractFee.includeQuantity ?? false + ? `` + : ''} + +
+ `; + tableRowElement + .querySelector('.button--editQuantity') + ?.addEventListener('click', editLotOccupancyFeeQuantity); + tableRowElement + .querySelector('.button--delete') + ?.addEventListener('click', deleteBurialSiteContractFee); + burialSiteContractFeesContainerElement + .querySelector('tbody') + ?.append(tableRowElement); + feeAmountTotal += + (burialSiteContractFee.feeAmount ?? 0) * (burialSiteContractFee.quantity ?? 0); + taxAmountTotal += + (burialSiteContractFee.taxAmount ?? 0) * (burialSiteContractFee.quantity ?? 0); + } + ; + burialSiteContractFeesContainerElement.querySelector('#burialSiteContractFees--feeAmountTotal').textContent = `$${feeAmountTotal.toFixed(2)}`; + burialSiteContractFeesContainerElement.querySelector('#burialSiteContractFees--taxAmountTotal').textContent = `$${taxAmountTotal.toFixed(2)}`; + burialSiteContractFeesContainerElement.querySelector('#burialSiteContractFees--grandTotal').textContent = `$${(feeAmountTotal + taxAmountTotal).toFixed(2)}`; + renderLotOccupancyTransactions(); + } + const addFeeButtonElement = document.querySelector('#button--addFee'); + addFeeButtonElement.addEventListener('click', () => { + if (los.hasUnsavedChanges()) { + bulmaJS.alert({ + message: 'Please save all unsaved changes before adding fees.', + contextualColorName: 'warning' + }); + return; + } + let feeCategories; + let feeFilterElement; + let feeFilterResultsElement; + function doAddFeeCategory(clickEvent) { + clickEvent.preventDefault(); + const feeCategoryId = Number.parseInt(clickEvent.currentTarget.dataset.feeCategoryId ?? + '', 10); + cityssm.postJSON(`${los.urlPrefix}/contracts/doAddBurialSiteContractFeeCategory`, { + burialSiteContractId, + feeCategoryId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + burialSiteContractFees = responseJSON.burialSiteContractFees; + renderLotOccupancyFees(); + bulmaJS.alert({ + message: 'Fee Group Added Successfully', + contextualColorName: 'success' + }); + } + else { + bulmaJS.alert({ + title: 'Error Adding Fee', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + function doAddFee(feeId, quantity = 1) { + cityssm.postJSON(`${los.urlPrefix}/contracts/doAddLotOccupancyFee`, { + burialSiteContractId, + feeId, + quantity + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + burialSiteContractFees = responseJSON.burialSiteContractFees; + renderLotOccupancyFees(); + filterFees(); + } + else { + bulmaJS.alert({ + title: 'Error Adding Fee', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + function doSetQuantityAndAddFee(fee) { + let quantityElement; + let quantityCloseModalFunction; + function doSetQuantity(submitEvent) { + submitEvent.preventDefault(); + doAddFee(fee.feeId, quantityElement.value); + quantityCloseModalFunction(); + } + cityssm.openHtmlModal('burialSiteContract-setFeeQuantity', { + onshow(modalElement) { + ; + modalElement.querySelector('#burialSiteContractFeeQuantity--quantityUnit').textContent = fee.quantityUnit ?? ''; + }, + onshown(modalElement, closeModalFunction) { + quantityCloseModalFunction = closeModalFunction; + quantityElement = modalElement.querySelector('#burialSiteContractFeeQuantity--quantity'); + modalElement + .querySelector('form') + ?.addEventListener('submit', doSetQuantity); + } + }); + } + function tryAddFee(clickEvent) { + clickEvent.preventDefault(); + const feeId = Number.parseInt(clickEvent.currentTarget.dataset.feeId ?? '', 10); + const feeCategoryId = Number.parseInt(clickEvent.currentTarget.dataset.feeCategoryId ?? + '', 10); + const feeCategory = feeCategories.find((currentFeeCategory) => { + return currentFeeCategory.feeCategoryId === feeCategoryId; + }); + const fee = feeCategory.fees.find((currentFee) => { + return currentFee.feeId === feeId; + }); + if (fee.includeQuantity ?? false) { + doSetQuantityAndAddFee(fee); + } + else { + doAddFee(feeId); + } + } + function filterFees() { + const filterStringPieces = feeFilterElement.value + .trim() + .toLowerCase() + .split(' '); + feeFilterResultsElement.innerHTML = ''; + for (const feeCategory of feeCategories) { + const categoryContainerElement = document.createElement('div'); + categoryContainerElement.className = 'container--feeCategory'; + categoryContainerElement.dataset.feeCategoryId = + feeCategory.feeCategoryId.toString(); + categoryContainerElement.innerHTML = `
+
+

+ ${cityssm.escapeHTML(feeCategory.feeCategory ?? '')} +

+
+
+
`; + if (feeCategory.isGroupedFee) { + // eslint-disable-next-line no-unsanitized/method + categoryContainerElement + .querySelector('.columns') + ?.insertAdjacentHTML('beforeend', `
+ +
`); + categoryContainerElement + .querySelector('button') + ?.addEventListener('click', doAddFeeCategory); + } + let hasFees = false; + for (const fee of feeCategory.fees) { + // Don't include already applied fees that limit quantity + if (burialSiteContractFeesContainerElement.querySelector(`.container--burialSiteContractFee[data-fee-id='${fee.feeId}'][data-include-quantity='0']`) !== null) { + continue; + } + let includeFee = true; + const feeSearchString = `${feeCategory.feeCategory ?? ''} ${fee.feeName ?? ''} ${fee.feeDescription ?? ''}`.toLowerCase(); + for (const filterStringPiece of filterStringPieces) { + if (!feeSearchString.includes(filterStringPiece)) { + includeFee = false; + break; + } + } + if (!includeFee) { + continue; + } + hasFees = true; + const panelBlockElement = document.createElement(feeCategory.isGroupedFee ? 'div' : 'a'); + panelBlockElement.className = + 'panel-block is-block container--fee'; + panelBlockElement.dataset.feeId = fee.feeId.toString(); + panelBlockElement.dataset.feeCategoryId = + feeCategory.feeCategoryId.toString(); + // eslint-disable-next-line no-unsanitized/property + panelBlockElement.innerHTML = `${cityssm.escapeHTML(fee.feeName ?? '')}
+ + ${ + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + cityssm + .escapeHTML(fee.feeDescription ?? '') + .replaceAll('\n', '
')} +
`; + if (!feeCategory.isGroupedFee) { + ; + panelBlockElement.href = '#'; + panelBlockElement.addEventListener('click', tryAddFee); + } + ; + categoryContainerElement.querySelector('.panel').append(panelBlockElement); + } + if (hasFees) { + feeFilterResultsElement.append(categoryContainerElement); + } + } + } + cityssm.openHtmlModal('burialSiteContract-addFee', { + onshow(modalElement) { + feeFilterElement = modalElement.querySelector('#feeSelect--feeName'); + feeFilterResultsElement = modalElement.querySelector('#resultsContainer--feeSelect'); + cityssm.postJSON(`${los.urlPrefix}/contracts/doGetFees`, { + burialSiteContractId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + feeCategories = responseJSON.feeCategories; + feeFilterElement.disabled = false; + feeFilterElement.addEventListener('keyup', filterFees); + feeFilterElement.focus(); + filterFees(); + }); + }, + onshown() { + bulmaJS.toggleHtmlClipped(); + }, + onhidden() { + renderLotOccupancyFees(); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + addFeeButtonElement.focus(); + } + }); + }); + let burialSiteContractTransactions = exports.burialSiteContractTransactions; + delete exports.burialSiteContractTransactions; + const burialSiteContractTransactionsContainerElement = document.querySelector('#container--burialSiteContractTransactions'); + function getTransactionGrandTotal() { + let transactionGrandTotal = 0; + for (const burialSiteContractTransaction of burialSiteContractTransactions) { + transactionGrandTotal += burialSiteContractTransaction.transactionAmount; + } + return transactionGrandTotal; + } + function editLotOccupancyTransaction(clickEvent) { + const transactionIndex = Number.parseInt(clickEvent.currentTarget.closest('tr')?.dataset + .transactionIndex ?? '', 10); + const transaction = burialSiteContractTransactions.find((possibleTransaction) => { + return possibleTransaction.transactionIndex === transactionIndex; + }); + let editCloseModalFunction; + function doEdit(formEvent) { + formEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/contracts/doUpdateBurialSiteContractTransaction`, formEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + burialSiteContractTransactions = responseJSON.burialSiteContractTransactions; + renderLotOccupancyTransactions(); + editCloseModalFunction(); + } + else { + bulmaJS.alert({ + title: 'Error Updating Transaction', + message: 'Please try again.', + contextualColorName: 'danger' + }); + } + }); + } + cityssm.openHtmlModal('burialSiteContract-editTransaction', { + onshow(modalElement) { + los.populateAliases(modalElement); + modalElement.querySelector('#burialSiteContractTransactionEdit--burialSiteContractId').value = burialSiteContractId; + modalElement.querySelector('#burialSiteContractTransactionEdit--transactionIndex').value = transaction.transactionIndex?.toString() ?? ''; + modalElement.querySelector('#burialSiteContractTransactionEdit--transactionAmount').value = transaction.transactionAmount.toFixed(2); + modalElement.querySelector('#burialSiteContractTransactionEdit--externalReceiptNumber').value = transaction.externalReceiptNumber ?? ''; + modalElement.querySelector('#burialSiteContractTransactionEdit--transactionNote').value = transaction.transactionNote ?? ''; + modalElement.querySelector('#burialSiteContractTransactionEdit--transactionDateString').value = transaction.transactionDateString ?? ''; + modalElement.querySelector('#burialSiteContractTransactionEdit--transactionTimeString').value = transaction.transactionTimeString ?? ''; + }, + onshown(modalElement, closeModalFunction) { + bulmaJS.toggleHtmlClipped(); + los.initializeDatePickers(modalElement); + modalElement.querySelector('#burialSiteContractTransactionEdit--transactionAmount').focus(); + modalElement + .querySelector('form') + ?.addEventListener('submit', doEdit); + editCloseModalFunction = closeModalFunction; + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + } + }); + } + function deleteBurialSiteContractTransaction(clickEvent) { + const transactionIndex = clickEvent.currentTarget.closest('.container--burialSiteContractTransaction').dataset.transactionIndex; + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/contracts/doDeleteBurialSiteContractTransaction`, { + burialSiteContractId, + transactionIndex + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + burialSiteContractTransactions = responseJSON.burialSiteContractTransactions; + renderLotOccupancyTransactions(); + } + else { + bulmaJS.alert({ + title: 'Error Deleting Transaction', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + bulmaJS.confirm({ + title: 'Delete Transaction', + message: 'Are you sure you want to delete this transaction?', + contextualColorName: 'warning', + okButton: { + text: 'Yes, Delete Transaction', + callbackFunction: doDelete + } + }); + } + function renderLotOccupancyTransactions() { + if (burialSiteContractTransactions.length === 0) { + // eslint-disable-next-line no-unsanitized/property + burialSiteContractTransactionsContainerElement.innerHTML = `
+

There are no transactions associated with this record.

+
`; + return; + } + // eslint-disable-next-line no-unsanitized/property + burialSiteContractTransactionsContainerElement.innerHTML = ` + + + + + + + + + + + + +
Date${los.escapedAliases.ExternalReceiptNumber}AmountOptions
Transaction Total
`; + let transactionGrandTotal = 0; + for (const burialSiteContractTransaction of burialSiteContractTransactions) { + transactionGrandTotal += burialSiteContractTransaction.transactionAmount; + const tableRowElement = document.createElement('tr'); + tableRowElement.className = 'container--burialSiteContractTransaction'; + tableRowElement.dataset.transactionIndex = + burialSiteContractTransaction.transactionIndex?.toString(); + let externalReceiptNumberHTML = ''; + if (burialSiteContractTransaction.externalReceiptNumber !== '') { + externalReceiptNumberHTML = cityssm.escapeHTML(burialSiteContractTransaction.externalReceiptNumber ?? ''); + if (los.dynamicsGPIntegrationIsEnabled) { + if (burialSiteContractTransaction.dynamicsGPDocument === undefined) { + externalReceiptNumberHTML += ` + + `; + } + else if (burialSiteContractTransaction.dynamicsGPDocument.documentTotal.toFixed(2) === burialSiteContractTransaction.transactionAmount.toFixed(2)) { + externalReceiptNumberHTML += ` + + `; + } + else { + externalReceiptNumberHTML += ` + + `; + } + } + externalReceiptNumberHTML += '
'; + } + // eslint-disable-next-line no-unsanitized/property + tableRowElement.innerHTML = ` + ${cityssm.escapeHTML(burialSiteContractTransaction.transactionDateString ?? '')} + + + ${externalReceiptNumberHTML} + ${cityssm.escapeHTML(burialSiteContractTransaction.transactionNote ?? '')} + + + $${cityssm.escapeHTML(burialSiteContractTransaction.transactionAmount.toFixed(2))} + + +
+ + +
+ `; + tableRowElement + .querySelector('.button--edit') + ?.addEventListener('click', editLotOccupancyTransaction); + tableRowElement + .querySelector('.button--delete') + ?.addEventListener('click', deleteBurialSiteContractTransaction); + burialSiteContractTransactionsContainerElement + .querySelector('tbody') + ?.append(tableRowElement); + } + ; + burialSiteContractTransactionsContainerElement.querySelector('#burialSiteContractTransactions--grandTotal').textContent = `$${transactionGrandTotal.toFixed(2)}`; + const feeGrandTotal = getFeeGrandTotal(); + if (feeGrandTotal.toFixed(2) !== transactionGrandTotal.toFixed(2)) { + burialSiteContractTransactionsContainerElement.insertAdjacentHTML('afterbegin', `
+
+
+
+
Outstanding Balance
+
+
+
+ $${cityssm.escapeHTML((feeGrandTotal - transactionGrandTotal).toFixed(2))} +
+
+
+
`); + } + } + const addTransactionButtonElement = document.querySelector('#button--addTransaction'); + addTransactionButtonElement.addEventListener('click', () => { + let transactionAmountElement; + let externalReceiptNumberElement; + let addCloseModalFunction; + function doAddTransaction(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/contracts/doAddLotOccupancyTransaction`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + burialSiteContractTransactions = responseJSON.burialSiteContractTransactions; + addCloseModalFunction(); + renderLotOccupancyTransactions(); + } + else { + bulmaJS.confirm({ + title: 'Error Adding Transaction', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + // eslint-disable-next-line @typescript-eslint/naming-convention + function dynamicsGP_refreshExternalReceiptNumberIcon() { + const externalReceiptNumber = externalReceiptNumberElement.value; + const iconElement = externalReceiptNumberElement + .closest('.control') + ?.querySelector('.icon'); + const helpTextElement = externalReceiptNumberElement + .closest('.field') + ?.querySelector('.help'); + if (externalReceiptNumber === '') { + helpTextElement.innerHTML = ' '; + iconElement.innerHTML = + ''; + return; + } + cityssm.postJSON(`${los.urlPrefix}/contracts/doGetDynamicsGPDocument`, { + externalReceiptNumber + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (!responseJSON.success || + responseJSON.dynamicsGPDocument === undefined) { + helpTextElement.textContent = 'No Matching Document Found'; + iconElement.innerHTML = + ''; + } + else if (transactionAmountElement.valueAsNumber === + responseJSON.dynamicsGPDocument.documentTotal) { + helpTextElement.textContent = 'Matching Document Found'; + iconElement.innerHTML = + ''; + } + else { + helpTextElement.textContent = `Matching Document: $${responseJSON.dynamicsGPDocument.documentTotal.toFixed(2)}`; + iconElement.innerHTML = + ''; + } + }); + } + cityssm.openHtmlModal('burialSiteContract-addTransaction', { + onshow(modalElement) { + los.populateAliases(modalElement); + modalElement.querySelector('#burialSiteContractTransactionAdd--burialSiteContractId').value = burialSiteContractId.toString(); + const feeGrandTotal = getFeeGrandTotal(); + const transactionGrandTotal = getTransactionGrandTotal(); + transactionAmountElement = modalElement.querySelector('#burialSiteContractTransactionAdd--transactionAmount'); + transactionAmountElement.min = (-1 * transactionGrandTotal).toFixed(2); + transactionAmountElement.max = Math.max(feeGrandTotal - transactionGrandTotal, 0).toFixed(2); + transactionAmountElement.value = Math.max(feeGrandTotal - transactionGrandTotal, 0).toFixed(2); + if (los.dynamicsGPIntegrationIsEnabled) { + externalReceiptNumberElement = modalElement.querySelector( + // eslint-disable-next-line no-secrets/no-secrets + '#burialSiteContractTransactionAdd--externalReceiptNumber'); + const externalReceiptNumberControlElement = externalReceiptNumberElement.closest('.control'); + externalReceiptNumberControlElement.classList.add('has-icons-right'); + externalReceiptNumberControlElement.insertAdjacentHTML('beforeend', ''); + externalReceiptNumberControlElement.insertAdjacentHTML('afterend', '

'); + externalReceiptNumberElement.addEventListener('change', dynamicsGP_refreshExternalReceiptNumberIcon); + transactionAmountElement.addEventListener('change', dynamicsGP_refreshExternalReceiptNumberIcon); + dynamicsGP_refreshExternalReceiptNumberIcon(); + } + }, + onshown(modalElement, closeModalFunction) { + bulmaJS.toggleHtmlClipped(); + transactionAmountElement.focus(); + addCloseModalFunction = closeModalFunction; + modalElement + .querySelector('form') + ?.addEventListener('submit', doAddTransaction); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + addTransactionButtonElement.focus(); + } + }); + }); + renderLotOccupancyFees(); + })(); + } +})(); diff --git a/public/javascripts/burialSiteContract.search.d.ts b/public/javascripts/burialSiteContract.search.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/burialSiteContract.search.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/burialSiteContract.search.js b/public/javascripts/burialSiteContract.search.js new file mode 100644 index 00000000..d0a01d3d --- /dev/null +++ b/public/javascripts/burialSiteContract.search.js @@ -0,0 +1,152 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const los = exports.los; + const searchFilterFormElement = document.querySelector('#form--searchFilters'); + const searchResultsContainerElement = document.querySelector('#container--searchResults'); + const limit = Number.parseInt(document.querySelector('#searchFilter--limit').value, 10); + const offsetElement = document.querySelector('#searchFilter--offset'); + function renderLotOccupancies(rawResponseJSON) { + const responseJSON = rawResponseJSON; + if (responseJSON.lotOccupancies.length === 0) { + // eslint-disable-next-line no-unsanitized/property + searchResultsContainerElement.innerHTML = `
+

+ There are no ${los.escapedAliases.occupancy} records that meet the search criteria. +

+
`; + return; + } + const resultsTbodyElement = document.createElement('tbody'); + const nowDateString = cityssm.dateToString(new Date()); + for (const burialSiteContract of responseJSON.lotOccupancies) { + let occupancyTimeHTML = ''; + if (burialSiteContract.contractStartDateString <= nowDateString && + (burialSiteContract.contractEndDateString === '' || + burialSiteContract.contractEndDateString >= nowDateString)) { + occupancyTimeHTML = ` + + `; + } + else if (burialSiteContract.contractStartDateString > nowDateString) { + occupancyTimeHTML = ` + + `; + } + else { + occupancyTimeHTML = ` + + `; + } + let occupantsHTML = ''; + for (const occupant of burialSiteContract.burialSiteContractOccupants ?? []) { + occupantsHTML += `
  • + + + + ${cityssm.escapeHTML(occupant.occupantName ?? '')} + ${cityssm.escapeHTML(occupant.occupantFamilyName ?? '')} +
  • `; + } + const feeTotal = (burialSiteContract.burialSiteContractFees?.reduce((soFar, currentFee) => soFar + + ((currentFee.feeAmount ?? 0) + (currentFee.taxAmount ?? 0)) * + (currentFee.quantity ?? 0), 0) ?? 0).toFixed(2); + const transactionTotal = (burialSiteContract.burialSiteContractTransactions?.reduce((soFar, currentTransaction) => soFar + currentTransaction.transactionAmount, 0) ?? 0).toFixed(2); + let feeIconHTML = ''; + if (feeTotal !== '0.00' || transactionTotal !== '0.00') { + feeIconHTML = ` + + `; + } + // eslint-disable-next-line no-unsanitized/method + resultsTbodyElement.insertAdjacentHTML('beforeend', ` + + ${occupancyTimeHTML} + +
    + ${cityssm.escapeHTML(burialSiteContract.occupancyType ?? '')} +
    + #${burialSiteContract.burialSiteContractId} + + ${(burialSiteContract.lotId ?? -1) === -1 + ? `(No ${los.escapedAliases.Lot})` + : `${cityssm.escapeHTML(burialSiteContract.lotName ?? '')}`}
    + ${cityssm.escapeHTML(burialSiteContract.cemeteryName ?? '')} + + ${burialSiteContract.contractStartDateString} + + ${burialSiteContract.contractEndDate + ? burialSiteContract.contractEndDateString + : '(No End Date)'} + + ${occupantsHTML === '' + ? '' + : `
      ${occupantsHTML}
    `} + + ${feeIconHTML} + + ${burialSiteContract.printEJS + ? ` + + ` + : ''}`); + } + // eslint-disable-next-line no-unsanitized/property + searchResultsContainerElement.innerHTML = ` + + + + + + + + + + +
    ${los.escapedAliases.Occupancy} Type${los.escapedAliases.Lot}${los.escapedAliases.contractStartDate}End Date${los.escapedAliases.Occupants}Fees and TransactionsPrint
    `; + searchResultsContainerElement + .querySelector('table') + ?.append(resultsTbodyElement); + // eslint-disable-next-line no-unsanitized/method + searchResultsContainerElement.insertAdjacentHTML('beforeend', los.getSearchResultsPagerHTML(limit, responseJSON.offset, responseJSON.count)); + searchResultsContainerElement + .querySelector("button[data-page='previous']") + ?.addEventListener('click', previousAndGetLotOccupancies); + searchResultsContainerElement + .querySelector("button[data-page='next']") + ?.addEventListener('click', nextAndGetLotOccupancies); + } + function getBurialSiteContracts() { + // eslint-disable-next-line no-unsanitized/property + searchResultsContainerElement.innerHTML = los.getLoadingParagraphHTML(`Loading ${los.escapedAliases.Occupancies}...`); + cityssm.postJSON(`${los.urlPrefix}/contracts/doSearchLotOccupancies`, searchFilterFormElement, renderLotOccupancies); + } + function resetOffsetAndGetLotOccupancies() { + offsetElement.value = '0'; + getBurialSiteContracts(); + } + function previousAndGetLotOccupancies() { + offsetElement.value = Math.max(Number.parseInt(offsetElement.value, 10) - limit, 0).toString(); + getBurialSiteContracts(); + } + function nextAndGetLotOccupancies() { + offsetElement.value = (Number.parseInt(offsetElement.value, 10) + limit).toString(); + getBurialSiteContracts(); + } + const filterElements = searchFilterFormElement.querySelectorAll('input, select'); + for (const filterElement of filterElements) { + filterElement.addEventListener('change', resetOffsetAndGetLotOccupancies); + } + searchFilterFormElement.addEventListener('submit', (formEvent) => { + formEvent.preventDefault(); + }); + getBurialSiteContracts(); +})(); diff --git a/public/javascripts/burialSiteContract.search.ts b/public/javascripts/burialSiteContract.search.ts index f95c347d..91ab2d0a 100644 --- a/public/javascripts/burialSiteContract.search.ts +++ b/public/javascripts/burialSiteContract.search.ts @@ -133,7 +133,7 @@ declare const exports: Record ${ (burialSiteContract.lotId ?? -1) === -1 ? `(No ${los.escapedAliases.Lot})` - : `${cityssm.escapeHTML(burialSiteContract.lotName ?? '')}` + : `${cityssm.escapeHTML(burialSiteContract.lotName ?? '')}` }
    ${cityssm.escapeHTML(burialSiteContract.cemeteryName ?? '')}
    + + `); + } + searchResultsContainerElement.innerHTML = ''; + if (searchResultCount === 0) { + // eslint-disable-next-line no-unsanitized/property + searchResultsContainerElement.innerHTML = `
    +

    There are no cemeteries that meet the search criteria.

    +
    `; + } + else { + const searchResultsTableElement = document.createElement('table'); + searchResultsTableElement.className = + 'table is-fullwidth is-striped is-hoverable has-sticky-header'; + // eslint-disable-next-line no-unsanitized/property + searchResultsTableElement.innerHTML = ` + + + + + + + `; + searchResultsTableElement.append(searchResultsTbodyElement); + searchResultsContainerElement.append(searchResultsTableElement); + } + } + searchFilterElement.addEventListener('keyup', renderResults); + document + .querySelector('#form--searchFilters') + ?.addEventListener('submit', (formEvent) => { + formEvent.preventDefault(); + renderResults(); + }); + renderResults(); +})(); diff --git a/public/javascripts/cemetery.search.ts b/public/javascripts/cemetery.search.ts index eaff5002..18f38484 100644 --- a/public/javascripts/cemetery.search.ts +++ b/public/javascripts/cemetery.search.ts @@ -1,7 +1,8 @@ import type { cityssmGlobal } from '@cityssm/bulma-webapp-js/src/types.js' -import type { LOS } from '../../types/globalTypes.js' -import type { MapRecord } from '../../types/recordTypes.js' +import type { Cemetery } from '../../types/recordTypes.js' + +import type { LOS } from './types.js' declare const cityssm: cityssmGlobal @@ -9,20 +10,21 @@ declare const exports: Record ;(() => { const los = exports.los as LOS - const maps = exports.maps as MapRecord[] + const cemeteries = exports.cemeteries as Cemetery[] const searchFilterElement = document.querySelector( - '#searchFilter--map' + '#searchFilter--cemetery' ) as HTMLInputElement const searchResultsContainerElement = document.querySelector( '#container--searchResults' ) as HTMLElement + // eslint-disable-next-line complexity function renderResults(): void { // eslint-disable-next-line no-unsanitized/property searchResultsContainerElement.innerHTML = los.getLoadingParagraphHTML( - `Loading ${los.escapedAliases.Maps}...` + `Loading Cemeteries...` ) let searchResultCount = 0 @@ -33,21 +35,21 @@ declare const exports: Record .toLowerCase() .split(' ') - for (const map of maps) { - const mapSearchString = `${map.cemeteryName ?? ''} ${ - map.mapDescription ?? '' - } ${map.mapAddress1 ?? ''} ${map.mapAddress2 ?? ''}`.toLowerCase() + for (const cemetery of cemeteries) { + const cemeterySearchString = `${cemetery.cemeteryName ?? ''} ${ + cemetery.cemeteryDescription ?? '' + } ${cemetery.cemeteryAddress1 ?? ''} ${cemetery.cemeteryAddress2 ?? ''}`.toLowerCase() - let showMap = true + let showCemetery = true for (const filterStringPiece of filterStringSplit) { - if (!mapSearchString.includes(filterStringPiece)) { - showMap = false + if (!cemeterySearchString.includes(filterStringPiece)) { + showCemetery = false break } } - if (!showMap) { + if (!showCemetery) { continue } @@ -58,40 +60,40 @@ declare const exports: Record 'beforeend', `` ) @@ -115,7 +117,7 @@ declare const exports: Record if (searchResultCount === 0) { // eslint-disable-next-line no-unsanitized/property searchResultsContainerElement.innerHTML = `
    -

    There are no ${los.escapedAliases.maps} that meet the search criteria.

    +

    There are no cemeteries that meet the search criteria.

    ` } else { const searchResultsTableElement = document.createElement('table') @@ -125,12 +127,12 @@ declare const exports: Record // eslint-disable-next-line no-unsanitized/property searchResultsTableElement.innerHTML = `
    - + - + ` searchResultsTableElement.append(searchResultsTbodyElement) diff --git a/public/javascripts/cemetery.view.d.ts b/public/javascripts/cemetery.view.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/cemetery.view.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/cemetery.view.js b/public/javascripts/cemetery.view.js new file mode 100644 index 00000000..b58e5b8c --- /dev/null +++ b/public/javascripts/cemetery.view.js @@ -0,0 +1,20 @@ +"use strict"; +// eslint-disable-next-line @eslint-community/eslint-comments/disable-enable-pair +/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */ +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const mapContainerElement = document.querySelector('#map--leaflet'); + if (mapContainerElement !== null) { + const mapLatitude = Number.parseFloat(mapContainerElement.dataset.mapLatitude ?? ''); + const mapLongitude = Number.parseFloat(mapContainerElement.dataset.mapLongitude ?? ''); + const mapCoordinates = [mapLatitude, mapLongitude]; + // eslint-disable-next-line unicorn/no-array-callback-reference + const map = L.map(mapContainerElement); + map.setView(mapCoordinates, 15); + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + attribution: '© OpenStreetMap' + }).addTo(map); + L.marker(mapCoordinates).addTo(map); + } +})(); diff --git a/public/javascripts/contractTypes.admin.d.ts b/public/javascripts/contractTypes.admin.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/contractTypes.admin.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/contractTypes.admin.js b/public/javascripts/contractTypes.admin.js new file mode 100644 index 00000000..780134fa --- /dev/null +++ b/public/javascripts/contractTypes.admin.js @@ -0,0 +1,602 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const los = exports.los; + const occupancyTypesContainerElement = document.querySelector('#container--occupancyTypes'); + const ContractTypePrintsContainerElement = document.querySelector('#container--ContractTypePrints'); + let occupancyTypes = exports.occupancyTypes; + delete exports.occupancyTypes; + let allContractTypeFields = exports.allContractTypeFields; + delete exports.allContractTypeFields; + const expandedOccupancyTypes = new Set(); + function toggleContractTypeFields(clickEvent) { + const toggleButtonElement = clickEvent.currentTarget; + const occupancyTypeElement = toggleButtonElement.closest('.container--occupancyType'); + const contractTypeId = Number.parseInt(occupancyTypeElement.dataset.contractTypeId ?? '', 10); + if (expandedOccupancyTypes.has(contractTypeId)) { + expandedOccupancyTypes.delete(contractTypeId); + } + else { + expandedOccupancyTypes.add(contractTypeId); + } + // eslint-disable-next-line no-unsanitized/property + toggleButtonElement.innerHTML = expandedOccupancyTypes.has(contractTypeId) + ? '' + : ''; + const panelBlockElements = occupancyTypeElement.querySelectorAll('.panel-block'); + for (const panelBlockElement of panelBlockElements) { + panelBlockElement.classList.toggle('is-hidden'); + } + } + function occupancyTypeResponseHandler(rawResponseJSON) { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + occupancyTypes = responseJSON.occupancyTypes; + allContractTypeFields = responseJSON.allContractTypeFields; + renderOccupancyTypes(); + } + else { + bulmaJS.alert({ + title: `Error Updating ${los.escapedAliases.Occupancy} Type`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + } + function deleteOccupancyType(clickEvent) { + const contractTypeId = Number.parseInt(clickEvent.currentTarget.closest('.container--occupancyType').dataset.contractTypeId ?? '', 10); + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/admin/doDeleteContractType`, { + contractTypeId + }, occupancyTypeResponseHandler); + } + bulmaJS.confirm({ + title: `Delete ${los.escapedAliases.Occupancy} Type`, + message: `Are you sure you want to delete this ${los.escapedAliases.occupancy} type?`, + contextualColorName: 'warning', + okButton: { + text: `Yes, Delete ${los.escapedAliases.Occupancy} Type`, + callbackFunction: doDelete + } + }); + } + function openEditOccupancyType(clickEvent) { + const contractTypeId = Number.parseInt(clickEvent.currentTarget.closest('.container--occupancyType').dataset.contractTypeId ?? '', 10); + const occupancyType = occupancyTypes.find((currentOccupancyType) => contractTypeId === currentOccupancyType.contractTypeId); + let editCloseModalFunction; + function doEdit(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doUpdateContractType`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + occupancyTypeResponseHandler(responseJSON); + if (responseJSON.success) { + editCloseModalFunction(); + } + }); + } + cityssm.openHtmlModal('adminOccupancyTypes-editOccupancyType', { + onshow(modalElement) { + los.populateAliases(modalElement); + modalElement.querySelector('#occupancyTypeEdit--contractTypeId').value = contractTypeId.toString(); + modalElement.querySelector('#occupancyTypeEdit--occupancyType').value = occupancyType.occupancyType; + }, + onshown(modalElement, closeModalFunction) { + editCloseModalFunction = closeModalFunction; + modalElement.querySelector('#occupancyTypeEdit--occupancyType').focus(); + modalElement.querySelector('form')?.addEventListener('submit', doEdit); + bulmaJS.toggleHtmlClipped(); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + } + }); + } + function openAddOccupancyTypeField(clickEvent) { + const contractTypeId = Number.parseInt(clickEvent.currentTarget.closest('.container--occupancyType').dataset.contractTypeId ?? '', 10); + let addCloseModalFunction; + function doAdd(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doAddContractTypeField`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + expandedOccupancyTypes.add(contractTypeId); + occupancyTypeResponseHandler(responseJSON); + if (responseJSON.success) { + addCloseModalFunction(); + openEditOccupancyTypeField(contractTypeId, responseJSON.contractTypeFieldId ?? 0); + } + }); + } + cityssm.openHtmlModal('adminOccupancyTypes-addOccupancyTypeField', { + onshow(modalElement) { + los.populateAliases(modalElement); + if (contractTypeId) { + ; + modalElement.querySelector('#occupancyTypeFieldAdd--contractTypeId').value = contractTypeId.toString(); + } + }, + onshown(modalElement, closeModalFunction) { + addCloseModalFunction = closeModalFunction; + modalElement.querySelector('#occupancyTypeFieldAdd--occupancyTypeField').focus(); + modalElement.querySelector('form')?.addEventListener('submit', doAdd); + bulmaJS.toggleHtmlClipped(); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + } + }); + } + function moveOccupancyType(clickEvent) { + const buttonElement = clickEvent.currentTarget; + const contractTypeId = clickEvent.currentTarget.closest('.container--occupancyType').dataset.contractTypeId; + cityssm.postJSON(`${los.urlPrefix}/admin/${buttonElement.dataset.direction === 'up' + ? 'doMoveContractTypeUp' + : 'doMoveContractTypeDown'}`, { + contractTypeId, + moveToEnd: clickEvent.shiftKey ? '1' : '0' + }, occupancyTypeResponseHandler); + } + function openEditOccupancyTypeField(contractTypeId, contractTypeFieldId) { + let occupancyType; + if (contractTypeId) { + occupancyType = occupancyTypes.find((currentOccupancyType) => currentOccupancyType.contractTypeId === contractTypeId); + } + const occupancyTypeField = (occupancyType + ? occupancyType.ContractTypeFields ?? [] + : allContractTypeFields).find((currentOccupancyTypeField) => currentOccupancyTypeField.contractTypeFieldId === contractTypeFieldId); + let fieldTypeElement; + let minimumLengthElement; + let maximumLengthElement; + let patternElement; + let occupancyTypeFieldValuesElement; + let editCloseModalFunction; + function updateMaximumLengthMin() { + maximumLengthElement.min = minimumLengthElement.value; + } + function toggleInputFields() { + switch (fieldTypeElement.value) { + case 'date': { + minimumLengthElement.disabled = true; + maximumLengthElement.disabled = true; + patternElement.disabled = true; + occupancyTypeFieldValuesElement.disabled = true; + break; + } + case 'select': { + minimumLengthElement.disabled = true; + maximumLengthElement.disabled = true; + patternElement.disabled = true; + occupancyTypeFieldValuesElement.disabled = false; + break; + } + default: { + minimumLengthElement.disabled = false; + maximumLengthElement.disabled = false; + patternElement.disabled = false; + occupancyTypeFieldValuesElement.disabled = true; + break; + } + } + } + function doUpdate(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doUpdateContractTypeField`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + occupancyTypeResponseHandler(responseJSON); + if (responseJSON.success) { + editCloseModalFunction(); + } + }); + } + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/admin/doDeleteContractTypeField`, { + contractTypeFieldId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + occupancyTypeResponseHandler(responseJSON); + if (responseJSON.success) { + editCloseModalFunction(); + } + }); + } + function confirmDoDelete() { + bulmaJS.confirm({ + title: 'Delete Field', + message: 'Are you sure you want to delete this field? Note that historical records that make use of this field will not be affected.', + contextualColorName: 'warning', + okButton: { + text: 'Yes, Delete Field', + callbackFunction: doDelete + } + }); + } + cityssm.openHtmlModal('adminOccupancyTypes-editOccupancyTypeField', { + onshow: (modalElement) => { + los.populateAliases(modalElement); + modalElement.querySelector('#occupancyTypeFieldEdit--contractTypeFieldId').value = occupancyTypeField.contractTypeFieldId.toString(); + modalElement.querySelector('#occupancyTypeFieldEdit--occupancyTypeField').value = occupancyTypeField.occupancyTypeField ?? ''; + modalElement.querySelector('#occupancyTypeFieldEdit--isRequired').value = occupancyTypeField.isRequired ?? false ? '1' : '0'; + fieldTypeElement = modalElement.querySelector('#occupancyTypeFieldEdit--fieldType'); + fieldTypeElement.value = occupancyTypeField.fieldType; + minimumLengthElement = modalElement.querySelector('#occupancyTypeFieldEdit--minimumLength'); + minimumLengthElement.value = + occupancyTypeField.minimumLength?.toString() ?? ''; + maximumLengthElement = modalElement.querySelector('#occupancyTypeFieldEdit--maximumLength'); + maximumLengthElement.value = + occupancyTypeField.maximumLength?.toString() ?? ''; + patternElement = modalElement.querySelector('#occupancyTypeFieldEdit--pattern'); + patternElement.value = occupancyTypeField.pattern ?? ''; + occupancyTypeFieldValuesElement = modalElement.querySelector('#occupancyTypeFieldEdit--occupancyTypeFieldValues'); + occupancyTypeFieldValuesElement.value = + occupancyTypeField.occupancyTypeFieldValues ?? ''; + toggleInputFields(); + }, + onshown: (modalElement, closeModalFunction) => { + editCloseModalFunction = closeModalFunction; + bulmaJS.init(modalElement); + bulmaJS.toggleHtmlClipped(); + cityssm.enableNavBlocker(); + modalElement.querySelector('form')?.addEventListener('submit', doUpdate); + minimumLengthElement.addEventListener('keyup', updateMaximumLengthMin); + updateMaximumLengthMin(); + fieldTypeElement.addEventListener('change', toggleInputFields); + modalElement + .querySelector('#button--deleteOccupancyTypeField') + ?.addEventListener('click', confirmDoDelete); + }, + onremoved: () => { + bulmaJS.toggleHtmlClipped(); + cityssm.disableNavBlocker(); + } + }); + } + function openEditOccupancyTypeFieldByClick(clickEvent) { + clickEvent.preventDefault(); + const contractTypeFieldId = Number.parseInt(clickEvent.currentTarget.closest('.container--occupancyTypeField').dataset.contractTypeFieldId ?? '', 10); + const contractTypeId = Number.parseInt(clickEvent.currentTarget.closest('.container--occupancyType').dataset.contractTypeId ?? '', 10); + openEditOccupancyTypeField(contractTypeId, contractTypeFieldId); + } + function moveOccupancyTypeField(clickEvent) { + const buttonElement = clickEvent.currentTarget; + const contractTypeFieldId = clickEvent.currentTarget.closest('.container--occupancyTypeField').dataset.contractTypeFieldId; + cityssm.postJSON(`${los.urlPrefix}/admin/${buttonElement.dataset.direction === 'up' + ? 'doMoveContractTypeFieldUp' + : // eslint-disable-next-line no-secrets/no-secrets + 'doMoveContractTypeFieldDown'}`, { + contractTypeFieldId, + moveToEnd: clickEvent.shiftKey ? '1' : '0' + }, occupancyTypeResponseHandler); + } + function renderContractTypeFields(panelElement, contractTypeId, ContractTypeFields) { + if (ContractTypeFields.length === 0) { + // eslint-disable-next-line no-unsanitized/method + panelElement.insertAdjacentHTML('beforeend', `
    +

    There are no additional fields.

    +
    `); + } + else { + for (const occupancyTypeField of ContractTypeFields) { + const panelBlockElement = document.createElement('div'); + panelBlockElement.className = + 'panel-block is-block container--occupancyTypeField'; + if (contractTypeId && !expandedOccupancyTypes.has(contractTypeId)) { + panelBlockElement.classList.add('is-hidden'); + } + panelBlockElement.dataset.contractTypeFieldId = + occupancyTypeField.contractTypeFieldId.toString(); + // eslint-disable-next-line no-unsanitized/property + panelBlockElement.innerHTML = `
    + +
    +
    + ${los.getMoveUpDownButtonFieldHTML('button--moveOccupancyTypeFieldUp', 'button--moveOccupancyTypeFieldDown')} +
    +
    +
    `; + panelBlockElement + .querySelector('.button--editOccupancyTypeField') + ?.addEventListener('click', openEditOccupancyTypeFieldByClick); + panelBlockElement.querySelector('.button--moveOccupancyTypeFieldUp').addEventListener('click', moveOccupancyTypeField); + panelBlockElement.querySelector('.button--moveOccupancyTypeFieldDown').addEventListener('click', moveOccupancyTypeField); + panelElement.append(panelBlockElement); + } + } + } + function openAddOccupancyTypePrint(clickEvent) { + const contractTypeId = clickEvent.currentTarget.closest('.container--occupancyTypePrintList').dataset.contractTypeId ?? ''; + let closeAddModalFunction; + function doAdd(formEvent) { + formEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doAddContractTypePrint`, formEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + closeAddModalFunction(); + } + occupancyTypeResponseHandler(responseJSON); + }); + } + cityssm.openHtmlModal('adminOccupancyTypes-addOccupancyTypePrint', { + onshow(modalElement) { + los.populateAliases(modalElement); + modalElement.querySelector('#occupancyTypePrintAdd--contractTypeId').value = contractTypeId; + const printSelectElement = modalElement.querySelector('#occupancyTypePrintAdd--printEJS'); + for (const [printEJS, printTitle] of Object.entries(exports.occupancyTypePrintTitles)) { + const optionElement = document.createElement('option'); + optionElement.value = printEJS; + optionElement.textContent = printTitle; + printSelectElement.append(optionElement); + } + }, + onshown(modalElement, closeModalFunction) { + closeAddModalFunction = closeModalFunction; + modalElement.querySelector('form')?.addEventListener('submit', doAdd); + } + }); + } + function moveOccupancyTypePrint(clickEvent) { + const buttonElement = clickEvent.currentTarget; + const printEJS = buttonElement.closest('.container--occupancyTypePrint').dataset.printEJS; + const contractTypeId = buttonElement.closest('.container--occupancyTypePrintList').dataset.contractTypeId; + cityssm.postJSON(`${los.urlPrefix}/admin/${buttonElement.dataset.direction === 'up' + ? // eslint-disable-next-line no-secrets/no-secrets + 'doMoveContractTypePrintUp' + : // eslint-disable-next-line no-secrets/no-secrets + 'doMoveContractTypePrintDown'}`, { + contractTypeId, + printEJS, + moveToEnd: clickEvent.shiftKey ? '1' : '0' + }, occupancyTypeResponseHandler); + } + function deleteOccupancyTypePrint(clickEvent) { + clickEvent.preventDefault(); + const printEJS = clickEvent.currentTarget.closest('.container--occupancyTypePrint').dataset.printEJS; + const contractTypeId = clickEvent.currentTarget.closest('.container--occupancyTypePrintList').dataset.contractTypeId; + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/admin/doDeleteContractTypePrint`, { + contractTypeId, + printEJS + }, occupancyTypeResponseHandler); + } + bulmaJS.confirm({ + title: 'Delete Print', + message: 'Are you sure you want to remove this print option?', + contextualColorName: 'warning', + okButton: { + text: 'Yes, Remove Print', + callbackFunction: doDelete + } + }); + } + function renderContractTypePrints(panelElement, contractTypeId, ContractTypePrints) { + if (ContractTypePrints.length === 0) { + panelElement.insertAdjacentHTML('beforeend', `
    +
    +

    There are no prints associated with this record.

    +
    +
    `); + } + else { + for (const printEJS of ContractTypePrints) { + const panelBlockElement = document.createElement('div'); + panelBlockElement.className = + 'panel-block is-block container--occupancyTypePrint'; + panelBlockElement.dataset.printEJS = printEJS; + const printTitle = printEJS === '*' + ? '(All Available Prints)' + : exports.occupancyTypePrintTitles[printEJS]; + let printIconClass = 'fa-star'; + if (printEJS.startsWith('pdf/')) { + printIconClass = 'fa-file-pdf'; + } + else if (printEJS.startsWith('screen/')) { + printIconClass = 'fa-file'; + } + // eslint-disable-next-line no-unsanitized/property + panelBlockElement.innerHTML = `
    +
    +
    + +
    +
    + ${cityssm.escapeHTML(printTitle || printEJS)} +
    +
    +
    +
    + ${los.getMoveUpDownButtonFieldHTML('button--moveOccupancyTypePrintUp', 'button--moveOccupancyTypePrintDown')} +
    +
    + +
    +
    +
    `; + panelBlockElement.querySelector('.button--moveOccupancyTypePrintUp').addEventListener('click', moveOccupancyTypePrint); + panelBlockElement.querySelector('.button--moveOccupancyTypePrintDown').addEventListener('click', moveOccupancyTypePrint); + panelBlockElement + .querySelector('.button--deleteOccupancyTypePrint') + ?.addEventListener('click', deleteOccupancyTypePrint); + panelElement.append(panelBlockElement); + } + } + } + function renderOccupancyTypes() { + // eslint-disable-next-line no-unsanitized/property + occupancyTypesContainerElement.innerHTML = `
    +
    +
    +
    +
    +

    (All ${los.escapedAliases.Occupancy} Types)

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    `; + ContractTypePrintsContainerElement.innerHTML = ''; + renderContractTypeFields(occupancyTypesContainerElement.querySelector('#container--allContractTypeFields'), undefined, allContractTypeFields); + occupancyTypesContainerElement + .querySelector('.button--addOccupancyTypeField') + ?.addEventListener('click', openAddOccupancyTypeField); + if (occupancyTypes.length === 0) { + // eslint-disable-next-line no-unsanitized/method + occupancyTypesContainerElement.insertAdjacentHTML('afterbegin', `
    There are no active ${los.escapedAliases.occupancy} types.

    +
    `); + // eslint-disable-next-line no-unsanitized/method + ContractTypePrintsContainerElement.insertAdjacentHTML('afterbegin', `
    There are no active ${los.escapedAliases.occupancy} types.

    +
    `); + return; + } + for (const occupancyType of occupancyTypes) { + /* + * Types and Fields + */ + const occupancyTypeContainer = document.createElement('div'); + occupancyTypeContainer.className = 'panel container--occupancyType'; + occupancyTypeContainer.dataset.contractTypeId = + occupancyType.contractTypeId.toString(); + // eslint-disable-next-line no-unsanitized/property + occupancyTypeContainer.innerHTML = `
    +
    +
    +
    + +
    +
    +

    ${cityssm.escapeHTML(occupancyType.occupancyType)}

    +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + ${los.getMoveUpDownButtonFieldHTML('button--moveOccupancyTypeUp', 'button--moveOccupancyTypeDown')} +
    +
    +
    +
    `; + renderContractTypeFields(occupancyTypeContainer, occupancyType.contractTypeId, occupancyType.ContractTypeFields ?? []); + occupancyTypeContainer + .querySelector('.button--toggleContractTypeFields') + ?.addEventListener('click', toggleContractTypeFields); + occupancyTypeContainer + .querySelector('.button--deleteOccupancyType') + ?.addEventListener('click', deleteOccupancyType); + occupancyTypeContainer + .querySelector('.button--editOccupancyType') + ?.addEventListener('click', openEditOccupancyType); + occupancyTypeContainer + .querySelector('.button--addOccupancyTypeField') + ?.addEventListener('click', openAddOccupancyTypeField); + occupancyTypeContainer.querySelector('.button--moveOccupancyTypeUp').addEventListener('click', moveOccupancyType); + occupancyTypeContainer.querySelector('.button--moveOccupancyTypeDown').addEventListener('click', moveOccupancyType); + occupancyTypesContainerElement.append(occupancyTypeContainer); + /* + * Prints + */ + const occupancyTypePrintContainer = document.createElement('div'); + occupancyTypePrintContainer.className = + 'panel container--occupancyTypePrintList'; + occupancyTypePrintContainer.dataset.contractTypeId = + occupancyType.contractTypeId.toString(); + occupancyTypePrintContainer.innerHTML = `
    +
    +
    +
    +

    ${cityssm.escapeHTML(occupancyType.occupancyType)}

    +
    +
    +
    +
    + +
    +
    +
    +
    `; + renderContractTypePrints(occupancyTypePrintContainer, occupancyType.contractTypeId, occupancyType.ContractTypePrints ?? []); + occupancyTypePrintContainer + .querySelector('.button--addOccupancyTypePrint') + ?.addEventListener('click', openAddOccupancyTypePrint); + ContractTypePrintsContainerElement.append(occupancyTypePrintContainer); + } + } + document + .querySelector('#button--addOccupancyType') + ?.addEventListener('click', () => { + let addCloseModalFunction; + function doAdd(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doAddContractType`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + addCloseModalFunction(); + occupancyTypes = responseJSON.occupancyTypes; + renderOccupancyTypes(); + } + else { + bulmaJS.alert({ + title: `Error Adding ${los.escapedAliases.Occupancy} Type`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + cityssm.openHtmlModal('adminOccupancyTypes-addOccupancyType', { + onshow(modalElement) { + los.populateAliases(modalElement); + }, + onshown(modalElement, closeModalFunction) { + addCloseModalFunction = closeModalFunction; + modalElement.querySelector('#occupancyTypeAdd--occupancyType').focus(); + modalElement.querySelector('form')?.addEventListener('submit', doAdd); + bulmaJS.toggleHtmlClipped(); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + } + }); + }); + renderOccupancyTypes(); +})(); diff --git a/public/javascripts/database.admin.d.ts b/public/javascripts/database.admin.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/database.admin.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/database.admin.js b/public/javascripts/database.admin.js new file mode 100644 index 00000000..29220e32 --- /dev/null +++ b/public/javascripts/database.admin.js @@ -0,0 +1,70 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const los = exports.los; + function doBackup() { + cityssm.postJSON(`${los.urlPrefix}/admin/doBackupDatabase`, {}, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + bulmaJS.alert({ + title: 'Database Backed Up Successfully', + message: `Backed up to ${responseJSON.fileName}
    + To request a copy of the backup, contact your application administrator.`, + messageIsHtml: true, + contextualColorName: 'success' + }); + } + else { + bulmaJS.alert({ + title: 'Error Backing Up Database', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + function doCleanup() { + cityssm.postJSON(`${los.urlPrefix}/admin/doCleanupDatabase`, {}, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + bulmaJS.alert({ + title: 'Database Cleaned Up Successfully', + message: `${responseJSON.inactivatedRecordCount} records inactivated, + ${responseJSON.purgedRecordCount} permanently deleted.`, + contextualColorName: 'success' + }); + } + else { + bulmaJS.alert({ + title: 'Error Cleaning Database', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + document + .querySelector('#button--cleanupDatabase') + ?.addEventListener('click', () => { + bulmaJS.confirm({ + title: 'Cleanup Database', + message: 'Are you sure you want to cleanup up the database?', + okButton: { + text: 'Yes, Cleanup Database', + callbackFunction: doCleanup + } + }); + }); + document + .querySelector('#button--backupDatabase') + ?.addEventListener('click', () => { + bulmaJS.confirm({ + title: 'Backup Database', + message: 'Are you sure you want to backup up the database?', + okButton: { + text: 'Yes, Backup Database', + callbackFunction: doBackup + } + }); + }); +})(); diff --git a/public/javascripts/fees.admin.d.ts b/public/javascripts/fees.admin.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/fees.admin.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/fees.admin.js b/public/javascripts/fees.admin.js new file mode 100644 index 00000000..9daae9f7 --- /dev/null +++ b/public/javascripts/fees.admin.js @@ -0,0 +1,659 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const los = exports.los; + const feeCategoriesContainerElement = document.querySelector('#container--feeCategories'); + let feeCategories = exports.feeCategories; + delete exports.feeCategories; + function getFeeCategory(feeCategoryId) { + return feeCategories.find((currentFeeCategory) => currentFeeCategory.feeCategoryId === feeCategoryId); + } + function getFee(feeCategory, feeId) { + return feeCategory.fees.find((currentFee) => currentFee.feeId === feeId); + } + function renderFeeCategories() { + if (feeCategories.length === 0) { + feeCategoriesContainerElement.innerHTML = `
    +

    There are no available fees.

    +
    `; + return; + } + feeCategoriesContainerElement.innerHTML = ''; + for (const feeCategory of feeCategories) { + const feeCategoryContainerElement = document.createElement('section'); + feeCategoryContainerElement.className = 'panel container--feeCategory'; + feeCategoryContainerElement.dataset.feeCategoryId = + feeCategory.feeCategoryId.toString(); + // eslint-disable-next-line no-unsanitized/property + feeCategoryContainerElement.innerHTML = `
    +
    +
    +

    ${cityssm.escapeHTML(feeCategory.feeCategory ?? '')}

    + ${feeCategory.isGroupedFee + ? 'Grouped Fee' + : ''} +
    +
    +
    + ${feeCategory.fees.length === 0 + ? `
    + +
    ` + : ''} +
    + +
    +
    + +
    +
    + ${los.getMoveUpDownButtonFieldHTML('button--moveFeeCategoryUp', 'button--moveFeeCategoryDown')} +
    +
    +
    +
    `; + if (feeCategory.fees.length === 0) { + feeCategoryContainerElement.insertAdjacentHTML('beforeend', `
    +
    +

    + There are no fees in the + "${cityssm.escapeHTML(feeCategory.feeCategory ?? '')}" + category. +

    +
    +
    `); + feeCategoryContainerElement + .querySelector('.button--deleteFeeCategory') + ?.addEventListener('click', confirmDeleteFeeCategory); + } + for (const fee of feeCategory.fees) { + const panelBlockElement = document.createElement('div'); + panelBlockElement.className = 'panel-block is-block container--fee'; + panelBlockElement.dataset.feeId = fee.feeId.toString(); + const hasTagsBlock = (fee.isRequired ?? false) || + fee.contractTypeId !== undefined || + fee.burialSiteTypeId !== undefined; + // eslint-disable-next-line no-unsanitized/property + panelBlockElement.innerHTML = `
    +
    +

    + ${cityssm.escapeHTML(fee.feeName ?? '')}
    + + ${ + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + cityssm + .escapeHTML(fee.feeDescription ?? '') + .replaceAll('\n', '
    ')} +
    +

    + ${hasTagsBlock + ? `

    + ${fee.isRequired ?? false + ? 'Required' + : ''} + ${(fee.contractTypeId ?? -1) === -1 + ? '' + : ` + + ${cityssm.escapeHTML(fee.occupancyType ?? '')} + `} + ${(fee.burialSiteTypeId ?? -1) === -1 + ? '' + : ` + + ${cityssm.escapeHTML(fee.lotType ?? '')} + `} +

    ` + : ''} +
    +
    +
    +
    + ${fee.feeFunction + ? `${cityssm.escapeHTML(fee.feeFunction)}
    + Fee Function` + : ` + $${(fee.feeAmount ?? 0).toFixed(2)}
    + Fee +
    `} +
    +
    + ${fee.taxPercentage + ? `${fee.taxPercentage.toString()}%` + : `$${(fee.taxAmount ?? 0).toFixed(2)}`}
    + Tax +
    +
    + ${fee.includeQuantity + ? `${cityssm.escapeHTML(fee.quantityUnit ?? '')}
    + Quantity` + : ''} +
    +
    +
    +
    + ${los.getMoveUpDownButtonFieldHTML('button--moveFeeUp', 'button--moveFeeDown')} +
    +
    `; + panelBlockElement + .querySelector('.a--editFee') + ?.addEventListener('click', openEditFee); + panelBlockElement + .querySelector('.a--editFeeAmount') + ?.addEventListener('click', openEditFeeAmount); + panelBlockElement.querySelector('.button--moveFeeUp').addEventListener('click', moveFee); + panelBlockElement.querySelector('.button--moveFeeDown').addEventListener('click', moveFee); + feeCategoryContainerElement.append(panelBlockElement); + } + feeCategoryContainerElement + .querySelector('.button--editFeeCategory') + ?.addEventListener('click', openEditFeeCategory); + feeCategoryContainerElement + .querySelector('.button--addFee') + ?.addEventListener('click', openAddFee); + feeCategoryContainerElement.querySelector('.button--moveFeeCategoryUp').addEventListener('click', moveFeeCategory); + feeCategoryContainerElement.querySelector('.button--moveFeeCategoryDown').addEventListener('click', moveFeeCategory); + feeCategoriesContainerElement.append(feeCategoryContainerElement); + } + } + /* + * Fee Categories + */ + document + .querySelector('#button--addFeeCategory') + ?.addEventListener('click', () => { + let addCloseModalFunction; + function doAddFeeCategory(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doAddFeeCategory`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + feeCategories = responseJSON.feeCategories; + addCloseModalFunction(); + renderFeeCategories(); + } + else { + bulmaJS.alert({ + title: 'Error Creating Fee Category', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + cityssm.openHtmlModal('adminFees-addFeeCategory', { + onshown(modalElement, closeModalFunction) { + bulmaJS.toggleHtmlClipped(); + modalElement.querySelector('#feeCategoryAdd--feeCategory').focus(); + addCloseModalFunction = closeModalFunction; + modalElement + .querySelector('form') + ?.addEventListener('submit', doAddFeeCategory); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + document.querySelector('#button--addFeeCategory').focus(); + } + }); + }); + function openEditFeeCategory(clickEvent) { + const feeCategoryId = Number.parseInt(clickEvent.currentTarget.closest('.container--feeCategory').dataset.feeCategoryId ?? '', 10); + const feeCategory = getFeeCategory(feeCategoryId); + let editCloseModalFunction; + function doUpdateFeeCategory(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doUpdateFeeCategory`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + feeCategories = responseJSON.feeCategories; + editCloseModalFunction(); + renderFeeCategories(); + } + else { + bulmaJS.alert({ + title: 'Error Updating Fee Category', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + cityssm.openHtmlModal('adminFees-editFeeCategory', { + onshow(modalElement) { + ; + modalElement.querySelector('#feeCategoryEdit--feeCategoryId').value = feeCategory.feeCategoryId.toString(); + modalElement.querySelector('#feeCategoryEdit--feeCategory').value = feeCategory.feeCategory; + if (feeCategory.isGroupedFee) { + ; + modalElement.querySelector('#feeCategoryEdit--isGroupedFee').checked = true; + } + }, + onshown(modalElement, closeModalFunction) { + bulmaJS.toggleHtmlClipped(); + editCloseModalFunction = closeModalFunction; + modalElement + .querySelector('form') + ?.addEventListener('submit', doUpdateFeeCategory); + modalElement.querySelector('#feeCategoryEdit--feeCategory').focus(); + }, + onremoved: () => { + bulmaJS.toggleHtmlClipped(); + } + }); + } + function confirmDeleteFeeCategory(clickEvent) { + const feeCategoryId = Number.parseInt(clickEvent.currentTarget.closest('.container--feeCategory').dataset.feeCategoryId ?? '', 10); + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/admin/doDeleteFeeCategory`, { + feeCategoryId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + feeCategories = responseJSON.feeCategories; + renderFeeCategories(); + } + else { + bulmaJS.alert({ + title: 'Error Updating Fee Category', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + bulmaJS.confirm({ + title: 'Delete Fee Category?', + message: 'Are you sure you want to delete this fee category?', + contextualColorName: 'warning', + okButton: { + text: 'Yes, Delete the Fee Category', + callbackFunction: doDelete + } + }); + } + function moveFeeCategory(clickEvent) { + const buttonElement = clickEvent.currentTarget; + const feeCategoryId = buttonElement.closest('.container--feeCategory').dataset + .feeCategoryId ?? ''; + cityssm.postJSON(`${los.urlPrefix}/admin/${buttonElement.dataset.direction === 'up' + ? 'doMoveFeeCategoryUp' + : 'doMoveFeeCategoryDown'}`, { + feeCategoryId, + moveToEnd: clickEvent.shiftKey ? '1' : '0' + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + feeCategories = responseJSON.feeCategories; + renderFeeCategories(); + } + else { + bulmaJS.alert({ + title: 'Error Moving Fee Category', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + /* + * Fees + */ + function openAddFee(clickEvent) { + const feeCategoryId = Number.parseInt(clickEvent.currentTarget.closest('.container--feeCategory').dataset.feeCategoryId ?? '', 10); + let addCloseModalFunction; + function doAddFee(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doAddFee`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + feeCategories = responseJSON.feeCategories; + addCloseModalFunction(); + renderFeeCategories(); + } + else { + bulmaJS.alert({ + title: 'Error Adding Fee', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + cityssm.openHtmlModal('adminFees-addFee', { + onshow(modalElement) { + const feeCategoryElement = modalElement.querySelector('#feeAdd--feeCategoryId'); + for (const feeCategory of feeCategories) { + const optionElement = document.createElement('option'); + optionElement.value = feeCategory.feeCategoryId.toString(); + optionElement.textContent = feeCategory.feeCategory; + if (feeCategory.feeCategoryId === feeCategoryId) { + optionElement.selected = true; + } + feeCategoryElement.append(optionElement); + } + const occupancyTypeElement = modalElement.querySelector('#feeAdd--contractTypeId'); + for (const occupancyType of exports.occupancyTypes) { + const optionElement = document.createElement('option'); + optionElement.value = occupancyType.contractTypeId.toString(); + optionElement.textContent = occupancyType.occupancyType; + occupancyTypeElement.append(optionElement); + } + const lotTypeElement = modalElement.querySelector('#feeAdd--burialSiteTypeId'); + for (const lotType of exports.lotTypes) { + const optionElement = document.createElement('option'); + optionElement.value = lotType.burialSiteTypeId.toString(); + optionElement.textContent = lotType.lotType; + lotTypeElement.append(optionElement); + } + ; + modalElement.querySelector('#feeAdd--taxPercentage').value = exports.taxPercentageDefault.toString(); + los.populateAliases(modalElement); + }, + onshown(modalElement, closeModalFunction) { + bulmaJS.toggleHtmlClipped(); + addCloseModalFunction = closeModalFunction; + modalElement.querySelector('form')?.addEventListener('submit', doAddFee); + modalElement.querySelector('#feeAdd--feeName').focus(); + modalElement.querySelector('#feeAdd--feeFunction').addEventListener('change', () => { + const feeAmountElement = modalElement.querySelector('#feeAdd--feeAmount'); + const feeFunctionElement = modalElement.querySelector('#feeAdd--feeFunction'); + if (feeFunctionElement.value === '') { + feeFunctionElement + .closest('.select') + ?.classList.remove('is-success'); + feeAmountElement.classList.add('is-success'); + feeAmountElement.disabled = false; + } + else { + feeFunctionElement.closest('.select')?.classList.add('is-success'); + feeAmountElement.classList.remove('is-success'); + feeAmountElement.disabled = true; + } + }); + modalElement + .querySelector('#feeAdd--taxPercentage') + ?.addEventListener('keyup', () => { + const taxAmountElement = modalElement.querySelector('#feeAdd--taxAmount'); + const taxPercentageElement = modalElement.querySelector('#feeAdd--taxPercentage'); + if (taxPercentageElement.value === '') { + taxPercentageElement.classList.remove('is-success'); + taxAmountElement.classList.add('is-success'); + taxAmountElement.disabled = false; + } + else { + taxPercentageElement.classList.add('is-success'); + taxAmountElement.classList.remove('is-success'); + taxAmountElement.disabled = true; + } + }); + modalElement + .querySelector('#feeAdd--includeQuantity') + ?.addEventListener('change', () => { + ; + modalElement.querySelector('#feeAdd--quantityUnit').disabled = + modalElement.querySelector('#feeAdd--includeQuantity').value === ''; + }); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + } + }); + } + function openEditFeeAmount(clickEvent) { + clickEvent.preventDefault(); + const feeContainerElement = clickEvent.currentTarget.closest('.container--fee'); + const feeId = Number.parseInt(feeContainerElement.dataset.feeId ?? '', 10); + const feeCategoryId = Number.parseInt(feeContainerElement.closest('.container--feeCategory') + .dataset.feeCategoryId ?? ''); + const feeCategory = getFeeCategory(feeCategoryId); + const fee = getFee(feeCategory, feeId); + let editCloseModalFunction; + function doUpdateFeeAmount(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doUpdateFeeAmount`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + feeCategories = responseJSON.feeCategories; + editCloseModalFunction(); + renderFeeCategories(); + } + else { + bulmaJS.alert({ + title: 'Error Updating Fee Amount', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + cityssm.openHtmlModal('adminFees-editFeeAmount', { + onshow(modalElement) { + ; + modalElement.querySelector('#feeAmountEdit--feeId').value = fee.feeId.toString(); + modalElement.querySelector('#feeAmountEdit--feeCategory').textContent = feeCategory.feeCategory; + modalElement.querySelector('#feeAmountEdit--feeName').textContent = fee.feeName ?? ''; + modalElement.querySelector('#feeAmountEdit--feeAmount').value = fee.feeAmount?.toFixed(2) ?? '0'; + }, + onshown(modalElement, closeModalFunction) { + ; + modalElement.querySelector('#feeAmountEdit--feeAmount').select(); + editCloseModalFunction = closeModalFunction; + modalElement + .querySelector('form') + ?.addEventListener('submit', doUpdateFeeAmount); + } + }); + } + function openEditFee(clickEvent) { + clickEvent.preventDefault(); + const feeContainerElement = clickEvent.currentTarget.closest('.container--fee'); + const feeId = Number.parseInt(feeContainerElement.dataset.feeId ?? '', 10); + const feeCategoryId = Number.parseInt(feeContainerElement.closest('.container--feeCategory') + .dataset.feeCategoryId ?? ''); + const feeCategory = getFeeCategory(feeCategoryId); + const fee = getFee(feeCategory, feeId); + let editCloseModalFunction; + let editModalElement; + function doUpdateFee(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doUpdateFee`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + feeCategories = responseJSON.feeCategories; + editCloseModalFunction(); + renderFeeCategories(); + } + else { + bulmaJS.alert({ + title: 'Error Updating Fee', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + function confirmDeleteFee(clickEvent) { + clickEvent.preventDefault(); + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/admin/doDeleteFee`, { + feeId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + feeCategories = responseJSON.feeCategories; + editCloseModalFunction(); + renderFeeCategories(); + } + else { + bulmaJS.alert({ + title: 'Error Deleting Fee', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + bulmaJS.confirm({ + title: 'Delete Fee?', + message: 'Are you sure you want to delete this fee?', + contextualColorName: 'warning', + okButton: { + text: 'Yes, Delete the Fee', + callbackFunction: doDelete + } + }); + } + function toggleFeeFields() { + const feeAmountElement = editModalElement.querySelector('#feeEdit--feeAmount'); + const feeFunctionElement = editModalElement.querySelector('#feeEdit--feeFunction'); + if (feeFunctionElement.value === '') { + feeFunctionElement.closest('.select')?.classList.remove('is-success'); + feeAmountElement.classList.add('is-success'); + feeAmountElement.disabled = false; + } + else { + feeFunctionElement.closest('.select')?.classList.add('is-success'); + feeAmountElement.classList.remove('is-success'); + feeAmountElement.disabled = true; + } + } + function toggleTaxFields() { + const taxAmountElement = editModalElement.querySelector('#feeEdit--taxAmount'); + const taxPercentageElement = editModalElement.querySelector('#feeEdit--taxPercentage'); + if (taxPercentageElement.value === '') { + taxPercentageElement.classList.remove('is-success'); + taxAmountElement.classList.add('is-success'); + taxAmountElement.disabled = false; + } + else { + taxPercentageElement.classList.add('is-success'); + taxAmountElement.classList.remove('is-success'); + taxAmountElement.disabled = true; + } + } + function toggleQuantityFields() { + const includeQuantityValue = editModalElement.querySelector('#feeEdit--includeQuantity').value; + editModalElement.querySelector('#feeEdit--quantityUnit').disabled = includeQuantityValue === ''; + } + cityssm.openHtmlModal('adminFees-editFee', { + onshow(modalElement) { + editModalElement = modalElement; + modalElement.querySelector('#feeEdit--feeId').value = fee.feeId.toString(); + const feeCategoryElement = modalElement.querySelector('#feeEdit--feeCategoryId'); + for (const feeCategory of feeCategories) { + const optionElement = document.createElement('option'); + optionElement.value = feeCategory.feeCategoryId.toString(); + optionElement.textContent = feeCategory.feeCategory; + if (feeCategory.feeCategoryId === feeCategoryId) { + optionElement.selected = true; + } + feeCategoryElement.append(optionElement); + } + ; + modalElement.querySelector('#feeEdit--feeName').value = fee.feeName ?? ''; + modalElement.querySelector('#feeEdit--feeAccount').value = fee.feeAccount ?? ''; + modalElement.querySelector('#feeEdit--feeDescription').value = fee.feeDescription ?? ''; + const occupancyTypeElement = modalElement.querySelector('#feeEdit--contractTypeId'); + for (const occupancyType of exports.occupancyTypes) { + const optionElement = document.createElement('option'); + optionElement.value = occupancyType.contractTypeId.toString(); + optionElement.textContent = occupancyType.occupancyType; + if (occupancyType.contractTypeId === fee.contractTypeId) { + optionElement.selected = true; + } + occupancyTypeElement.append(optionElement); + } + const lotTypeElement = modalElement.querySelector('#feeEdit--burialSiteTypeId'); + for (const lotType of exports.lotTypes) { + const optionElement = document.createElement('option'); + optionElement.value = lotType.burialSiteTypeId.toString(); + optionElement.textContent = lotType.lotType; + if (lotType.burialSiteTypeId === fee.burialSiteTypeId) { + optionElement.selected = true; + } + lotTypeElement.append(optionElement); + } + ; + modalElement.querySelector('#feeEdit--feeAmount').value = fee.feeAmount ? fee.feeAmount.toFixed(2) : ''; + modalElement + .querySelector('#feeEdit--feeFunction') + ?.addEventListener('change', toggleFeeFields); + toggleFeeFields(); + modalElement.querySelector('#feeEdit--taxAmount').value = fee.taxAmount ? fee.taxAmount.toFixed(2) : ''; + const taxPercentageElement = modalElement.querySelector('#feeEdit--taxPercentage'); + taxPercentageElement.value = fee.taxPercentage + ? fee.taxPercentage.toString() + : ''; + taxPercentageElement.addEventListener('keyup', toggleTaxFields); + toggleTaxFields(); + const includeQuantityElement = modalElement.querySelector('#feeEdit--includeQuantity'); + if (fee.includeQuantity ?? false) { + includeQuantityElement.value = '1'; + } + includeQuantityElement.addEventListener('change', toggleQuantityFields); + modalElement.querySelector('#feeEdit--quantityUnit').value = fee.quantityUnit ?? ''; + toggleQuantityFields(); + if (fee.isRequired ?? false) { + ; + modalElement.querySelector('#feeEdit--isRequired').value = '1'; + } + los.populateAliases(modalElement); + }, + onshown(modalElement, closeModalFunction) { + bulmaJS.toggleHtmlClipped(); + editCloseModalFunction = closeModalFunction; + modalElement + .querySelector('form') + ?.addEventListener('submit', doUpdateFee); + bulmaJS.init(modalElement); + modalElement + .querySelector('.button--deleteFee') + ?.addEventListener('click', confirmDeleteFee); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + } + }); + } + function moveFee(clickEvent) { + const buttonElement = clickEvent.currentTarget; + const feeContainerElement = buttonElement.closest('.container--fee'); + const feeId = feeContainerElement.dataset.feeId ?? ''; + cityssm.postJSON(`${los.urlPrefix}/admin/${buttonElement.dataset.direction === 'up' + ? 'doMoveFeeUp' + : 'doMoveFeeDown'}`, { + feeId, + moveToEnd: clickEvent.shiftKey ? '1' : '0' + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + feeCategories = responseJSON.feeCategories; + renderFeeCategories(); + } + else { + bulmaJS.alert({ + title: 'Error Moving Fee', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + /* + * Initialize + */ + renderFeeCategories(); +})(); diff --git a/public/javascripts/main.js b/public/javascripts/main.js index 62a9c809..cd63d226 100644 --- a/public/javascripts/main.js +++ b/public/javascripts/main.js @@ -25,7 +25,6 @@ Object.defineProperty(exports, "__esModule", { value: true }); // Search for ID let svgId = mapKey; let svgElementToHighlight; - // eslint-disable-next-line no-constant-condition while (true) { svgElementToHighlight = mapContainerElement.querySelector(`#${svgId}`); if (svgElementToHighlight !== null || !svgId.includes('-')) { @@ -44,7 +43,6 @@ Object.defineProperty(exports, "__esModule", { value: true }); } function unlockField(clickEvent) { const fieldElement = clickEvent.currentTarget.closest('.field'); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const inputOrSelectElement = fieldElement.querySelector('input, select'); inputOrSelectElement.classList.remove('is-readonly'); if (inputOrSelectElement.tagName === 'INPUT') { @@ -141,63 +139,15 @@ Object.defineProperty(exports, "__esModule", { value: true }); function populateAliases(containerElement) { const aliasElements = containerElement.querySelectorAll('.alias'); for (const aliasElement of aliasElements) { - switch (aliasElement.dataset.alias) { - case 'Map': { - aliasElement.textContent = exports.aliases.map; - break; - } - case 'Lot': { - aliasElement.textContent = exports.aliases.lot; - break; - } - case 'lot': { - aliasElement.textContent = exports.aliases.lot.toLowerCase(); - break; - } - case 'Occupancy': { - aliasElement.textContent = exports.aliases.occupancy; - break; - } - case 'occupancy': { - aliasElement.textContent = exports.aliases.occupancy.toLowerCase(); - break; - } - case 'Occupant': { - aliasElement.textContent = exports.aliases.occupant; - break; - } - case 'occupant': { - aliasElement.textContent = exports.aliases.occupant.toLowerCase(); - break; - } - case 'ExternalReceiptNumber': { - aliasElement.textContent = exports.aliases.externalReceiptNumber; - break; - } + if (aliasElement.dataset.alias === 'ExternalReceiptNumber') { + aliasElement.textContent = exports.aliases.externalReceiptNumber; + break; } } } const escapedAliases = Object.freeze({ - Map: cityssm.escapeHTML(exports.aliases.map), - map: cityssm.escapeHTML(exports.aliases.map.toLowerCase()), - Maps: cityssm.escapeHTML(exports.aliases.maps), - maps: cityssm.escapeHTML(exports.aliases.maps.toLowerCase()), - Lot: cityssm.escapeHTML(exports.aliases.lot), - lot: cityssm.escapeHTML(exports.aliases.lot.toLowerCase()), - Lots: cityssm.escapeHTML(exports.aliases.lots), - lots: cityssm.escapeHTML(exports.aliases.lots.toLowerCase()), - Occupancy: cityssm.escapeHTML(exports.aliases.occupancy), - occupancy: cityssm.escapeHTML(exports.aliases.occupancy.toLowerCase()), - Occupancies: cityssm.escapeHTML(exports.aliases.occupancies), - occupancies: cityssm.escapeHTML(exports.aliases.occupancies.toLowerCase()), - Occupant: cityssm.escapeHTML(exports.aliases.occupant), - occupant: cityssm.escapeHTML(exports.aliases.occupant.toLowerCase()), - Occupants: cityssm.escapeHTML(exports.aliases.occupants), - occupants: cityssm.escapeHTML(exports.aliases.occupants.toLowerCase()), ExternalReceiptNumber: cityssm.escapeHTML(exports.aliases.externalReceiptNumber), externalReceiptNumber: cityssm.escapeHTML(exports.aliases.externalReceiptNumber.toLowerCase()), - OccupancyStartDate: cityssm.escapeHTML(exports.aliases.occupancyStartDate), - occupancyStartDate: cityssm.escapeHTML(exports.aliases.occupancyStartDate.toLowerCase()), WorkOrderOpenDate: cityssm.escapeHTML(exports.aliases.workOrderOpenDate), workOrderOpenDate: cityssm.escapeHTML(exports.aliases.workOrderOpenDate.toLowerCase()), WorkOrderCloseDate: cityssm.escapeHTML(exports.aliases.workOrderCloseDate), @@ -298,14 +248,14 @@ Object.defineProperty(exports, "__esModule", { value: true }); (recordId && edit ? '/edit' : '') + (time ? `/?t=${Date.now().toString()}` : '')); } - function getMapURL(mapId = '', edit = false, time = false) { - return getRecordURL('maps', mapId, edit, time); + function getCemeteryURL(cemeteryId = '', edit = false, time = false) { + return getRecordURL('cemeteries', cemeteryId, edit, time); } - function getLotURL(lotId = '', edit = false, time = false) { - return getRecordURL('lots', lotId, edit, time); + function getBurialSiteURL(burialSiteId = '', edit = false, time = false) { + return getRecordURL('burialSites', burialSiteId, edit, time); } - function getLotOccupancyURL(lotOccupancyId = '', edit = false, time = false) { - return getRecordURL('lotOccupancies', lotOccupancyId, edit, time); + function getBurialSiteContractURL(burialSiteContractId = '', edit = false, time = false) { + return getRecordURL('contracts', burialSiteContractId, edit, time); } function getWorkOrderURL(workOrderId = '', edit = false, time = false) { return getRecordURL('workOrders', workOrderId, edit, time); @@ -333,9 +283,9 @@ Object.defineProperty(exports, "__esModule", { value: true }); getMoveUpDownButtonFieldHTML, getLoadingParagraphHTML, getSearchResultsPagerHTML, - getMapURL, - getLotURL, - getLotOccupancyURL, + getCemeteryURL, + getBurialSiteURL, + getBurialSiteContractURL, getWorkOrderURL }; exports.los = los; diff --git a/public/javascripts/main.ts b/public/javascripts/main.ts index 5907c5d9..1d81e24a 100644 --- a/public/javascripts/main.ts +++ b/public/javascripts/main.ts @@ -2,7 +2,7 @@ import type { BulmaJS } from '@cityssm/bulma-js/types.js' import type { cityssmGlobal } from '@cityssm/bulma-webapp-js/src/types.js' import type { Options as BulmaCalendarOptions } from 'bulma-calendar' -import type { LOS } from '../../types/globalTypes.js' +import type { LOS } from './types.js' type RandomColorHue = | 'red' @@ -64,7 +64,6 @@ declare const exports: Record & { let svgId = mapKey let svgElementToHighlight: SVGElement | null - // eslint-disable-next-line no-constant-condition while (true) { svgElementToHighlight = mapContainerElement.querySelector(`#${svgId}`) @@ -92,7 +91,6 @@ declare const exports: Record & { '.field' ) as HTMLElement - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const inputOrSelectElement = fieldElement.querySelector( 'input, select' ) as HTMLElement @@ -230,64 +228,14 @@ declare const exports: Record & { containerElement.querySelectorAll('.alias') for (const aliasElement of aliasElements) { - switch (aliasElement.dataset.alias) { - case 'Map': { - aliasElement.textContent = exports.aliases.map - break - } - case 'Lot': { - aliasElement.textContent = exports.aliases.lot - break - } - case 'lot': { - aliasElement.textContent = exports.aliases.lot.toLowerCase() - break - } - case 'Occupancy': { - aliasElement.textContent = exports.aliases.occupancy - break - } - case 'occupancy': { - aliasElement.textContent = exports.aliases.occupancy.toLowerCase() - break - } - case 'Occupant': { - aliasElement.textContent = exports.aliases.occupant - break - } - case 'occupant': { - aliasElement.textContent = exports.aliases.occupant.toLowerCase() - break - } - case 'ExternalReceiptNumber': { - aliasElement.textContent = exports.aliases.externalReceiptNumber - break - } + if (aliasElement.dataset.alias === 'ExternalReceiptNumber') { + aliasElement.textContent = exports.aliases.externalReceiptNumber + break } } } const escapedAliases = Object.freeze({ - Map: cityssm.escapeHTML(exports.aliases.map), - map: cityssm.escapeHTML(exports.aliases.map.toLowerCase()), - Maps: cityssm.escapeHTML(exports.aliases.maps), - maps: cityssm.escapeHTML(exports.aliases.maps.toLowerCase()), - - Lot: cityssm.escapeHTML(exports.aliases.lot), - lot: cityssm.escapeHTML(exports.aliases.lot.toLowerCase()), - Lots: cityssm.escapeHTML(exports.aliases.lots), - lots: cityssm.escapeHTML(exports.aliases.lots.toLowerCase()), - - Occupancy: cityssm.escapeHTML(exports.aliases.occupancy), - occupancy: cityssm.escapeHTML(exports.aliases.occupancy.toLowerCase()), - Occupancies: cityssm.escapeHTML(exports.aliases.occupancies), - occupancies: cityssm.escapeHTML(exports.aliases.occupancies.toLowerCase()), - - Occupant: cityssm.escapeHTML(exports.aliases.occupant), - occupant: cityssm.escapeHTML(exports.aliases.occupant.toLowerCase()), - Occupants: cityssm.escapeHTML(exports.aliases.occupants), - occupants: cityssm.escapeHTML(exports.aliases.occupants.toLowerCase()), - ExternalReceiptNumber: cityssm.escapeHTML( exports.aliases.externalReceiptNumber ), @@ -295,11 +243,6 @@ declare const exports: Record & { exports.aliases.externalReceiptNumber.toLowerCase() ), - contractStartDate: cityssm.escapeHTML(exports.aliases.contractStartDate), - contractStartDate: cityssm.escapeHTML( - exports.aliases.contractStartDate.toLowerCase() - ), - WorkOrderOpenDate: cityssm.escapeHTML(exports.aliases.workOrderOpenDate), workOrderOpenDate: cityssm.escapeHTML( exports.aliases.workOrderOpenDate.toLowerCase() @@ -429,7 +372,7 @@ declare const exports: Record & { const urlPrefix = document.querySelector('main')?.dataset.urlPrefix ?? '' function getRecordURL( - recordTypePlural: 'maps' | 'lots' | 'lotOccupancies' | 'workOrders', + recordTypePlural: 'cemeteries' | 'burialSites' | 'contracts' | 'workOrders', recordId: number | string, edit: boolean, time: boolean @@ -444,20 +387,20 @@ declare const exports: Record & { ) } - function getMapURL( + function getCemeteryURL( cemeteryId: number | string = '', edit = false, time = false ): string { - return getRecordURL('maps', cemeteryId, edit, time) + return getRecordURL('cemeteries', cemeteryId, edit, time) } - function getLotURL( - lotId: number | string = '', + function getBurialSiteURL( + burialSiteId: number | string = '', edit = false, time = false ): string { - return getRecordURL('lots', lotId, edit, time) + return getRecordURL('burialSites', burialSiteId, edit, time) } function getBurialSiteContractURL( @@ -465,7 +408,7 @@ declare const exports: Record & { edit = false, time = false ): string { - return getRecordURL('lotOccupancies', burialSiteContractId, edit, time) + return getRecordURL('contracts', burialSiteContractId, edit, time) } function getWorkOrderURL( @@ -508,8 +451,8 @@ declare const exports: Record & { getLoadingParagraphHTML, getSearchResultsPagerHTML, - getMapURL, - getLotURL, + getCemeteryURL, + getBurialSiteURL, getBurialSiteContractURL, getWorkOrderURL } diff --git a/public/javascripts/report.search.d.ts b/public/javascripts/report.search.d.ts new file mode 100644 index 00000000..e69de29b diff --git a/public/javascripts/report.search.js b/public/javascripts/report.search.js new file mode 100644 index 00000000..ba0bae8e --- /dev/null +++ b/public/javascripts/report.search.js @@ -0,0 +1,28 @@ +; +(() => { + const menuTabElements = document.querySelectorAll('.menu a'); + const tabContainerElements = document.querySelectorAll('.tabs-container > div'); + function selectTab(clickEvent) { + clickEvent.preventDefault(); + // Remove .is-active from all tabs + for (const menuTabElement of menuTabElements) { + menuTabElement.classList.remove('is-active'); + } + // Set .is-active on clicked tab + const selectedTabElement = clickEvent.currentTarget; + selectedTabElement.classList.add('is-active'); + // Hide all but selected tab + const selectedTabContainerId = selectedTabElement.href.slice(Math.max(0, selectedTabElement.href.indexOf('#') + 1)); + for (const tabContainerElement of tabContainerElements) { + if (tabContainerElement.id === selectedTabContainerId) { + tabContainerElement.classList.remove('is-hidden'); + } + else { + tabContainerElement.classList.add('is-hidden'); + } + } + } + for (const menuTabElement of menuTabElements) { + menuTabElement.addEventListener('click', selectTab); + } +})(); diff --git a/public/javascripts/tables.admin.d.ts b/public/javascripts/tables.admin.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/tables.admin.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/tables.admin.js b/public/javascripts/tables.admin.js new file mode 100644 index 00000000..9e40ee02 --- /dev/null +++ b/public/javascripts/tables.admin.js @@ -0,0 +1,716 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const los = exports.los; + function refreshFontAwesomeIcon(changeEvent) { + const inputElement = changeEvent.currentTarget; + const fontAwesomeIconClass = inputElement.value; + (inputElement + .closest('.field') + ?.querySelectorAll('.button.is-static'))[1].innerHTML = + ``; + } + /** + * Work Order Types + */ + ; + (() => { + let workOrderTypes = exports.workOrderTypes; + delete exports.workOrderTypes; + function updateWorkOrderType(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doUpdateWorkOrderType`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + workOrderTypes = responseJSON.workOrderTypes; + bulmaJS.alert({ + message: 'Work Order Type Updated Successfully', + contextualColorName: 'success' + }); + } + else { + bulmaJS.alert({ + title: 'Error Updating Work Order Type', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + function deleteWorkOrderType(clickEvent) { + const tableRowElement = clickEvent.currentTarget.closest('tr'); + const workOrderTypeId = tableRowElement.dataset.workOrderTypeId; + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/admin/doDeleteWorkOrderType`, { + workOrderTypeId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + workOrderTypes = responseJSON.workOrderTypes; + if (workOrderTypes.length === 0) { + renderWorkOrderTypes(); + } + else { + tableRowElement.remove(); + } + bulmaJS.alert({ + message: 'Work Order Type Deleted Successfully', + contextualColorName: 'success' + }); + } + else { + bulmaJS.alert({ + title: 'Error Deleting Work Order Type', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + bulmaJS.confirm({ + title: 'Delete Work Order Type', + message: `Are you sure you want to delete this work order type?
    + Note that no work orders will be removed.`, + messageIsHtml: true, + contextualColorName: 'warning', + okButton: { + text: 'Yes, Delete Work Order Type', + callbackFunction: doDelete + } + }); + } + function moveWorkOrderType(clickEvent) { + const buttonElement = clickEvent.currentTarget; + const tableRowElement = buttonElement.closest('tr'); + const workOrderTypeId = tableRowElement.dataset.workOrderTypeId; + cityssm.postJSON(`${los.urlPrefix}/admin/${buttonElement.dataset.direction === 'up' + ? 'doMoveWorkOrderTypeUp' + : 'doMoveWorkOrderTypeDown'}`, { + workOrderTypeId, + moveToEnd: clickEvent.shiftKey ? '1' : '0' + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + workOrderTypes = responseJSON.workOrderTypes; + renderWorkOrderTypes(); + } + else { + bulmaJS.alert({ + title: 'Error Moving Work Order Type', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + function renderWorkOrderTypes() { + const containerElement = document.querySelector('#container--workOrderTypes'); + if (workOrderTypes.length === 0) { + containerElement.innerHTML = `
    `; + return; + } + containerElement.innerHTML = ''; + for (const workOrderType of workOrderTypes) { + const tableRowElement = document.createElement('tr'); + tableRowElement.dataset.workOrderTypeId = + workOrderType.workOrderTypeId.toString(); + // eslint-disable-next-line no-unsanitized/property + tableRowElement.innerHTML = ``; + tableRowElement + .querySelector('form') + ?.addEventListener('submit', updateWorkOrderType); + tableRowElement.querySelector('.button--moveWorkOrderTypeUp').addEventListener('click', moveWorkOrderType); + tableRowElement.querySelector('.button--moveWorkOrderTypeDown').addEventListener('click', moveWorkOrderType); + tableRowElement + .querySelector('.button--deleteWorkOrderType') + ?.addEventListener('click', deleteWorkOrderType); + containerElement.append(tableRowElement); + } + } + ; + document.querySelector('#form--addWorkOrderType').addEventListener('submit', (submitEvent) => { + submitEvent.preventDefault(); + const formElement = submitEvent.currentTarget; + cityssm.postJSON(`${los.urlPrefix}/admin/doAddWorkOrderType`, formElement, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + workOrderTypes = responseJSON.workOrderTypes; + renderWorkOrderTypes(); + formElement.reset(); + formElement.querySelector('input')?.focus(); + } + else { + bulmaJS.alert({ + title: 'Error Adding Work Order Type', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + }); + renderWorkOrderTypes(); + })(); + (() => { + let workOrderMilestoneTypes = exports.workOrderMilestoneTypes; + delete exports.workOrderMilestoneTypes; + function updateWorkOrderMilestoneType(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doUpdateWorkOrderMilestoneType`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + workOrderMilestoneTypes = responseJSON.workOrderMilestoneTypes; + bulmaJS.alert({ + message: 'Work Order Milestone Type Updated Successfully', + contextualColorName: 'success' + }); + } + else { + bulmaJS.alert({ + title: 'Error Updating Work Order Milestone Type', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + function deleteWorkOrderMilestoneType(clickEvent) { + const tableRowElement = clickEvent.currentTarget.closest('tr'); + const workOrderMilestoneTypeId = tableRowElement.dataset.workOrderMilestoneTypeId; + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/admin/doDeleteWorkOrderMilestoneType`, { + workOrderMilestoneTypeId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + workOrderMilestoneTypes = responseJSON.workOrderMilestoneTypes; + if (workOrderMilestoneTypes.length === 0) { + renderWorkOrderMilestoneTypes(); + } + else { + tableRowElement.remove(); + } + bulmaJS.alert({ + message: 'Work Order Milestone Type Deleted Successfully', + contextualColorName: 'success' + }); + } + else { + bulmaJS.alert({ + title: 'Error Deleting Work Order Milestone Type', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + bulmaJS.confirm({ + title: 'Delete Work Order Milestone Type', + message: `Are you sure you want to delete this work order milestone type?
    + Note that no work orders will be removed.`, + messageIsHtml: true, + contextualColorName: 'warning', + okButton: { + text: 'Yes, Delete Work Order Milestone Type', + callbackFunction: doDelete + } + }); + } + function moveWorkOrderMilestoneType(clickEvent) { + const buttonElement = clickEvent.currentTarget; + const tableRowElement = buttonElement.closest('tr'); + const workOrderMilestoneTypeId = tableRowElement.dataset.workOrderMilestoneTypeId; + cityssm.postJSON(`${los.urlPrefix}/admin/${buttonElement.dataset.direction === 'up' + ? 'doMoveWorkOrderMilestoneTypeUp' + : 'doMoveWorkOrderMilestoneTypeDown'}`, { + workOrderMilestoneTypeId, + moveToEnd: clickEvent.shiftKey ? '1' : '0' + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + workOrderMilestoneTypes = responseJSON.workOrderMilestoneTypes; + renderWorkOrderMilestoneTypes(); + } + else { + bulmaJS.alert({ + title: 'Error Moving Work Order Milestone Type', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + function renderWorkOrderMilestoneTypes() { + const containerElement = document.querySelector('#container--workOrderMilestoneTypes'); + if (workOrderMilestoneTypes.length === 0) { + containerElement.innerHTML = ``; + return; + } + containerElement.innerHTML = ''; + for (const workOrderMilestoneType of workOrderMilestoneTypes) { + const tableRowElement = document.createElement('tr'); + tableRowElement.dataset.workOrderMilestoneTypeId = + workOrderMilestoneType.workOrderMilestoneTypeId.toString(); + // eslint-disable-next-line no-unsanitized/property, no-secrets/no-secrets + tableRowElement.innerHTML = ``; + tableRowElement + .querySelector('form') + ?.addEventListener('submit', updateWorkOrderMilestoneType); + tableRowElement.querySelector('.button--moveWorkOrderMilestoneTypeUp').addEventListener('click', moveWorkOrderMilestoneType); + tableRowElement.querySelector('.button--moveWorkOrderMilestoneTypeDown').addEventListener('click', moveWorkOrderMilestoneType); + tableRowElement + .querySelector('.button--deleteWorkOrderMilestoneType') + ?.addEventListener('click', deleteWorkOrderMilestoneType); + containerElement.append(tableRowElement); + } + } + ; + document.querySelector('#form--addWorkOrderMilestoneType').addEventListener('submit', (submitEvent) => { + submitEvent.preventDefault(); + const formElement = submitEvent.currentTarget; + cityssm.postJSON(`${los.urlPrefix}/admin/doAddWorkOrderMilestoneType`, formElement, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + workOrderMilestoneTypes = responseJSON.workOrderMilestoneTypes; + renderWorkOrderMilestoneTypes(); + formElement.reset(); + formElement.querySelector('input')?.focus(); + } + else { + bulmaJS.alert({ + title: 'Error Adding Work Order Milestone Type', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + }); + renderWorkOrderMilestoneTypes(); + })(); + (() => { + let lotStatuses = exports.lotStatuses; + delete exports.lotStatuses; + function updateBurialSiteStatus(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doUpdateBurialSiteStatus`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + lotStatuses = responseJSON.lotStatuses; + bulmaJS.alert({ + message: `${los.escapedAliases.Lot} Status Updated Successfully`, + contextualColorName: 'success' + }); + } + else { + bulmaJS.alert({ + title: `Error Updating ${los.escapedAliases.Lot} Status`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + function deleteLotStatus(clickEvent) { + const tableRowElement = clickEvent.currentTarget.closest('tr'); + const burialSiteStatusId = tableRowElement.dataset.burialSiteStatusId; + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/admin/doDeleteBurialSiteStatus`, { + burialSiteStatusId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + lotStatuses = responseJSON.lotStatuses; + if (lotStatuses.length === 0) { + renderLotStatuses(); + } + else { + tableRowElement.remove(); + } + bulmaJS.alert({ + message: `${los.escapedAliases.Lot} Status Deleted Successfully`, + contextualColorName: 'success' + }); + } + else { + bulmaJS.alert({ + title: `Error Deleting ${los.escapedAliases.Lot} Status`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + bulmaJS.confirm({ + title: `Delete ${los.escapedAliases.Lot} Status`, + message: `Are you sure you want to delete this status?
    + Note that no ${los.escapedAliases.lot} will be removed.`, + messageIsHtml: true, + contextualColorName: 'warning', + okButton: { + text: 'Yes, Delete Status', + callbackFunction: doDelete + } + }); + } + function moveLotStatus(clickEvent) { + const buttonElement = clickEvent.currentTarget; + const tableRowElement = buttonElement.closest('tr'); + const burialSiteStatusId = tableRowElement.dataset.burialSiteStatusId; + cityssm.postJSON(`${los.urlPrefix}/admin/${buttonElement.dataset.direction === 'up' + ? 'doMoveBurialSiteStatusUp' + : 'doMoveBurialSiteStatusDown'}`, { + burialSiteStatusId, + moveToEnd: clickEvent.shiftKey ? '1' : '0' + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + lotStatuses = responseJSON.lotStatuses; + renderLotStatuses(); + } + else { + bulmaJS.alert({ + title: `Error Moving ${los.escapedAliases.Lot} Status`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + function renderLotStatuses() { + const containerElement = document.querySelector('#container--lotStatuses'); + if (lotStatuses.length === 0) { + // eslint-disable-next-line no-unsanitized/property + containerElement.innerHTML = ``; + return; + } + containerElement.innerHTML = ''; + for (const lotStatus of lotStatuses) { + const tableRowElement = document.createElement('tr'); + tableRowElement.dataset.burialSiteStatusId = lotStatus.burialSiteStatusId.toString(); + // eslint-disable-next-line no-unsanitized/property + tableRowElement.innerHTML = ``; + tableRowElement + .querySelector('form') + ?.addEventListener('submit', updateBurialSiteStatus); + tableRowElement.querySelector('.button--moveLotStatusUp').addEventListener('click', moveLotStatus); + tableRowElement.querySelector('.button--moveLotStatusDown').addEventListener('click', moveLotStatus); + tableRowElement + .querySelector('.button--deleteLotStatus') + ?.addEventListener('click', deleteLotStatus); + containerElement.append(tableRowElement); + } + } + ; + document.querySelector('#form--addBurialSiteStatus').addEventListener('submit', (submitEvent) => { + submitEvent.preventDefault(); + const formElement = submitEvent.currentTarget; + cityssm.postJSON(`${los.urlPrefix}/admin/doAddBurialSiteStatus`, formElement, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + lotStatuses = responseJSON.lotStatuses; + renderLotStatuses(); + formElement.reset(); + formElement.querySelector('input')?.focus(); + } + else { + bulmaJS.alert({ + title: `Error Adding ${los.escapedAliases.Lot} Status`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + }); + renderLotStatuses(); + })(); + (() => { + let lotOccupantTypes = exports.lotOccupantTypes; + delete exports.lotOccupantTypes; + function updateBurialSiteOccupantType(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doUpdateBurialSiteOccupantType`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + lotOccupantTypes = responseJSON.lotOccupantTypes; + bulmaJS.alert({ + message: `${los.escapedAliases.Lot} ${los.escapedAliases.Occupant} Type Updated Successfully`, + contextualColorName: 'success' + }); + } + else { + bulmaJS.alert({ + title: `Error Updating ${los.escapedAliases.Lot} ${los.escapedAliases.Occupant} Type`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + function deleteLotOccupantType(clickEvent) { + const tableRowElement = clickEvent.currentTarget.closest('tr'); + const lotOccupantTypeId = tableRowElement.dataset.lotOccupantTypeId; + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/admin/doDeleteBurialSiteOccupantType`, { + lotOccupantTypeId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + lotOccupantTypes = responseJSON.lotOccupantTypes; + if (lotOccupantTypes.length === 0) { + renderLotOccupantTypes(); + } + else { + tableRowElement.remove(); + } + bulmaJS.alert({ + message: `${los.escapedAliases.Lot} ${los.escapedAliases.Occupant} Type Deleted Successfully`, + contextualColorName: 'success' + }); + } + else { + bulmaJS.alert({ + title: `Error Deleting ${los.escapedAliases.Lot} ${los.escapedAliases.Occupant} Type`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + bulmaJS.confirm({ + title: `Delete ${los.escapedAliases.Lot} ${los.escapedAliases.Occupant} Type`, + message: `Are you sure you want to delete this ${los.escapedAliases.lot} ${los.escapedAliases.occupant} type?
    + Note that no ${los.escapedAliases.lot} ${los.escapedAliases.occupants} will be removed.`, + messageIsHtml: true, + contextualColorName: 'warning', + okButton: { + text: `Yes, Delete ${los.escapedAliases.Lot} ${los.escapedAliases.Occupant} Type`, + callbackFunction: doDelete + } + }); + } + function moveLotOccupantType(clickEvent) { + const buttonElement = clickEvent.currentTarget; + const tableRowElement = buttonElement.closest('tr'); + const lotOccupantTypeId = tableRowElement.dataset.lotOccupantTypeId; + cityssm.postJSON(`${los.urlPrefix}/admin/${buttonElement.dataset.direction === 'up' + ? 'doMoveLotOccupantTypeUp' + : 'doMoveLotOccupantTypeDown'}`, { + lotOccupantTypeId, + moveToEnd: clickEvent.shiftKey ? '1' : '0' + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + lotOccupantTypes = responseJSON.lotOccupantTypes; + renderLotOccupantTypes(); + } + else { + bulmaJS.alert({ + title: `Error Moving ${los.escapedAliases.Lot} ${los.escapedAliases.Occupant} Type`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + function renderLotOccupantTypes() { + const containerElement = document.querySelector('#container--lotOccupantTypes'); + if (lotOccupantTypes.length === 0) { + // eslint-disable-next-line no-unsanitized/property + containerElement.innerHTML = ``; + return; + } + containerElement.innerHTML = ''; + for (const lotOccupantType of lotOccupantTypes) { + const tableRowElement = document.createElement('tr'); + tableRowElement.dataset.lotOccupantTypeId = + lotOccupantType.lotOccupantTypeId.toString(); + const formId = `form--lotOccupantType-${lotOccupantType.lotOccupantTypeId.toString()}`; + // eslint-disable-next-line no-unsanitized/property + tableRowElement.innerHTML = ``; + const fontAwesomeInputElement = tableRowElement.querySelector("input[name='fontAwesomeIconClass']"); + fontAwesomeInputElement.addEventListener('keyup', refreshFontAwesomeIcon); + fontAwesomeInputElement.addEventListener('change', refreshFontAwesomeIcon); + tableRowElement + .querySelector('form') + ?.addEventListener('submit', updateBurialSiteOccupantType); + tableRowElement.querySelector('.button--moveLotOccupantTypeUp').addEventListener('click', moveLotOccupantType); + tableRowElement.querySelector('.button--moveLotOccupantTypeDown').addEventListener('click', moveLotOccupantType); + tableRowElement + .querySelector('.button--deleteLotOccupantType') + ?.addEventListener('click', deleteLotOccupantType); + containerElement.append(tableRowElement); + } + } + ; + document.querySelector('#form--addBurialSiteOccupantType').addEventListener('submit', (submitEvent) => { + submitEvent.preventDefault(); + const formElement = submitEvent.currentTarget; + cityssm.postJSON(`${los.urlPrefix}/admin/doAddLotOccupantType`, formElement, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + lotOccupantTypes = responseJSON.lotOccupantTypes; + renderLotOccupantTypes(); + formElement.reset(); + formElement.querySelector('input')?.focus(); + } + else { + bulmaJS.alert({ + title: `Error Adding ${los.escapedAliases.Lot} ${los.escapedAliases.Occupant} Type`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + }); + renderLotOccupantTypes(); + })(); +})(); diff --git a/public/javascripts/tables.admin.ts b/public/javascripts/tables.admin.ts index c9d9604c..9104526c 100644 --- a/public/javascripts/tables.admin.ts +++ b/public/javascripts/tables.admin.ts @@ -723,7 +723,7 @@ declare const bulmaJS: BulmaJS } } ;( - document.querySelector('#form--addLotStatus') as HTMLFormElement + document.querySelector('#form--addBurialSiteStatus') as HTMLFormElement ).addEventListener('submit', (submitEvent: SubmitEvent) => { submitEvent.preventDefault() @@ -1010,7 +1010,7 @@ declare const bulmaJS: BulmaJS } } ;( - document.querySelector('#form--addLotOccupantType') as HTMLFormElement + document.querySelector('#form--addBurialSiteOccupantType') as HTMLFormElement ).addEventListener('submit', (submitEvent: SubmitEvent) => { submitEvent.preventDefault() diff --git a/public/javascripts/types.d.ts b/public/javascripts/types.d.ts new file mode 100644 index 00000000..686a35ad --- /dev/null +++ b/public/javascripts/types.d.ts @@ -0,0 +1,28 @@ +export interface LOS { + urlPrefix: string; + apiKey: string; + highlightMap: (mapContainerElement: HTMLElement, mapKey: string, contextualClass: 'success' | 'danger') => void; + initializeDatePickers: (containerElement: HTMLElement) => void; + initializeUnlockFieldButtons: (containerElement: HTMLElement) => void; + populateAliases: (containerElement: HTMLElement) => void; + escapedAliases: { + ExternalReceiptNumber: string; + externalReceiptNumber: string; + WorkOrderOpenDate: string; + workOrderOpenDate: string; + WorkOrderCloseDate: string; + workOrderCloseDate: string; + }; + dynamicsGPIntegrationIsEnabled: boolean; + getRandomColor: (seedString: string) => string; + setUnsavedChanges: () => void; + clearUnsavedChanges: () => void; + hasUnsavedChanges: () => boolean; + getMoveUpDownButtonFieldHTML: (upButtonClassNames: string, downButtonClassNames: string, isSmall?: boolean) => string; + getLoadingParagraphHTML: (captionText?: string) => string; + getSearchResultsPagerHTML: (limit: number, offset: number, count: number) => string; + getCemeteryURL: (cemeteryId?: number | string, edit?: boolean, time?: boolean) => string; + getBurialSiteURL: (burialSiteId?: number | string, edit?: boolean, time?: boolean) => string; + getBurialSiteContractURL: (burialSiteContractId?: number | string, edit?: boolean, time?: boolean) => string; + getWorkOrderURL: (workOrderId?: number | string, edit?: boolean, time?: boolean) => string; +} diff --git a/public/javascripts/types.js b/public/javascripts/types.js new file mode 100644 index 00000000..c8ad2e54 --- /dev/null +++ b/public/javascripts/types.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/public/javascripts/types.ts b/public/javascripts/types.ts index e0d48fb0..0d0a1582 100644 --- a/public/javascripts/types.ts +++ b/public/javascripts/types.ts @@ -18,8 +18,6 @@ export interface LOS { escapedAliases: { ExternalReceiptNumber: string externalReceiptNumber: string - contractStartDate: string - contractStartDate: string WorkOrderOpenDate: string workOrderOpenDate: string WorkOrderCloseDate: string @@ -46,8 +44,8 @@ export interface LOS { count: number ) => string - getMapURL: (cemeteryId?: number | string, edit?: boolean, time?: boolean) => string - getLotURL: (lotId?: number | string, edit?: boolean, time?: boolean) => string + getCemeteryURL: (cemeteryId?: number | string, edit?: boolean, time?: boolean) => string + getBurialSiteURL: (burialSiteId?: number | string, edit?: boolean, time?: boolean) => string getBurialSiteContractURL: ( burialSiteContractId?: number | string, edit?: boolean, diff --git a/public/javascripts/workOrder.edit.d.ts b/public/javascripts/workOrder.edit.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/workOrder.edit.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/workOrder.edit.js b/public/javascripts/workOrder.edit.js new file mode 100644 index 00000000..8bac72b5 --- /dev/null +++ b/public/javascripts/workOrder.edit.js @@ -0,0 +1,1243 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const los = exports.los; + const workOrderId = document.querySelector('#workOrderEdit--workOrderId').value; + const isCreate = workOrderId === ''; + const workOrderFormElement = document.querySelector('#form--workOrderEdit'); + los.initializeDatePickers(workOrderFormElement + .querySelector('#workOrderEdit--workOrderOpenDateString') + ?.closest('.field')); + los.initializeUnlockFieldButtons(workOrderFormElement); + function setUnsavedChanges() { + los.setUnsavedChanges(); + document + .querySelector("button[type='submit'][form='form--workOrderEdit']") + ?.classList.remove('is-light'); + } + function clearUnsavedChanges() { + 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; + 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 = workOrderFormElement.querySelectorAll('input, select, textarea'); + for (const inputElement of inputElements) { + inputElement.addEventListener('change', setUnsavedChanges); + } + /* + * Work Order Options + */ + function doClose() { + cityssm.postJSON(`${los.urlPrefix}/workOrders/doCloseWorkOrder`, { + workOrderId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + 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() { + cityssm.postJSON(`${los.urlPrefix}/workOrders/doDeleteWorkOrder`, { + workOrderId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + 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; + 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) => { + 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; + delete exports.workOrderLots; + let workOrderBurialSiteContracts = exports.workOrderBurialSiteContracts; + delete exports.workOrderBurialSiteContracts; + function deleteLotOccupancy(clickEvent) { + const burialSiteContractId = clickEvent.currentTarget.closest('.container--burialSiteContract').dataset.burialSiteContractId; + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/workOrders/doDeleteWorkOrderBurialSiteContract`, { + workOrderId, + burialSiteContractId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + workOrderBurialSiteContracts = responseJSON.workOrderBurialSiteContracts; + 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 addBurialSite(lotId, callbackFunction) { + cityssm.postJSON(`${los.urlPrefix}/workOrders/doAddWorkOrderBurialSite`, { + workOrderId, + lotId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + 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 addBurialSiteContract(burialSiteContractId, callbackFunction) { + cityssm.postJSON(`${los.urlPrefix}/workOrders/doAddWorkOrderBurialSiteContract`, { + workOrderId, + burialSiteContractId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + workOrderBurialSiteContracts = responseJSON.workOrderBurialSiteContracts; + renderRelatedLotsAndOccupancies(); + } + else { + bulmaJS.alert({ + title: `Error Adding ${los.escapedAliases.Occupancy}`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + if (callbackFunction !== undefined) { + callbackFunction(responseJSON.success); + } + }); + } + function addBurialSiteFromLotOccupancy(clickEvent) { + const lotId = clickEvent.currentTarget.dataset.lotId ?? ''; + addBurialSite(lotId); + } + function renderRelatedOccupancies() { + const occupanciesContainerElement = document.querySelector('#container--lotOccupancies'); + document.querySelector(".tabs a[href='#relatedTab--lotOccupancies'] .tag").textContent = workOrderBurialSiteContracts.length.toString(); + if (workOrderBurialSiteContracts.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 = `
    diff --git a/public/javascripts/burialSiteTypes.admin.d.ts b/public/javascripts/burialSiteTypes.admin.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/burialSiteTypes.admin.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/burialSiteTypes.admin.js b/public/javascripts/burialSiteTypes.admin.js new file mode 100644 index 00000000..070e8366 --- /dev/null +++ b/public/javascripts/burialSiteTypes.admin.js @@ -0,0 +1,409 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const los = exports.los; + const containerElement = document.querySelector('#container--lotTypes'); + let lotTypes = exports.lotTypes; + delete exports.lotTypes; + const expandedLotTypes = new Set(); + function toggleBurialSiteTypeFields(clickEvent) { + const toggleButtonElement = clickEvent.currentTarget; + const lotTypeElement = toggleButtonElement.closest('.container--lotType'); + const burialSiteTypeId = Number.parseInt(lotTypeElement.dataset.burialSiteTypeId ?? '', 10); + if (expandedLotTypes.has(burialSiteTypeId)) { + expandedLotTypes.delete(burialSiteTypeId); + } + else { + expandedLotTypes.add(burialSiteTypeId); + } + // eslint-disable-next-line no-unsanitized/property + toggleButtonElement.innerHTML = expandedLotTypes.has(burialSiteTypeId) + ? '' + : ''; + const panelBlockElements = lotTypeElement.querySelectorAll('.panel-block'); + for (const panelBlockElement of panelBlockElements) { + panelBlockElement.classList.toggle('is-hidden'); + } + } + function lotTypeResponseHandler(rawResponseJSON) { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + lotTypes = responseJSON.lotTypes; + renderLotTypes(); + } + else { + bulmaJS.alert({ + title: `Error Updating ${los.escapedAliases.Lot} Type`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + } + function deleteLotType(clickEvent) { + const burialSiteTypeId = Number.parseInt(clickEvent.currentTarget.closest('.container--lotType').dataset.burialSiteTypeId ?? '', 10); + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/admin/doDeleteBurialSiteType`, { + burialSiteTypeId + }, lotTypeResponseHandler); + } + bulmaJS.confirm({ + title: `Delete ${los.escapedAliases.Lot} Type`, + message: `Are you sure you want to delete this ${los.escapedAliases.lot} type?`, + contextualColorName: 'warning', + okButton: { + text: `Yes, Delete ${los.escapedAliases.Lot} Type`, + callbackFunction: doDelete + } + }); + } + function openEditLotType(clickEvent) { + const burialSiteTypeId = Number.parseInt(clickEvent.currentTarget.closest('.container--lotType').dataset.burialSiteTypeId ?? '', 10); + const lotType = lotTypes.find((currentLotType) => burialSiteTypeId === currentLotType.burialSiteTypeId); + let editCloseModalFunction; + function doEdit(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doUpdateBurialSiteType`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + lotTypeResponseHandler(responseJSON); + if (responseJSON.success) { + editCloseModalFunction(); + } + }); + } + cityssm.openHtmlModal('adminLotTypes-editLotType', { + onshow(modalElement) { + los.populateAliases(modalElement); + modalElement.querySelector('#lotTypeEdit--burialSiteTypeId').value = burialSiteTypeId.toString(); + modalElement.querySelector('#lotTypeEdit--lotType').value = lotType.lotType; + }, + onshown(modalElement, closeModalFunction) { + editCloseModalFunction = closeModalFunction; + modalElement.querySelector('#lotTypeEdit--lotType').focus(); + modalElement.querySelector('form')?.addEventListener('submit', doEdit); + bulmaJS.toggleHtmlClipped(); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + } + }); + } + function openAddLotTypeField(clickEvent) { + const burialSiteTypeId = Number.parseInt(clickEvent.currentTarget.closest('.container--lotType').dataset.burialSiteTypeId ?? '', 10); + let addCloseModalFunction; + function doAdd(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doAddBurialSiteTypeField`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + expandedLotTypes.add(burialSiteTypeId); + lotTypeResponseHandler(responseJSON); + if (responseJSON.success) { + addCloseModalFunction(); + openEditLotTypeField(burialSiteTypeId, responseJSON.lotTypeFieldId); + } + }); + } + cityssm.openHtmlModal('adminLotTypes-addBurialSiteTypeField', { + onshow(modalElement) { + los.populateAliases(modalElement); + if (burialSiteTypeId) { + ; + modalElement.querySelector('#lotTypeFieldAdd--burialSiteTypeId').value = burialSiteTypeId.toString(); + } + }, + onshown(modalElement, closeModalFunction) { + addCloseModalFunction = closeModalFunction; + modalElement.querySelector('#lotTypeFieldAdd--lotTypeField').focus(); + modalElement.querySelector('form')?.addEventListener('submit', doAdd); + bulmaJS.toggleHtmlClipped(); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + } + }); + } + function moveLotType(clickEvent) { + const buttonElement = clickEvent.currentTarget; + const burialSiteTypeId = buttonElement.closest('.container--lotType').dataset.burialSiteTypeId; + cityssm.postJSON(`${los.urlPrefix}/admin/${buttonElement.dataset.direction === 'up' + ? 'doMoveBurialSiteTypeUp' + : 'doMoveBurialSiteTypeDown'}`, { + burialSiteTypeId, + moveToEnd: clickEvent.shiftKey ? '1' : '0' + }, lotTypeResponseHandler); + } + function openEditLotTypeField(burialSiteTypeId, lotTypeFieldId) { + const lotType = lotTypes.find((currentLotType) => currentLotType.burialSiteTypeId === burialSiteTypeId); + const lotTypeField = (lotType.BurialSiteTypeFields ?? []).find((currentLotTypeField) => currentLotTypeField.lotTypeFieldId === lotTypeFieldId); + let fieldTypeElement; + let minimumLengthElement; + let maximumLengthElement; + let patternElement; + let lotTypeFieldValuesElement; + let editCloseModalFunction; + function updateMaximumLengthMin() { + maximumLengthElement.min = minimumLengthElement.value; + } + function toggleInputFields() { + switch (fieldTypeElement.value) { + case 'date': { + minimumLengthElement.disabled = true; + maximumLengthElement.disabled = true; + patternElement.disabled = true; + lotTypeFieldValuesElement.disabled = true; + break; + } + case 'select': { + minimumLengthElement.disabled = true; + maximumLengthElement.disabled = true; + patternElement.disabled = true; + lotTypeFieldValuesElement.disabled = false; + break; + } + default: { + minimumLengthElement.disabled = false; + maximumLengthElement.disabled = false; + patternElement.disabled = false; + lotTypeFieldValuesElement.disabled = true; + break; + } + } + } + function doUpdate(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doUpdateBurialSiteTypeField`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + lotTypeResponseHandler(responseJSON); + if (responseJSON.success) { + editCloseModalFunction(); + } + }); + } + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/admin/doDeleteBurialSiteTypeField`, { + lotTypeFieldId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + lotTypeResponseHandler(responseJSON); + if (responseJSON.success) { + editCloseModalFunction(); + } + }); + } + function confirmDoDelete() { + bulmaJS.confirm({ + title: 'Delete Field', + message: 'Are you sure you want to delete this field? Note that historical records that make use of this field will not be affected.', + contextualColorName: 'warning', + okButton: { + text: 'Yes, Delete Field', + callbackFunction: doDelete + } + }); + } + cityssm.openHtmlModal('adminLotTypes-editLotTypeField', { + onshow(modalElement) { + los.populateAliases(modalElement); + modalElement.querySelector('#lotTypeFieldEdit--lotTypeFieldId').value = lotTypeField.lotTypeFieldId.toString(); + modalElement.querySelector('#lotTypeFieldEdit--lotTypeField').value = lotTypeField.lotTypeField ?? ''; + modalElement.querySelector('#lotTypeFieldEdit--isRequired').value = lotTypeField.isRequired ? '1' : '0'; + fieldTypeElement = modalElement.querySelector('#lotTypeFieldEdit--fieldType'); + fieldTypeElement.value = lotTypeField.fieldType; + minimumLengthElement = modalElement.querySelector('#lotTypeFieldEdit--minimumLength'); + minimumLengthElement.value = + lotTypeField.minimumLength?.toString() ?? ''; + maximumLengthElement = modalElement.querySelector('#lotTypeFieldEdit--maximumLength'); + maximumLengthElement.value = + lotTypeField.maximumLength?.toString() ?? ''; + patternElement = modalElement.querySelector('#lotTypeFieldEdit--pattern'); + patternElement.value = lotTypeField.pattern ?? ''; + lotTypeFieldValuesElement = modalElement.querySelector('#lotTypeFieldEdit--lotTypeFieldValues'); + lotTypeFieldValuesElement.value = lotTypeField.lotTypeFieldValues ?? ''; + toggleInputFields(); + }, + onshown(modalElement, closeModalFunction) { + editCloseModalFunction = closeModalFunction; + bulmaJS.init(modalElement); + bulmaJS.toggleHtmlClipped(); + cityssm.enableNavBlocker(); + modalElement.querySelector('form')?.addEventListener('submit', doUpdate); + minimumLengthElement.addEventListener('keyup', updateMaximumLengthMin); + updateMaximumLengthMin(); + fieldTypeElement.addEventListener('change', toggleInputFields); + modalElement + .querySelector('#button--deleteLotTypeField') + ?.addEventListener('click', confirmDoDelete); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + cityssm.disableNavBlocker(); + } + }); + } + function openEditLotTypeFieldByClick(clickEvent) { + clickEvent.preventDefault(); + const lotTypeFieldId = Number.parseInt(clickEvent.currentTarget.closest('.container--lotTypeField').dataset.lotTypeFieldId ?? '', 10); + const burialSiteTypeId = Number.parseInt(clickEvent.currentTarget.closest('.container--lotType').dataset.burialSiteTypeId ?? '', 10); + openEditLotTypeField(burialSiteTypeId, lotTypeFieldId); + } + function moveLotTypeField(clickEvent) { + const buttonElement = clickEvent.currentTarget; + const lotTypeFieldId = buttonElement.closest('.container--lotTypeField').dataset.lotTypeFieldId; + cityssm.postJSON(`${los.urlPrefix}/admin/${buttonElement.dataset.direction === 'up' + ? 'doMoveBurialSiteTypeFieldUp' + : 'doMoveBurialSiteTypeFieldDown'}`, { + lotTypeFieldId, + moveToEnd: clickEvent.shiftKey ? '1' : '0' + }, lotTypeResponseHandler); + } + function renderBurialSiteTypeFields(panelElement, burialSiteTypeId, BurialSiteTypeFields) { + if (BurialSiteTypeFields.length === 0) { + // eslint-disable-next-line no-unsanitized/method + panelElement.insertAdjacentHTML('beforeend', `
    +

    There are no additional fields.

    +
    `); + } + else { + for (const lotTypeField of BurialSiteTypeFields) { + const panelBlockElement = document.createElement('div'); + panelBlockElement.className = + 'panel-block is-block container--lotTypeField'; + if (!expandedLotTypes.has(burialSiteTypeId)) { + panelBlockElement.classList.add('is-hidden'); + } + panelBlockElement.dataset.lotTypeFieldId = + lotTypeField.lotTypeFieldId.toString(); + // eslint-disable-next-line no-unsanitized/property + panelBlockElement.innerHTML = `
    + +
    +
    + ${los.getMoveUpDownButtonFieldHTML('button--moveLotTypeFieldUp', 'button--moveLotTypeFieldDown')} +
    +
    +
    `; + panelBlockElement + .querySelector('.button--editLotTypeField') + ?.addEventListener('click', openEditLotTypeFieldByClick); + panelBlockElement.querySelector('.button--moveLotTypeFieldUp').addEventListener('click', moveLotTypeField); + panelBlockElement.querySelector('.button--moveLotTypeFieldDown').addEventListener('click', moveLotTypeField); + panelElement.append(panelBlockElement); + } + } + } + function renderLotTypes() { + containerElement.innerHTML = ''; + if (lotTypes.length === 0) { + // eslint-disable-next-line no-unsanitized/method + containerElement.insertAdjacentHTML('afterbegin', `
    There are no active ${los.escapedAliases.lot} types.

    +
    `); + return; + } + for (const lotType of lotTypes) { + const lotTypeContainer = document.createElement('div'); + lotTypeContainer.className = 'panel container--lotType'; + lotTypeContainer.dataset.burialSiteTypeId = lotType.burialSiteTypeId.toString(); + // eslint-disable-next-line no-unsanitized/property + lotTypeContainer.innerHTML = `
    +
    +
    +
    + +
    +
    +

    ${cityssm.escapeHTML(lotType.lotType)}

    +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + ${los.getMoveUpDownButtonFieldHTML('button--moveLotTypeUp', 'button--moveLotTypeDown')} +
    +
    +
    +
    `; + renderBurialSiteTypeFields(lotTypeContainer, lotType.burialSiteTypeId, lotType.BurialSiteTypeFields ?? []); + lotTypeContainer + .querySelector('.button--toggleBurialSiteTypeFields') + ?.addEventListener('click', toggleBurialSiteTypeFields); + lotTypeContainer + .querySelector('.button--deleteLotType') + ?.addEventListener('click', deleteLotType); + lotTypeContainer + .querySelector('.button--editLotType') + ?.addEventListener('click', openEditLotType); + lotTypeContainer + .querySelector('.button--addBurialSiteTypeField') + ?.addEventListener('click', openAddLotTypeField); + lotTypeContainer.querySelector('.button--moveLotTypeUp').addEventListener('click', moveLotType); + lotTypeContainer.querySelector('.button--moveLotTypeDown').addEventListener('click', moveLotType); + containerElement.append(lotTypeContainer); + } + } + document + .querySelector('#button--addBurialSiteType') + ?.addEventListener('click', () => { + let addCloseModalFunction; + function doAdd(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/admin/doAddLotType`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + addCloseModalFunction(); + lotTypes = responseJSON.lotTypes; + renderLotTypes(); + } + else { + bulmaJS.alert({ + title: `Error Adding ${los.escapedAliases.Lot} Type`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + cityssm.openHtmlModal('adminLotTypes-addBurialSiteType', { + onshow(modalElement) { + los.populateAliases(modalElement); + }, + onshown(modalElement, closeModalFunction) { + addCloseModalFunction = closeModalFunction; + modalElement.querySelector('#lotTypeAdd--lotType').focus(); + modalElement.querySelector('form')?.addEventListener('submit', doAdd); + bulmaJS.toggleHtmlClipped(); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + } + }); + }); + renderLotTypes(); +})(); diff --git a/public/javascripts/burialSiteTypes.admin.ts b/public/javascripts/burialSiteTypes.admin.ts index f807463f..bcf96652 100644 --- a/public/javascripts/burialSiteTypes.admin.ts +++ b/public/javascripts/burialSiteTypes.admin.ts @@ -208,7 +208,7 @@ type ResponseJSON = ) } - cityssm.openHtmlModal('adminLotTypes-addLotTypeField', { + cityssm.openHtmlModal('adminLotTypes-addBurialSiteTypeField', { onshow(modalElement) { los.populateAliases(modalElement) @@ -598,7 +598,7 @@ type ResponseJSON =
    - @@ -632,7 +632,7 @@ type ResponseJSON = ?.addEventListener('click', openEditLotType) lotTypeContainer - .querySelector('.button--addLotTypeField') + .querySelector('.button--addBurialSiteTypeField') ?.addEventListener('click', openAddLotTypeField) ;( lotTypeContainer.querySelector( @@ -650,7 +650,7 @@ type ResponseJSON = } document - .querySelector('#button--addLotType') + .querySelector('#button--addBurialSiteType') ?.addEventListener('click', () => { let addCloseModalFunction: () => void @@ -678,7 +678,7 @@ type ResponseJSON = ) } - cityssm.openHtmlModal('adminLotTypes-addLotType', { + cityssm.openHtmlModal('adminLotTypes-addBurialSiteType', { onshow(modalElement) { los.populateAliases(modalElement) }, diff --git a/public/javascripts/cemetery.edit.d.ts b/public/javascripts/cemetery.edit.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/cemetery.edit.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/cemetery.edit.js b/public/javascripts/cemetery.edit.js new file mode 100644 index 00000000..e1589133 --- /dev/null +++ b/public/javascripts/cemetery.edit.js @@ -0,0 +1,81 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const los = exports.los; + const cemeteryId = document.querySelector('#cemetery--cemeteryId').value; + const isCreate = cemeteryId === ''; + const cemeteryForm = document.querySelector('#form--cemetery'); + function setUnsavedChanges() { + los.setUnsavedChanges(); + document + .querySelector("button[type='submit'][form='form--cemetery']") + ?.classList.remove('is-light'); + } + function clearUnsavedChanges() { + los.clearUnsavedChanges(); + document + .querySelector("button[type='submit'][form='form--cemetery']") + ?.classList.add('is-light'); + } + function updateCemetery(formEvent) { + formEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/cemeteries/${isCreate ? 'doCreateCemetery' : 'doUpdateCemetery'}`, cemeteryForm, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + clearUnsavedChanges(); + if (isCreate) { + globalThis.location.href = los.getCemeteryURL(responseJSON.cemeteryId, true); + } + else { + bulmaJS.alert({ + message: `Cemetery Updated Successfully`, + contextualColorName: 'success' + }); + } + } + else { + bulmaJS.alert({ + title: `Error Updating Cemetery`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + cemeteryForm.addEventListener('submit', updateCemetery); + const inputElements = cemeteryForm.querySelectorAll('input, select'); + for (const inputElement of inputElements) { + inputElement.addEventListener('change', setUnsavedChanges); + } + document + .querySelector('#button--deleteCemetery') + ?.addEventListener('click', (clickEvent) => { + clickEvent.preventDefault(); + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/cemeteries/doDeleteCemetery`, { + cemeteryId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + globalThis.location.href = los.getCemeteryURL(); + } + else { + bulmaJS.alert({ + title: `Error Deleting Cemetery`, + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + bulmaJS.confirm({ + title: `Delete Cemetery`, + message: `Are you sure you want to delete this cemetery and all related burial sites?`, + contextualColorName: 'warning', + okButton: { + text: `Yes, Delete Cemetery`, + callbackFunction: doDelete + } + }); + }); +})(); diff --git a/public/javascripts/cemetery.edit.ts b/public/javascripts/cemetery.edit.ts index 4d6b33e6..cb7f1b6d 100644 --- a/public/javascripts/cemetery.edit.ts +++ b/public/javascripts/cemetery.edit.ts @@ -1,7 +1,7 @@ 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 { LOS } from './types.js' declare const cityssm: cityssmGlobal declare const bulmaJS: BulmaJS @@ -10,32 +10,35 @@ declare const exports: Record ;(() => { const los = exports.los as LOS - const cemeteryId = (document.querySelector('#map--cemeteryId') as HTMLInputElement) - .value + const cemeteryId = ( + document.querySelector('#cemetery--cemeteryId') as HTMLInputElement + ).value const isCreate = cemeteryId === '' - const mapForm = document.querySelector('#form--map') as HTMLFormElement + const cemeteryForm = document.querySelector( + '#form--cemetery' + ) as HTMLFormElement function setUnsavedChanges(): void { los.setUnsavedChanges() document - .querySelector("button[type='submit'][form='form--map']") + .querySelector("button[type='submit'][form='form--cemetery']") ?.classList.remove('is-light') } function clearUnsavedChanges(): void { los.clearUnsavedChanges() document - .querySelector("button[type='submit'][form='form--map']") + .querySelector("button[type='submit'][form='form--cemetery']") ?.classList.add('is-light') } - function updateMap(formEvent: SubmitEvent): void { + function updateCemetery(formEvent: SubmitEvent): void { formEvent.preventDefault() cityssm.postJSON( - `${los.urlPrefix}/maps/${isCreate ? 'doCreateMap' : 'doUpdateMap'}`, - mapForm, + `${los.urlPrefix}/cemeteries/${isCreate ? 'doCreateCemetery' : 'doUpdateCemetery'}`, + cemeteryForm, (rawResponseJSON) => { const responseJSON = rawResponseJSON as { success: boolean @@ -47,16 +50,19 @@ declare const exports: Record clearUnsavedChanges() if (isCreate) { - globalThis.location.href = los.getMapURL(responseJSON.cemeteryId, true) + globalThis.location.href = los.getCemeteryURL( + responseJSON.cemeteryId, + true + ) } else { bulmaJS.alert({ - message: `${los.escapedAliases.Map} Updated Successfully`, + message: `Cemetery Updated Successfully`, contextualColorName: 'success' }) } } else { bulmaJS.alert({ - title: `Error Updating ${los.escapedAliases.Map}`, + title: `Error Updating Cemetery`, message: responseJSON.errorMessage ?? '', contextualColorName: 'danger' }) @@ -65,23 +71,23 @@ declare const exports: Record ) } - mapForm.addEventListener('submit', updateMap) + cemeteryForm.addEventListener('submit', updateCemetery) const inputElements: NodeListOf = - mapForm.querySelectorAll('input, select') + cemeteryForm.querySelectorAll('input, select') for (const inputElement of inputElements) { inputElement.addEventListener('change', setUnsavedChanges) } document - .querySelector('#button--deleteMap') + .querySelector('#button--deleteCemetery') ?.addEventListener('click', (clickEvent) => { clickEvent.preventDefault() function doDelete(): void { cityssm.postJSON( - `${los.urlPrefix}/maps/doDeleteMap`, + `${los.urlPrefix}/cemeteries/doDeleteCemetery`, { cemeteryId }, @@ -92,10 +98,10 @@ declare const exports: Record } if (responseJSON.success) { - globalThis.location.href = los.getMapURL() + globalThis.location.href = los.getCemeteryURL() } else { bulmaJS.alert({ - title: `Error Deleting ${los.escapedAliases.Map}`, + title: `Error Deleting Cemetery`, message: responseJSON.errorMessage ?? '', contextualColorName: 'danger' }) @@ -105,11 +111,11 @@ declare const exports: Record } bulmaJS.confirm({ - title: `Delete ${los.escapedAliases.Map}`, - message: `Are you sure you want to delete this ${los.escapedAliases.map} and all related ${los.escapedAliases.lots}?`, + title: `Delete Cemetery`, + message: `Are you sure you want to delete this cemetery and all related burial sites?`, contextualColorName: 'warning', okButton: { - text: `Yes, Delete ${los.escapedAliases.Map}`, + text: `Yes, Delete Cemetery`, callbackFunction: doDelete } }) diff --git a/public/javascripts/cemetery.search.d.ts b/public/javascripts/cemetery.search.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/cemetery.search.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/cemetery.search.js b/public/javascripts/cemetery.search.js new file mode 100644 index 00000000..e00340de --- /dev/null +++ b/public/javascripts/cemetery.search.js @@ -0,0 +1,102 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const los = exports.los; + const cemeteries = exports.cemeteries; + const searchFilterElement = document.querySelector('#searchFilter--cemetery'); + const searchResultsContainerElement = document.querySelector('#container--searchResults'); + // eslint-disable-next-line complexity + function renderResults() { + // eslint-disable-next-line no-unsanitized/property + searchResultsContainerElement.innerHTML = los.getLoadingParagraphHTML(`Loading Cemeteries...`); + let searchResultCount = 0; + const searchResultsTbodyElement = document.createElement('tbody'); + const filterStringSplit = searchFilterElement.value + .trim() + .toLowerCase() + .split(' '); + for (const cemetery of cemeteries) { + const cemeterySearchString = `${cemetery.cemeteryName ?? ''} ${cemetery.cemeteryDescription ?? ''} ${cemetery.cemeteryAddress1 ?? ''} ${cemetery.cemeteryAddress2 ?? ''}`.toLowerCase(); + let showCemetery = true; + for (const filterStringPiece of filterStringSplit) { + if (!cemeterySearchString.includes(filterStringPiece)) { + showCemetery = false; + break; + } + } + if (!showCemetery) { + continue; + } + searchResultCount += 1; + // eslint-disable-next-line no-unsanitized/method + searchResultsTbodyElement.insertAdjacentHTML('beforeend', `
    + + ${cityssm.escapeHTML((cemetery.cemeteryName ?? '') === '' ? '(No Name)' : cemetery.cemeteryName ?? '')} +
    + + ${cityssm.escapeHTML(cemetery.cemeteryDescription ?? '')} + +
    + ${(cemetery.cemeteryAddress1 ?? '') === '' + ? '' + : `${cityssm.escapeHTML(cemetery.cemeteryAddress1 ?? '')}
    `} + ${(cemetery.cemeteryAddress2 ?? '') === '' + ? '' + : `${cityssm.escapeHTML(cemetery.cemeteryAddress2 ?? '')}
    `} + ${cemetery.cemeteryCity || cemetery.cemeteryProvince + ? `${cityssm.escapeHTML(cemetery.cemeteryCity ?? '')}, ${cityssm.escapeHTML(cemetery.cemeteryProvince ?? '')}
    ` + : ''} + ${(cemetery.cemeteryPostalCode ?? '') === '' + ? '' + : cityssm.escapeHTML(cemetery.cemeteryPostalCode ?? '')} +
    + ${cityssm.escapeHTML(cemetery.cemeteryPhoneNumber ?? '')} + + ${cemetery.cemeteryLatitude && cemetery.cemeteryLongitude + ? ` + + ` + : ''} + + ${(cemetery.cemeterySvg ?? '') === '' + ? '' + : ''} + + ${cemetery.burialSiteCount} +
    CemeteryAddressPhone NumberCoordinatesImageBurial Site Count
    - + ${cityssm.escapeHTML( - (map.cemeteryName ?? '') === '' ? '(No Name)' : map.cemeteryName ?? '' + (cemetery.cemeteryName ?? '') === '' ? '(No Name)' : cemetery.cemeteryName ?? '' )}
    - ${cityssm.escapeHTML(map.mapDescription ?? '')} + ${cityssm.escapeHTML(cemetery.cemeteryDescription ?? '')}
    ${ - (map.mapAddress1 ?? '') === '' + (cemetery.cemeteryAddress1 ?? '') === '' ? '' - : `${cityssm.escapeHTML(map.mapAddress1 ?? '')}
    ` + : `${cityssm.escapeHTML(cemetery.cemeteryAddress1 ?? '')}
    ` } ${ - (map.mapAddress2 ?? '') === '' + (cemetery.cemeteryAddress2 ?? '') === '' ? '' - : `${cityssm.escapeHTML(map.mapAddress2 ?? '')}
    ` + : `${cityssm.escapeHTML(cemetery.cemeteryAddress2 ?? '')}
    ` } ${ - map.mapCity || map.mapProvince - ? `${cityssm.escapeHTML(map.mapCity ?? '')}, ${cityssm.escapeHTML(map.mapProvince ?? '')}
    ` + cemetery.cemeteryCity || cemetery.cemeteryProvince + ? `${cityssm.escapeHTML(cemetery.cemeteryCity ?? '')}, ${cityssm.escapeHTML(cemetery.cemeteryProvince ?? '')}
    ` : '' } ${ - (map.mapPostalCode ?? '') === '' + (cemetery.cemeteryPostalCode ?? '') === '' ? '' - : cityssm.escapeHTML(map.mapPostalCode ?? '') + : cityssm.escapeHTML(cemetery.cemeteryPostalCode ?? '') }
    - ${cityssm.escapeHTML(map.mapPhoneNumber ?? '')} + ${cityssm.escapeHTML(cemetery.cemeteryPhoneNumber ?? '')} ${ - map.mapLatitude && map.mapLongitude + cemetery.cemeteryLatitude && cemetery.cemeteryLongitude ? ` ` @@ -99,12 +101,12 @@ declare const exports: Record } ${ - (map.mapSVG ?? '') === '' + (cemetery.cemeterySvg ?? '') === '' ? '' : '' } - ${map.lotCount} + ${cemetery.burialSiteCount}
    ${los.escapedAliases.Map}Cemetery Address Phone Number Coordinates Image${los.escapedAliases.Lot} CountBurial Site Count
    +

    There are no active work order types.

    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + ${los.getMoveUpDownButtonFieldHTML('button--moveWorkOrderTypeUp', 'button--moveWorkOrderTypeDown', false)} +
    +
    + +
    +
    +
    +
    +

    There are no active work order milestone types.

    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + ${los.getMoveUpDownButtonFieldHTML('button--moveWorkOrderMilestoneTypeUp', 'button--moveWorkOrderMilestoneTypeDown', false)} +
    +
    + +
    +
    +
    +
    +

    There are no active ${los.escapedAliases.lot} statuses.

    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + ${los.getMoveUpDownButtonFieldHTML('button--moveLotStatusUp', 'button--moveLotStatusDown', false)} +
    +
    + +
    +
    +
    +
    +

    There are no active ${los.escapedAliases.lot} ${los.escapedAliases.occupant} types.

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + fa- +
    +
    + +
    +
    + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + +
    +
    +
    +
    + ${los.getMoveUpDownButtonFieldHTML('button--moveLotOccupantTypeUp', 'button--moveLotOccupantTypeDown', false)} +
    +
    + +
    +
    +
    + + + + + + + + + + +
    ${los.escapedAliases.Occupancy} Type${los.escapedAliases.Lot}${los.escapedAliases.contractStartDate}End Date${los.escapedAliases.Occupants}
    `; + const currentDateString = cityssm.dateToString(new Date()); + for (const burialSiteContract of workOrderBurialSiteContracts) { + const rowElement = document.createElement('tr'); + rowElement.className = 'container--burialSiteContract'; + rowElement.dataset.burialSiteContractId = + burialSiteContract.burialSiteContractId.toString(); + const isActive = !(burialSiteContract.contractEndDate && + burialSiteContract.contractEndDateString < currentDateString); + const hasLotRecord = burialSiteContract.lotId && + workOrderLots.some((lot) => burialSiteContract.lotId === lot.lotId); + // eslint-disable-next-line no-unsanitized/property + rowElement.innerHTML = ` + ${isActive + ? `` + : ``} + + + ${cityssm.escapeHTML(burialSiteContract.occupancyType ?? '')} +
    + #${burialSiteContract.burialSiteContractId} + `; + if (burialSiteContract.lotId) { + // eslint-disable-next-line no-unsanitized/method + rowElement.insertAdjacentHTML('beforeend', ` + ${cityssm.escapeHTML(burialSiteContract.lotName ?? '')} + ${hasLotRecord + ? '' + : ` `} + `); + } + else { + // eslint-disable-next-line no-unsanitized/method + rowElement.insertAdjacentHTML('beforeend', `(No ${los.escapedAliases.Lot})`); + } + let occupantsHTML = ''; + for (const occupant of burialSiteContract.burialSiteContractOccupants) { + occupantsHTML += `
  • + + + + ${cityssm.escapeHTML(occupant.occupantName ?? '')} + ${cityssm.escapeHTML(occupant.occupantFamilyName ?? '')} +
  • `; + } + // eslint-disable-next-line no-unsanitized/method + rowElement.insertAdjacentHTML('beforeend', ` + ${burialSiteContract.contractStartDateString} + + ${burialSiteContract.contractEndDate + ? burialSiteContract.contractEndDateString + : '(No End Date)'} + + ${burialSiteContract.burialSiteContractOccupants.length === 0 + ? `(No ${los.escapedAliases.Occupants})` + : `
      ${occupantsHTML}
    `} + + + `); + rowElement + .querySelector('.button--addBurialSite') + ?.addEventListener('click', addBurialSiteFromLotOccupancy); + rowElement + .querySelector('.button--deleteLotOccupancy') + ?.addEventListener('click', deleteLotOccupancy); + occupanciesContainerElement.querySelector('tbody')?.append(rowElement); + } + } + function openEditLotStatus(clickEvent) { + const lotId = Number.parseInt(clickEvent.currentTarget.closest('.container--lot').dataset.lotId ?? '', 10); + const lot = workOrderLots.find((possibleLot) => possibleLot.lotId === lotId); + let editCloseModalFunction; + function doUpdateBurialSiteStatus(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/workOrders/doUpdateBurialSiteStatus`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + 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').value = lotId.toString(); + modalElement.querySelector('#lotStatusEdit--lotName').value = lot.lotName ?? ''; + const lotStatusElement = modalElement.querySelector('#lotStatusEdit--burialSiteStatusId'); + let lotStatusFound = false; + for (const lotStatus of exports.lotStatuses) { + const optionElement = document.createElement('option'); + optionElement.value = lotStatus.burialSiteStatusId.toString(); + optionElement.textContent = lotStatus.lotStatus; + if (lotStatus.burialSiteStatusId === lot.burialSiteStatusId) { + lotStatusFound = true; + } + lotStatusElement.append(optionElement); + } + if (!lotStatusFound && lot.burialSiteStatusId) { + const optionElement = document.createElement('option'); + optionElement.value = lot.burialSiteStatusId.toString(); + optionElement.textContent = lot.lotStatus ?? ''; + lotStatusElement.append(optionElement); + } + if (lot.burialSiteStatusId) { + lotStatusElement.value = lot.burialSiteStatusId.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', doUpdateBurialSiteStatus); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + } + }); + } + function deleteLot(clickEvent) { + const lotId = clickEvent.currentTarget.closest('.container--lot').dataset.lotId; + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/workOrders/doDeleteWorkOrderBurialSite`, { + workOrderId, + lotId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + 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() { + const lotsContainerElement = document.querySelector('#container--lots'); + document.querySelector(".tabs a[href='#relatedTab--lots'] .tag").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} TypeStatus
    `; + 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.cemeteryName ?? '')} + + ${cityssm.escapeHTML(lot.lotType ?? '')} + + ${lot.burialSiteStatusId + ? 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() { + renderRelatedOccupancies(); + renderRelatedLots(); + } + renderRelatedLotsAndOccupancies(); + function doAddLotOccupancy(clickEvent) { + const rowElement = clickEvent.currentTarget.closest('tr'); + const burialSiteContractId = rowElement.dataset.burialSiteContractId ?? ''; + addBurialSiteContract(burialSiteContractId, (success) => { + if (success) { + rowElement.remove(); + } + }); + } + document + .querySelector('#button--addBurialSiteContract') + ?.addEventListener('click', () => { + let searchFormElement; + let searchResultsContainerElement; + function doSearch(event) { + if (event) { + event.preventDefault(); + } + // eslint-disable-next-line no-unsanitized/property + searchResultsContainerElement.innerHTML = + los.getLoadingParagraphHTML('Searching...'); + cityssm.postJSON(`${los.urlPrefix}/contracts/doSearchLotOccupancies`, searchFormElement, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + 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.contractStartDate}End Date${los.escapedAliases.Occupants}
    `; + for (const burialSiteContract of responseJSON.lotOccupancies) { + const rowElement = document.createElement('tr'); + rowElement.className = 'container--burialSiteContract'; + rowElement.dataset.burialSiteContractId = + burialSiteContract.burialSiteContractId.toString(); + rowElement.innerHTML = ` + + + + ${cityssm.escapeHTML(burialSiteContract.occupancyType ?? '')} + `; + if (burialSiteContract.lotId) { + rowElement.insertAdjacentHTML('beforeend', `${cityssm.escapeHTML(burialSiteContract.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', ` + ${burialSiteContract.contractStartDateString} + + ${burialSiteContract.contractEndDate + ? burialSiteContract.contractEndDateString + : '(No End Date)'} + + ${burialSiteContract.burialSiteContractOccupants.length === 0 + ? ` + (No ${cityssm.escapeHTML(los.escapedAliases.Occupants)}) + ` + : cityssm.escapeHTML(`${burialSiteContract.burialSiteContractOccupants[0].occupantName} + ${burialSiteContract.burialSiteContractOccupants[0] + .occupantFamilyName}`) + + (burialSiteContract.burialSiteContractOccupants.length > 1 + ? ` plus + ${(burialSiteContract.burialSiteContractOccupants.length - 1).toString()}` + : '')}`); + rowElement + .querySelector('.button--addBurialSiteContract') + ?.addEventListener('click', doAddLotOccupancy); + searchResultsContainerElement + .querySelector('tbody') + ?.append(rowElement); + } + }); + } + cityssm.openHtmlModal('workOrder-addBurialSiteContract', { + onshow(modalElement) { + los.populateAliases(modalElement); + searchFormElement = modalElement.querySelector('form'); + searchResultsContainerElement = modalElement.querySelector('#resultsContainer--burialSiteContractAdd'); + modalElement.querySelector('#burialSiteContractSearch--notWorkOrderId').value = workOrderId; + modalElement.querySelector('#burialSiteContractSearch--occupancyEffectiveDateString').value = document.querySelector('#workOrderEdit--workOrderOpenDateString').value; + doSearch(); + }, + onshown(modalElement) { + bulmaJS.toggleHtmlClipped(); + const occupantNameElement = modalElement.querySelector('#burialSiteContractSearch--occupantName'); + occupantNameElement.addEventListener('change', doSearch); + occupantNameElement.focus(); + modalElement.querySelector('#burialSiteContractSearch--lotName').addEventListener('change', doSearch); + searchFormElement.addEventListener('submit', doSearch); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + document.querySelector('#button--addBurialSiteContract').focus(); + } + }); + }); + function doAddLot(clickEvent) { + const rowElement = clickEvent.currentTarget.closest('tr'); + const lotId = rowElement.dataset.lotId ?? ''; + addBurialSite(lotId, (success) => { + if (success) { + rowElement.remove(); + } + }); + } + document + .querySelector('#button--addBurialSite') + ?.addEventListener('click', () => { + let searchFormElement; + let searchResultsContainerElement; + function doSearch(event) { + if (event) { + event.preventDefault(); + } + // eslint-disable-next-line no-unsanitized/property + searchResultsContainerElement.innerHTML = + los.getLoadingParagraphHTML('Searching...'); + cityssm.postJSON(`${los.urlPrefix}/lots/doSearchBurialSites`, searchFormElement, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + 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} TypeStatus
    `; + 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.cemeteryName ?? '')} + + ${cityssm.escapeHTML(lot.lotType ?? '')} + + ${cityssm.escapeHTML(lot.lotStatus ?? '')} + `; + rowElement + .querySelector('.button--addBurialSite') + ?.addEventListener('click', doAddLot); + searchResultsContainerElement + .querySelector('tbody') + ?.append(rowElement); + } + }); + } + cityssm.openHtmlModal('workOrder-addBurialSite', { + onshow(modalElement) { + los.populateAliases(modalElement); + searchFormElement = modalElement.querySelector('form'); + searchResultsContainerElement = modalElement.querySelector('#resultsContainer--lotAdd'); + modalElement.querySelector('#lotSearch--notWorkOrderId').value = workOrderId; + const lotStatusElement = modalElement.querySelector('#lotSearch--burialSiteStatusId'); + for (const lotStatus of exports.lotStatuses) { + const optionElement = document.createElement('option'); + optionElement.value = lotStatus.burialSiteStatusId.toString(); + optionElement.textContent = lotStatus.lotStatus; + lotStatusElement.append(optionElement); + } + doSearch(); + }, + onshown(modalElement) { + bulmaJS.toggleHtmlClipped(); + const lotNameElement = modalElement.querySelector('#lotSearch--lotName'); + lotNameElement.addEventListener('change', doSearch); + lotNameElement.focus(); + modalElement + .querySelector('#lotSearch--burialSiteStatusId') + ?.addEventListener('change', doSearch); + searchFormElement.addEventListener('submit', doSearch); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + document.querySelector('#button--addBurialSite').focus(); + } + }); + }); + })(); + } + /** + * Comments + */ + ; + (() => { + let workOrderComments = exports.workOrderComments; + delete exports.workOrderComments; + function openEditWorkOrderComment(clickEvent) { + const workOrderCommentId = Number.parseInt(clickEvent.currentTarget.closest('tr')?.dataset + .workOrderCommentId ?? '', 10); + const workOrderComment = workOrderComments.find((currentComment) => currentComment.workOrderCommentId === workOrderCommentId); + let editFormElement; + let editCloseModalFunction; + function editComment(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/workOrders/doUpdateWorkOrderComment`, editFormElement, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + 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').value = workOrderId; + modalElement.querySelector('#workOrderCommentEdit--workOrderCommentId').value = workOrderCommentId.toString(); + modalElement.querySelector('#workOrderCommentEdit--workOrderComment').value = workOrderComment.workOrderComment ?? ''; + const workOrderCommentDateStringElement = modalElement.querySelector('#workOrderCommentEdit--workOrderCommentDateString'); + workOrderCommentDateStringElement.value = + workOrderComment.workOrderCommentDateString ?? ''; + const currentDateString = cityssm.dateToString(new Date()); + workOrderCommentDateStringElement.max = + workOrderComment.workOrderCommentDateString <= currentDateString + ? currentDateString + : workOrderComment.workOrderCommentDateString ?? ''; + modalElement.querySelector('#workOrderCommentEdit--workOrderCommentTimeString').value = workOrderComment.workOrderCommentTimeString ?? ''; + }, + onshown(modalElement, closeModalFunction) { + bulmaJS.toggleHtmlClipped(); + los.initializeDatePickers(modalElement); + modalElement.querySelector('#workOrderCommentEdit--workOrderComment').focus(); + editFormElement = modalElement.querySelector('form'); + editFormElement.addEventListener('submit', editComment); + editCloseModalFunction = closeModalFunction; + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + } + }); + } + function deleteWorkOrderComment(clickEvent) { + const workOrderCommentId = Number.parseInt(clickEvent.currentTarget.closest('tr')?.dataset + .workOrderCommentId ?? '', 10); + function doDelete() { + cityssm.postJSON(`${los.urlPrefix}/workOrders/doDeleteWorkOrderComment`, { + workOrderId, + workOrderCommentId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + 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() { + const containerElement = document.querySelector('#container--workOrderComments'); + 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() { + let addCommentCloseModalFunction; + function doAddComment(formEvent) { + formEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/workOrders/doAddWorkOrderComment`, formEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + workOrderComments = responseJSON.workOrderComments; + renderWorkOrderComments(); + addCommentCloseModalFunction(); + } + }); + } + cityssm.openHtmlModal('workOrder-addComment', { + onshow(modalElement) { + los.populateAliases(modalElement); + modalElement.querySelector('#workOrderCommentAdd--workOrderId').value = workOrderId; + modalElement + .querySelector('form') + ?.addEventListener('submit', doAddComment); + }, + onshown(modalElement, closeModalFunction) { + bulmaJS.toggleHtmlClipped(); + addCommentCloseModalFunction = closeModalFunction; + modalElement.querySelector('#workOrderCommentAdd--workOrderComment').focus(); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + document.querySelector('#workOrderComments--add').focus(); + } + }); + } + document + .querySelector('#workOrderComments--add') + ?.addEventListener('click', openAddCommentModal); + if (!isCreate) { + renderWorkOrderComments(); + } + })(); + /* + * Milestones + */ + function clearPanelBlockElements(panelElement) { + for (const panelBlockElement of panelElement.querySelectorAll('.panel-block')) { + panelBlockElement.remove(); + } + } + function refreshConflictingMilestones(workOrderMilestoneDateString, targetPanelElement) { + // 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; + 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) { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + workOrderMilestones = responseJSON.workOrderMilestones; + renderMilestones(); + } + else { + bulmaJS.alert({ + title: 'Error Reopening Milestone', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + } + function completeMilestone(clickEvent) { + clickEvent.preventDefault(); + const currentDateString = cityssm.dateToString(new Date()); + const workOrderMilestoneId = Number.parseInt(clickEvent.currentTarget.closest('.container--milestone').dataset.workOrderMilestoneId ?? '', 10); + const workOrderMilestone = workOrderMilestones.find((currentMilestone) => currentMilestone.workOrderMilestoneId === workOrderMilestoneId); + function doComplete() { + 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) { + clickEvent.preventDefault(); + const workOrderMilestoneId = clickEvent.currentTarget.closest('.container--milestone').dataset.workOrderMilestoneId; + function doReopen() { + 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) { + clickEvent.preventDefault(); + const workOrderMilestoneId = clickEvent.currentTarget.closest('.container--milestone').dataset.workOrderMilestoneId; + function doDelete() { + 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) { + clickEvent.preventDefault(); + const workOrderMilestoneId = Number.parseInt(clickEvent.currentTarget.closest('.container--milestone').dataset.workOrderMilestoneId ?? '', 10); + const workOrderMilestone = workOrderMilestones.find((currentMilestone) => currentMilestone.workOrderMilestoneId === workOrderMilestoneId); + let editCloseModalFunction; + let workOrderMilestoneDateStringElement; + function doEdit(submitEvent) { + submitEvent.preventDefault(); + cityssm.postJSON(`${los.urlPrefix}/workOrders/doUpdateWorkOrderMilestone`, submitEvent.currentTarget, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + processMilestoneResponse(responseJSON); + if (responseJSON.success) { + editCloseModalFunction(); + } + }); + } + cityssm.openHtmlModal('workOrder-editMilestone', { + onshow(modalElement) { + ; + modalElement.querySelector('#milestoneEdit--workOrderId').value = workOrderId; + modalElement.querySelector('#milestoneEdit--workOrderMilestoneId').value = workOrderMilestone.workOrderMilestoneId?.toString() ?? ''; + const milestoneTypeElement = modalElement.querySelector('#milestoneEdit--workOrderMilestoneTypeId'); + let milestoneTypeFound = false; + for (const milestoneType of exports.workOrderMilestoneTypes) { + 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'); + workOrderMilestoneDateStringElement.value = + workOrderMilestone.workOrderMilestoneDateString ?? ''; + if (workOrderMilestone.workOrderMilestoneTime) { + ; + modalElement.querySelector('#milestoneEdit--workOrderMilestoneTimeString').value = workOrderMilestone.workOrderMilestoneTimeString ?? ''; + } + ; + modalElement.querySelector('#milestoneEdit--workOrderMilestoneDescription').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'); + workOrderMilestoneDateStringElement.addEventListener('change', () => { + refreshConflictingMilestones(workOrderMilestoneDateStringElement.value, conflictingMilestonePanelElement); + }); + refreshConflictingMilestones(workOrderMilestoneDateStringElement.value, conflictingMilestonePanelElement); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + } + }); + } + function renderMilestones() { + // Clear milestones panel + const milestonesPanelElement = document.querySelector('#panel--milestones'); + 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; + delete exports.workOrderMilestones; + renderMilestones(); + document + .querySelector('#button--addMilestone') + ?.addEventListener('click', () => { + let addFormElement; + let workOrderMilestoneDateStringElement; + let addCloseModalFunction; + function doAdd(submitEvent) { + if (submitEvent) { + submitEvent.preventDefault(); + } + const currentDateString = cityssm.dateToString(new Date()); + function _doAdd() { + cityssm.postJSON(`${los.urlPrefix}/workOrders/doAddWorkOrderMilestone`, addFormElement, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + 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').value = workOrderId; + const milestoneTypeElement = modalElement.querySelector('#milestoneAdd--workOrderMilestoneTypeId'); + for (const milestoneType of exports.workOrderMilestoneTypes) { + const optionElement = document.createElement('option'); + optionElement.value = + milestoneType.workOrderMilestoneTypeId.toString(); + optionElement.textContent = milestoneType.workOrderMilestoneType; + milestoneTypeElement.append(optionElement); + } + workOrderMilestoneDateStringElement = modalElement.querySelector('#milestoneAdd--workOrderMilestoneDateString'); + workOrderMilestoneDateStringElement.valueAsDate = new Date(); + }, + onshown(modalElement, closeModalFunction) { + addCloseModalFunction = closeModalFunction; + los.initializeDatePickers(modalElement); + // los.initializeTimePickers(modalElement); + bulmaJS.toggleHtmlClipped(); + modalElement.querySelector('#milestoneAdd--workOrderMilestoneTypeId').focus(); + addFormElement = modalElement.querySelector('form'); + addFormElement.addEventListener('submit', doAdd); + const conflictingMilestonePanelElement = document.querySelector('#milestoneAdd--conflictingMilestonesPanel'); + workOrderMilestoneDateStringElement.addEventListener('change', () => { + refreshConflictingMilestones(workOrderMilestoneDateStringElement.value, conflictingMilestonePanelElement); + }); + refreshConflictingMilestones(workOrderMilestoneDateStringElement.value, conflictingMilestonePanelElement); + }, + onremoved() { + bulmaJS.toggleHtmlClipped(); + document.querySelector('#button--addMilestone').focus(); + } + }); + }); + } +})(); diff --git a/public/javascripts/workOrder.edit.ts b/public/javascripts/workOrder.edit.ts index f6141e42..ceb8f1e5 100644 --- a/public/javascripts/workOrder.edit.ts +++ b/public/javascripts/workOrder.edit.ts @@ -270,7 +270,7 @@ declare const exports: Record }) } - function addLot( + function addBurialSite( lotId: number | string, callbackFunction?: (success: boolean) => void ): void { @@ -340,10 +340,10 @@ declare const exports: Record ) } - function addLotFromLotOccupancy(clickEvent: Event): void { + function addBurialSiteFromLotOccupancy(clickEvent: Event): void { const lotId = (clickEvent.currentTarget as HTMLElement).dataset.lotId ?? '' - addLot(lotId) + addBurialSite(lotId) } function renderRelatedOccupancies(): void { @@ -420,7 +420,7 @@ declare const exports: Record ${ hasLotRecord ? '' - : ` @@ -991,7 +991,7 @@ declare const exports: Record ` rowElement - .querySelector('.button--addLot') + .querySelector('.button--addBurialSite') ?.addEventListener('click', doAddLot) searchResultsContainerElement @@ -1002,7 +1002,7 @@ declare const exports: Record ) } - cityssm.openHtmlModal('workOrder-addLot', { + cityssm.openHtmlModal('workOrder-addBurialSite', { onshow(modalElement) { los.populateAliases(modalElement) @@ -1051,7 +1051,7 @@ declare const exports: Record onremoved() { bulmaJS.toggleHtmlClipped() ;( - document.querySelector('#button--addLot') as HTMLButtonElement + document.querySelector('#button--addBurialSite') as HTMLButtonElement ).focus() } }) diff --git a/public/javascripts/workOrder.milestoneCalendar.d.ts b/public/javascripts/workOrder.milestoneCalendar.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/workOrder.milestoneCalendar.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/workOrder.milestoneCalendar.js b/public/javascripts/workOrder.milestoneCalendar.js new file mode 100644 index 00000000..6180f0b4 --- /dev/null +++ b/public/javascripts/workOrder.milestoneCalendar.js @@ -0,0 +1,118 @@ +"use strict"; +// eslint-disable-next-line @eslint-community/eslint-comments/disable-enable-pair +/* eslint-disable unicorn/prefer-module */ +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const los = exports.los; + const workOrderSearchFiltersFormElement = document.querySelector('#form--searchFilters'); + const workOrderMilestoneDateFilterElement = workOrderSearchFiltersFormElement.querySelector('#searchFilter--workOrderMilestoneDateFilter'); + const workOrderMilestoneDateStringElement = workOrderSearchFiltersFormElement.querySelector('#searchFilter--workOrderMilestoneDateString'); + const milestoneCalendarContainerElement = document.querySelector('#container--milestoneCalendar'); + function renderMilestones(workOrderMilestones) { + if (workOrderMilestones.length === 0) { + milestoneCalendarContainerElement.innerHTML = `
    +

    There are no milestones that meet the search criteria.

    +
    `; + return; + } + milestoneCalendarContainerElement.innerHTML = ''; + const currentDate = cityssm.dateToString(new Date()); + let currentPanelElement; + let currentPanelDateString = 'x'; + for (const milestone of workOrderMilestones) { + if (currentPanelDateString !== milestone.workOrderMilestoneDateString) { + if (currentPanelElement) { + milestoneCalendarContainerElement.append(currentPanelElement); + } + currentPanelElement = document.createElement('div'); + currentPanelElement.className = 'panel'; + currentPanelElement.innerHTML = `

    + ${cityssm.escapeHTML(milestone.workOrderMilestoneDate === 0 + ? 'No Set Date' + : milestone.workOrderMilestoneDateString ?? '')} +

    `; + currentPanelDateString = milestone.workOrderMilestoneDateString ?? ''; + } + const panelBlockElement = document.createElement('div'); + panelBlockElement.className = 'panel-block is-block'; + if (!milestone.workOrderMilestoneCompletionDate && + milestone.workOrderMilestoneDateString !== '' && + milestone.workOrderMilestoneDateString < currentDate) { + panelBlockElement.classList.add('has-background-warning-light'); + } + let burialSiteContractHTML = ''; + for (const lot of milestone.workOrderLots ?? []) { + burialSiteContractHTML += `
  • + + + + ${cityssm.escapeHTML(lot.lotName ?? '')} +
  • `; + } + for (const burialSiteContract of milestone.workOrderBurialSiteContracts ?? []) { + for (const occupant of burialSiteContract.burialSiteContractOccupants ?? []) { + burialSiteContractHTML += `
  • + + + + ${cityssm.escapeHTML(occupant.occupantName ?? '')} + ${cityssm.escapeHTML(occupant.occupantFamilyName ?? '')} +
  • `; + } + } + // eslint-disable-next-line no-unsanitized/property + panelBlockElement.innerHTML = `
    +
    + + ${milestone.workOrderMilestoneCompletionDate + ? '' + : ''} + +
    + ${milestone.workOrderMilestoneTime === 0 + ? '' + : `${milestone.workOrderMilestoneTimePeriodString}
    `} + ${milestone.workOrderMilestoneTypeId + ? `${cityssm.escapeHTML(milestone.workOrderMilestoneType ?? '')}
    ` + : ''} + + ${cityssm.escapeHTML(milestone.workOrderMilestoneDescription ?? '')} + +
    + + + ${cityssm.escapeHTML(milestone.workOrderNumber ?? '')} +
    + ${cityssm.escapeHTML(milestone.workOrderDescription ?? '')} +
    + ${burialSiteContractHTML === '' + ? '' + : `
      ${burialSiteContractHTML}
    `}
    `; + currentPanelElement.append(panelBlockElement); + } + milestoneCalendarContainerElement.append(currentPanelElement); + } + function getMilestones(event) { + if (event) { + event.preventDefault(); + } + // eslint-disable-next-line no-unsanitized/property + milestoneCalendarContainerElement.innerHTML = los.getLoadingParagraphHTML('Loading Milestones...'); + cityssm.postJSON(`${los.urlPrefix}/workOrders/doGetWorkOrderMilestones`, workOrderSearchFiltersFormElement, (responseJSON) => { + renderMilestones(responseJSON.workOrderMilestones); + }); + } + workOrderMilestoneDateFilterElement.addEventListener('change', () => { + ; + workOrderMilestoneDateStringElement.closest('fieldset').disabled = workOrderMilestoneDateFilterElement.value !== 'date'; + getMilestones(); + }); + los.initializeDatePickers(workOrderSearchFiltersFormElement); + workOrderMilestoneDateStringElement.addEventListener('change', getMilestones); + workOrderSearchFiltersFormElement.addEventListener('submit', getMilestones); + getMilestones(); +})(); diff --git a/public/javascripts/workOrder.outlook.d.ts b/public/javascripts/workOrder.outlook.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/workOrder.outlook.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/workOrder.outlook.js b/public/javascripts/workOrder.outlook.js new file mode 100644 index 00000000..2313ce94 --- /dev/null +++ b/public/javascripts/workOrder.outlook.js @@ -0,0 +1,46 @@ +"use strict"; +// eslint-disable-next-line @eslint-community/eslint-comments/disable-enable-pair +/* eslint-disable unicorn/prefer-module */ +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const los = exports.los; + const workOrderTypeIdsElement = document.querySelector('#icsFilters--workOrderTypeIds'); + const workOrderMilestoneTypeIdsElement = document.querySelector('#icsFilters--workOrderMilestoneTypeIds'); + const calendarLinkElement = document.querySelector('#icsFilters--calendarURL'); + function updateCalendarURL() { + let url = `${window.location.href.slice(0, Math.max(0, window.location.href.indexOf(window.location.pathname) + 1)) + los.urlPrefix}api/${los.apiKey}/milestoneICS/?`; + if (!workOrderTypeIdsElement.disabled && + workOrderTypeIdsElement.selectedOptions.length > 0) { + url += 'workOrderTypeIds='; + for (const optionElement of workOrderTypeIdsElement.selectedOptions) { + url += `${optionElement.value},`; + } + url = `${url.slice(0, -1)}&`; + } + if (!workOrderMilestoneTypeIdsElement.disabled && + workOrderMilestoneTypeIdsElement.selectedOptions.length > 0) { + url += 'workOrderMilestoneTypeIds='; + for (const optionElement of workOrderMilestoneTypeIdsElement.selectedOptions) { + url += `${optionElement.value},`; + } + url = `${url.slice(0, -1)}&`; + } + calendarLinkElement.value = url.slice(0, -1); + } + ; + document.querySelector('#icsFilters--workOrderTypeIds-all').addEventListener('change', (changeEvent) => { + workOrderTypeIdsElement.disabled = changeEvent.currentTarget.checked; + }); + document.querySelector('#icsFilters--workOrderMilestoneTypeIds-all').addEventListener('change', (changeEvent) => { + workOrderMilestoneTypeIdsElement.disabled = changeEvent.currentTarget.checked; + }); + const inputSelectElements = document.querySelector('#panel--icsFilters').querySelectorAll('input, select'); + for (const element of inputSelectElements) { + element.addEventListener('change', updateCalendarURL); + } + updateCalendarURL(); + calendarLinkElement.addEventListener('click', () => { + calendarLinkElement.focus(); + calendarLinkElement.select(); + }); +})(); diff --git a/public/javascripts/workOrder.search.d.ts b/public/javascripts/workOrder.search.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/workOrder.search.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/workOrder.search.js b/public/javascripts/workOrder.search.js new file mode 100644 index 00000000..91d921ec --- /dev/null +++ b/public/javascripts/workOrder.search.js @@ -0,0 +1,180 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const los = exports.los; + const workOrderPrints = exports.workOrderPrints; + const searchFilterFormElement = document.querySelector('#form--searchFilters'); + los.initializeDatePickers(searchFilterFormElement); + const searchResultsContainerElement = document.querySelector('#container--searchResults'); + const limit = Number.parseInt(document.querySelector('#searchFilter--limit').value, 10); + const offsetElement = document.querySelector('#searchFilter--offset'); + function renderWorkOrders(rawResponseJSON) { + const responseJSON = rawResponseJSON; + if (responseJSON.workOrders.length === 0) { + searchResultsContainerElement.innerHTML = `
    +

    There are no work orders that meet the search criteria.

    +
    `; + return; + } + const resultsTbodyElement = document.createElement('tbody'); + for (const workOrder of responseJSON.workOrders) { + let relatedHTML = ''; + for (const lot of workOrder.workOrderLots ?? []) { + relatedHTML += `
  • + + + + ${cityssm.escapeHTML((lot.lotName ?? '') === '' + ? `(No ${los.escapedAliases.Lot} Name)` + : lot.lotName ?? '')} +
  • `; + } + for (const occupancy of workOrder.workOrderBurialSiteContracts ?? []) { + for (const occupant of occupancy.burialSiteContractOccupants ?? []) { + relatedHTML += `
  • + + + + ${cityssm.escapeHTML((occupant.occupantName ?? '') === '' && + (occupant.occupantFamilyName ?? '') === '' + ? '(No Name)' + : `${occupant.occupantName} ${occupant.occupantFamilyName}`)} +
  • `; + } + } + // eslint-disable-next-line no-unsanitized/method + resultsTbodyElement.insertAdjacentHTML('beforeend', ` + + + ${workOrder.workOrderNumber?.trim() === '' + ? '(No Number)' + : cityssm.escapeHTML(workOrder.workOrderNumber ?? '')} + + + ${cityssm.escapeHTML(workOrder.workOrderType ?? '')}
    + + ${cityssm.escapeHTML(workOrder.workOrderDescription ?? '')} + + + ${relatedHTML === '' + ? '' + : `
      ${relatedHTML}
    `} + +
      +
    • + + + + ${workOrder.workOrderOpenDateString} +
    • +
    • + + + + ${workOrder.workOrderCloseDate + ? workOrder.workOrderCloseDateString + : `(No ${los.escapedAliases.WorkOrderCloseDate})`} +
    • +
    + + ${workOrder.workOrderMilestoneCount === 0 + ? '-' + : `${(workOrder.workOrderMilestoneCompletionCount ?? '').toString()} + / + ${(workOrder.workOrderMilestoneCount ?? '').toString()}`} + + ${workOrderPrints.length > 0 + ? ` + + + + ` + : ''}`); + } + // eslint-disable-next-line no-unsanitized/property + searchResultsContainerElement.innerHTML = ` + + + + + + + ${workOrderPrints.length > 0 ? '' : ''} + +
    Work Order NumberDescriptionRelatedDateProgress
    `; + // eslint-disable-next-line no-unsanitized/method + searchResultsContainerElement.insertAdjacentHTML('beforeend', los.getSearchResultsPagerHTML(limit, responseJSON.offset, responseJSON.count)); + searchResultsContainerElement + .querySelector('table') + ?.append(resultsTbodyElement); + searchResultsContainerElement + .querySelector("button[data-page='previous']") + ?.addEventListener('click', previousAndGetWorkOrders); + searchResultsContainerElement + .querySelector("button[data-page='next']") + ?.addEventListener('click', nextAndGetWorkOrders); + } + function getWorkOrders() { + // eslint-disable-next-line no-unsanitized/property + searchResultsContainerElement.innerHTML = los.getLoadingParagraphHTML('Loading Work Orders...'); + cityssm.postJSON(`${los.urlPrefix}/workOrders/doSearchWorkOrders`, searchFilterFormElement, renderWorkOrders); + } + function resetOffsetAndGetWorkOrders() { + offsetElement.value = '0'; + getWorkOrders(); + } + function previousAndGetWorkOrders() { + offsetElement.value = Math.max(Number.parseInt(offsetElement.value, 10) - limit, 0).toString(); + getWorkOrders(); + } + function nextAndGetWorkOrders() { + offsetElement.value = (Number.parseInt(offsetElement.value, 10) + limit).toString(); + getWorkOrders(); + } + const filterElements = searchFilterFormElement.querySelectorAll('input, select'); + for (const filterElement of filterElements) { + filterElement.addEventListener('change', resetOffsetAndGetWorkOrders); + } + searchFilterFormElement.addEventListener('submit', (formEvent) => { + formEvent.preventDefault(); + }); + // eslint-disable-next-line no-secrets/no-secrets + /* + const workOrderOpenDateStringElement = document.querySelector("#searchFilter--workOrderOpenDateString") as HTMLInputElement; + + document.querySelector("#button--workOrderOpenDateString-previous").addEventListener("click", () => { + + if (workOrderOpenDateStringElement.value === "") { + workOrderOpenDateStringElement.valueAsDate = new Date(); + } else { + const openDate = workOrderOpenDateStringElement.valueAsDate; + openDate.setDate(openDate.getDate() - 1); + workOrderOpenDateStringElement.valueAsDate = openDate; + } + + resetOffsetAndGetWorkOrders(); + }); + + document.querySelector("#button--workOrderOpenDateString-next").addEventListener("click", () => { + + if (workOrderOpenDateStringElement.value === "") { + workOrderOpenDateStringElement.valueAsDate = new Date(); + } else { + const openDate = workOrderOpenDateStringElement.valueAsDate; + openDate.setDate(openDate.getDate() + 1); + workOrderOpenDateStringElement.valueAsDate = openDate; + } + + resetOffsetAndGetWorkOrders(); + }); + */ + getWorkOrders(); +})(); diff --git a/public/javascripts/workOrder.view.d.ts b/public/javascripts/workOrder.view.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/public/javascripts/workOrder.view.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/javascripts/workOrder.view.js b/public/javascripts/workOrder.view.js new file mode 100644 index 00000000..e9929751 --- /dev/null +++ b/public/javascripts/workOrder.view.js @@ -0,0 +1,37 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + const los = exports.los; + const reopenWorkOrderButtonElement = document.querySelector('#button--reopenWorkOrder'); + if (reopenWorkOrderButtonElement !== null) { + const workOrderId = reopenWorkOrderButtonElement.dataset.workOrderId ?? ''; + reopenWorkOrderButtonElement.addEventListener('click', () => { + function doReopen() { + cityssm.postJSON(`${los.urlPrefix}/workOrders/doReopenWorkOrder`, { + workOrderId + }, (rawResponseJSON) => { + const responseJSON = rawResponseJSON; + if (responseJSON.success) { + globalThis.location.href = los.getWorkOrderURL(workOrderId, true, true); + } + else { + bulmaJS.alert({ + title: 'Error Reopening Work Order', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }); + } + }); + } + bulmaJS.confirm({ + title: 'Reopen Work Order', + message: 'Are you sure you want to remove the close date from this work order and reopen it?', + contextualColorName: 'warning', + okButton: { + text: 'Yes, Reopen Work Order', + callbackFunction: doReopen + } + }); + }); + } +})(); diff --git a/temp/legacy.importFromCSV.js b/temp/legacy.importFromCSV.js index 76d599a3..a7f50fbf 100644 --- a/temp/legacy.importFromCSV.js +++ b/temp/legacy.importFromCSV.js @@ -1,24 +1,25 @@ +/* eslint-disable max-lines */ import fs from 'node:fs'; import { dateIntegerToString, dateToString } from '@cityssm/utils-datetime'; import sqlite from 'better-sqlite3'; import papa from 'papaparse'; -import { sunriseDB as databasePath } from '../data/databasePaths.js'; -import addLot from '../database/addLot.js'; +import { sunriseDB as databasePath } from '../helpers/database.helpers.js'; +import addBurialSite from '../database/addBurialSite.js'; import addBurialSiteContract from '../database/addBurialSiteContract.js'; import addBurialSiteContractComment from '../database/addBurialSiteContractComment.js'; import addBurialSiteContractFee from '../database/addBurialSiteContractFee.js'; import addBurialSiteContractOccupant from '../database/addBurialSiteContractOccupant.js'; import addBurialSiteContractTransaction from '../database/addBurialSiteContractTransaction.js'; -import addMap from '../database/addMap.js'; -import addOrUpdateLotOccupancyField from '../database/addOrUpdateLotOccupancyField.js'; +import addCemetery from '../database/addCemetery.js'; +import addOrUpdateBurialSiteContractField from '../database/addOrUpdateBurialSiteContractField.js'; import addWorkOrder from '../database/addWorkOrder.js'; -import addWorkOrderLot from '../database/addWorkOrderLot.js'; +import addWorkOrderBurialSite from '../database/addWorkOrderBurialSite.js'; import addWorkOrderBurialSiteContract from '../database/addWorkOrderBurialSiteContract.js'; import addWorkOrderMilestone from '../database/addWorkOrderMilestone.js'; import closeWorkOrder from '../database/closeWorkOrder.js'; -import getLot, { getLotByLotName } from '../database/getLot.js'; +import getBurialSite from '../database/getBurialSite.js'; import getBurialSiteContracts from '../database/getBurialSiteContracts.js'; -import getMapFromDatabase from '../database/getMap.js'; +import getCemeteryFromDatabase from '../database/getCemetery.js'; import getWorkOrder, { getWorkOrderByWorkOrderNumber } from '../database/getWorkOrder.js'; import reopenWorkOrder from '../database/reopenWorkOrder.js'; import { updateBurialSiteStatus } from '../database/updateBurialSite.js'; @@ -37,18 +38,18 @@ function purgeTables() { const tablesToPurge = [ 'WorkOrderMilestones', 'WorkOrderComments', - 'WorkOrderLots', - 'WorkOrderLotOccupancies', + 'WorkOrderBurialSites', + 'WorkOrderBurialSiteContracts', 'WorkOrders', - 'LotOccupancyTransactions', - 'LotOccupancyFees', - 'LotOccupancyFields', - 'LotOccupancyComments', - 'LotOccupancyOccupants', - 'LotOccupancies', - 'LotFields', - 'LotComments', - 'Lots' + 'BurialSiteContractTransactions', + 'BurialSiteContractFees', + 'BurialSiteContractFields', + 'BurialSiteContractComments', + 'BurialSiteContractInterments', + 'BurialSiteContracts', + 'BurialSiteFields', + 'BurialSiteComments', + 'BurialSites' ]; const database = sqlite(databasePath); for (const tableName of tablesToPurge) { @@ -63,20 +64,20 @@ function purgeTables() { function purgeConfigTables() { console.time('purgeConfigTables'); const database = sqlite(databasePath); - database.prepare('delete from Maps').run(); - database.prepare("delete from sqlite_sequence where name in ('Maps')").run(); + database.prepare('delete from Cemeteries').run(); + database.prepare("delete from sqlite_sequence where name in ('Cemeteries')").run(); database.close(); console.timeEnd('purgeConfigTables'); } -function getMapByMapDescription(mapDescription) { +function getCemeteryByDescription(cemeteryDescription) { const database = sqlite(databasePath, { readonly: true }); - const map = database - .prepare('select * from Maps where mapDescription = ?') - .get(mapDescription); + const cemetery = database + .prepare('select * from Cemeteries where cemeteryDescription = ?') + .get(cemeteryDescription); database.close(); - return map; + return cemetery; } function formatDateString(year, month, day) { const formattedYear = `0000${year}`.slice(-4); @@ -102,8 +103,8 @@ const cemeteryTocemeteryName = { UG: 'New Greenwood - Urn Garden', WK: 'West Korah' }; -const mapCache = new Map(); -async function getMap(dataRow) { +const cemeteryCache = new Map(); +async function getCemetery(dataRow) { const mapCacheKey = dataRow.cemetery; /* if (masterRow.CM_CEMETERY === "HS" && @@ -111,29 +112,29 @@ async function getMap(dataRow) { mapCacheKey += "-" + masterRow.CM_BLOCK; } */ - if (mapCache.has(mapCacheKey)) { - return mapCache.get(mapCacheKey); + if (cemeteryCache.has(mapCacheKey)) { + return cemeteryCache.get(mapCacheKey); } - let map = getMapByMapDescription(mapCacheKey); - if (!map) { - console.log(`Creating map: ${dataRow.cemetery}`); - const cemeteryId = await addMap({ + let cemetery = getCemeteryByDescription(mapCacheKey); + if (cemetery === undefined) { + console.log(`Creating cemetery: ${dataRow.cemetery}`); + const cemeteryId = await addCemetery({ cemeteryName: cemeteryTocemeteryName[dataRow.cemetery] ?? dataRow.cemetery, - mapDescription: dataRow.cemetery, - mapSVG: '', - mapLatitude: '', - mapLongitude: '', - mapAddress1: '', - mapAddress2: '', - mapCity: 'Sault Ste. Marie', - mapProvince: 'ON', - mapPostalCode: '', - mapPhoneNumber: '' + cemeteryDescription: dataRow.cemetery, + cemeterySvg: '', + cemeteryLatitude: '', + cemeteryLongitude: '', + cemeteryAddress1: '', + cemeteryAddress2: '', + cemeteryCity: 'Sault Ste. Marie', + cemeteryProvince: 'ON', + cemeteryPostalCode: '', + cemeteryPhoneNumber: '' }, user); - map = (await getMapFromDatabase(cemeteryId)); + cemetery = (await getCemeteryFromDatabase(cemeteryId)); } - mapCache.set(mapCacheKey, map); - return map; + cemeteryCache.set(mapCacheKey, cemetery); + return cemetery; } async function importFromMasterCSV() { console.time('importFromMasterCSV'); @@ -149,31 +150,20 @@ async function importFromMasterCSV() { } try { for (masterRow of cmmaster.data) { - const map = await getMap({ + const cemetery = await getCemetery({ cemetery: masterRow.CM_CEMETERY }); - const lotName = importData.buildLotName({ - cemetery: masterRow.CM_CEMETERY, - block: masterRow.CM_BLOCK, - range1: masterRow.CM_RANGE1, - range2: masterRow.CM_RANGE2, - lot1: masterRow.CM_LOT1, - lot2: masterRow.CM_LOT2, - grave1: masterRow.CM_GRAVE1, - grave2: masterRow.CM_GRAVE2, - interment: masterRow.CM_INTERMENT - }); const burialSiteTypeId = importIds.getburialSiteTypeId({ cemetery: masterRow.CM_CEMETERY }); - let lotId; + let burialSiteId; if (masterRow.CM_CEMETERY !== '00') { - lotId = await addLot({ + burialSiteId = await addBurialSite({ lotName, burialSiteTypeId, - burialSiteStatusId: importIds.availableburialSiteStatusId, - cemeteryId: map.cemeteryId, - mapKey: lotName.includes(',') ? lotName.split(',')[0] : lotName, + burialSiteStatusId: importIds.availableBurialSiteStatusId, + cemeteryId: cemetery.cemeteryId, + cemeterySvgId: '', burialSiteLatitude: '', burialSiteLongitude: '' }, user); @@ -209,7 +199,7 @@ async function importFromMasterCSV() { } preneedburialSiteContractId = await addBurialSiteContract({ contractTypeId: importIds.preneedOccupancyType.contractTypeId, - lotId: lotId ?? '', + lotId: burialSiteId ?? '', contractStartDateString: preneedcontractStartDateString, contractEndDateString, contractTypeFieldIds: '' @@ -253,7 +243,7 @@ async function importFromMasterCSV() { }, user); } if (contractEndDateString === '') { - await updateBurialSiteStatus(lotId ?? '', importIds.reservedburialSiteStatusId, user); + await updateBurialSiteStatus(burialSiteId ?? '', importIds.reservedburialSiteStatusId, user); } } let deceasedcontractStartDateString; @@ -270,15 +260,15 @@ async function importFromMasterCSV() { deceasedcontractStartDateString === '0000-00-00') { deceasedcontractStartDateString = '0001-01-01'; } - const deceasedcontractEndDateString = lotId + const deceasedcontractEndDateString = burialSiteId ? '' : deceasedcontractStartDateString; - const occupancyType = lotId + const occupancyType = burialSiteId ? importIds.deceasedOccupancyType : importIds.cremationOccupancyType; deceasedburialSiteContractId = await addBurialSiteContract({ contractTypeId: occupancyType.contractTypeId, - lotId: lotId ?? '', + lotId: burialSiteId ?? '', contractStartDateString: deceasedcontractStartDateString, contractEndDateString: deceasedcontractEndDateString, contractTypeFieldIds: '' @@ -299,14 +289,14 @@ async function importFromMasterCSV() { }, user); if (masterRow.CM_DEATH_YR !== '') { const burialSiteContractFieldValue = formatDateString(masterRow.CM_DEATH_YR, masterRow.CM_DEATH_MON, masterRow.CM_DEATH_DAY); - await addOrUpdateLotOccupancyField({ + await addOrUpdateBurialSiteContractField({ burialSiteContractId: deceasedburialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Death Date').contractTypeFieldId, burialSiteContractFieldValue }, user); } if (masterRow.CM_AGE !== '') { - await addOrUpdateLotOccupancyField({ + await addOrUpdateBurialSiteContractField({ burialSiteContractId: deceasedburialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Death Age').contractTypeFieldId, burialSiteContractFieldValue: masterRow.CM_AGE @@ -314,9 +304,9 @@ async function importFromMasterCSV() { } if (masterRow.CM_PERIOD !== '') { const period = importData.getDeathAgePeriod(masterRow.CM_PERIOD); - await addOrUpdateLotOccupancyField({ + await addOrUpdateBurialSiteContractField({ burialSiteContractId: deceasedburialSiteContractId, - contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => (occupancyTypeField.occupancyTypeField === 'Death Age Period')).contractTypeFieldId, + contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Death Age Period').contractTypeFieldId, burialSiteContractFieldValue: period }, user); } @@ -336,7 +326,7 @@ async function importFromMasterCSV() { occupantEmailAddress: funeralHomeOccupant.occupantEmailAddress ?? '' }, user); /* - addOrUpdateLotOccupancyField( + addOrUpdateBurialSiteContractField( { burialSiteContractId: deceasedburialSiteContractId, contractTypeFieldId: allContractTypeFields.find( @@ -352,17 +342,17 @@ async function importFromMasterCSV() { } if (masterRow.CM_FUNERAL_YR !== '') { const burialSiteContractFieldValue = formatDateString(masterRow.CM_FUNERAL_YR, masterRow.CM_FUNERAL_MON, masterRow.CM_FUNERAL_DAY); - await addOrUpdateLotOccupancyField({ + await addOrUpdateBurialSiteContractField({ burialSiteContractId: deceasedburialSiteContractId, - contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => (occupancyTypeField.occupancyTypeField === 'Funeral Date')).contractTypeFieldId, + contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Funeral Date').contractTypeFieldId, burialSiteContractFieldValue }, user); } if (occupancyType.occupancyType !== 'Cremation') { if (masterRow.CM_CONTAINER_TYPE !== '') { - await addOrUpdateLotOccupancyField({ + await addOrUpdateBurialSiteContractField({ burialSiteContractId: deceasedburialSiteContractId, - contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => (occupancyTypeField.occupancyTypeField === 'Container Type')).contractTypeFieldId, + contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Container Type').contractTypeFieldId, burialSiteContractFieldValue: masterRow.CM_CONTAINER_TYPE }, user); } @@ -371,9 +361,9 @@ async function importFromMasterCSV() { if (commitalType === 'GS') { commitalType = 'Graveside'; } - await addOrUpdateLotOccupancyField({ + await addOrUpdateBurialSiteContractField({ burialSiteContractId: deceasedburialSiteContractId, - contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => (occupancyTypeField.occupancyTypeField === 'Committal Type')).contractTypeFieldId, + contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Committal Type').contractTypeFieldId, burialSiteContractFieldValue: commitalType }, user); } @@ -402,7 +392,7 @@ async function importFromMasterCSV() { burialSiteContractComment: `Imported Contract #${masterRow.CM_WORK_ORDER}` }, user); } - await updateBurialSiteStatus(lotId ?? '', importIds.takenburialSiteStatusId, user); + await updateBurialSiteStatus(burialSiteId ?? '', importIds.takenburialSiteStatusId, user); if (masterRow.CM_PRENEED_OWNER !== '') { await addBurialSiteContractOccupant({ burialSiteContractId: deceasedburialSiteContractId, @@ -450,7 +440,7 @@ async function importFromPrepaidCSV() { } let lot; if (cemetery !== '') { - const map = await getMap({ + const map = await getCemetery({ cemetery }); const lotName = importData.buildLotName({ @@ -464,12 +454,12 @@ async function importFromPrepaidCSV() { grave2: prepaidRow.CMPP_GRAVE2, interment: prepaidRow.CMPP_INTERMENT }); - lot = await getLotByLotName(lotName); + lot = await getBurialSiteByLotName(lotName); if (!lot) { const burialSiteTypeId = importIds.getburialSiteTypeId({ cemetery }); - const lotId = await addLot({ + const lotId = await addBurialSite({ lotName, burialSiteTypeId, burialSiteStatusId: importIds.reservedburialSiteStatusId, @@ -478,10 +468,11 @@ async function importFromPrepaidCSV() { burialSiteLatitude: '', burialSiteLongitude: '' }, user); - lot = await getLot(lotId); + lot = await getBurialSite(lotId); } } - if (lot && lot.burialSiteStatusId === importIds.availableburialSiteStatusId) { + if (lot && + lot.burialSiteStatusId === importIds.availableburialSiteStatusId) { await updateBurialSiteStatus(lot.lotId, importIds.reservedburialSiteStatusId, user); } const contractStartDateString = formatDateString(prepaidRow.CMPP_PURCH_YR, prepaidRow.CMPP_PURCH_MON, prepaidRow.CMPP_PURCH_DAY); @@ -711,16 +702,16 @@ async function importFromWorkOrderCSV() { grave2: workOrderRow.WO_GRAVE2, interment: workOrderRow.WO_INTERMENT }); - lot = await getLotByLotName(lotName); + lot = await getBurialSiteByLotName(lotName); if (lot) { await updateBurialSiteStatus(lot.lotId, importIds.takenburialSiteStatusId, user); } else { - const map = await getMap({ cemetery: workOrderRow.WO_CEMETERY }); + const map = await getCemetery({ cemetery: workOrderRow.WO_CEMETERY }); const burialSiteTypeId = importIds.getburialSiteTypeId({ cemetery: workOrderRow.WO_CEMETERY }); - const lotId = await addLot({ + const lotId = await addBurialSite({ cemeteryId: map.cemeteryId, lotName, mapKey: lotName.includes(',') ? lotName.split(',')[0] : lotName, @@ -729,11 +720,11 @@ async function importFromWorkOrderCSV() { burialSiteLatitude: '', burialSiteLongitude: '' }, user); - lot = await getLot(lotId); + lot = await getBurialSite(lotId); } const workOrderContainsLot = workOrder.workOrderLots.find((possibleLot) => (possibleLot.lotId = lot.lotId)); if (!workOrderContainsLot) { - await addWorkOrderLot({ + await addWorkOrderBurialSite({ workOrderId: workOrder.workOrderId, lotId: lot.lotId }, user); @@ -768,21 +759,21 @@ async function importFromWorkOrderCSV() { }, user); if (workOrderRow.WO_DEATH_YR !== '') { const burialSiteContractFieldValue = formatDateString(workOrderRow.WO_DEATH_YR, workOrderRow.WO_DEATH_MON, workOrderRow.WO_DEATH_DAY); - await addOrUpdateLotOccupancyField({ + await addOrUpdateBurialSiteContractField({ burialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Death Date').contractTypeFieldId, burialSiteContractFieldValue }, user); } if (workOrderRow.WO_DEATH_PLACE !== '') { - await addOrUpdateLotOccupancyField({ + await addOrUpdateBurialSiteContractField({ burialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Death Place').contractTypeFieldId, burialSiteContractFieldValue: workOrderRow.WO_DEATH_PLACE }, user); } if (workOrderRow.WO_AGE !== '') { - await addOrUpdateLotOccupancyField({ + await addOrUpdateBurialSiteContractField({ burialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Death Age').contractTypeFieldId, burialSiteContractFieldValue: workOrderRow.WO_AGE @@ -790,9 +781,9 @@ async function importFromWorkOrderCSV() { } if (workOrderRow.WO_PERIOD !== '') { const period = importData.getDeathAgePeriod(workOrderRow.WO_PERIOD); - await addOrUpdateLotOccupancyField({ + await addOrUpdateBurialSiteContractField({ burialSiteContractId, - contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => (occupancyTypeField.occupancyTypeField === 'Death Age Period')).contractTypeFieldId, + contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Death Age Period').contractTypeFieldId, burialSiteContractFieldValue: period }, user); } @@ -812,7 +803,7 @@ async function importFromWorkOrderCSV() { occupantEmailAddress: funeralHomeOccupant.occupantEmailAddress }, user); /* - addOrUpdateLotOccupancyField( + addOrUpdateBurialSiteContractField( { burialSiteContractId: burialSiteContractId, contractTypeFieldId: allContractTypeFields.find((occupancyTypeField) => { @@ -826,7 +817,7 @@ async function importFromWorkOrderCSV() { } if (workOrderRow.WO_FUNERAL_YR !== '') { const burialSiteContractFieldValue = formatDateString(workOrderRow.WO_FUNERAL_YR, workOrderRow.WO_FUNERAL_MON, workOrderRow.WO_FUNERAL_DAY); - await addOrUpdateLotOccupancyField({ + await addOrUpdateBurialSiteContractField({ burialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Funeral Date').contractTypeFieldId, burialSiteContractFieldValue @@ -834,9 +825,9 @@ async function importFromWorkOrderCSV() { } if (occupancyType.occupancyType !== 'Cremation') { if (workOrderRow.WO_CONTAINER_TYPE !== '') { - await addOrUpdateLotOccupancyField({ + await addOrUpdateBurialSiteContractField({ burialSiteContractId, - contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => (occupancyTypeField.occupancyTypeField === 'Container Type')).contractTypeFieldId, + contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Container Type').contractTypeFieldId, burialSiteContractFieldValue: workOrderRow.WO_CONTAINER_TYPE }, user); } @@ -845,9 +836,9 @@ async function importFromWorkOrderCSV() { if (commitalType === 'GS') { commitalType = 'Graveside'; } - await addOrUpdateLotOccupancyField({ + await addOrUpdateBurialSiteContractField({ burialSiteContractId, - contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => (occupancyTypeField.occupancyTypeField === 'Committal Type')).contractTypeFieldId, + contractTypeFieldId: occupancyType.ContractTypeFields.find((occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Committal Type').contractTypeFieldId, burialSiteContractFieldValue: commitalType }, user); } diff --git a/temp/legacy.importFromCSV.ts b/temp/legacy.importFromCSV.ts index a6b7b784..542bf241 100644 --- a/temp/legacy.importFromCSV.ts +++ b/temp/legacy.importFromCSV.ts @@ -1,3 +1,5 @@ +/* eslint-disable max-lines */ + import fs from 'node:fs' import { @@ -9,23 +11,25 @@ import { import sqlite from 'better-sqlite3' import papa from 'papaparse' -import { sunriseDB as databasePath } from '../data/databasePaths.js' -import addLot from '../database/addLot.js' +import { sunriseDB as databasePath } from '../helpers/database.helpers.js' +import addBurialSite from '../database/addBurialSite.js' import addBurialSiteContract from '../database/addBurialSiteContract.js' import addBurialSiteContractComment from '../database/addBurialSiteContractComment.js' import addBurialSiteContractFee from '../database/addBurialSiteContractFee.js' import addBurialSiteContractOccupant from '../database/addBurialSiteContractOccupant.js' import addBurialSiteContractTransaction from '../database/addBurialSiteContractTransaction.js' -import addMap from '../database/addMap.js' -import addOrUpdateLotOccupancyField from '../database/addOrUpdateLotOccupancyField.js' +import addCemetery from '../database/addCemetery.js' +import addOrUpdateBurialSiteContractField from '../database/addOrUpdateBurialSiteContractField.js' import addWorkOrder from '../database/addWorkOrder.js' -import addWorkOrderLot from '../database/addWorkOrderLot.js' +import addWorkOrderBurialSite from '../database/addWorkOrderBurialSite.js' import addWorkOrderBurialSiteContract from '../database/addWorkOrderBurialSiteContract.js' import addWorkOrderMilestone from '../database/addWorkOrderMilestone.js' import closeWorkOrder from '../database/closeWorkOrder.js' -import getLot, { getLotByLotName } from '../database/getLot.js' +import getBurialSite, { + getBurialSiteByBurialSiteName +} from '../database/getBurialSite.js' import getBurialSiteContracts from '../database/getBurialSiteContracts.js' -import getMapFromDatabase from '../database/getMap.js' +import getCemeteryFromDatabase from '../database/getCemetery.js' import getWorkOrder, { getWorkOrderByWorkOrderNumber } from '../database/getWorkOrder.js' @@ -194,18 +198,18 @@ function purgeTables(): void { const tablesToPurge = [ 'WorkOrderMilestones', 'WorkOrderComments', - 'WorkOrderLots', - 'WorkOrderLotOccupancies', + 'WorkOrderBurialSites', + 'WorkOrderBurialSiteContracts', 'WorkOrders', - 'LotOccupancyTransactions', - 'LotOccupancyFees', - 'LotOccupancyFields', - 'LotOccupancyComments', - 'LotOccupancyOccupants', - 'LotOccupancies', - 'LotFields', - 'LotComments', - 'Lots' + 'BurialSiteContractTransactions', + 'BurialSiteContractFees', + 'BurialSiteContractFields', + 'BurialSiteContractComments', + 'BurialSiteContractInterments', + 'BurialSiteContracts', + 'BurialSiteFields', + 'BurialSiteComments', + 'BurialSites' ] const database = sqlite(databasePath) @@ -226,25 +230,27 @@ function purgeConfigTables(): void { console.time('purgeConfigTables') const database = sqlite(databasePath) - database.prepare('delete from Maps').run() - database.prepare("delete from sqlite_sequence where name in ('Maps')").run() + database.prepare('delete from Cemeteries').run() + database.prepare("delete from sqlite_sequence where name in ('Cemeteries')").run() database.close() console.timeEnd('purgeConfigTables') } -function getMapByMapDescription(mapDescription: string): recordTypes.MapRecord { +function getCemeteryByDescription( + cemeteryDescription: string +): recordTypes.Cemetery | undefined { const database = sqlite(databasePath, { readonly: true }) - const map = database - .prepare('select * from Maps where mapDescription = ?') - .get(mapDescription) as recordTypes.MapRecord + const cemetery = database + .prepare('select * from Cemeteries where cemeteryDescription = ?') + .get(cemeteryDescription) as recordTypes.Cemetery database.close() - return map + return cemetery } function formatDateString( @@ -280,11 +286,11 @@ const cemeteryTocemeteryName = { WK: 'West Korah' } -const mapCache = new Map() +const cemeteryCache = new Map() -async function getMap(dataRow: { +async function getCemetery(dataRow: { cemetery: string -}): Promise { +}): Promise { const mapCacheKey = dataRow.cemetery /* @@ -294,38 +300,39 @@ async function getMap(dataRow: { } */ - if (mapCache.has(mapCacheKey)) { - return mapCache.get(mapCacheKey)! + if (cemeteryCache.has(mapCacheKey)) { + return cemeteryCache.get(mapCacheKey)! } - let map = getMapByMapDescription(mapCacheKey) + let cemetery = getCemeteryByDescription(mapCacheKey) - if (!map) { - console.log(`Creating map: ${dataRow.cemetery}`) + if (cemetery === undefined) { + console.log(`Creating cemetery: ${dataRow.cemetery}`) - const cemeteryId = await addMap( + const cemeteryId = await addCemetery( { - cemeteryName: cemeteryTocemeteryName[dataRow.cemetery] ?? dataRow.cemetery, - mapDescription: dataRow.cemetery, - mapSVG: '', - mapLatitude: '', - mapLongitude: '', - mapAddress1: '', - mapAddress2: '', - mapCity: 'Sault Ste. Marie', - mapProvince: 'ON', - mapPostalCode: '', - mapPhoneNumber: '' + cemeteryName: + cemeteryTocemeteryName[dataRow.cemetery] ?? dataRow.cemetery, + cemeteryDescription: dataRow.cemetery, + cemeterySvg: '', + cemeteryLatitude: '', + cemeteryLongitude: '', + cemeteryAddress1: '', + cemeteryAddress2: '', + cemeteryCity: 'Sault Ste. Marie', + cemeteryProvince: 'ON', + cemeteryPostalCode: '', + cemeteryPhoneNumber: '' }, user ) - map = (await getMapFromDatabase(cemeteryId)) as recordTypes.MapRecord + cemetery = (await getCemeteryFromDatabase(cemeteryId)) as recordTypes.Cemetery } - mapCache.set(mapCacheKey, map) + cemeteryCache.set(mapCacheKey, cemetery) - return map + return cemetery } async function importFromMasterCSV(): Promise { @@ -347,36 +354,24 @@ async function importFromMasterCSV(): Promise { try { for (masterRow of cmmaster.data) { - const map = await getMap({ + const cemetery = await getCemetery({ cemetery: masterRow.CM_CEMETERY })! - const lotName = importData.buildLotName({ - cemetery: masterRow.CM_CEMETERY, - block: masterRow.CM_BLOCK, - range1: masterRow.CM_RANGE1, - range2: masterRow.CM_RANGE2, - lot1: masterRow.CM_LOT1, - lot2: masterRow.CM_LOT2, - grave1: masterRow.CM_GRAVE1, - grave2: masterRow.CM_GRAVE2, - interment: masterRow.CM_INTERMENT - }) - const burialSiteTypeId = importIds.getburialSiteTypeId({ cemetery: masterRow.CM_CEMETERY })! - let lotId: number | undefined + let burialSiteId: number | undefined if (masterRow.CM_CEMETERY !== '00') { - lotId = await addLot( + burialSiteId = await addBurialSite( { lotName, burialSiteTypeId, - burialSiteStatusId: importIds.availableburialSiteStatusId, - cemeteryId: map.cemeteryId!, - mapKey: lotName.includes(',') ? lotName.split(',')[0] : lotName, + burialSiteStatusId: importIds.availableBurialSiteStatusId, + cemeteryId: cemetery.cemeteryId!, + cemeterySvgId: '', burialSiteLatitude: '', burialSiteLongitude: '' }, @@ -446,7 +441,7 @@ async function importFromMasterCSV(): Promise { preneedburialSiteContractId = await addBurialSiteContract( { contractTypeId: importIds.preneedOccupancyType.contractTypeId, - lotId: lotId ?? '', + lotId: burialSiteId ?? '', contractStartDateString: preneedcontractStartDateString, contractEndDateString, contractTypeFieldIds: '' @@ -478,7 +473,8 @@ async function importFromMasterCSV(): Promise { await addBurialSiteContractComment( { burialSiteContractId: preneedburialSiteContractId, - burialSiteContractCommentDateString: preneedcontractStartDateString, + burialSiteContractCommentDateString: + preneedcontractStartDateString, burialSiteContractCommentTimeString: '00:00', burialSiteContractComment: masterRow.CM_REMARK1 }, @@ -490,7 +486,8 @@ async function importFromMasterCSV(): Promise { await addBurialSiteContractComment( { burialSiteContractId: preneedburialSiteContractId, - burialSiteContractCommentDateString: preneedcontractStartDateString, + burialSiteContractCommentDateString: + preneedcontractStartDateString, burialSiteContractCommentTimeString: '00:00', burialSiteContractComment: masterRow.CM_REMARK2 }, @@ -502,7 +499,8 @@ async function importFromMasterCSV(): Promise { await addBurialSiteContractComment( { burialSiteContractId: preneedburialSiteContractId, - burialSiteContractCommentDateString: preneedcontractStartDateString, + burialSiteContractCommentDateString: + preneedcontractStartDateString, burialSiteContractCommentTimeString: '00:00', burialSiteContractComment: `Imported Contract #${masterRow.CM_WORK_ORDER}` }, @@ -512,7 +510,7 @@ async function importFromMasterCSV(): Promise { if (contractEndDateString === '') { await updateBurialSiteStatus( - lotId ?? '', + burialSiteId ?? '', importIds.reservedburialSiteStatusId, user ) @@ -549,18 +547,18 @@ async function importFromMasterCSV(): Promise { deceasedcontractStartDateString = '0001-01-01' } - const deceasedcontractEndDateString = lotId + const deceasedcontractEndDateString = burialSiteId ? '' : deceasedcontractStartDateString - const occupancyType = lotId + const occupancyType = burialSiteId ? importIds.deceasedOccupancyType : importIds.cremationOccupancyType deceasedburialSiteContractId = await addBurialSiteContract( { contractTypeId: occupancyType.contractTypeId, - lotId: lotId ?? '', + lotId: burialSiteId ?? '', contractStartDateString: deceasedcontractStartDateString, contractEndDateString: deceasedcontractEndDateString, contractTypeFieldIds: '' @@ -595,11 +593,12 @@ async function importFromMasterCSV(): Promise { masterRow.CM_DEATH_DAY ) - await addOrUpdateLotOccupancyField( + await addOrUpdateBurialSiteContractField( { burialSiteContractId: deceasedburialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields!.find( - (occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Death Date' + (occupancyTypeField) => + occupancyTypeField.occupancyTypeField === 'Death Date' )!.contractTypeFieldId!, burialSiteContractFieldValue }, @@ -608,11 +607,12 @@ async function importFromMasterCSV(): Promise { } if (masterRow.CM_AGE !== '') { - await addOrUpdateLotOccupancyField( + await addOrUpdateBurialSiteContractField( { burialSiteContractId: deceasedburialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields!.find( - (occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Death Age' + (occupancyTypeField) => + occupancyTypeField.occupancyTypeField === 'Death Age' )!.contractTypeFieldId!, burialSiteContractFieldValue: masterRow.CM_AGE }, @@ -623,13 +623,12 @@ async function importFromMasterCSV(): Promise { if (masterRow.CM_PERIOD !== '') { const period = importData.getDeathAgePeriod(masterRow.CM_PERIOD) - await addOrUpdateLotOccupancyField( + await addOrUpdateBurialSiteContractField( { burialSiteContractId: deceasedburialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields!.find( - (occupancyTypeField) => ( - occupancyTypeField.occupancyTypeField === 'Death Age Period' - ) + (occupancyTypeField) => + occupancyTypeField.occupancyTypeField === 'Death Age Period' )!.contractTypeFieldId!, burialSiteContractFieldValue: period }, @@ -654,14 +653,16 @@ async function importFromMasterCSV(): Promise { occupantCity: funeralHomeOccupant.occupantCity ?? '', occupantProvince: funeralHomeOccupant.occupantProvince ?? '', occupantPostalCode: funeralHomeOccupant.occupantPostalCode ?? '', - occupantPhoneNumber: funeralHomeOccupant.occupantPhoneNumber ?? '', - occupantEmailAddress: funeralHomeOccupant.occupantEmailAddress ?? '' + occupantPhoneNumber: + funeralHomeOccupant.occupantPhoneNumber ?? '', + occupantEmailAddress: + funeralHomeOccupant.occupantEmailAddress ?? '' }, user ) /* - addOrUpdateLotOccupancyField( + addOrUpdateBurialSiteContractField( { burialSiteContractId: deceasedburialSiteContractId, contractTypeFieldId: allContractTypeFields.find( @@ -683,13 +684,12 @@ async function importFromMasterCSV(): Promise { masterRow.CM_FUNERAL_DAY ) - await addOrUpdateLotOccupancyField( + await addOrUpdateBurialSiteContractField( { burialSiteContractId: deceasedburialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields!.find( - (occupancyTypeField) => ( - occupancyTypeField.occupancyTypeField === 'Funeral Date' - ) + (occupancyTypeField) => + occupancyTypeField.occupancyTypeField === 'Funeral Date' )!.contractTypeFieldId!, burialSiteContractFieldValue }, @@ -699,13 +699,12 @@ async function importFromMasterCSV(): Promise { if (occupancyType.occupancyType !== 'Cremation') { if (masterRow.CM_CONTAINER_TYPE !== '') { - await addOrUpdateLotOccupancyField( + await addOrUpdateBurialSiteContractField( { burialSiteContractId: deceasedburialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields!.find( - (occupancyTypeField) => ( - occupancyTypeField.occupancyTypeField === 'Container Type' - ) + (occupancyTypeField) => + occupancyTypeField.occupancyTypeField === 'Container Type' )!.contractTypeFieldId!, burialSiteContractFieldValue: masterRow.CM_CONTAINER_TYPE }, @@ -720,13 +719,12 @@ async function importFromMasterCSV(): Promise { commitalType = 'Graveside' } - await addOrUpdateLotOccupancyField( + await addOrUpdateBurialSiteContractField( { burialSiteContractId: deceasedburialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields!.find( - (occupancyTypeField) => ( - occupancyTypeField.occupancyTypeField === 'Committal Type' - ) + (occupancyTypeField) => + occupancyTypeField.occupancyTypeField === 'Committal Type' )!.contractTypeFieldId!, burialSiteContractFieldValue: commitalType }, @@ -739,7 +737,8 @@ async function importFromMasterCSV(): Promise { await addBurialSiteContractComment( { burialSiteContractId: deceasedburialSiteContractId, - burialSiteContractCommentDateString: deceasedcontractStartDateString, + burialSiteContractCommentDateString: + deceasedcontractStartDateString, burialSiteContractCommentTimeString: '00:00', burialSiteContractComment: masterRow.CM_REMARK1 }, @@ -751,7 +750,8 @@ async function importFromMasterCSV(): Promise { await addBurialSiteContractComment( { burialSiteContractId: deceasedburialSiteContractId, - burialSiteContractCommentDateString: deceasedcontractStartDateString, + burialSiteContractCommentDateString: + deceasedcontractStartDateString, burialSiteContractCommentTimeString: '00:00', burialSiteContractComment: masterRow.CM_REMARK2 }, @@ -763,7 +763,8 @@ async function importFromMasterCSV(): Promise { await addBurialSiteContractComment( { burialSiteContractId: deceasedburialSiteContractId, - burialSiteContractCommentDateString: deceasedcontractStartDateString, + burialSiteContractCommentDateString: + deceasedcontractStartDateString, burialSiteContractCommentTimeString: '00:00', burialSiteContractComment: `Imported Contract #${masterRow.CM_WORK_ORDER}` }, @@ -771,7 +772,11 @@ async function importFromMasterCSV(): Promise { ) } - await updateBurialSiteStatus(lotId ?? '', importIds.takenburialSiteStatusId, user) + await updateBurialSiteStatus( + burialSiteId ?? '', + importIds.takenburialSiteStatusId, + user + ) if (masterRow.CM_PRENEED_OWNER !== '') { await addBurialSiteContractOccupant( @@ -833,7 +838,7 @@ async function importFromPrepaidCSV(): Promise { let lot: recordTypes.Lot | undefined if (cemetery !== '') { - const map = await getMap({ + const map = await getCemetery({ cemetery }) @@ -849,14 +854,14 @@ async function importFromPrepaidCSV(): Promise { interment: prepaidRow.CMPP_INTERMENT }) - lot = await getLotByLotName(lotName) + lot = await getBurialSiteByLotName(lotName) if (!lot) { const burialSiteTypeId = importIds.getburialSiteTypeId({ cemetery }) - const lotId = await addLot( + const lotId = await addBurialSite( { lotName, burialSiteTypeId, @@ -869,12 +874,19 @@ async function importFromPrepaidCSV(): Promise { user ) - lot = await getLot(lotId) + lot = await getBurialSite(lotId) } } - if (lot && lot.burialSiteStatusId === importIds.availableburialSiteStatusId) { - await updateBurialSiteStatus(lot.lotId, importIds.reservedburialSiteStatusId, user) + if ( + lot && + lot.burialSiteStatusId === importIds.availableburialSiteStatusId + ) { + await updateBurialSiteStatus( + lot.lotId, + importIds.reservedburialSiteStatusId, + user + ) } const contractStartDateString = formatDateString( @@ -909,14 +921,14 @@ async function importFromPrepaidCSV(): Promise { } burialSiteContractId ||= await addBurialSiteContract( - { - lotId: lot ? lot.lotId : '', - contractTypeId: importIds.preneedOccupancyType.contractTypeId, - contractStartDateString, - contractEndDateString: '' - }, - user - ); + { + lotId: lot ? lot.lotId : '', + contractTypeId: importIds.preneedOccupancyType.contractTypeId, + contractStartDateString, + contractEndDateString: '' + }, + user + ) await addBurialSiteContractOccupant( { @@ -1197,18 +1209,22 @@ async function importFromWorkOrderCSV(): Promise { interment: workOrderRow.WO_INTERMENT }) - lot = await getLotByLotName(lotName) + lot = await getBurialSiteByLotName(lotName) if (lot) { - await updateBurialSiteStatus(lot.lotId, importIds.takenburialSiteStatusId, user) + await updateBurialSiteStatus( + lot.lotId, + importIds.takenburialSiteStatusId, + user + ) } else { - const map = await getMap({ cemetery: workOrderRow.WO_CEMETERY }) + const map = await getCemetery({ cemetery: workOrderRow.WO_CEMETERY }) const burialSiteTypeId = importIds.getburialSiteTypeId({ cemetery: workOrderRow.WO_CEMETERY }) - const lotId = await addLot( + const lotId = await addBurialSite( { cemeteryId: map.cemeteryId!, lotName, @@ -1221,7 +1237,7 @@ async function importFromWorkOrderCSV(): Promise { user ) - lot = await getLot(lotId) + lot = await getBurialSite(lotId) } const workOrderContainsLot = workOrder.workOrderLots!.find( @@ -1229,7 +1245,7 @@ async function importFromWorkOrderCSV(): Promise { ) if (!workOrderContainsLot) { - await addWorkOrderLot( + await addWorkOrderBurialSite( { workOrderId: workOrder.workOrderId!, lotId: lot.lotId @@ -1289,11 +1305,12 @@ async function importFromWorkOrderCSV(): Promise { workOrderRow.WO_DEATH_DAY ) - await addOrUpdateLotOccupancyField( + await addOrUpdateBurialSiteContractField( { burialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields!.find( - (occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Death Date' + (occupancyTypeField) => + occupancyTypeField.occupancyTypeField === 'Death Date' )!.contractTypeFieldId!, burialSiteContractFieldValue }, @@ -1302,11 +1319,12 @@ async function importFromWorkOrderCSV(): Promise { } if (workOrderRow.WO_DEATH_PLACE !== '') { - await addOrUpdateLotOccupancyField( + await addOrUpdateBurialSiteContractField( { burialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields!.find( - (occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Death Place' + (occupancyTypeField) => + occupancyTypeField.occupancyTypeField === 'Death Place' )!.contractTypeFieldId!, burialSiteContractFieldValue: workOrderRow.WO_DEATH_PLACE }, @@ -1315,11 +1333,12 @@ async function importFromWorkOrderCSV(): Promise { } if (workOrderRow.WO_AGE !== '') { - await addOrUpdateLotOccupancyField( + await addOrUpdateBurialSiteContractField( { burialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields!.find( - (occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Death Age' + (occupancyTypeField) => + occupancyTypeField.occupancyTypeField === 'Death Age' )!.contractTypeFieldId!, burialSiteContractFieldValue: workOrderRow.WO_AGE }, @@ -1330,13 +1349,12 @@ async function importFromWorkOrderCSV(): Promise { if (workOrderRow.WO_PERIOD !== '') { const period = importData.getDeathAgePeriod(workOrderRow.WO_PERIOD) - await addOrUpdateLotOccupancyField( + await addOrUpdateBurialSiteContractField( { burialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields!.find( - (occupancyTypeField) => ( - occupancyTypeField.occupancyTypeField === 'Death Age Period' - ) + (occupancyTypeField) => + occupancyTypeField.occupancyTypeField === 'Death Age Period' )!.contractTypeFieldId!, burialSiteContractFieldValue: period }, @@ -1368,7 +1386,7 @@ async function importFromWorkOrderCSV(): Promise { ) /* - addOrUpdateLotOccupancyField( + addOrUpdateBurialSiteContractField( { burialSiteContractId: burialSiteContractId, contractTypeFieldId: allContractTypeFields.find((occupancyTypeField) => { @@ -1388,11 +1406,12 @@ async function importFromWorkOrderCSV(): Promise { workOrderRow.WO_FUNERAL_DAY ) - await addOrUpdateLotOccupancyField( + await addOrUpdateBurialSiteContractField( { burialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields!.find( - (occupancyTypeField) => occupancyTypeField.occupancyTypeField === 'Funeral Date' + (occupancyTypeField) => + occupancyTypeField.occupancyTypeField === 'Funeral Date' )!.contractTypeFieldId!, burialSiteContractFieldValue }, @@ -1402,13 +1421,12 @@ async function importFromWorkOrderCSV(): Promise { if (occupancyType.occupancyType !== 'Cremation') { if (workOrderRow.WO_CONTAINER_TYPE !== '') { - await addOrUpdateLotOccupancyField( + await addOrUpdateBurialSiteContractField( { burialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields!.find( - (occupancyTypeField) => ( - occupancyTypeField.occupancyTypeField === 'Container Type' - ) + (occupancyTypeField) => + occupancyTypeField.occupancyTypeField === 'Container Type' )!.contractTypeFieldId!, burialSiteContractFieldValue: workOrderRow.WO_CONTAINER_TYPE }, @@ -1423,13 +1441,12 @@ async function importFromWorkOrderCSV(): Promise { commitalType = 'Graveside' } - await addOrUpdateLotOccupancyField( + await addOrUpdateBurialSiteContractField( { burialSiteContractId, contractTypeFieldId: occupancyType.ContractTypeFields!.find( - (occupancyTypeField) => ( - occupancyTypeField.occupancyTypeField === 'Committal Type' - ) + (occupancyTypeField) => + occupancyTypeField.occupancyTypeField === 'Committal Type' )!.contractTypeFieldId!, burialSiteContractFieldValue: commitalType }, diff --git a/temp/legacy.importFromCsv.data.d.ts b/temp/legacy.importFromCsv.data.d.ts index 22a93891..2b5afdea 100644 --- a/temp/legacy.importFromCsv.data.d.ts +++ b/temp/legacy.importFromCsv.data.d.ts @@ -1,14 +1,3 @@ import type { LotOccupancyOccupant } from '../types/recordTypes.js'; -export declare function buildLotName(lotNamePieces: { - cemetery: string; - block: string; - range1: string; - range2: string; - lot1: string; - lot2: string; - grave1: string; - grave2: string; - interment: string; -}): string; export declare function getFuneralHomeLotOccupancyOccupantData(funeralHomeKey: string): LotOccupancyOccupant; export declare function getDeathAgePeriod(legacyDeathAgePeriod: string): string; diff --git a/temp/legacy.importFromCsv.data.js b/temp/legacy.importFromCsv.data.js index aa5370b3..b396f64d 100644 --- a/temp/legacy.importFromCsv.data.js +++ b/temp/legacy.importFromCsv.data.js @@ -1,18 +1,6 @@ +// eslint-disable-next-line @eslint-community/eslint-comments/disable-enable-pair +/* eslint-disable @cspell/spellchecker */ import * as importIds from './legacy.importFromCsv.ids.js'; -export function buildLotName(lotNamePieces) { - let lotName = `${lotNamePieces.cemetery}-`; - if (lotNamePieces.block !== '') { - lotName += `B${lotNamePieces.block}-`; - } - if (lotNamePieces.range1 !== '0' || lotNamePieces.range2 !== '') { - lotName += `R${lotNamePieces.range1 === '0' ? '' : lotNamePieces.range1}${lotNamePieces.range2}-`; - } - if (lotNamePieces.lot1 !== '0' || lotNamePieces.lot2 === '') { - lotName += `L${lotNamePieces.lot1}${lotNamePieces.lot2}-`; - } - lotName += `G${lotNamePieces.grave1}${lotNamePieces.grave2}, Interment ${lotNamePieces.interment}`; - return lotName; -} export function getFuneralHomeLotOccupancyOccupantData(funeralHomeKey) { switch (funeralHomeKey) { case 'AR': { diff --git a/temp/legacy.importFromCsv.data.ts b/temp/legacy.importFromCsv.data.ts index 63354951..90f6b72e 100644 --- a/temp/legacy.importFromCsv.data.ts +++ b/temp/legacy.importFromCsv.data.ts @@ -1,39 +1,10 @@ +// eslint-disable-next-line @eslint-community/eslint-comments/disable-enable-pair +/* eslint-disable @cspell/spellchecker */ + import type { LotOccupancyOccupant } from '../types/recordTypes.js' import * as importIds from './legacy.importFromCsv.ids.js' -export function buildLotName(lotNamePieces: { - cemetery: string - block: string - range1: string - range2: string - lot1: string - lot2: string - grave1: string - grave2: string - interment: string -}): string { - let lotName = `${lotNamePieces.cemetery}-` - - if (lotNamePieces.block !== '') { - lotName += `B${lotNamePieces.block}-` - } - - if (lotNamePieces.range1 !== '0' || lotNamePieces.range2 !== '') { - lotName += `R${lotNamePieces.range1 === '0' ? '' : lotNamePieces.range1}${ - lotNamePieces.range2 - }-` - } - - if (lotNamePieces.lot1 !== '0' || lotNamePieces.lot2 === '') { - lotName += `L${lotNamePieces.lot1}${lotNamePieces.lot2}-` - } - - lotName += `G${lotNamePieces.grave1}${lotNamePieces.grave2}, Interment ${lotNamePieces.interment}` - - return lotName -} - export function getFuneralHomeLotOccupancyOccupantData( funeralHomeKey: string ): LotOccupancyOccupant { diff --git a/temp/legacy.importFromCsv.ids.js b/temp/legacy.importFromCsv.ids.js index 32f808e8..2cb30094 100644 --- a/temp/legacy.importFromCsv.ids.js +++ b/temp/legacy.importFromCsv.ids.js @@ -25,19 +25,19 @@ export function getFeeIdByFeeDescription(feeDescription) { /* * Lot Occupant Type IDs */ -export const preneedOwnerLotOccupantTypeId = (await cacheFunctions.getLotOccupantTypeByLotOccupantType('Preneed Owner')) +export const preneedOwnerLotOccupantTypeId = (await cacheFunctions.getBurialSiteOccupantTypeByLotOccupantType('Preneed Owner')) .lotOccupantTypeId; -export const funeralDirectorLotOccupantTypeId = (await cacheFunctions.getLotOccupantTypeByLotOccupantType('Funeral Director')).lotOccupantTypeId; -export const deceasedLotOccupantTypeId = (await cacheFunctions.getLotOccupantTypeByLotOccupantType('Deceased')) +export const funeralDirectorLotOccupantTypeId = (await cacheFunctions.getBurialSiteOccupantTypeByLotOccupantType('Funeral Director')).lotOccupantTypeId; +export const deceasedLotOccupantTypeId = (await cacheFunctions.getBurialSiteOccupantTypeByLotOccupantType('Deceased')) .lotOccupantTypeId; -export const purchaserLotOccupantTypeId = (await cacheFunctions.getLotOccupantTypeByLotOccupantType('Purchaser')) +export const purchaserLotOccupantTypeId = (await cacheFunctions.getBurialSiteOccupantTypeByLotOccupantType('Purchaser')) .lotOccupantTypeId; /* * Lot Status IDs */ -export const availableburialSiteStatusId = (await cacheFunctions.getLotStatusByLotStatus('Available')).burialSiteStatusId; -export const reservedburialSiteStatusId = (await cacheFunctions.getLotStatusByLotStatus('Reserved')).burialSiteStatusId; -export const takenburialSiteStatusId = (await cacheFunctions.getLotStatusByLotStatus('Taken')).burialSiteStatusId; +export const availableburialSiteStatusId = (await cacheFunctions.getBurialSiteStatusByLotStatus('Available')).burialSiteStatusId; +export const reservedburialSiteStatusId = (await cacheFunctions.getBurialSiteStatusByLotStatus('Reserved')).burialSiteStatusId; +export const takenburialSiteStatusId = (await cacheFunctions.getBurialSiteStatusByLotStatus('Taken')).burialSiteStatusId; /* * Lot Type IDs */ diff --git a/temp/legacy.importFromCsv.ids.ts b/temp/legacy.importFromCsv.ids.ts index 3d5d85ff..d1fa206b 100644 --- a/temp/legacy.importFromCsv.ids.ts +++ b/temp/legacy.importFromCsv.ids.ts @@ -42,20 +42,20 @@ export function getFeeIdByFeeDescription(feeDescription: string): number { */ export const preneedOwnerLotOccupantTypeId = - (await cacheFunctions.getLotOccupantTypeByLotOccupantType('Preneed Owner'))! + (await cacheFunctions.getBurialSiteOccupantTypeByLotOccupantType('Preneed Owner'))! .lotOccupantTypeId export const funeralDirectorLotOccupantTypeId = - (await cacheFunctions.getLotOccupantTypeByLotOccupantType( + (await cacheFunctions.getBurialSiteOccupantTypeByLotOccupantType( 'Funeral Director' ))!.lotOccupantTypeId export const deceasedLotOccupantTypeId = - (await cacheFunctions.getLotOccupantTypeByLotOccupantType('Deceased'))! + (await cacheFunctions.getBurialSiteOccupantTypeByLotOccupantType('Deceased'))! .lotOccupantTypeId export const purchaserLotOccupantTypeId = - (await cacheFunctions.getLotOccupantTypeByLotOccupantType('Purchaser'))! + (await cacheFunctions.getBurialSiteOccupantTypeByLotOccupantType('Purchaser'))! .lotOccupantTypeId /* @@ -63,10 +63,10 @@ export const purchaserLotOccupantTypeId = */ export const availableburialSiteStatusId = - (await cacheFunctions.getLotStatusByLotStatus('Available'))!.burialSiteStatusId + (await cacheFunctions.getBurialSiteStatusByLotStatus('Available'))!.burialSiteStatusId export const reservedburialSiteStatusId = - (await cacheFunctions.getLotStatusByLotStatus('Reserved'))!.burialSiteStatusId -export const takenburialSiteStatusId = (await cacheFunctions.getLotStatusByLotStatus( + (await cacheFunctions.getBurialSiteStatusByLotStatus('Reserved'))!.burialSiteStatusId +export const takenburialSiteStatusId = (await cacheFunctions.getBurialSiteStatusByLotStatus( 'Taken' ))!.burialSiteStatusId diff --git a/test/functions.js b/test/functions.js index 5b3f8445..cbbdeade 100644 --- a/test/functions.js +++ b/test/functions.js @@ -19,18 +19,18 @@ describe('functions.cache', () => { const lotStatuses = await cacheFunctions.getBurialSiteStatuses(); assert.ok(lotStatuses.length > 0); for (const lotStatus of lotStatuses) { - const byId = await cacheFunctions.getLotStatusById(lotStatus.burialSiteStatusId); + const byId = await cacheFunctions.getBurialSiteStatusById(lotStatus.burialSiteStatusId); assert.strictEqual(lotStatus.burialSiteStatusId, byId?.burialSiteStatusId); - const byName = await cacheFunctions.getLotStatusByLotStatus(lotStatus.lotStatus); + const byName = await cacheFunctions.getBurialSiteStatusByLotStatus(lotStatus.lotStatus); assert.strictEqual(lotStatus.lotStatus, byName?.lotStatus); } }); it('returns undefined with a bad burialSiteStatusId', async () => { - const byBadId = await cacheFunctions.getLotStatusById(badId); + const byBadId = await cacheFunctions.getBurialSiteStatusById(badId); assert.ok(byBadId === undefined); }); it('returns undefined with a bad lotStatus', async () => { - const byBadName = await cacheFunctions.getLotStatusByLotStatus(badName); + const byBadName = await cacheFunctions.getBurialSiteStatusByLotStatus(badName); assert.ok(byBadName === undefined); }); }); @@ -40,14 +40,14 @@ describe('functions.cache', () => { const lotTypes = await cacheFunctions.getBurialSiteTypes(); assert.ok(lotTypes.length > 0); for (const lotType of lotTypes) { - const byId = await cacheFunctions.getLotTypeById(lotType.burialSiteTypeId); + const byId = await cacheFunctions.getBurialSiteTypeById(lotType.burialSiteTypeId); assert.strictEqual(lotType.burialSiteTypeId, byId?.burialSiteTypeId); const byName = await cacheFunctions.getBurialSiteTypesByBurialSiteType(lotType.lotType); assert.strictEqual(lotType.lotType, byName?.lotType); } }); it('returns undefined with a bad burialSiteTypeId', async () => { - const byBadId = await cacheFunctions.getLotTypeById(badId); + const byBadId = await cacheFunctions.getBurialSiteTypeById(badId); assert.ok(byBadId === undefined); }); it('returns undefined with a bad lotType', async () => { diff --git a/test/functions.ts b/test/functions.ts index 90f9ba5e..b012be1a 100644 --- a/test/functions.ts +++ b/test/functions.ts @@ -26,12 +26,12 @@ describe('functions.cache', () => { assert.ok(lotStatuses.length > 0) for (const lotStatus of lotStatuses) { - const byId = await cacheFunctions.getLotStatusById( + const byId = await cacheFunctions.getBurialSiteStatusById( lotStatus.burialSiteStatusId ) assert.strictEqual(lotStatus.burialSiteStatusId, byId?.burialSiteStatusId) - const byName = await cacheFunctions.getLotStatusByLotStatus( + const byName = await cacheFunctions.getBurialSiteStatusByLotStatus( lotStatus.lotStatus ) assert.strictEqual(lotStatus.lotStatus, byName?.lotStatus) @@ -39,12 +39,12 @@ describe('functions.cache', () => { }) it('returns undefined with a bad burialSiteStatusId', async () => { - const byBadId = await cacheFunctions.getLotStatusById(badId) + const byBadId = await cacheFunctions.getBurialSiteStatusById(badId) assert.ok(byBadId === undefined) }) it('returns undefined with a bad lotStatus', async () => { - const byBadName = await cacheFunctions.getLotStatusByLotStatus(badName) + const byBadName = await cacheFunctions.getBurialSiteStatusByLotStatus(badName) assert.ok(byBadName === undefined) }) }) @@ -58,7 +58,7 @@ describe('functions.cache', () => { assert.ok(lotTypes.length > 0) for (const lotType of lotTypes) { - const byId = await cacheFunctions.getLotTypeById(lotType.burialSiteTypeId) + const byId = await cacheFunctions.getBurialSiteTypeById(lotType.burialSiteTypeId) assert.strictEqual(lotType.burialSiteTypeId, byId?.burialSiteTypeId) const byName = await cacheFunctions.getBurialSiteTypesByBurialSiteType( @@ -69,7 +69,7 @@ describe('functions.cache', () => { }) it('returns undefined with a bad burialSiteTypeId', async () => { - const byBadId = await cacheFunctions.getLotTypeById(badId) + const byBadId = await cacheFunctions.getBurialSiteTypeById(badId) assert.ok(byBadId === undefined) }) diff --git a/views/_footerA.ejs b/views/_footerA.ejs index 518f37e0..44c821ac 100644 --- a/views/_footerA.ejs +++ b/views/_footerA.ejs @@ -14,15 +14,6 @@ + <%- include('_footerB'); -%> \ No newline at end of file diff --git a/views/cemetery-search.ejs b/views/cemetery-search.ejs index a1166023..c9e656ea 100644 --- a/views/cemetery-search.ejs +++ b/views/cemetery-search.ejs @@ -6,7 +6,7 @@
  • - <%= configFunctions.getConfigProperty("aliases.maps") %> + Cemeteries
  • @@ -15,11 +15,11 @@

    - Find a <%= configFunctions.getConfigProperty("aliases.map") %> + Find a Cemetery

    - + Export @@ -29,11 +29,11 @@ <% if (user.userProperties.canUpdate) { %> <% } %> @@ -42,8 +42,8 @@
    - name, description, and address" + @@ -58,8 +58,8 @@ <%- include('_footerA'); -%> - + <%- include('_footerB'); -%> \ No newline at end of file diff --git a/views/cemetery-view.ejs b/views/cemetery-view.ejs index 875dc2c0..9b4afbd5 100644 --- a/views/cemetery-view.ejs +++ b/views/cemetery-view.ejs @@ -4,44 +4,44 @@

    - <%= map.mapName || "(No Name)" %> + <%= cemetery.cemeteryName || "(No Name)" %>

    - <%= map.mapName || "(No Name)" %> + <%= cemetery.cemeteryName || "(No Name)" %>
    -
    -
    - <% if (map.mapDescription && map.mapDescription !== "") { %> -
    - Description
    - <%= map.mapDescription %> -
    - <% } %> -
    - Address
    - <% if (map.mapAddress1 !== "") { %> - <%= map.mapAddress1 %>
    - <% } %> - <% if (map.mapAddress2 !== "") { %> - <%= map.mapAddress2 %>
    - <% } %> - <%= map.mapCity %>, <%= map.mapProvince %>
    - <%= map.mapPostalCode %> -
    - <% if (map.mapPhoneNumber !== "") { %> -
    - Phone Number
    - <%= map.mapPhoneNumber %> -
    - <% } %> +
    +
    + <% if (cemetery.cemeteryDescription && cemetery.cemeteryDescription !== "") { %> +
    + Description
    + <%= cemetery.cemeteryDescription %>
    + <% } %> +
    + Address
    + <% if (cemetery.cemeteryAddress1 !== "") { %> + <%= cemetery.cemeteryAddress1 %>
    + <% } %> + <% if (cemetery.cemeteryAddress2 !== "") { %> + <%= cemetery.cemeteryAddress2 %>
    + <% } %> + <%= cemetery.cemeteryCity %>, <%= cemetery.cemeteryProvince %>
    + <%= cemetery.cemeteryPostalCode %> +
    + <% if (cemetery.cemeteryPhoneNumber !== "") { %> +
    + Phone Number
    + <%= cemetery.cemeteryPhoneNumber %> +
    + <% } %>
    +
    -
    -

    Geographic Location

    -
    - <% if (map.mapLatitude && map.mapLongitude) { %> -
    - <% } else { %> -
    -

    There are no geographic coordinates associated with this <%= configFunctions.getConfigProperty("aliases.map").toLowerCase() %>.

    -
    - <% } %> -
    +
    +

    Geographic Location

    +
    + <% if (cemetery.cemeteryLatitude && cemetery.cemeteryLongitude) { %> +
    + <% } else { %> +
    +

    There are no geographic coordinates associated with this cemetery.

    +
    + <% } %>
    +

    Image

    - <% if (map.mapSVG) { %> - <% const imageURL = urlPrefix + "/images/maps/" + map.mapSVG %> - - <%- include('../public/images/maps/' + map.mapSVG); -%> + <% if (cemetery.cemeterySvg) { %> + <% const imageURL = urlPrefix + "/images/cemeteries/" + cemetery.cemeterySvg %> + + <%- include('../public/images/cemeteries/' + cemetery.cemeterySvg); -%> <% } else { %>
    -

    There are no image associated with this <%= configFunctions.getConfigProperty("aliases.map").toLowerCase() %>.

    +

    There are no image associated with this cemetery.

    <% } %>
    @@ -124,38 +124,37 @@
    -<% const lotSearchUrl = urlPrefix + "/lots?mapId=" + map.mapId; %> +<% const burialSiteSearchUrl = urlPrefix + "/burialSites?cemeteryId=" + cemetery.cemeteryId; %>
    -
    -

    - <%= configFunctions.getConfigProperty("aliases.lot") %> Summaries - - <%= map.lotCount %> - -

    -
    +
    +

    + Burial Site Summaries + + <%= cemetery.burialSiteCount %> + +

    +
    - <% if (map.lotCount === 0) { %> -
    -

    - There are no <%= configFunctions.getConfigProperty("aliases.lots").toLowerCase() %> - associated with this <%= configFunctions.getConfigProperty("aliases.map").toLowerCase() %>. -

    -
    + <% if (cemetery.burialSiteCount === 0) { %> +
    +

    + There are no burial sites associated with this cemetery. +

    +
    <% } else { %>
    @@ -164,56 +163,56 @@
    - <% for (const lotType of lotTypeSummary) { %> - - - - - - <% } %> + <% for (const burialSiteType of burialSiteTypeSummary) { %> + + + + + + <% } %>
    Type - <%= configFunctions.getConfigProperty("aliases.lot") %> Count + Burial Site Count Percentage
    - - <%= lotType.lotType %> - - - <%= lotType.lotCount %> - - <%= ((lotType.lotCount / map.lotCount) * 100).toFixed(1) %>% -
    + + <%= burialSiteType.burialSiteType %> + + + <%= burialSiteType.burialSiteCount %> + + <%= ((burialSiteType.burialSiteCount / cemetery.burialSiteCount) * 100).toFixed(1) %>% +
    - - - - - + + + + + - <% for (const lotStatus of lotStatusSummary) { %> - - - - - + <% for (const burialSiteStatus of burialSiteStatusSummary) { %> + + + + + <% } %>
    Status - <%= configFunctions.getConfigProperty("aliases.lot") %> Count - Percentage
    Status + Burial Site Count + Percentage
    - - <%= lotStatus.lotStatus %> - - - <%= lotStatus.lotCount %> - - <%= ((lotStatus.lotCount / map.lotCount) * 100).toFixed(1) %>% -
    + + <%= burialSiteStatus.burialSiteStatus %> + + + <%= burialSiteStatus.burialSiteCount %> + + <%= ((burialSiteStatus.burialSiteCount / map.burialSiteCount) * 100).toFixed(1) %>% +
    @@ -225,6 +224,6 @@ <%- include('_footerA'); -%> - + <%- include('_footerB'); -%> \ No newline at end of file diff --git a/views/dashboard.ejs b/views/dashboard.ejs index 408e7280..b349198d 100644 --- a/views/dashboard.ejs +++ b/views/dashboard.ejs @@ -59,25 +59,24 @@ <%= milestone.workOrderNumber %>
    <% - if (milestone.workOrderLots.length > 0) { - for (const lot of milestone.workOrderLots) { + if (milestone.workOrderBurialSites.length > 0) { + for (const burialSite of milestone.workOrderBurialSites) { %> - - "> - <%= lot.lotName %> + + + <%= burialSite.burialSiteName %>
    <% } } - if (milestone.workOrderLotOccupancies.length > 0) { - for (const occupancy of milestone.workOrderLotOccupancies) { - for (const occupant of occupancy.lotOccupancyOccupants) { + if (milestone.workOrderBurialSiteContracts.length > 0) { + for (const burialSiteContract of milestone.workOrderBurialSiteContracts) { + for (const interment of burialSiteContract.lotOccupancyInterments) { %> - - "> - <%= occupant.occupantName %> - <%= occupant.occupantFamilyName %> + + + <%= interment.deceasedName %>
    <% } @@ -94,87 +93,86 @@ <% } %>
    -
    -
    -
    - - <% if (user.userProperties.canUpdate) { %> - - - - - New Work Order - - <% } %> - - - + -
    - <% if (user.userProperties.canUpdate) { %> - + - New <%= configFunctions.getConfigProperty("aliases.map") %> + New Cemetery <% } %>
    @@ -262,12 +258,13 @@
    @@ -291,11 +288,8 @@ Fee Management

    - Manage fees for - <%= configFunctions.getConfigProperty("aliases.lot").toLowerCase() %> - <%= configFunctions.getConfigProperty("aliases.occupancy").toLowerCase() %> - and specific - <%= configFunctions.getConfigProperty("aliases.lot").toLowerCase() %> types. + Manage fees for contracts + and specific burial site types.

    @@ -308,13 +302,12 @@
    - +

    - Manage - <%= configFunctions.getConfigProperty("aliases.occupancy").toLowerCase() %> types, + Manage contract types, the fields associated with them, and their available print options.

    @@ -329,14 +322,12 @@
    - +

    - Manage - <%= configFunctions.getConfigProperty("aliases.lot").toLowerCase() %> types - and fields associated with them. + Manage burial site types and fields associated with them.

    @@ -355,9 +346,8 @@

    Manage simple configuration tables fees for - work order types, - <%= configFunctions.getConfigProperty("aliases.lot").toLowerCase() %> statuses, - and <%= configFunctions.getConfigProperty("aliases.lot").toLowerCase() %> <%= configFunctions.getConfigProperty("aliases.occupant").toLowerCase() %> types. + work order types + and burial site statuses.