From c69984898b465af76f8fff1bad9d06ef613cacaf Mon Sep 17 00:00:00 2001 From: Christian Hood Date: Fri, 20 Mar 2026 02:22:04 -0400 Subject: [PATCH] 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 --- client/src/pages/AnnualOverview.jsx | 24 +------------------- client/src/pages/Bills.jsx | 14 +----------- client/src/pages/Financing.jsx | 6 +---- client/src/pages/MonthlySummary.jsx | 20 +---------------- client/src/pages/PaycheckView.jsx | 17 +-------------- client/src/utils/formatting.js | 34 +++++++++++++++++++++++++++++ server/src/constants.js | 12 ++++++++++ server/src/routes/config.js | 10 +-------- server/src/routes/paychecks.js | 10 +-------- 9 files changed, 53 insertions(+), 94 deletions(-) create mode 100644 client/src/utils/formatting.js create mode 100644 server/src/constants.js diff --git a/client/src/pages/AnnualOverview.jsx b/client/src/pages/AnnualOverview.jsx index ad799d4..6da4693 100644 --- a/client/src/pages/AnnualOverview.jsx +++ b/client/src/pages/AnnualOverview.jsx @@ -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'; diff --git a/client/src/pages/Bills.jsx b/client/src/pages/Bills.jsx index ef32d2d..ac9f795 100644 --- a/client/src/pages/Bills.jsx +++ b/client/src/pages/Bills.jsx @@ -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([]); diff --git a/client/src/pages/Financing.jsx b/client/src/pages/Financing.jsx index f103fe1..c3eb97e 100644 --- a/client/src/pages/Financing.jsx +++ b/client/src/pages/Financing.jsx @@ -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; diff --git a/client/src/pages/MonthlySummary.jsx b/client/src/pages/MonthlySummary.jsx index a2435bc..21daff5 100644 --- a/client/src/pages/MonthlySummary.jsx +++ b/client/src/pages/MonthlySummary.jsx @@ -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 ( diff --git a/client/src/pages/PaycheckView.jsx b/client/src/pages/PaycheckView.jsx index ef0e872..c47683e 100644 --- a/client/src/pages/PaycheckView.jsx +++ b/client/src/pages/PaycheckView.jsx @@ -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); diff --git a/client/src/utils/formatting.js b/client/src/utils/formatting.js new file mode 100644 index 0000000..d8f2278 --- /dev/null +++ b/client/src/utils/formatting.js @@ -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}`; +} diff --git a/server/src/constants.js b/server/src/constants.js new file mode 100644 index 0000000..4434faa --- /dev/null +++ b/server/src/constants.js @@ -0,0 +1,12 @@ +'use strict'; + +const CONFIG_KEYS = [ + 'paycheck1_day', + 'paycheck2_day', + 'paycheck1_gross', + 'paycheck1_net', + 'paycheck2_gross', + 'paycheck2_net', +]; + +module.exports = { CONFIG_KEYS }; diff --git a/server/src/routes/config.js b/server/src/routes/config.js index c1428d2..e34348a 100644 --- a/server/src/routes/config.js +++ b/server/src/routes/config.js @@ -1,15 +1,7 @@ const express = require('express'); const router = express.Router(); const { pool } = require('../db'); - -const CONFIG_KEYS = [ - 'paycheck1_day', - 'paycheck2_day', - 'paycheck1_gross', - 'paycheck1_net', - 'paycheck2_gross', - 'paycheck2_net', -]; +const { CONFIG_KEYS } = require('../constants'); const DEFAULTS = { paycheck1_day: 1, diff --git a/server/src/routes/paychecks.js b/server/src/routes/paychecks.js index b34ed0b..be933bb 100644 --- a/server/src/routes/paychecks.js +++ b/server/src/routes/paychecks.js @@ -2,15 +2,7 @@ const express = require('express'); const router = express.Router(); const { pool } = require('../db'); const { calcPaymentAmount } = require('./financing'); - -const CONFIG_KEYS = [ - 'paycheck1_day', - 'paycheck2_day', - 'paycheck1_gross', - 'paycheck1_net', - 'paycheck2_gross', - 'paycheck2_net', -]; +const { CONFIG_KEYS } = require('../constants'); const CONFIG_DEFAULTS = { paycheck1_day: 1,