lot fields in lot view and edit

deepsource-autofix-76c6eb20
Dan Gowans 2022-12-21 13:12:46 -05:00
parent 1212066071
commit 15afd72ed8
29 changed files with 819 additions and 53 deletions

View File

@ -0,0 +1,3 @@
import type { RequestHandler } from "express";
export declare const handler: RequestHandler;
export default handler;

View File

@ -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;

View File

@ -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;

View File

@ -7,6 +7,8 @@ interface AddLotForm {
mapKey: string; mapKey: string;
lotLatitude: string; lotLatitude: string;
lotLongitude: string; lotLongitude: string;
lotTypeFieldIds?: string;
[lotFieldValue_lotTypeFieldId: string]: unknown;
} }
export declare const addLot: (lotForm: AddLotForm, requestSession: recordTypes.PartialSession) => number; export declare const addLot: (lotForm: AddLotForm, requestSession: recordTypes.PartialSession) => number;
export default addLot; export default addLot;

View File

@ -1,5 +1,6 @@
import sqlite from "better-sqlite3"; import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
import { addOrUpdateLotField } from "./addOrUpdateLotField.js";
export const addLot = (lotForm, requestSession) => { export const addLot = (lotForm, requestSession) => {
const database = sqlite(databasePath); const database = sqlite(databasePath);
const rightNowMillis = Date.now(); const rightNowMillis = Date.now();
@ -12,7 +13,19 @@ export const addLot = (lotForm, requestSession) => {
" recordUpdate_userName, recordUpdate_timeMillis)" + " recordUpdate_userName, recordUpdate_timeMillis)" +
" values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") " 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); .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(); database.close();
return result.lastInsertRowid; return lotId;
}; };
export default addLot; export default addLot;

View File

@ -2,6 +2,8 @@ import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
import { addOrUpdateLotField } from "./addOrUpdateLotField.js";
import type * as recordTypes from "../../types/recordTypes"; import type * as recordTypes from "../../types/recordTypes";
interface AddLotForm { interface AddLotForm {
@ -14,12 +16,12 @@ interface AddLotForm {
lotLatitude: string; lotLatitude: string;
lotLongitude: string; lotLongitude: string;
lotTypeFieldIds?: string;
[lotFieldValue_lotTypeFieldId: string]: unknown;
} }
export const addLot = ( export const addLot = (lotForm: AddLotForm, requestSession: recordTypes.PartialSession): number => {
lotForm: AddLotForm,
requestSession: recordTypes.PartialSession
): number => {
const database = sqlite(databasePath); const database = sqlite(databasePath);
const rightNowMillis = Date.now(); const rightNowMillis = Date.now();
@ -48,9 +50,29 @@ export const addLot = (
rightNowMillis 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(); database.close();
return result.lastInsertRowid as number; return lotId;
}; };
export default addLot; export default addLot;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -1,5 +1,6 @@
import sqlite from "better-sqlite3"; import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
import { getLotFields } from "./getLotFields.js";
import { getLotComments } from "./getLotComments.js"; import { getLotComments } from "./getLotComments.js";
import { getLotOccupancies } from "./getLotOccupancies.js"; import { getLotOccupancies } from "./getLotOccupancies.js";
const baseSQL = "select l.lotId," + const baseSQL = "select l.lotId," +
@ -26,7 +27,8 @@ const _getLot = (sql, lotId_or_lotName) => {
limit: -1, limit: -1,
offset: 0 offset: 0
}, database).lotOccupancies; }, database).lotOccupancies;
lot.lotComments = getLotComments(lot.lotId); lot.lotFields = getLotFields(lot.lotId, database);
lot.lotComments = getLotComments(lot.lotId, database);
} }
database.close(); database.close();
return lot; return lot;

View File

