UI tweaks for match columns step + auto hide empty columns
This commit is contained in:
@@ -1,17 +1,7 @@
|
||||
const express = require('express');
|
||||
const { DateTime } = require('luxon');
|
||||
|
||||
const router = express.Router();
|
||||
const { getDbConnection, getPoolStatus } = require('../db/connection');
|
||||
const {
|
||||
getTimeRangeConditions,
|
||||
formatBusinessDate,
|
||||
getBusinessDayBounds,
|
||||
_internal: timeHelpers
|
||||
} = require('../utils/timeUtils');
|
||||
|
||||
const TIMEZONE = 'America/New_York';
|
||||
const BUSINESS_DAY_START_HOUR = timeHelpers?.BUSINESS_DAY_START_HOUR ?? 1;
|
||||
const { getTimeRangeConditions, formatBusinessDate, getBusinessDayBounds } = require('../utils/timeUtils');
|
||||
|
||||
// Image URL generation utility
|
||||
const getImageUrls = (pid, iid = 1) => {
|
||||
@@ -429,25 +419,7 @@ router.get('/financials', async (req, res) => {
|
||||
release = releaseConn;
|
||||
|
||||
const { whereClause, params, dateRange } = getTimeRangeConditions(timeRange, startDate, endDate);
|
||||
const financialWhere = whereClause.replace(/date_placed/g, 'date_change');
|
||||
|
||||
const formatDebugBound = (value) => {
|
||||
if (!value) return 'n/a';
|
||||
const parsed = DateTime.fromSQL(value, { zone: 'UTC-05:00' });
|
||||
if (!parsed.isValid) {
|
||||
return `invalid(${value})`;
|
||||
}
|
||||
return parsed.setZone(TIMEZONE).toISO();
|
||||
};
|
||||
|
||||
console.log('[FINANCIALS] request params', {
|
||||
timeRange: timeRange || 'default',
|
||||
startDate,
|
||||
endDate,
|
||||
whereClause: financialWhere,
|
||||
params,
|
||||
boundsEastern: Array.isArray(params) ? params.map(formatDebugBound) : [],
|
||||
});
|
||||
const financialWhere = whereClause.replace(/date_placed/g, 'DATE_SUB(date_change, INTERVAL 1 HOUR)');
|
||||
|
||||
const [totalsRows] = await connection.execute(
|
||||
buildFinancialTotalsQuery(financialWhere),
|
||||
@@ -456,11 +428,6 @@ router.get('/financials', async (req, res) => {
|
||||
|
||||
const totals = normalizeFinancialTotals(totalsRows[0]);
|
||||
|
||||
console.log('[FINANCIALS] totals query result', {
|
||||
rows: totalsRows.length,
|
||||
totals,
|
||||
});
|
||||
|
||||
const [trendRows] = await connection.execute(
|
||||
buildFinancialTrendQuery(financialWhere),
|
||||
params
|
||||
@@ -468,26 +435,12 @@ router.get('/financials', async (req, res) => {
|
||||
|
||||
const trend = trendRows.map(normalizeFinancialTrendRow);
|
||||
|
||||
console.log('[FINANCIALS] trend query result', {
|
||||
rows: trendRows.length,
|
||||
first: trend[0] || null,
|
||||
last: trend[trend.length - 1] || null,
|
||||
});
|
||||
|
||||
let previousTotals = null;
|
||||
let comparison = null;
|
||||
|
||||
const previousRange = getPreviousPeriodRange(timeRange, startDate, endDate);
|
||||
if (previousRange) {
|
||||
console.log('[FINANCIALS] previous range params', {
|
||||
timeRange: timeRange || 'default',
|
||||
prevWhere: previousRange.whereClause.replace(/date_placed/g, 'date_change'),
|
||||
params: previousRange.params,
|
||||
boundsEastern: Array.isArray(previousRange.params)
|
||||
? previousRange.params.map(formatDebugBound)
|
||||
: [],
|
||||
});
|
||||
const prevWhere = previousRange.whereClause.replace(/date_placed/g, 'date_change');
|
||||
const prevWhere = previousRange.whereClause.replace(/date_placed/g, 'DATE_SUB(date_change, INTERVAL 1 HOUR)');
|
||||
const [previousRows] = await connection.execute(
|
||||
buildFinancialTotalsQuery(prevWhere),
|
||||
previousRange.params
|
||||
@@ -505,37 +458,12 @@ router.get('/financials', async (req, res) => {
|
||||
};
|
||||
}
|
||||
|
||||
const trendDebugSample = trend.slice(-3).map((item) => ({
|
||||
date: item.date,
|
||||
timestamp: item.timestamp,
|
||||
income: item.income,
|
||||
grossSales: item.grossSales,
|
||||
}));
|
||||
|
||||
const debugInfo = {
|
||||
serverTimeUtc: new Date().toISOString(),
|
||||
timeRange: timeRange || 'default',
|
||||
params,
|
||||
boundsEastern: Array.isArray(params) ? params.map(formatDebugBound) : [],
|
||||
trendCount: trend.length,
|
||||
trendSample: trendDebugSample,
|
||||
previousRange: previousRange
|
||||
? {
|
||||
params: previousRange.params,
|
||||
boundsEastern: Array.isArray(previousRange.params)
|
||||
? previousRange.params.map(formatDebugBound)
|
||||
: [],
|
||||
}
|
||||
: null,
|
||||
};
|
||||
|
||||
res.json({
|
||||
dateRange,
|
||||
totals,
|
||||
previousTotals,
|
||||
comparison,
|
||||
trend,
|
||||
debug: debugInfo,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in /financials:', error);
|
||||
@@ -734,35 +662,44 @@ function processShippingData(shippingResult, totalShipped) {
|
||||
}
|
||||
|
||||
function calculatePeriodProgress(timeRange) {
|
||||
if (!['today', 'thisWeek', 'thisMonth'].includes(timeRange)) {
|
||||
return 100;
|
||||
const now = new Date();
|
||||
const easternTime = new Date(now.getTime() - (5 * 60 * 60 * 1000)); // UTC-5
|
||||
|
||||
switch (timeRange) {
|
||||
case 'today': {
|
||||
const { start } = getBusinessDayBounds('today');
|
||||
const businessStart = new Date(start);
|
||||
const businessEnd = new Date(businessStart);
|
||||
businessEnd.setDate(businessEnd.getDate() + 1);
|
||||
businessEnd.setHours(0, 59, 59, 999); // 12:59 AM next day
|
||||
|
||||
const elapsed = easternTime.getTime() - businessStart.getTime();
|
||||
const total = businessEnd.getTime() - businessStart.getTime();
|
||||
return Math.min(100, Math.max(0, (elapsed / total) * 100));
|
||||
}
|
||||
case 'thisWeek': {
|
||||
const startOfWeek = new Date(easternTime);
|
||||
startOfWeek.setDate(easternTime.getDate() - easternTime.getDay()); // Sunday
|
||||
startOfWeek.setHours(1, 0, 0, 0); // 1 AM business day start
|
||||
|
||||
const endOfWeek = new Date(startOfWeek);
|
||||
endOfWeek.setDate(endOfWeek.getDate() + 7);
|
||||
|
||||
const elapsed = easternTime.getTime() - startOfWeek.getTime();
|
||||
const total = endOfWeek.getTime() - startOfWeek.getTime();
|
||||
return Math.min(100, Math.max(0, (elapsed / total) * 100));
|
||||
}
|
||||
case 'thisMonth': {
|
||||
const startOfMonth = new Date(easternTime.getFullYear(), easternTime.getMonth(), 1, 1, 0, 0, 0);
|
||||
const endOfMonth = new Date(easternTime.getFullYear(), easternTime.getMonth() + 1, 1, 0, 59, 59, 999);
|
||||
|
||||
const elapsed = easternTime.getTime() - startOfMonth.getTime();
|
||||
const total = endOfMonth.getTime() - startOfMonth.getTime();
|
||||
return Math.min(100, Math.max(0, (elapsed / total) * 100));
|
||||
}
|
||||
default:
|
||||
return 100;
|
||||
}
|
||||
|
||||
const now = DateTime.now().setZone(TIMEZONE);
|
||||
|
||||
let range;
|
||||
try {
|
||||
range = timeHelpers.getRangeForTimeRange(timeRange, now);
|
||||
} catch (error) {
|
||||
console.error(`[STATS] Failed to derive range for ${timeRange}:`, error);
|
||||
return 100;
|
||||
}
|
||||
|
||||
if (!range?.start || !range?.end) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
const total = range.end.toMillis() - range.start.toMillis();
|
||||
if (total <= 0) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
const elapsed = Math.min(
|
||||
Math.max(now.toMillis() - range.start.toMillis(), 0),
|
||||
total
|
||||
);
|
||||
|
||||
return Math.min(100, Math.max(0, (elapsed / total) * 100));
|
||||
}
|
||||
|
||||
function buildFinancialTotalsQuery(whereClause) {
|
||||
@@ -781,13 +718,9 @@ function buildFinancialTotalsQuery(whereClause) {
|
||||
}
|
||||
|
||||
function buildFinancialTrendQuery(whereClause) {
|
||||
const businessDayOffset = BUSINESS_DAY_START_HOUR;
|
||||
return `
|
||||
SELECT
|
||||
DATE_FORMAT(
|
||||
DATE_SUB(date_change, INTERVAL ${businessDayOffset} HOUR),
|
||||
'%Y-%m-%d'
|
||||
) as businessDate,
|
||||
DATE(DATE_SUB(date_change, INTERVAL 1 HOUR)) as date,
|
||||
SUM(sale_amount) as grossSales,
|
||||
SUM(refund_amount) as refunds,
|
||||
SUM(shipping_collected_amount + small_order_fee_amount + rush_fee_amount) as shippingFees,
|
||||
@@ -797,8 +730,8 @@ function buildFinancialTrendQuery(whereClause) {
|
||||
FROM report_sales_data
|
||||
WHERE ${whereClause}
|
||||
AND action IN (1, 2, 3)
|
||||
GROUP BY businessDate
|
||||
ORDER BY businessDate ASC
|
||||
GROUP BY DATE(DATE_SUB(date_change, INTERVAL 1 HOUR))
|
||||
ORDER BY date ASC
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -839,44 +772,16 @@ function normalizeFinancialTrendRow(row = {}) {
|
||||
const profit = income - cogs;
|
||||
const margin = income !== 0 ? (profit / income) * 100 : 0;
|
||||
let timestamp = null;
|
||||
let dateValue = row.businessDate || row.date || null;
|
||||
let dateValue = null;
|
||||
|
||||
const resolveBusinessDayStart = (value) => {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let dt;
|
||||
if (value instanceof Date) {
|
||||
dt = DateTime.fromJSDate(value, { zone: TIMEZONE });
|
||||
} else if (typeof value === 'string') {
|
||||
dt = DateTime.fromISO(value, { zone: TIMEZONE });
|
||||
if (!dt.isValid) {
|
||||
dt = DateTime.fromSQL(value, { zone: TIMEZONE });
|
||||
}
|
||||
}
|
||||
|
||||
if (!dt || !dt.isValid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hour = BUSINESS_DAY_START_HOUR;
|
||||
return dt.set({
|
||||
hour,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
millisecond: 0,
|
||||
});
|
||||
};
|
||||
|
||||
const businessDayStart = resolveBusinessDayStart(dateValue);
|
||||
if (businessDayStart) {
|
||||
timestamp = businessDayStart.toUTC().toISO();
|
||||
dateValue = businessDayStart.toISO();
|
||||
} else if (row.date instanceof Date) {
|
||||
timestamp = new Date(row.date.getTime()).toISOString();
|
||||
if (row.date instanceof Date) {
|
||||
dateValue = row.date.toISOString().slice(0, 10);
|
||||
} else if (typeof row.date === 'string') {
|
||||
timestamp = new Date(`${row.date}T00:00:00Z`).toISOString();
|
||||
dateValue = row.date;
|
||||
}
|
||||
|
||||
if (typeof dateValue === 'string') {
|
||||
timestamp = new Date(`${dateValue}T06:00:00.000Z`).toISOString();
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user