diff --git a/handlers/lots-post/doGetLotTypeFields.d.ts b/handlers/lots-post/doGetLotTypeFields.d.ts new file mode 100644 index 00000000..9621c611 --- /dev/null +++ b/handlers/lots-post/doGetLotTypeFields.d.ts @@ -0,0 +1,3 @@ +import type { RequestHandler } from "express"; +export declare const handler: RequestHandler; +export default handler; diff --git a/handlers/lots-post/doGetLotTypeFields.js b/handlers/lots-post/doGetLotTypeFields.js new file mode 100644 index 00000000..e8b1643a --- /dev/null +++ b/handlers/lots-post/doGetLotTypeFields.js @@ -0,0 +1,8 @@ +import { getLotTypeById } from "../../helpers/functions.cache.js"; +export const handler = async (request, response) => { + const lotType = getLotTypeById(Number.parseInt(request.body.lotTypeId, 10)); + response.json({ + lotTypeFields: lotType.lotTypeFields + }); +}; +export default handler; diff --git a/handlers/lots-post/doGetLotTypeFields.ts b/handlers/lots-post/doGetLotTypeFields.ts new file mode 100644 index 00000000..6c3ff5f5 --- /dev/null +++ b/handlers/lots-post/doGetLotTypeFields.ts @@ -0,0 +1,13 @@ +import type { RequestHandler } from "express"; + +import { getLotTypeById } from "../../helpers/functions.cache.js"; + +export const handler: RequestHandler = async (request, response) => { + const lotType = getLotTypeById(Number.parseInt(request.body.lotTypeId, 10)); + + response.json({ + lotTypeFields: lotType.lotTypeFields + }); +}; + +export default handler; diff --git a/helpers/lotOccupancyDB/addLot.d.ts b/helpers/lotOccupancyDB/addLot.d.ts index 0c9d4cc6..df637278 100644 --- a/helpers/lotOccupancyDB/addLot.d.ts +++ b/helpers/lotOccupancyDB/addLot.d.ts @@ -7,6 +7,8 @@ interface AddLotForm { mapKey: string; lotLatitude: string; lotLongitude: string; + lotTypeFieldIds?: string; + [lotFieldValue_lotTypeFieldId: string]: unknown; } export declare const addLot: (lotForm: AddLotForm, requestSession: recordTypes.PartialSession) => number; export default addLot; diff --git a/helpers/lotOccupancyDB/addLot.js b/helpers/lotOccupancyDB/addLot.js index 730205bb..749b8cad 100644 --- a/helpers/lotOccupancyDB/addLot.js +++ b/helpers/lotOccupancyDB/addLot.js @@ -1,5 +1,6 @@ import sqlite from "better-sqlite3"; import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +import { addOrUpdateLotField } from "./addOrUpdateLotField.js"; export const addLot = (lotForm, requestSession) => { const database = sqlite(databasePath); const rightNowMillis = Date.now(); @@ -12,7 +13,19 @@ export const addLot = (lotForm, requestSession) => { " recordUpdate_userName, recordUpdate_timeMillis)" + " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") .run(lotForm.lotName, lotForm.lotTypeId, lotForm.lotStatusId === "" ? undefined : lotForm.lotStatusId, lotForm.mapId === "" ? undefined : lotForm.mapId, lotForm.mapKey, lotForm.lotLatitude === "" ? undefined : lotForm.lotLatitude, lotForm.lotLongitude === "" ? undefined : lotForm.lotLongitude, requestSession.user.userName, rightNowMillis, requestSession.user.userName, rightNowMillis); + const lotId = result.lastInsertRowid; + const lotTypeFieldIds = (lotForm.lotTypeFieldIds || "").split(","); + for (const lotTypeFieldId of lotTypeFieldIds) { + const lotFieldValue = lotForm["lotFieldValue_" + lotTypeFieldId]; + if (lotFieldValue && lotFieldValue !== "") { + addOrUpdateLotField({ + lotId, + lotTypeFieldId, + lotFieldValue + }, requestSession, database); + } + } database.close(); - return result.lastInsertRowid; + return lotId; }; export default addLot; diff --git a/helpers/lotOccupancyDB/addLot.ts b/helpers/lotOccupancyDB/addLot.ts index 5049bc79..5dda11c0 100644 --- a/helpers/lotOccupancyDB/addLot.ts +++ b/helpers/lotOccupancyDB/addLot.ts @@ -2,6 +2,8 @@ import sqlite from "better-sqlite3"; import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +import { addOrUpdateLotField } from "./addOrUpdateLotField.js"; + import type * as recordTypes from "../../types/recordTypes"; interface AddLotForm { @@ -14,12 +16,12 @@ interface AddLotForm { lotLatitude: string; lotLongitude: string; + + lotTypeFieldIds?: string; + [lotFieldValue_lotTypeFieldId: string]: unknown; } -export const addLot = ( - lotForm: AddLotForm, - requestSession: recordTypes.PartialSession -): number => { +export const addLot = (lotForm: AddLotForm, requestSession: recordTypes.PartialSession): number => { const database = sqlite(databasePath); const rightNowMillis = Date.now(); @@ -48,9 +50,29 @@ export const addLot = ( rightNowMillis ); + const lotId = result.lastInsertRowid as number; + + const lotTypeFieldIds = (lotForm.lotTypeFieldIds || "").split(","); + + for (const lotTypeFieldId of lotTypeFieldIds) { + const lotFieldValue = lotForm["lotFieldValue_" + lotTypeFieldId] as string; + + if (lotFieldValue && lotFieldValue !== "") { + addOrUpdateLotField( + { + lotId, + lotTypeFieldId, + lotFieldValue + }, + requestSession, + database + ); + } + } + database.close(); - return result.lastInsertRowid as number; + return lotId; }; export default addLot; diff --git a/helpers/lotOccupancyDB/addOrUpdateLotField.d.ts b/helpers/lotOccupancyDB/addOrUpdateLotField.d.ts new file mode 100644 index 00000000..14eedbdf --- /dev/null +++ b/helpers/lotOccupancyDB/addOrUpdateLotField.d.ts @@ -0,0 +1,9 @@ +import sqlite from "better-sqlite3"; +import type * as recordTypes from "../../types/recordTypes"; +interface LotFieldForm { + lotId: string | number; + lotTypeFieldId: string | number; + lotFieldValue: string; +} +export declare const addOrUpdateLotField: (lotFieldForm: LotFieldForm, requestSession: recordTypes.PartialSession, connectedDatabase?: sqlite.Database) => boolean; +export default addOrUpdateLotField; diff --git a/helpers/lotOccupancyDB/addOrUpdateLotField.js b/helpers/lotOccupancyDB/addOrUpdateLotField.js new file mode 100644 index 00000000..17c2fb6b --- /dev/null +++ b/helpers/lotOccupancyDB/addOrUpdateLotField.js @@ -0,0 +1,31 @@ +import sqlite from "better-sqlite3"; +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +export const addOrUpdateLotField = (lotFieldForm, requestSession, connectedDatabase) => { + const database = connectedDatabase || sqlite(databasePath); + const rightNowMillis = Date.now(); + let result = database + .prepare("update LotFields" + + " set lotFieldValue = ?," + + " recordUpdate_userName = ?," + + " recordUpdate_timeMillis = ?," + + " recordDelete_userName = null," + + " recordDelete_timeMillis = null" + + " where lotId = ?" + + " and lotTypeFieldId = ?") + .run(lotFieldForm.lotFieldValue, requestSession.user.userName, rightNowMillis, lotFieldForm.lotId, lotFieldForm.lotTypeFieldId); + if (result.changes === 0) { + result = database + .prepare("insert into LotFields (" + + "lotId, lotTypeFieldId," + + " lotFieldValue," + + " recordCreate_userName, recordCreate_timeMillis," + + " recordUpdate_userName, recordUpdate_timeMillis)" + + " values (?, ?, ?, ?, ?, ?, ?)") + .run(lotFieldForm.lotId, lotFieldForm.lotTypeFieldId, lotFieldForm.lotFieldValue, requestSession.user.userName, rightNowMillis, requestSession.user.userName, rightNowMillis); + } + if (!connectedDatabase) { + database.close(); + } + return result.changes > 0; +}; +export default addOrUpdateLotField; diff --git a/helpers/lotOccupancyDB/addOrUpdateLotField.ts b/helpers/lotOccupancyDB/addOrUpdateLotField.ts new file mode 100644 index 00000000..a47679d9 --- /dev/null +++ b/helpers/lotOccupancyDB/addOrUpdateLotField.ts @@ -0,0 +1,69 @@ +import sqlite from "better-sqlite3"; + +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; + +import type * as recordTypes from "../../types/recordTypes"; + +interface LotFieldForm { + lotId: string | number; + lotTypeFieldId: string | number; + lotFieldValue: string; +} + +export const addOrUpdateLotField = ( + lotFieldForm: LotFieldForm, + requestSession: recordTypes.PartialSession, + connectedDatabase?: sqlite.Database +): boolean => { + const database = connectedDatabase || sqlite(databasePath); + + const rightNowMillis = Date.now(); + + let result = database + .prepare( + "update LotFields" + + " set lotFieldValue = ?," + + " recordUpdate_userName = ?," + + " recordUpdate_timeMillis = ?," + + " recordDelete_userName = null," + + " recordDelete_timeMillis = null" + + " where lotId = ?" + + " and lotTypeFieldId = ?" + ) + .run( + lotFieldForm.lotFieldValue, + requestSession.user.userName, + rightNowMillis, + lotFieldForm.lotId, + lotFieldForm.lotTypeFieldId + ); + + if (result.changes === 0) { + result = database + .prepare( + "insert into LotFields (" + + "lotId, lotTypeFieldId," + + " lotFieldValue," + + " recordCreate_userName, recordCreate_timeMillis," + + " recordUpdate_userName, recordUpdate_timeMillis)" + + " values (?, ?, ?, ?, ?, ?, ?)" + ) + .run( + lotFieldForm.lotId, + lotFieldForm.lotTypeFieldId, + lotFieldForm.lotFieldValue, + requestSession.user.userName, + rightNowMillis, + requestSession.user.userName, + rightNowMillis + ); + } + + if (!connectedDatabase) { + database.close(); + } + + return result.changes > 0; +}; + +export default addOrUpdateLotField; diff --git a/helpers/lotOccupancyDB/deleteLotField.d.ts b/helpers/lotOccupancyDB/deleteLotField.d.ts new file mode 100644 index 00000000..7681aeae --- /dev/null +++ b/helpers/lotOccupancyDB/deleteLotField.d.ts @@ -0,0 +1,4 @@ +import sqlite from "better-sqlite3"; +import type * as recordTypes from "../../types/recordTypes"; +export declare const deleteLotField: (lotId: number | string, lotTypeFieldId: number | string, requestSession: recordTypes.PartialSession, connectedDatabase?: sqlite.Database) => boolean; +export default deleteLotField; diff --git a/helpers/lotOccupancyDB/deleteLotField.js b/helpers/lotOccupancyDB/deleteLotField.js new file mode 100644 index 00000000..a8e1b392 --- /dev/null +++ b/helpers/lotOccupancyDB/deleteLotField.js @@ -0,0 +1,18 @@ +import sqlite from "better-sqlite3"; +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +export const deleteLotField = (lotId, lotTypeFieldId, requestSession, connectedDatabase) => { + const database = connectedDatabase || sqlite(databasePath); + const rightNowMillis = Date.now(); + const result = database + .prepare("update LotFields" + + " set recordDelete_userName = ?," + + " recordDelete_timeMillis = ?" + + " where lotId = ?" + + " and lotTypeFieldId = ?") + .run(requestSession.user.userName, rightNowMillis, lotId, lotTypeFieldId); + if (!connectedDatabase) { + database.close(); + } + return result.changes > 0; +}; +export default deleteLotField; diff --git a/helpers/lotOccupancyDB/deleteLotField.ts b/helpers/lotOccupancyDB/deleteLotField.ts new file mode 100644 index 00000000..af253080 --- /dev/null +++ b/helpers/lotOccupancyDB/deleteLotField.ts @@ -0,0 +1,34 @@ +import sqlite from "better-sqlite3"; + +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; + +import type * as recordTypes from "../../types/recordTypes"; + +export const deleteLotField = ( + lotId: number | string, + lotTypeFieldId: number | string, + requestSession: recordTypes.PartialSession, + connectedDatabase?: sqlite.Database +): boolean => { + const database = connectedDatabase || sqlite(databasePath); + + const rightNowMillis = Date.now(); + + const result = database + .prepare( + "update LotFields" + + " set recordDelete_userName = ?," + + " recordDelete_timeMillis = ?" + + " where lotId = ?" + + " and lotTypeFieldId = ?" + ) + .run(requestSession.user.userName, rightNowMillis, lotId, lotTypeFieldId); + + if (!connectedDatabase) { + database.close(); + } + + return result.changes > 0; +}; + +export default deleteLotField; diff --git a/helpers/lotOccupancyDB/getLot.js b/helpers/lotOccupancyDB/getLot.js index 3688fce4..f525d7d7 100644 --- a/helpers/lotOccupancyDB/getLot.js +++ b/helpers/lotOccupancyDB/getLot.js @@ -1,5 +1,6 @@ import sqlite from "better-sqlite3"; import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +import { getLotFields } from "./getLotFields.js"; import { getLotComments } from "./getLotComments.js"; import { getLotOccupancies } from "./getLotOccupancies.js"; const baseSQL = "select l.lotId," + @@ -26,7 +27,8 @@ const _getLot = (sql, lotId_or_lotName) => { limit: -1, offset: 0 }, database).lotOccupancies; - lot.lotComments = getLotComments(lot.lotId); + lot.lotFields = getLotFields(lot.lotId, database); + lot.lotComments = getLotComments(lot.lotId, database); } database.close(); return lot; diff --git a/helpers/lotOccupancyDB/getLot.ts b/helpers/lotOccupancyDB/getLot.ts index 2675fa58..c3c4d956 100644 --- a/helpers/lotOccupancyDB/getLot.ts +++ b/helpers/lotOccupancyDB/getLot.ts @@ -2,6 +2,8 @@ import sqlite from "better-sqlite3"; import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +import { getLotFields } from "./getLotFields.js"; + import { getLotComments } from "./getLotComments.js"; import { getLotOccupancies } from "./getLotOccupancies.js"; @@ -41,7 +43,9 @@ const _getLot = (sql: string, lotId_or_lotName: number | string): recordTypes.Lo database ).lotOccupancies; - lot.lotComments = getLotComments(lot.lotId); + lot.lotFields = getLotFields(lot.lotId, database); + + lot.lotComments = getLotComments(lot.lotId, database); } database.close(); diff --git a/helpers/lotOccupancyDB/getLotFields.d.ts b/helpers/lotOccupancyDB/getLotFields.d.ts new file mode 100644 index 00000000..00406acc --- /dev/null +++ b/helpers/lotOccupancyDB/getLotFields.d.ts @@ -0,0 +1,4 @@ +import sqlite from "better-sqlite3"; +import type * as recordTypes from "../../types/recordTypes"; +export declare const getLotFields: (lotId: number | string, connectedDatabase?: sqlite.Database) => recordTypes.LotField[]; +export default getLotFields; diff --git a/helpers/lotOccupancyDB/getLotFields.js b/helpers/lotOccupancyDB/getLotFields.js new file mode 100644 index 00000000..824f551e --- /dev/null +++ b/helpers/lotOccupancyDB/getLotFields.js @@ -0,0 +1,37 @@ +import sqlite from "better-sqlite3"; +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +export const getLotFields = (lotId, connectedDatabase) => { + const database = connectedDatabase || + sqlite(databasePath, { + readonly: true + }); + const lotFields = database + .prepare("select l.lotId," + + " l.lotTypeFieldId, l.lotFieldValue," + + " f.lotTypeField," + + " f.lotTypeFieldValues, f.isRequired, f.pattern, f.minimumLength, f.maximumLength," + + " f.orderNumber, t.orderNumber as lotTypeOrderNumber" + + " from LotFields l" + + " left join LotTypeFields f on l.lotTypeFieldId = f.lotTypeFieldId" + + " left join LotTypes t on f.lotTypeId = t.lotTypeId" + + " where l.recordDelete_timeMillis is null" + + " and l.lotId = ?" + + " union" + + " select ? as lotId," + + " f.lotTypeFieldId, '' as lotFieldValue," + + " f.lotTypeField," + + " f.lotTypeFieldValues, f.isRequired, f.pattern, f.minimumLength, f.maximumLength," + + " f.orderNumber, t.orderNumber as lotTypeOrderNumber" + + " from LotTypeFields f" + + " left join LotTypes t on f.lotTypeId = t.lotTypeId" + + " where f.recordDelete_timeMillis is null" + + " and (f.lotTypeId is null or f.lotTypeId in (select lotTypeId from Lots where lotId = ?))" + + " and f.lotTypeFieldId not in (select lotTypeFieldId from LotFields where lotId = ? and recordDelete_timeMillis is null)" + + " order by lotTypeOrderNumber, f.orderNumber, f.lotTypeField") + .all(lotId, lotId, lotId, lotId); + if (!connectedDatabase) { + database.close(); + } + return lotFields; +}; +export default getLotFields; diff --git a/helpers/lotOccupancyDB/getLotFields.ts b/helpers/lotOccupancyDB/getLotFields.ts new file mode 100644 index 00000000..523a5aac --- /dev/null +++ b/helpers/lotOccupancyDB/getLotFields.ts @@ -0,0 +1,51 @@ +import sqlite from "better-sqlite3"; + +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; + +import type * as recordTypes from "../../types/recordTypes"; + +export const getLotFields = ( + lotId: number | string, + connectedDatabase?: sqlite.Database +): recordTypes.LotField[] => { + const database = + connectedDatabase || + sqlite(databasePath, { + readonly: true + }); + + const lotFields: recordTypes.LotField[] = database + .prepare( + "select l.lotId," + + " l.lotTypeFieldId, l.lotFieldValue," + + " f.lotTypeField," + + " f.lotTypeFieldValues, f.isRequired, f.pattern, f.minimumLength, f.maximumLength," + + " f.orderNumber, t.orderNumber as lotTypeOrderNumber" + + " from LotFields l" + + " left join LotTypeFields f on l.lotTypeFieldId = f.lotTypeFieldId" + + " left join LotTypes t on f.lotTypeId = t.lotTypeId" + + " where l.recordDelete_timeMillis is null" + + " and l.lotId = ?" + + " union" + + " select ? as lotId," + + " f.lotTypeFieldId, '' as lotFieldValue," + + " f.lotTypeField," + + " f.lotTypeFieldValues, f.isRequired, f.pattern, f.minimumLength, f.maximumLength," + + " f.orderNumber, t.orderNumber as lotTypeOrderNumber" + + " from LotTypeFields f" + + " left join LotTypes t on f.lotTypeId = t.lotTypeId" + + " where f.recordDelete_timeMillis is null" + + " and (f.lotTypeId is null or f.lotTypeId in (select lotTypeId from Lots where lotId = ?))" + + " and f.lotTypeFieldId not in (select lotTypeFieldId from LotFields where lotId = ? and recordDelete_timeMillis is null)" + + " order by lotTypeOrderNumber, f.orderNumber, f.lotTypeField" + ) + .all(lotId, lotId, lotId, lotId); + + if (!connectedDatabase) { + database.close(); + } + + return lotFields; +}; + +export default getLotFields; diff --git a/helpers/lotOccupancyDB/updateLot.d.ts b/helpers/lotOccupancyDB/updateLot.d.ts index e538be2c..a973ef2e 100644 --- a/helpers/lotOccupancyDB/updateLot.d.ts +++ b/helpers/lotOccupancyDB/updateLot.d.ts @@ -8,6 +8,8 @@ interface UpdateLotForm { mapKey: string; lotLatitude: string; lotLongitude: string; + lotTypeFieldIds?: string; + [lotFieldValue_lotTypeFieldId: string]: unknown; } export declare function updateLot(lotForm: UpdateLotForm, requestSession: recordTypes.PartialSession): boolean; export declare function updateLotStatus(lotId: number | string, lotStatusId: number | string, requestSession: recordTypes.PartialSession): boolean; diff --git a/helpers/lotOccupancyDB/updateLot.js b/helpers/lotOccupancyDB/updateLot.js index 2cf65a28..892d6117 100644 --- a/helpers/lotOccupancyDB/updateLot.js +++ b/helpers/lotOccupancyDB/updateLot.js @@ -1,5 +1,7 @@ import sqlite from "better-sqlite3"; import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +import { addOrUpdateLotField } from "./addOrUpdateLotField.js"; +import { deleteLotField } from "./deleteLotField.js"; export function updateLot(lotForm, requestSession) { const database = sqlite(databasePath); const rightNowMillis = Date.now(); @@ -17,6 +19,22 @@ export function updateLot(lotForm, requestSession) { " where lotId = ?" + " and recordDelete_timeMillis is null") .run(lotForm.lotName, lotForm.lotTypeId, lotForm.lotStatusId === "" ? undefined : lotForm.lotStatusId, lotForm.mapId === "" ? undefined : lotForm.mapId, lotForm.mapKey, lotForm.lotLatitude === "" ? undefined : lotForm.lotLatitude, lotForm.lotLongitude === "" ? undefined : lotForm.lotLongitude, requestSession.user.userName, rightNowMillis, lotForm.lotId); + if (result.changes > 0) { + const lotTypeFieldIds = (lotForm.lotTypeFieldIds || "").split(","); + for (const lotTypeFieldId of lotTypeFieldIds) { + const lotFieldValue = lotForm["lotFieldValue_" + lotTypeFieldId]; + if (lotFieldValue && lotFieldValue !== "") { + addOrUpdateLotField({ + lotId: lotForm.lotId, + lotTypeFieldId, + lotFieldValue + }, requestSession, database); + } + else { + deleteLotField(lotForm.lotId, lotTypeFieldId, requestSession, database); + } + } + } database.close(); return result.changes > 0; } diff --git a/helpers/lotOccupancyDB/updateLot.ts b/helpers/lotOccupancyDB/updateLot.ts index ed298b0b..b2cd9cc3 100644 --- a/helpers/lotOccupancyDB/updateLot.ts +++ b/helpers/lotOccupancyDB/updateLot.ts @@ -2,6 +2,9 @@ import sqlite from "better-sqlite3"; import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +import { addOrUpdateLotField } from "./addOrUpdateLotField.js"; +import { deleteLotField } from "./deleteLotField.js"; + import type * as recordTypes from "../../types/recordTypes"; interface UpdateLotForm { @@ -15,6 +18,9 @@ interface UpdateLotForm { lotLatitude: string; lotLongitude: string; + + lotTypeFieldIds?: string; + [lotFieldValue_lotTypeFieldId: string]: unknown; } export function updateLot( @@ -53,6 +59,28 @@ export function updateLot( lotForm.lotId ); + if (result.changes > 0) { + const lotTypeFieldIds = (lotForm.lotTypeFieldIds || "").split(","); + + for (const lotTypeFieldId of lotTypeFieldIds) { + const lotFieldValue = lotForm["lotFieldValue_" + lotTypeFieldId] as string; + + if (lotFieldValue && lotFieldValue !== "") { + addOrUpdateLotField( + { + lotId: lotForm.lotId, + lotTypeFieldId, + lotFieldValue + }, + requestSession, + database + ); + } else { + deleteLotField(lotForm.lotId, lotTypeFieldId, requestSession, database); + } + } + } + database.close(); return result.changes > 0; diff --git a/public-typescript/lotEdit.js b/public-typescript/lotEdit.js index a93f9804..bbf3a31b 100644 --- a/public-typescript/lotEdit.js +++ b/public-typescript/lotEdit.js @@ -4,14 +4,27 @@ Object.defineProperty(exports, "__esModule", { value: true }); const los = exports.los; const lotId = document.querySelector("#lot--lotId").value; const isCreate = lotId === ""; + let hasUnsavedChanges = false; + let refreshAfterSave = isCreate; + const setUnsavedChanges = () => { + if (!hasUnsavedChanges) { + hasUnsavedChanges = true; + cityssm.enableNavBlocker(); + } + }; + const clearUnsavedChanges = () => { + hasUnsavedChanges = false; + cityssm.disableNavBlocker(); + }; const formElement = document.querySelector("#form--lot"); const updateLot = (formEvent) => { formEvent.preventDefault(); cityssm.postJSON(los.urlPrefix + "/lots/" + (isCreate ? "doCreateLot" : "doUpdateLot"), formElement, (responseJSON) => { if (responseJSON.success) { - if (isCreate) { + clearUnsavedChanges(); + if (isCreate || refreshAfterSave) { window.location.href = - los.urlPrefix + "/lots/" + responseJSON.lotId + "/edit"; + los.urlPrefix + "/lots/" + responseJSON.lotId + "/edit?t=" + Date.now(); } else { bulmaJS.alert({ @@ -30,6 +43,10 @@ Object.defineProperty(exports, "__esModule", { value: true }); }); }; formElement.addEventListener("submit", updateLot); + const formInputElements = formElement.querySelectorAll("input, select"); + for (const formInputElement of formInputElements) { + formInputElement.addEventListener("change", setUnsavedChanges); + } los.initializeUnlockFieldButtons(formElement); if (!isCreate) { document.querySelector("#button--deleteLot").addEventListener("click", (clickEvent) => { @@ -64,6 +81,114 @@ Object.defineProperty(exports, "__esModule", { value: true }); }); }); } + const lotTypeIdElement = document.querySelector("#lot--lotTypeId"); + if (isCreate) { + const lotFieldsContainerElement = document.querySelector("#container--lotFields"); + lotTypeIdElement.addEventListener("change", () => { + if (lotTypeIdElement.value === "") { + lotFieldsContainerElement.innerHTML = + '
"; + return; + } + cityssm.postJSON(los.urlPrefix + "/lots/doGetLotTypeFields", { + lotTypeId: lotTypeIdElement.value + }, (responseJSON) => { + if (responseJSON.lotTypeFields.length === 0) { + lotFieldsContainerElement.innerHTML = + '"; + return; + } + lotFieldsContainerElement.innerHTML = ""; + let lotTypeFieldIds = ""; + for (const lotTypeField of responseJSON.lotTypeFields) { + lotTypeFieldIds += "," + lotTypeField.lotTypeFieldId; + const fieldName = "lotFieldValue_" + lotTypeField.lotTypeFieldId; + const fieldId = "lot--" + fieldName; + const fieldElement = document.createElement("div"); + fieldElement.className = "field"; + 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 && lotTypeField.pattern !== "") { + inputElement.pattern = lotTypeField.pattern; + } + fieldElement.querySelector(".control").append(inputElement); + } + else { + 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 originalLotTypeId = lotTypeIdElement.value; + lotTypeIdElement.addEventListener("change", () => { + if (lotTypeIdElement.value !== originalLotTypeId) { + bulmaJS.confirm({ + title: "Confirm Change", + message: "Are you sure you want to change the " + + exports.aliases.lot.toLowerCase() + + " 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: () => { + lotTypeIdElement.value = originalLotTypeId; + } + } + }); + } + }); + } let lotComments = exports.lotComments; delete exports.lotComments; const openEditLotComment = (clickEvent) => { diff --git a/public-typescript/lotEdit.ts b/public-typescript/lotEdit.ts index c291ef18..0fe621b6 100644 --- a/public-typescript/lotEdit.ts +++ b/public-typescript/lotEdit.ts @@ -18,6 +18,21 @@ declare const bulmaJS: BulmaJS; // Main form + let hasUnsavedChanges = false; + let refreshAfterSave = isCreate; + + const setUnsavedChanges = () => { + if (!hasUnsavedChanges) { + hasUnsavedChanges = true; + cityssm.enableNavBlocker(); + } + }; + + const clearUnsavedChanges = () => { + hasUnsavedChanges = false; + cityssm.disableNavBlocker(); + }; + const formElement = document.querySelector("#form--lot") as HTMLFormElement; const updateLot = (formEvent: SubmitEvent) => { @@ -28,9 +43,11 @@ declare const bulmaJS: BulmaJS; formElement, (responseJSON: { success: boolean; lotId?: number; errorMessage?: string }) => { if (responseJSON.success) { - if (isCreate) { + clearUnsavedChanges(); + + if (isCreate || refreshAfterSave) { window.location.href = - los.urlPrefix + "/lots/" + responseJSON.lotId + "/edit"; + los.urlPrefix + "/lots/" + responseJSON.lotId + "/edit?t=" + Date.now(); } else { bulmaJS.alert({ message: exports.aliases.lot + " Updated Successfully", @@ -50,6 +67,12 @@ declare const bulmaJS: BulmaJS; formElement.addEventListener("submit", updateLot); + const formInputElements = formElement.querySelectorAll("input, select"); + + for (const formInputElement of formInputElements) { + formInputElement.addEventListener("change", setUnsavedChanges); + } + los.initializeUnlockFieldButtons(formElement); if (!isCreate) { @@ -95,6 +118,158 @@ declare const bulmaJS: BulmaJS; ); } + // Lot Type + + const lotTypeIdElement = document.querySelector("#lot--lotTypeId") as HTMLSelectElement; + + if (isCreate) { + const lotFieldsContainerElement = document.querySelector( + "#container--lotFields" + ) as HTMLElement; + + lotTypeIdElement.addEventListener("change", () => { + if (lotTypeIdElement.value === "") { + lotFieldsContainerElement.innerHTML = + '"; + + return; + } + + cityssm.postJSON( + los.urlPrefix + "/lots/doGetLotTypeFields", + { + lotTypeId: lotTypeIdElement.value + }, + (responseJSON: { lotTypeFields: recordTypes.LotTypeField[] }) => { + if (responseJSON.lotTypeFields.length === 0) { + lotFieldsContainerElement.innerHTML = + '"; + + return; + } + + lotFieldsContainerElement.innerHTML = ""; + + let lotTypeFieldIds = ""; + + for (const lotTypeField of responseJSON.lotTypeFields) { + lotTypeFieldIds += "," + lotTypeField.lotTypeFieldId; + + const fieldName = "lotFieldValue_" + lotTypeField.lotTypeFieldId; + + const fieldId = "lot--" + fieldName; + + const fieldElement = document.createElement("div"); + fieldElement.className = "field"; + fieldElement.innerHTML = + '' + + ''; + + (fieldElement.querySelector("label") as HTMLLabelElement).textContent = + lotTypeField.lotTypeField as string; + + if (lotTypeField.lotTypeFieldValues === "") { + const inputElement = document.createElement("input"); + + inputElement.className = "input"; + + inputElement.id = fieldId; + + inputElement.name = fieldName; + + inputElement.type = "text"; + + inputElement.required = lotTypeField.isRequired as boolean; + inputElement.minLength = lotTypeField.minimumLength as number; + inputElement.maxLength = lotTypeField.maximumLength as number; + + if (lotTypeField.pattern && lotTypeField.pattern !== "") { + inputElement.pattern = lotTypeField.pattern; + } + + (fieldElement.querySelector(".control") as HTMLElement).append( + inputElement + ); + } else { + (fieldElement.querySelector(".control") as HTMLElement).innerHTML = + '"; + + const selectElement = fieldElement.querySelector( + "select" + ) as HTMLSelectElement; + + selectElement.required = lotTypeField.isRequired as boolean; + + const optionValues = (lotTypeField.lotTypeFieldValues as string).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 originalLotTypeId = lotTypeIdElement.value; + + lotTypeIdElement.addEventListener("change", () => { + if (lotTypeIdElement.value !== originalLotTypeId) { + bulmaJS.confirm({ + title: "Confirm Change", + message: + "Are you sure you want to change the " + + exports.aliases.lot.toLowerCase() + + " 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: () => { + lotTypeIdElement.value = originalLotTypeId; + } + } + }); + } + }); + } + // Comments let lotComments: recordTypes.LotComment[] = exports.lotComments; diff --git a/public/javascripts/lotEdit.min.js b/public/javascripts/lotEdit.min.js index 26867a68..76c44fa6 100644 --- a/public/javascripts/lotEdit.min.js +++ b/public/javascripts/lotEdit.min.js @@ -1 +1 @@ -"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),(()=>{const t=exports.los,e=document.querySelector("#lot--lotId").value,o=""===e,l=document.querySelector("#form--lot");l.addEventListener("submit",e=>{e.preventDefault(),cityssm.postJSON(t.urlPrefix+"/lots/"+(o?"doCreateLot":"doUpdateLot"),l,e=>{e.success?o?window.location.href=t.urlPrefix+"/lots/"+e.lotId+"/edit":bulmaJS.alert({message:exports.aliases.lot+" Updated Successfully",contextualColorName:"success"}):bulmaJS.alert({title:"Error Updating "+exports.aliases.lot,message:e.errorMessage||"",contextualColorName:"danger"})})}),t.initializeUnlockFieldButtons(l),o||document.querySelector("#button--deleteLot").addEventListener("click",o=>{o.preventDefault();bulmaJS.confirm({title:"Delete "+exports.aliases.lot,message:"Are you sure you want to delete this "+exports.aliases.lot.toLowerCase()+"?",contextualColorName:"warning",okButton:{text:"Yes, Delete "+exports.aliases.lot,callbackFunction:()=>{cityssm.postJSON(t.urlPrefix+"/lots/doDeleteLot",{lotId:e},e=>{e.success?(cityssm.disableNavBlocker(),window.location.href=t.urlPrefix+"/lots/?t="+Date.now()):bulmaJS.alert({title:"Error Deleting "+exports.aliases.lot,message:e.errorMessage||"",contextualColorName:"danger"})})}}})});let s=exports.lotComments;delete exports.lotComments;const n=o=>{const l=Number.parseInt(o.currentTarget.closest("tr").dataset.lotCommentId,10),n=s.find(t=>t.lotCommentId===l);let r,m;const i=e=>{e.preventDefault(),cityssm.postJSON(t.urlPrefix+"/lots/doUpdateLotComment",r,t=>{t.success?(s=t.lotComments,m(),a()):bulmaJS.alert({title:"Error Updating Comment",message:t.errorMessage||"",contextualColorName:"danger"})})};cityssm.openHtmlModal("lot-editComment",{onshow:o=>{t.populateAliases(o),o.querySelector("#lotCommentEdit--lotId").value=e,o.querySelector("#lotCommentEdit--lotCommentId").value=l.toString(),o.querySelector("#lotCommentEdit--lotComment").value=n.lotComment;const s=o.querySelector("#lotCommentEdit--lotCommentDateString");s.value=n.lotCommentDateString;const r=cityssm.dateToString(new Date);s.max=n.lotCommentDateString<=r?r:n.lotCommentDateString,o.querySelector("#lotCommentEdit--lotCommentTimeString").value=n.lotCommentTimeString},onshown:(e,o)=>{bulmaJS.toggleHtmlClipped(),t.initializeDatePickers(e),e.querySelector("#lotCommentEdit--lotComment").focus(),(r=e.querySelector("form")).addEventListener("submit",i),m=o},onremoved:()=>{bulmaJS.toggleHtmlClipped()}})},r=o=>{const l=Number.parseInt(o.currentTarget.closest("tr").dataset.lotCommentId,10);bulmaJS.confirm({title:"Remove Comment?",message:"Are you sure you want to remove this comment?",okButton:{text:"Yes, Remove Comment",callbackFunction:()=>{cityssm.postJSON(t.urlPrefix+"/lots/doDeleteLotComment",{lotId:e,lotCommentId:l},t=>{t.success?(s=t.lotComments,a()):bulmaJS.alert({title:"Error Removing Comment",message:t.errorMessage||"",contextualColorName:"danger"})})}},contextualColorName:"warning"})},a=()=>{const t=document.querySelector("#container--lotComments");if(0===s.length)return void(t.innerHTML='');const e=document.createElement("table");e.className="table is-fullwidth is-striped is-hoverable",e.innerHTML='
+ <%= configFunctions.getProperty("aliases.map") %>
+
+ <%= lot.mapName || "(No Name)" %>
+
+
- <%= configFunctions.getProperty("aliases.map") %>
-
- <%= lot.mapName || "(No Name)" %>
-
+ <%= configFunctions.getProperty("aliases.lot") %> Type
+ <%= lot.lotType %>
- <%= configFunctions.getProperty("aliases.lot") %> Type
- <%= lot.lotType %>
-
- Status
- <%= lot.lotStatus %>
-
+ Status
+ <%= lot.lotStatus %>
+
+ <%= lotField.lotTypeField %>
+ <% if (lotField.lotFieldValue) { %>
+ <%= lotField.lotFieldValue %>
+ <% } else { %>
+ (No Value)
+ <% } %>
+