@ -2,6 +2,8 @@ import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
import { getLotFields } from "./getLotFields.js";
import { getLotComments } from "./getLotComments.js"; import { getLotComments } from "./getLotComments.js";
import { getLotOccupancies } from "./getLotOccupancies.js"; import { getLotOccupancies } from "./getLotOccupancies.js";
@ -41,7 +43,9 @@ const _getLot = (sql: string, lotId_or_lotName: number | string): recordTypes.Lo
database database
).lotOccupancies; ).lotOccupancies;
lot.lotComments = getLotComments(lot.lotId); lot.lotFields = getLotFields(lot.lotId, database);
lot.lotComments = getLotComments(lot.lotId, database);
} }
database.close(); database.close();

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -8,6 +8,8 @@ interface UpdateLotForm {
mapKey: string; mapKey: string;
lotLatitude: string; lotLatitude: string;
lotLongitude: string; lotLongitude: string;
lotTypeFieldIds?: string;
[lotFieldValue_lotTypeFieldId: string]: unknown;
} }
export declare function updateLot(lotForm: UpdateLotForm, requestSession: recordTypes.PartialSession): boolean; export declare function updateLot(lotForm: UpdateLotForm, requestSession: recordTypes.PartialSession): boolean;
export declare function updateLotStatus(lotId: number | string, lotStatusId: number | string, requestSession: recordTypes.PartialSession): boolean; export declare function updateLotStatus(lotId: number | string, lotStatusId: number | string, requestSession: recordTypes.PartialSession): boolean;

View File

@ -1,5 +1,7 @@
import sqlite from "better-sqlite3"; import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
import { addOrUpdateLotField } from "./addOrUpdateLotField.js";
import { deleteLotField } from "./deleteLotField.js";
export function updateLot(lotForm, requestSession) { export function updateLot(lotForm, requestSession) {
const database = sqlite(databasePath); const database = sqlite(databasePath);
const rightNowMillis = Date.now(); const rightNowMillis = Date.now();
@ -17,6 +19,22 @@ export function updateLot(lotForm, requestSession) {
" where lotId = ?" + " where lotId = ?" +
" and recordDelete_timeMillis is null") " 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); .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(); database.close();
return result.changes > 0; return result.changes > 0;
} }

View File

