mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 13:55:21 +02:00
fix(app/edge-configs): high numbers UI overlap (#931)
This commit is contained in:
parent
1afae99345
commit
e7d97d7a2b
2 changed files with 120 additions and 0 deletions
75
app/react/common/utils/numbers.test.ts
Normal file
75
app/react/common/utils/numbers.test.ts
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-loss-of-precision */
|
||||||
|
|
||||||
|
import { abbreviateNumber } from './numbers';
|
||||||
|
|
||||||
|
describe('abbreviateNumber', () => {
|
||||||
|
test('errors', () => {
|
||||||
|
expect(() => abbreviateNumber(Number.NaN)).toThrowError();
|
||||||
|
expect(() => abbreviateNumber(1, -1)).toThrowError();
|
||||||
|
expect(() => abbreviateNumber(1, 21)).toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('zero', () => {
|
||||||
|
expect(abbreviateNumber(0)).toBe('0');
|
||||||
|
expect(abbreviateNumber(-0)).toBe('0');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('decimals=0', () => {
|
||||||
|
const cases: [number, string][] = [
|
||||||
|
[123, '123'],
|
||||||
|
[123_123, '123k'],
|
||||||
|
[123_123_123, '123M'],
|
||||||
|
[123_123_123_123, '123G'],
|
||||||
|
[123_123_123_123_123, '123T'],
|
||||||
|
[123_123_123_123_123_123, '123P'],
|
||||||
|
[123_123_123_123_123_123_123, '123E'],
|
||||||
|
[123_123_123_123_123_123_123_123, '123Z'],
|
||||||
|
[123_123_123_123_123_123_123_123_123, '123Y'],
|
||||||
|
[123_123_123_123_123_123_123_123_123_123, '123123Y'],
|
||||||
|
];
|
||||||
|
cases.forEach(([num, str]) => {
|
||||||
|
expect(abbreviateNumber(num, 0)).toBe(str);
|
||||||
|
expect(abbreviateNumber(-num, 0)).toBe(`-${str}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('decimals=1 (default)', () => {
|
||||||
|
const cases: [number, string][] = [
|
||||||
|
[123, '123'],
|
||||||
|
[123_123, '123.1k'],
|
||||||
|
[123_123_123, '123.1M'],
|
||||||
|
[123_123_123_123, '123.1G'],
|
||||||
|
[123_123_123_123_123, '123.1T'],
|
||||||
|
[123_123_123_123_123_123, '123.1P'],
|
||||||
|
[123_123_123_123_123_123_123, '123.1E'],
|
||||||
|
[123_123_123_123_123_123_123_123, '123.1Z'],
|
||||||
|
[123_123_123_123_123_123_123_123_123, '123.1Y'],
|
||||||
|
[123_123_123_123_123_123_123_123_123_123, '123123.1Y'],
|
||||||
|
];
|
||||||
|
cases.forEach(([num, str]) => {
|
||||||
|
expect(abbreviateNumber(num)).toBe(str);
|
||||||
|
expect(abbreviateNumber(-num)).toBe(`-${str}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('decimals=10', () => {
|
||||||
|
const cases: [number, string][] = [
|
||||||
|
[123, '123'],
|
||||||
|
[123_123, '123.123k'],
|
||||||
|
[123_123_123, '123.123123M'],
|
||||||
|
[123_123_123_123, '123.123123123G'],
|
||||||
|
[123_123_123_123_123, '123.1231231231T'],
|
||||||
|
[123_123_123_123_123_123, '123.1231231231P'],
|
||||||
|
[123_123_123_123_123_123_123, '123.1231231231E'],
|
||||||
|
[123_123_123_123_123_123_123_123, '123.1231231231Z'],
|
||||||
|
[123_123_123_123_123_123_123_123_123, '123.1231231231Y'],
|
||||||
|
[123_123_123_123_123_123_123_123_123_123, '123123.1231231231Y'],
|
||||||
|
];
|
||||||
|
cases.forEach(([num, str]) => {
|
||||||
|
expect(abbreviateNumber(num, 10)).toBe(str);
|
||||||
|
expect(abbreviateNumber(-num, 10)).toBe(`-${str}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* eslint-enable @typescript-eslint/no-loss-of-precision */
|
45
app/react/common/utils/numbers.ts
Normal file
45
app/react/common/utils/numbers.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
const suffixes = ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a number to a human-readable abbreviated format
|
||||||
|
* Uses base 10 and standard SI prefixes
|
||||||
|
*
|
||||||
|
* @param num - The number to abbreviate
|
||||||
|
* @param decimals - Number of decimal places (default: 1)
|
||||||
|
* @returns Abbreviated number as string (e.g., "90k", "123M")
|
||||||
|
*/
|
||||||
|
export function abbreviateNumber(num: number, decimals: number = 1): string {
|
||||||
|
if (Number.isNaN(num)) {
|
||||||
|
throw new Error('Invalid number');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decimals < 0 || decimals > 20) {
|
||||||
|
throw new Error('Invalid decimals. Must be in [0;20] range');
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNegative = num < 0;
|
||||||
|
const absNum = Math.abs(num);
|
||||||
|
|
||||||
|
if (absNum === 0) {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
let exponent = Math.floor(Math.log10(absNum) / 3);
|
||||||
|
|
||||||
|
if (exponent > suffixes.length - 1) {
|
||||||
|
exponent = suffixes.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exponent < 0) {
|
||||||
|
exponent = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = absNum / 1000 ** exponent;
|
||||||
|
|
||||||
|
const roundedValue =
|
||||||
|
exponent > 0 ? Number(value.toFixed(decimals)) : Math.floor(value);
|
||||||
|
|
||||||
|
const finalValue = isNegative ? -roundedValue : roundedValue;
|
||||||
|
|
||||||
|
return `${finalValue}${suffixes[exponent]}`;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue