assessment-model/src/app/utils.ts
2026-05-08 09:02:21 +00:00

194 lines
5 KiB
TypeScript

import { Rating } from "./db/schema/property";
import { KeyboardEvent } from "react";
export function handleNumericKeyDown(event: KeyboardEvent<HTMLInputElement>) {
/**
* Allowing: Integers | Backspace | Tab | Delete | Left & Right arrow keys
**/
const regex = new RegExp(
/(^\d*$)|(Backspace|Tab|Delete|ArrowLeft|ArrowRight|ArrowUp|ArrowDown)/
);
return !event.key.match(regex) && event.preventDefault();
}
export function convertDaysToWorkingWeeks(days: number | null) {
if (days === null) {
return "-";
}
const workingDaysPerWeek = 5;
// Convert days to working weeks
const workingWeeks = days / workingDaysPerWeek;
// Determine the range
let lowerBound = Math.floor(workingWeeks);
let upperBound = Math.ceil(workingWeeks);
// Adjust if the fraction is very small, you might not count it as a full extra week
if (workingWeeks - lowerBound < 0.2) {
upperBound = lowerBound;
}
if (lowerBound === 0 && upperBound === 1) {
return "1 week";
}
if (lowerBound === 1 && upperBound === 1) {
return "1-2 weeks";
}
// Format the output
return lowerBound === upperBound
? `${lowerBound} weeks`
: `${lowerBound}-${upperBound} weeks`;
}
export const getEpcColorClass = (letter: string) => {
switch (letter.toUpperCase()) {
case "A":
return "bg-epc_a";
case "B":
return "bg-epc_b";
case "C":
return "bg-epc_c";
case "D":
return "bg-epc_d";
case "E":
return "bg-epc_e";
case "F":
return "bg-epc_f";
case "G":
return "bg-epc_g";
default:
return "bg-gray-500";
}
};
/** Border + text colour classes for a transparent-background EPC badge */
export const getEpcAccentClasses = (letter: string) => {
switch (letter.toUpperCase()) {
case "A": return "border-epc_a text-epc_a";
case "B": return "border-epc_b text-epc_b";
case "C": return "border-epc_c text-epc_c";
case "D": return "border-epc_d text-epc_d";
case "E": return "border-epc_e text-epc_e";
case "F": return "border-epc_f text-epc_f";
case "G": return "border-epc_g text-epc_g";
default: return "border-slate-300 text-slate-400";
}
};
export const getRating = (rating: number | null): Rating => {
if (rating == null) {
return "N/A";
}
if (![1, 2, 3, 4, 5].includes(rating)) {
throw new Error("Rating should be between 1 and 5.");
}
const ratingMap: { [key in 1 | 2 | 3 | 4 | 5]: string } = {
1: "Very poor",
2: "Poor",
3: "Average",
4: "Good",
5: "Very good",
};
return ratingMap[rating as 1 | 2 | 3 | 4 | 5] as Rating; // You can assert that rating is one of these values since you've already checked it
};
export const serializeBigInt = (_: any, value: any): string | any => {
// Simple utility function to convert BigInts to strings with JSON.stringify
return typeof value === "bigint" ? value.toString() : value;
};
export function sapToEpc(sapPoints: number | null): string {
if (sapPoints === null) {
return "Unknown";
}
if (sapPoints < 0) {
throw new Error("SAP points should be above 0.");
}
if (sapPoints >= 92) {
return "A";
} else if (sapPoints >= 81) {
return "B";
} else if (sapPoints >= 69) {
return "C";
} else if (sapPoints >= 55) {
return "D";
} else if (sapPoints >= 39) {
return "E";
} else if (sapPoints >= 21) {
return "F";
} else {
return "G";
}
}
// Handles "D55" (new format) and "56" (legacy numeric-only format)
export function parsePreSap(raw: string | null | undefined): { letter: string; display: string } | null {
if (!raw) return null;
const match = raw.trim().match(/^([A-Za-z]?)(\d+)$/);
if (!match) return null;
const num = parseInt(match[2], 10);
const letter = match[1] ? match[1].toUpperCase() : sapToEpc(num);
if (letter === "Unknown") return null;
return { letter, display: `${letter}${num}` };
}
export function formatDateTime(dateTimeString: string | Date): string {
// Create a new Date object
const dateTime = new Date(dateTimeString);
// Get the various parts of the date
const options: Intl.DateTimeFormatOptions = {
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
};
return dateTime.toLocaleDateString("en-GB", options);
}
export function formatNumber(number: number): string {
if (number === 0) return "0";
if (number < 1) {
return number.toFixed(2);
}
const suffixes = ["", "k", "m", "b", "t"];
const suffixIndex = Math.floor(Math.log10(Math.abs(number)) / 3);
if (suffixIndex >= suffixes.length) {
return number.toString();
}
const scaledNumber = number / Math.pow(1000, suffixIndex);
// Format the number to one decimal place
let formatted = scaledNumber.toFixed(1);
// Remove trailing '.0' if present
if (formatted.endsWith(".0")) {
formatted = formatted.slice(0, -2);
}
return formatted + suffixes[suffixIndex];
}
export function roundToDecimalPlaces(
number: number,
decimalPlaces: number
): number {
const factor = 10 ** decimalPlaces;
return Math.round(number * factor) / factor;
}