@ -2,6 +2,9 @@ import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; 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"; import type * as recordTypes from "../../types/recordTypes";
interface UpdateLotForm { interface UpdateLotForm {
@ -15,6 +18,9 @@ interface UpdateLotForm {
lotLatitude: string; lotLatitude: string;
lotLongitude: string; lotLongitude: string;
lotTypeFieldIds?: string;
[lotFieldValue_lotTypeFieldId: string]: unknown;
} }
export function updateLot( export function updateLot(
@ -53,6 +59,28 @@ export function updateLot(
lotForm.lotId 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(); database.close();
return result.changes > 0; return result.changes > 0;

View File

@ -4,14 +4,27 @@ Object.defineProperty(exports, "__esModule", { value: true });
const los = exports.los; const los = exports.los;
const lotId = document.querySelector("#lot--lotId").value; const lotId = document.querySelector("#lot--lotId").value;
const isCreate = lotId === ""; 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 formElement = document.querySelector("#form--lot");
const updateLot = (formEvent) => { const updateLot = (formEvent) => {
formEvent.preventDefault(); formEvent.preventDefault();
cityssm.postJSON(los.urlPrefix + "/lots/" + (isCreate ? "doCreateLot" : "doUpdateLot"), formElement, (responseJSON) => { cityssm.postJSON(los.urlPrefix + "/lots/" + (isCreate ? "doCreateLot" : "doUpdateLot"), formElement, (responseJSON) => {
if (responseJSON.success) { if (responseJSON.success) {
if (isCreate) { clearUnsavedChanges();
if (isCreate || refreshAfterSave) {
window.location.href = window.location.href =
los.urlPrefix + "/lots/" + responseJSON.lotId + "/edit"; los.urlPrefix + "/lots/" + responseJSON.lotId + "/edit?t=" + Date.now();
} }
else { else {
bulmaJS.alert({ bulmaJS.alert({
@ -30,6 +43,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
}); });
}; };
formElement.addEventListener("submit", updateLot); formElement.addEventListener("submit", updateLot);
const formInputElements = formElement.querySelectorAll("input, select");
for (const formInputElement of formInputElements) {
formInputElement.addEventListener("change", setUnsavedChanges);
}
los.initializeUnlockFieldButtons(formElement); los.initializeUnlockFieldButtons(formElement);
if (!isCreate) { if (!isCreate) {
document.querySelector("#button--deleteLot").addEventListener("click", (clickEvent) => { 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 =
'<div class="message is-info">' +
'<p class="message-body">Select the ' +
exports.aliases.lot.toLowerCase() +
" type to load the available fields.</p>" +
"</div>";
return;
}
cityssm.postJSON(los.urlPrefix + "/lots/doGetLotTypeFields", {
lotTypeId: lotTypeIdElement.value
}, (responseJSON) => {
if (responseJSON.lotTypeFields.length === 0) {
lotFieldsContainerElement.innerHTML =
'<div class="message is-info">' +
'<p class="message-body">There are no additional fields for this ' +
exports.aliases.lot.toLowerCase() +
" type.</p>" +
"</div>";
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 =
'<label class="label" for="' +
fieldId +
'"></label>' +
'<div class="control"></div>';
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 =
'<div class="select is-fullwidth"><select id="' +
fieldId +
'" name="' +
fieldName +
'">' +
'<option value="">(Not Set)</option>' +
"</select></div>";
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", '<input name="lotTypeFieldIds" type="hidden" value="' +
lotTypeFieldIds.slice(1) +
'" />');
});
});
}
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; let lotComments = exports.lotComments;
delete exports.lotComments; delete exports.lotComments;
const openEditLotComment = (clickEvent) => { const openEditLotComment = (clickEvent) => {

View File

@ -18,6 +18,21 @@ declare const bulmaJS: BulmaJS;
// Main form // 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 formElement = document.querySelector("#form--lot") as HTMLFormElement;
const updateLot = (formEvent: SubmitEvent) => { const updateLot = (formEvent: SubmitEvent) => {
@ -28,9 +43,11 @@ declare const bulmaJS: BulmaJS;
formElement, formElement,
(responseJSON: { success: boolean; lotId?: number; errorMessage?: string }) => { (responseJSON: { success: boolean; lotId?: number; errorMessage?: string }) => {
if (responseJSON.success) { if (responseJSON.success) {
if (isCreate) { clearUnsavedChanges();
if (isCreate || refreshAfterSave) {
window.location.href = window.location.href =
los.urlPrefix + "/lots/" + responseJSON.lotId + "/edit"; los.urlPrefix + "/lots/" + responseJSON.lotId + "/edit?t=" + Date.now();
} else { } else {
bulmaJS.alert({ bulmaJS.alert({
message: exports.aliases.lot + " Updated Successfully", message: exports.aliases.lot + " Updated Successfully",
@ -50,6 +67,12 @@ declare const bulmaJS: BulmaJS;
formElement.addEventListener("submit", updateLot); formElement.addEventListener("submit", updateLot);
const formInputElements = formElement.querySelectorAll("input, select");
for (const formInputElement of formInputElements) {
formInputElement.addEventListener("change", setUnsavedChanges);
}
los.initializeUnlockFieldButtons(formElement); los.initializeUnlockFieldButtons(formElement);
if (!isCreate) { 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 =
'<div class="message is-info">' +
'<p class="message-body">Select the ' +
exports.aliases.lot.toLowerCase() +
" type to load the available fields.</p>" +
"</div>";
return;
}
cityssm.postJSON(
los.urlPrefix + "/lots/doGetLotTypeFields",
{
lotTypeId: lotTypeIdElement.value
},
(responseJSON: { lotTypeFields: recordTypes.LotTypeField[] }) => {
if (responseJSON.lotTypeFields.length === 0) {
lotFieldsContainerElement.innerHTML =
'<div class="message is-info">' +
'<p class="message-body">There are no additional fields for this ' +
exports.aliases.lot.toLowerCase() +
" type.</p>" +
"</div>";
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 =
'<label class="label" for="' +
fieldId +
'"></label>' +
'<div class="control"></div>';
(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 =
'<div class="select is-fullwidth"><select id="' +
fieldId +
'" name="' +
fieldName +
'">' +
'<option value="">(Not Set)</option>' +
"</select></div>";
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",
'<input name="lotTypeFieldIds" type="hidden" value="' +
lotTypeFieldIds.slice(1) +
'" />'
);
}
);
});
} 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 // Comments
let lotComments: recordTypes.LotComment[] = exports.lotComments; let lotComments: recordTypes.LotComment[] = exports.lotComments;

File diff suppressed because one or more lines are too long

View File

@ -7,6 +7,7 @@ import handler_next from "../handlers/lots-get/next.js";
import handler_previous from "../handlers/lots-get/previous.js"; import handler_previous from "../handlers/lots-get/previous.js";
import handler_new from "../handlers/lots-get/new.js"; import handler_new from "../handlers/lots-get/new.js";
import handler_edit from "../handlers/lots-get/edit.js"; import handler_edit from "../handlers/lots-get/edit.js";
import handler_doGetLotTypeFields from "../handlers/lots-post/doGetLotTypeFields.js";
import handler_doCreateLot from "../handlers/lots-post/doCreateLot.js"; import handler_doCreateLot from "../handlers/lots-post/doCreateLot.js";
import handler_doUpdateLot from "../handlers/lots-post/doUpdateLot.js"; import handler_doUpdateLot from "../handlers/lots-post/doUpdateLot.js";
import handler_doDeleteLot from "../handlers/lots-post/doDeleteLot.js"; import handler_doDeleteLot from "../handlers/lots-post/doDeleteLot.js";
@ -21,6 +22,7 @@ router.get("/:lotId", handler_view);
router.get("/:lotId/next", handler_next); router.get("/:lotId/next", handler_next);
router.get("/:lotId/previous", handler_previous); router.get("/:lotId/previous", handler_previous);
router.get("/:lotId/edit", permissionHandlers.updateGetHandler, handler_edit); router.get("/:lotId/edit", permissionHandlers.updateGetHandler, handler_edit);
router.post("/doGetLotTypeFields", permissionHandlers.updatePostHandler, handler_doGetLotTypeFields);
router.post("/doCreateLot", permissionHandlers.updatePostHandler, handler_doCreateLot); router.post("/doCreateLot", permissionHandlers.updatePostHandler, handler_doCreateLot);
router.post("/doUpdateLot", permissionHandlers.updatePostHandler, handler_doUpdateLot); router.post("/doUpdateLot", permissionHandlers.updatePostHandler, handler_doUpdateLot);
router.post("/doDeleteLot", permissionHandlers.updatePostHandler, handler_doDeleteLot); router.post("/doDeleteLot", permissionHandlers.updatePostHandler, handler_doDeleteLot);

View File

@ -12,6 +12,8 @@ import handler_previous from "../handlers/lots-get/previous.js";
import handler_new from "../handlers/lots-get/new.js"; import handler_new from "../handlers/lots-get/new.js";
import handler_edit from "../handlers/lots-get/edit.js"; import handler_edit from "../handlers/lots-get/edit.js";
import handler_doGetLotTypeFields from "../handlers/lots-post/doGetLotTypeFields.js";
import handler_doCreateLot from "../handlers/lots-post/doCreateLot.js"; import handler_doCreateLot from "../handlers/lots-post/doCreateLot.js";
import handler_doUpdateLot from "../handlers/lots-post/doUpdateLot.js"; import handler_doUpdateLot from "../handlers/lots-post/doUpdateLot.js";
import handler_doDeleteLot from "../handlers/lots-post/doDeleteLot.js"; import handler_doDeleteLot from "../handlers/lots-post/doDeleteLot.js";
@ -44,6 +46,8 @@ router.get("/:lotId/previous", handler_previous);
router.get("/:lotId/edit", permissionHandlers.updateGetHandler, handler_edit); router.get("/:lotId/edit", permissionHandlers.updateGetHandler, handler_edit);
router.post("/doGetLotTypeFields", permissionHandlers.updatePostHandler, handler_doGetLotTypeFields);
router.post("/doCreateLot", permissionHandlers.updatePostHandler, handler_doCreateLot); router.post("/doCreateLot", permissionHandlers.updatePostHandler, handler_doCreateLot);
router.post("/doUpdateLot", permissionHandlers.updatePostHandler, handler_doUpdateLot); router.post("/doUpdateLot", permissionHandlers.updatePostHandler, handler_doUpdateLot);

View File

@ -62,6 +62,7 @@ export interface Lot extends Record {
lotLongitude?: number; lotLongitude?: number;
lotStatusId?: number; lotStatusId?: number;
lotStatus?: string; lotStatus?: string;
lotFields?: LotField[];
lotOccupancyCount?: number; lotOccupancyCount?: number;
lotOccupancies?: LotOccupancy[]; lotOccupancies?: LotOccupancy[];
lotComments?: LotComment[]; lotComments?: LotComment[];
@ -75,6 +76,11 @@ export interface LotComment extends Record {
lotCommentTimeString?: string; lotCommentTimeString?: string;
lotComment?: string; lotComment?: string;
} }
export interface LotField extends LotTypeField, Record {
lotId?: number;
lotTypeFieldId?: number;
lotFieldValue?: string;
}
export interface OccupancyType extends Record { export interface OccupancyType extends Record {
occupancyTypeId: number; occupancyTypeId: number;
occupancyType: string; occupancyType: string;

View File

@ -84,6 +84,8 @@ export interface Lot extends Record {
lotStatusId?: number; lotStatusId?: number;
lotStatus?: string; lotStatus?: string;
lotFields?: LotField[];
lotOccupancyCount?: number; lotOccupancyCount?: number;
lotOccupancies?: LotOccupancy[]; lotOccupancies?: LotOccupancy[];
@ -103,6 +105,12 @@ export interface LotComment extends Record {
lotComment?: string; lotComment?: string;
} }
export interface LotField extends LotTypeField, Record {
lotId?: number;
lotTypeFieldId?: number;
lotFieldValue?: string;
}
export interface OccupancyType extends Record { export interface OccupancyType extends Record {
occupancyTypeId: number; occupancyTypeId: number;
occupancyType: string; occupancyType: string;

View File

@ -106,8 +106,6 @@
</button> </button>
</div> </div>
</div> </div>
</div>
<div class="column">
<label class="label" for="lot--lotStatusId"> <label class="label" for="lot--lotStatusId">
<%= configFunctions.getProperty("aliases.lot") %> Status <%= configFunctions.getProperty("aliases.lot") %> Status
</label> </label>
@ -138,6 +136,78 @@
</div> </div>
</div> </div>
</div> </div>
<div class="column">
<div id="container--lotFields">
<% if (isCreate) { %>
<div class="message is-info">
<p class="message-body">
Select the <%= configFunctions.getProperty("aliases.lot").toLowerCase() %> type to load the available fields.
</p>
</div>
<% } else if (lot.lotFields.length === 0) { %>
<div class="message is-info">
<p class="message-body">
The current <%= configFunctions.getProperty("aliases.lot").toLowerCase() %> type has no additional fields.
</p>
</div>
<% } else { %>
<% let lotTypeFieldIds = ""; %>
<% for (const lotField of lot.lotFields) { %>
<% lotTypeFieldIds += "," + lotField.lotTypeFieldId; %>
<div class="field">
<label class="label" for="lot--lotFieldValue_<%= lotField.lotTypeFieldId %>">
<%= lotField.lotTypeField %>
</label>
<div class="control">
<% if (!lotField.lotTypeFieldValues || lotField.lotTypeFieldValues === "") { %>
<input class="input"
id="lot--lotFieldValue_<%= lotField.lotTypeFieldId %>"
name="lotFieldValue_<%= lotField.lotTypeFieldId %>"
type="text"
value="<%= lotField.lotFieldValue %>"
<% if (lotField.pattern !== "") { %>
pattern="<%= lotField.pattern %>"
<% } %>
minlength="<%= lotField.minimumLength %>"
maxlength="<%= lotField.maximumLength %>"
<%= lotField.isRequired ? " required" : "" %> />
<% } else { %>
<%
const fieldValues = lotField.lotTypeFieldValues.split("\n");
let valueFound = false;
%>
<div class="select is-fullwidth">
<select id="lot--lotFieldValue_<%= lotField.lotTypeFieldId %>"
name="lotFieldValue_<%= lotField.lotTypeFieldId %>">
<% if (!lotField.isRequired || lotField.lotFieldValue === "") { %>
<option value="">(Not Set)</option>
<% } %>
<% for (const fieldValue of fieldValues) { %>
<%
if (fieldValue === lotField.lotFieldValue) {
valueFound = true;
}
%>
<option value="<%= fieldValue %>"
<%= (fieldValue === lotField.lotFieldValue ? " selected" : "") %>>
<%= fieldValue %>
</option>
<% } %>
<% if (!valueFound && lotField.lotFieldValue !== "") { %>
<option value="<%= lotField.lotFieldValue %>" selected>
<%= lotField.lotFieldValue %>
</option>
<% } %>
</select>
</div>
<% } %>
</div>
</div>
<% } %>
<input id="lot--lotTypeFieldIds" name="lotTypeFieldIds" type="hidden" value="<%= lotTypeFieldIds.slice(1) %>" />
<% } %>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -46,8 +46,6 @@
<% } %> <% } %>
</div> </div>
<div class="columns">
<div class="column">
<div class="panel"> <div class="panel">
<div class="panel-block is-block"> <div class="panel-block is-block">
<p> <p>
@ -69,30 +67,36 @@
<%= lot.lotStatus %> <%= lot.lotStatus %>
</p> </p>
</div> </div>
</div>
</div>
</div>
</div>
<div class="column"> <div class="column">
<div class="panel"> <% if (lot.lotFields.length > 0) { %>
<h2 class="panel-heading">Image</h2> <% for (const lotField of lot.lotFields) { %>
<div class="panel-block is-block"> <p class="mb-2">
<% if (lot.mapSVG) { %> <strong><%= lotField.lotTypeField %></strong><br />
<% const imageURL = urlPrefix + "/images/maps/" + lot.mapSVG %> <% if (lotField.lotFieldValue) { %>
<div class="image" id="lot--map" data-map-key="<%= lot.mapKey %>"> <%= lotField.lotFieldValue %>
<%- include('../public/images/maps/' + lot.mapSVG); -%>
</div>
<% } else { %> <% } else { %>
<div class="message is-info"> <span class="has-text-grey">(No Value)</span>
<p class="message-body">There are no image associated with this <% } %>
<%= configFunctions.getProperty("aliases.lot").toLowerCase() %>.</p> </p>
</div> <% } %>
<% } %> <% } %>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<% if (lot.mapSVG) { %>
<div class="panel">
<h2 class="panel-heading">Image</h2>
<div class="panel-block is-block">
<% const imageURL = urlPrefix + "/images/maps/" + lot.mapSVG %>
<div class="image" id="lot--map" data-map-key="<%= lot.mapKey %>">
<%- include('../public/images/maps/' + lot.mapSVG); -%>
</div>
</div>
</div>
<% } %>
<% if (lot.lotComments.length > 0) { %> <% if (lot.lotComments.length > 0) { %>
<div class="panel"> <div class="panel">
<h2 class="panel-heading">Comments</h2> <h2 class="panel-heading">Comments</h2>