/ client / src / utils / parsing.tsx
parsing.tsx
  1  import React from "react";
  2  
  3  /**
  4   * Insert blankspace thousands separator into a number
  5   * Supports both period or comma as decimal separator
  6   * @param input
  7   * @returns
  8   */
  9  export function formatNumberWithSpaceSeparator(input: string): string {
 10    const isPeriodDecimalSeparator = input.indexOf(".") > -1;
 11  
 12    const parts = input.split(isPeriodDecimalSeparator ? "." : ",");
 13  
 14    const integerPart = parts[0];
 15    const decimalPart = parts[1] || "";
 16  
 17    // Add the thousands separator (blank space) to the integer part
 18    const formattedIntegerPart = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, " ");
 19  
 20    // Combine the formatted integer and decimal parts
 21    const suffix = decimalPart ? (isPeriodDecimalSeparator ? "." : ",") + decimalPart : "";
 22    const formattedNumber = formattedIntegerPart + suffix;
 23  
 24    return formattedNumber;
 25  }
 26  
 27  /**
 28   * Number formatter compatible with Ant Design's InputNumber component, handling UX properly.
 29   * @param value
 30   * @param info
 31   * @returns
 32   */
 33  export function formatNumberOnUserInput(
 34    value: number | string | undefined,
 35    info: { userTyping: boolean; input: string },
 36  ): string {
 37    if (info.userTyping) {
 38      return info.input;
 39    }
 40    return numberFormatter(value);
 41  }
 42  
 43  /**
 44   * Number formatter that nicely formats numbers with correct decimal separator based on locale
 45   * Always uses blank space as thousands separator to prevent confusion with the decimal separator
 46   * @param value
 47   * @returns
 48   */
 49  export function numberFormatter(value: number | string | undefined): string {
 50    const formattedValue = value
 51      ? Number(value).toLocaleString(undefined, {
 52          useGrouping: false, // Disable thousands separator and do it manually instead so it's always spaces
 53        })
 54      : "";
 55  
 56    return formatNumberWithSpaceSeparator(formattedValue);
 57  }
 58  
 59  /**
 60   * Number parser that supports both comma and dot as decimal separator
 61   * @param value
 62   * @returns
 63   */
 64  export function numberParser(value: string | undefined): number {
 65    // Convert comma to dot
 66    value = value?.replace(",", ".");
 67  
 68    // Remove all non-digit characters
 69    value = value?.replace(/[^\d.-]/g, "");
 70  
 71    // Parse as float
 72    return parseFloat(value || "0");
 73  }
 74  
 75  /**
 76   * Number parser that supports both comma and dot as decimal separator
 77   * Same as numberParser but allows empty values. numberParser will always return a valid number
 78   * this one returns an empty string if the value is empty
 79   * @param value
 80   * @returns
 81   */
 82  export function numberParserAllowEmpty(value: string | undefined): number | string {
 83    // Convert comma to dot
 84    value = value?.replace(",", ".");
 85  
 86    // Remove all non-digit characters
 87    value = value?.replace(/[^\d.-]/g, "");
 88  
 89    if (value === "" || value === undefined) {
 90      return "";
 91    }
 92  
 93    // Parse as float
 94    return parseFloat(value);
 95  }
 96  
 97  /**
 98   * Enrich text with links
 99   * @param text
100   * @returns
101   */
102  export function enrichText(text: string | undefined) {
103    // Regular expression to match URLs
104    const urlRegex =
105      /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi;
106  
107    // Split the input text by URLs
108    const parts = (text ?? "").split(urlRegex);
109  
110    // Convert URLs to <a> tags
111    const elements = parts.map((part, index) => {
112      if (part.match(urlRegex)) {
113        return (
114          <a href={part} key={index} target="_blank" rel="noopener noreferrer">
115            {part}
116          </a>
117        );
118      } else {
119        return <React.Fragment key={index}>{part}</React.Fragment>;
120      }
121    });
122  
123    return <>{elements}</>;
124  }
125  
126  /**
127   * Formats the weight in grams to either kilograms or grams based on the provided precision.
128   *
129   * @param {number} weightInGrams - The weight in grams to be formatted.
130   * @param {number} [precision=2] - The precision of the formatting (number of decimal places).
131   * @return {string} The formatted weight with the appropriate unit (kg or g).
132   */
133  export function formatWeight(weightInGrams: number, precision: number = 2): string {
134    if (weightInGrams >= 1000) {
135      const kilograms = removeTrailingZeros((weightInGrams / 1000).toFixed(precision));
136      return `${kilograms} kg`;
137    } else {
138      const grams = removeTrailingZeros(weightInGrams.toFixed(precision));
139      return `${grams} g`;
140    }
141  }
142  
143  /**
144   * Formats the length in millimeters to either meters or millimeters based on the provided precision.
145   *
146   * @param {number} lengthInMillimeter - The length in millimeters to be formatted.
147   * @param {number} [precision=2] - The precision of the formatting (number of decimal places).
148   * @return {string} The formatted length with the appropriate unit (m or mm).
149   */
150  export function formatLength(lengthInMillimeter: number, precision: number = 2): string {
151    if (lengthInMillimeter >= 1000) {
152      const meters = removeTrailingZeros((lengthInMillimeter / 1000).toFixed(precision));
153      return `${meters} m`;
154    } else {
155      return `${lengthInMillimeter} mm`;
156    }
157  }
158  
159  /**
160   * Removes trailing zeros from a numeric string, including unnecessary decimal points.
161   *
162   * This function takes a string representation of a number and removes any trailing zeros
163   * after the decimal point. If the number ends with a decimal point followed by only zeros,
164   * the entire decimal portion is removed.
165   *
166   * @param num - The numeric string to process.
167   * @returns The numeric string with trailing zeros removed.
168   *
169   * @example
170   * ```typescript
171   * removeTrailingZeros("123.45000"); // Returns "123.45"
172   * removeTrailingZeros("100.000");   // Returns "100"
173   * removeTrailingZeros("0.0000");    // Returns "0"
174   * ```
175   */
176  function removeTrailingZeros(num: string): string {
177    return num.replace(/(\.\d*?[1-9])0+|\.0*$/, "$1");
178  }