Remove duplicate utility code via shared formatting module
Consolidate MONTH_NAMES, PALETTE, formatCurrency, formatCurrencyShort, ordinal, and fmt into client/src/utils/formatting.js. Consolidate CONFIG_KEYS into server/src/constants.js. All consumer files updated to import from these shared modules. PaycheckView re-exports formatCurrency, ordinal, and formatPayDate for backward compatibility with existing tests. Nightshift-Task: dead-code Nightshift-Ref: https://github.com/marcus/nightshift
This commit is contained in:
@@ -3,32 +3,10 @@ import {
|
||||
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend,
|
||||
Cell, ResponsiveContainer, ReferenceLine,
|
||||
} from 'recharts';
|
||||
|
||||
const MONTH_NAMES = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December',
|
||||
];
|
||||
import { MONTH_NAMES, PALETTE, fmt, formatCurrencyShort } from '../utils/formatting.js';
|
||||
|
||||
const MONTH_SHORT = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
|
||||
const PALETTE = ['#3b82f6', '#8b5cf6', '#ec4899', '#f97316', '#22c55e', '#14b8a6', '#eab308', '#64748b'];
|
||||
|
||||
function fmt(value) {
|
||||
if (value == null) return '—';
|
||||
const num = Number(value);
|
||||
if (isNaN(num)) return '—';
|
||||
const abs = Math.abs(num).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
return num < 0 ? `-$${abs}` : `$${abs}`;
|
||||
}
|
||||
|
||||
function formatCurrencyShort(value) {
|
||||
if (value == null || isNaN(value)) return '';
|
||||
const abs = Math.abs(value);
|
||||
const sign = value < 0 ? '-' : '';
|
||||
if (abs >= 1000) return `${sign}$${(abs / 1000).toFixed(1)}k`;
|
||||
return `${sign}$${abs.toFixed(0)}`;
|
||||
}
|
||||
|
||||
function surplusClass(value) {
|
||||
if (value == null || isNaN(Number(value))) return '';
|
||||
return Number(value) >= 0 ? 'text-success' : 'text-danger';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { formatCurrency, ordinal } from '../utils/formatting.js';
|
||||
|
||||
const CATEGORIES = [
|
||||
'Housing', 'Utilities', 'Subscriptions', 'Insurance',
|
||||
@@ -14,19 +15,6 @@ const EMPTY_FORM = {
|
||||
variable_amount: false,
|
||||
};
|
||||
|
||||
function formatCurrency(value) {
|
||||
const num = parseFloat(value);
|
||||
if (isNaN(num)) return '$0.00';
|
||||
return num.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
|
||||
}
|
||||
|
||||
function ordinal(n) {
|
||||
const int = parseInt(n, 10);
|
||||
if (isNaN(int)) return n;
|
||||
const suffix = ['th', 'st', 'nd', 'rd'];
|
||||
const v = int % 100;
|
||||
return int + (suffix[(v - 20) % 10] || suffix[v] || suffix[0]);
|
||||
}
|
||||
|
||||
function Bills() {
|
||||
const [bills, setBills] = useState([]);
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
function fmt(value) {
|
||||
const num = parseFloat(value) || 0;
|
||||
return '$' + num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
import { fmt } from '../utils/formatting.js';
|
||||
|
||||
function ProgressBar({ paid, total }) {
|
||||
const pct = total > 0 ? Math.min(100, (paid / total) * 100) : 0;
|
||||
|
||||
@@ -3,25 +3,7 @@ import {
|
||||
PieChart, Pie, Cell, Tooltip, Legend, ResponsiveContainer,
|
||||
BarChart, Bar, XAxis, YAxis, CartesianGrid,
|
||||
} from 'recharts';
|
||||
|
||||
const MONTH_NAMES = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December',
|
||||
];
|
||||
|
||||
function formatCurrency(value) {
|
||||
const num = parseFloat(value) || 0;
|
||||
return '$' + num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
function formatCurrencyShort(value) {
|
||||
const num = parseFloat(value) || 0;
|
||||
if (Math.abs(num) >= 1000) return '$' + (num / 1000).toFixed(1) + 'k';
|
||||
return '$' + num.toFixed(0);
|
||||
}
|
||||
|
||||
// Accessible palette that works in light and dark
|
||||
const PALETTE = ['#3b82f6', '#8b5cf6', '#ec4899', '#f97316', '#22c55e', '#14b8a6', '#eab308', '#64748b'];
|
||||
import { MONTH_NAMES, PALETTE, formatCurrency, formatCurrencyShort } from '../utils/formatting.js';
|
||||
|
||||
function StatCard({ label, value, valueClass }) {
|
||||
return (
|
||||
|
||||
@@ -1,20 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
const MONTH_NAMES = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December',
|
||||
];
|
||||
|
||||
function ordinal(n) {
|
||||
const s = ['th', 'st', 'nd', 'rd'];
|
||||
const v = n % 100;
|
||||
return n + (s[(v - 20) % 10] || s[v] || s[0]);
|
||||
}
|
||||
|
||||
function formatCurrency(value) {
|
||||
const num = parseFloat(value) || 0;
|
||||
return '$' + num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
import { MONTH_NAMES, formatCurrency, ordinal } from '../utils/formatting.js';
|
||||
|
||||
function formatPayDate(dateStr) {
|
||||
const [year, month, day] = dateStr.split('-').map(Number);
|
||||
|
||||
34
client/src/utils/formatting.js
Normal file
34
client/src/utils/formatting.js
Normal file
@@ -0,0 +1,34 @@
|
||||
export const MONTH_NAMES = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December',
|
||||
];
|
||||
|
||||
// Accessible palette that works in light and dark
|
||||
export const PALETTE = ['#3b82f6', '#8b5cf6', '#ec4899', '#f97316', '#22c55e', '#14b8a6', '#eab308', '#64748b'];
|
||||
|
||||
export function formatCurrency(value) {
|
||||
const num = parseFloat(value) || 0;
|
||||
return '$' + num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
export function formatCurrencyShort(value) {
|
||||
if (value == null || isNaN(value)) return '';
|
||||
const abs = Math.abs(value);
|
||||
const sign = value < 0 ? '-' : '';
|
||||
if (abs >= 1000) return `${sign}$${(abs / 1000).toFixed(1)}k`;
|
||||
return `${sign}$${abs.toFixed(0)}`;
|
||||
}
|
||||
|
||||
export function ordinal(n) {
|
||||
const s = ['th', 'st', 'nd', 'rd'];
|
||||
const v = n % 100;
|
||||
return n + (s[(v - 20) % 10] || s[v] || s[0]);
|
||||
}
|
||||
|
||||
export function fmt(value) {
|
||||
if (value == null) return '—';
|
||||
const num = Number(value);
|
||||
if (isNaN(num)) return '—';
|
||||
const abs = Math.abs(num).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
return num < 0 ? `-$${abs}` : `$${abs}`;
|
||||
}
|
||||
Reference in New Issue
Block a user