Fix time periods on financial overview, remove some logging
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"luxon": "^3.5.0",
|
||||
"morgan": "^1.10.0",
|
||||
"mysql2": "^3.6.5",
|
||||
"ssh2": "^1.14.0"
|
||||
@@ -826,6 +827,15 @@
|
||||
"url": "https://github.com/sponsors/wellwelwel"
|
||||
}
|
||||
},
|
||||
"node_modules/luxon": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
|
||||
"integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"morgan": "^1.10.0",
|
||||
"ssh2": "^1.14.0",
|
||||
"mysql2": "^3.6.5",
|
||||
"compression": "^1.7.4"
|
||||
"compression": "^1.7.4",
|
||||
"luxon": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1"
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
const express = require('express');
|
||||
const { DateTime } = require('luxon');
|
||||
|
||||
const router = express.Router();
|
||||
const { getDbConnection, getPoolStatus } = require('../db/connection');
|
||||
const { getTimeRangeConditions, formatBusinessDate, getBusinessDayBounds } = require('../utils/timeUtils');
|
||||
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;
|
||||
|
||||
// Image URL generation utility
|
||||
const getImageUrls = (pid, iid = 1) => {
|
||||
@@ -421,6 +431,24 @@ router.get('/financials', async (req, res) => {
|
||||
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 [totalsRows] = await connection.execute(
|
||||
buildFinancialTotalsQuery(financialWhere),
|
||||
params
|
||||
@@ -428,6 +456,11 @@ 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
|
||||
@@ -435,11 +468,25 @@ 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 [previousRows] = await connection.execute(
|
||||
buildFinancialTotalsQuery(prevWhere),
|
||||
@@ -458,12 +505,37 @@ 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);
|
||||
@@ -662,44 +734,35 @@ function processShippingData(shippingResult, totalShipped) {
|
||||
}
|
||||
|
||||
function calculatePeriodProgress(timeRange) {
|
||||
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:
|
||||
if (!['today', 'thisWeek', 'thisMonth'].includes(timeRange)) {
|
||||
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) {
|
||||
@@ -718,9 +781,13 @@ function buildFinancialTotalsQuery(whereClause) {
|
||||
}
|
||||
|
||||
function buildFinancialTrendQuery(whereClause) {
|
||||
const businessDayOffset = BUSINESS_DAY_START_HOUR;
|
||||
return `
|
||||
SELECT
|
||||
DATE(date_change) as date,
|
||||
DATE_FORMAT(
|
||||
DATE_SUB(date_change, INTERVAL ${businessDayOffset} HOUR),
|
||||
'%Y-%m-%d'
|
||||
) as businessDate,
|
||||
SUM(sale_amount) as grossSales,
|
||||
SUM(refund_amount) as refunds,
|
||||
SUM(shipping_collected_amount + small_order_fee_amount + rush_fee_amount) as shippingFees,
|
||||
@@ -730,8 +797,8 @@ function buildFinancialTrendQuery(whereClause) {
|
||||
FROM report_sales_data
|
||||
WHERE ${whereClause}
|
||||
AND action IN (1, 2, 3)
|
||||
GROUP BY DATE(date_change)
|
||||
ORDER BY date ASC
|
||||
GROUP BY businessDate
|
||||
ORDER BY businessDate ASC
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -772,15 +839,48 @@ 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;
|
||||
|
||||
if (row.date instanceof Date) {
|
||||
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();
|
||||
} else if (typeof row.date === 'string') {
|
||||
timestamp = new Date(`${row.date}T00:00:00Z`).toISOString();
|
||||
}
|
||||
|
||||
return {
|
||||
date: row.date,
|
||||
date: dateValue,
|
||||
grossSales,
|
||||
refunds,
|
||||
shippingFees,
|
||||
|
||||
@@ -1,217 +1,219 @@
|
||||
// Time utilities for handling business day logic and time ranges
|
||||
// Business day is 1am-12:59am Eastern time (UTC-5)
|
||||
const { DateTime } = require('luxon');
|
||||
|
||||
const getBusinessDayBounds = (timeRange) => {
|
||||
const now = new Date();
|
||||
const easternTime = new Date(now.getTime() - (5 * 60 * 60 * 1000)); // UTC-5
|
||||
const TIMEZONE = 'America/New_York';
|
||||
const DB_TIMEZONE = 'UTC-05:00';
|
||||
const BUSINESS_DAY_START_HOUR = 1; // 1 AM Eastern
|
||||
const WEEK_START_DAY = 7; // Sunday (Luxon uses 1 = Monday, 7 = Sunday)
|
||||
const DB_DATETIME_FORMAT = 'yyyy-LL-dd HH:mm:ss';
|
||||
|
||||
const isDateTime = (value) => DateTime.isDateTime(value);
|
||||
|
||||
const ensureDateTime = (value, { zone = TIMEZONE } = {}) => {
|
||||
if (!value) return null;
|
||||
|
||||
if (isDateTime(value)) {
|
||||
return value.setZone(zone);
|
||||
}
|
||||
|
||||
if (value instanceof Date) {
|
||||
return DateTime.fromJSDate(value, { zone });
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return DateTime.fromMillis(value, { zone });
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
let dt = DateTime.fromISO(value, { zone, setZone: true });
|
||||
if (!dt.isValid) {
|
||||
dt = DateTime.fromSQL(value, { zone });
|
||||
}
|
||||
return dt.isValid ? dt : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const getNow = () => DateTime.now().setZone(TIMEZONE);
|
||||
|
||||
const getDayStart = (input = getNow()) => {
|
||||
const dt = ensureDateTime(input);
|
||||
if (!dt || !dt.isValid) {
|
||||
const fallback = getNow();
|
||||
return fallback.set({
|
||||
hour: BUSINESS_DAY_START_HOUR,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
millisecond: 0
|
||||
});
|
||||
}
|
||||
|
||||
const sameDayStart = dt.set({
|
||||
hour: BUSINESS_DAY_START_HOUR,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
millisecond: 0
|
||||
});
|
||||
|
||||
return dt.hour < BUSINESS_DAY_START_HOUR
|
||||
? sameDayStart.minus({ days: 1 })
|
||||
: sameDayStart;
|
||||
};
|
||||
|
||||
const getDayEnd = (input = getNow()) => {
|
||||
return getDayStart(input).plus({ days: 1 }).minus({ milliseconds: 1 });
|
||||
};
|
||||
|
||||
const getWeekStart = (input = getNow()) => {
|
||||
const dt = ensureDateTime(input);
|
||||
if (!dt || !dt.isValid) {
|
||||
return getDayStart();
|
||||
}
|
||||
|
||||
const startOfWeek = dt.set({ weekday: WEEK_START_DAY }).startOf('day');
|
||||
const normalized = startOfWeek > dt ? startOfWeek.minus({ weeks: 1 }) : startOfWeek;
|
||||
return normalized.set({
|
||||
hour: BUSINESS_DAY_START_HOUR,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
millisecond: 0
|
||||
});
|
||||
};
|
||||
|
||||
const getRangeForTimeRange = (timeRange = 'today', now = getNow()) => {
|
||||
const current = ensureDateTime(now);
|
||||
if (!current || !current.isValid) {
|
||||
throw new Error('Invalid reference time for range calculation');
|
||||
}
|
||||
|
||||
switch (timeRange) {
|
||||
case 'today': {
|
||||
const start = new Date(easternTime);
|
||||
start.setHours(1, 0, 0, 0); // 1 AM start of business day
|
||||
|
||||
const end = new Date(start);
|
||||
end.setDate(end.getDate() + 1);
|
||||
end.setHours(0, 59, 59, 999); // 12:59 AM next day
|
||||
|
||||
return { start, end };
|
||||
return {
|
||||
start: getDayStart(current),
|
||||
end: getDayEnd(current)
|
||||
};
|
||||
}
|
||||
|
||||
case 'yesterday': {
|
||||
const start = new Date(easternTime);
|
||||
start.setDate(start.getDate() - 1);
|
||||
start.setHours(1, 0, 0, 0);
|
||||
|
||||
const end = new Date(start);
|
||||
end.setDate(end.getDate() + 1);
|
||||
end.setHours(0, 59, 59, 999);
|
||||
|
||||
return { start, end };
|
||||
const target = current.minus({ days: 1 });
|
||||
return {
|
||||
start: getDayStart(target),
|
||||
end: getDayEnd(target)
|
||||
};
|
||||
}
|
||||
|
||||
case 'thisWeek': {
|
||||
const start = new Date(easternTime);
|
||||
start.setDate(easternTime.getDate() - easternTime.getDay()); // Sunday
|
||||
start.setHours(1, 0, 0, 0);
|
||||
|
||||
const end = new Date(easternTime);
|
||||
end.setDate(end.getDate() + 1);
|
||||
end.setHours(0, 59, 59, 999);
|
||||
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
case 'lastWeek': {
|
||||
const start = new Date(easternTime);
|
||||
start.setDate(easternTime.getDate() - easternTime.getDay() - 7); // Previous Sunday
|
||||
start.setHours(1, 0, 0, 0);
|
||||
|
||||
const end = new Date(start);
|
||||
end.setDate(end.getDate() + 7);
|
||||
end.setHours(0, 59, 59, 999);
|
||||
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
case 'thisMonth': {
|
||||
const start = new Date(easternTime.getFullYear(), easternTime.getMonth(), 1, 1, 0, 0, 0);
|
||||
const end = new Date(easternTime);
|
||||
end.setDate(end.getDate() + 1);
|
||||
end.setHours(0, 59, 59, 999);
|
||||
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
case 'lastMonth': {
|
||||
const start = new Date(easternTime.getFullYear(), easternTime.getMonth() - 1, 1, 1, 0, 0, 0);
|
||||
const end = new Date(easternTime.getFullYear(), easternTime.getMonth(), 1, 0, 59, 59, 999);
|
||||
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
case 'last7days': {
|
||||
const end = new Date(easternTime);
|
||||
end.setHours(0, 59, 59, 999);
|
||||
|
||||
const start = new Date(end);
|
||||
start.setDate(start.getDate() - 7);
|
||||
start.setHours(1, 0, 0, 0);
|
||||
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
case 'last30days': {
|
||||
const end = new Date(easternTime);
|
||||
end.setHours(0, 59, 59, 999);
|
||||
|
||||
const start = new Date(end);
|
||||
start.setDate(start.getDate() - 30);
|
||||
start.setHours(1, 0, 0, 0);
|
||||
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
case 'last90days': {
|
||||
const end = new Date(easternTime);
|
||||
end.setHours(0, 59, 59, 999);
|
||||
|
||||
const start = new Date(end);
|
||||
start.setDate(start.getDate() - 90);
|
||||
start.setHours(1, 0, 0, 0);
|
||||
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
case 'previous7days': {
|
||||
const end = new Date(easternTime);
|
||||
end.setDate(end.getDate() - 1);
|
||||
end.setHours(0, 59, 59, 999);
|
||||
|
||||
const start = new Date(end);
|
||||
start.setDate(start.getDate() - 6);
|
||||
start.setHours(1, 0, 0, 0);
|
||||
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
case 'previous30days': {
|
||||
const end = new Date(easternTime);
|
||||
end.setDate(end.getDate() - 1);
|
||||
end.setHours(0, 59, 59, 999);
|
||||
|
||||
const start = new Date(end);
|
||||
start.setDate(start.getDate() - 29);
|
||||
start.setHours(1, 0, 0, 0);
|
||||
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
case 'previous90days': {
|
||||
const end = new Date(easternTime);
|
||||
end.setDate(end.getDate() - 1);
|
||||
end.setHours(0, 59, 59, 999);
|
||||
|
||||
const start = new Date(end);
|
||||
start.setDate(start.getDate() - 89);
|
||||
start.setHours(1, 0, 0, 0);
|
||||
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
case 'twoDaysAgo': {
|
||||
const start = new Date(easternTime);
|
||||
start.setDate(start.getDate() - 2);
|
||||
start.setHours(1, 0, 0, 0);
|
||||
|
||||
const end = new Date(start);
|
||||
end.setDate(end.getDate() + 1);
|
||||
end.setHours(0, 59, 59, 999);
|
||||
|
||||
return { start, end };
|
||||
const target = current.minus({ days: 2 });
|
||||
return {
|
||||
start: getDayStart(target),
|
||||
end: getDayEnd(target)
|
||||
};
|
||||
}
|
||||
case 'thisWeek': {
|
||||
return {
|
||||
start: getWeekStart(current),
|
||||
end: getDayEnd(current)
|
||||
};
|
||||
}
|
||||
case 'lastWeek': {
|
||||
const lastWeek = current.minus({ weeks: 1 });
|
||||
const weekStart = getWeekStart(lastWeek);
|
||||
const weekEnd = weekStart.plus({ days: 6 });
|
||||
return {
|
||||
start: weekStart,
|
||||
end: getDayEnd(weekEnd)
|
||||
};
|
||||
}
|
||||
case 'thisMonth': {
|
||||
const dayStart = getDayStart(current);
|
||||
const monthStart = dayStart.startOf('month').set({ hour: BUSINESS_DAY_START_HOUR });
|
||||
return {
|
||||
start: monthStart,
|
||||
end: getDayEnd(current)
|
||||
};
|
||||
}
|
||||
case 'lastMonth': {
|
||||
const lastMonth = current.minus({ months: 1 });
|
||||
const monthStart = lastMonth
|
||||
.startOf('month')
|
||||
.set({ hour: BUSINESS_DAY_START_HOUR, minute: 0, second: 0, millisecond: 0 });
|
||||
const monthEnd = monthStart.plus({ months: 1 }).minus({ days: 1 });
|
||||
return {
|
||||
start: monthStart,
|
||||
end: getDayEnd(monthEnd)
|
||||
};
|
||||
}
|
||||
case 'last7days': {
|
||||
const dayStart = getDayStart(current);
|
||||
return {
|
||||
start: dayStart.minus({ days: 6 }),
|
||||
end: getDayEnd(current)
|
||||
};
|
||||
}
|
||||
case 'last30days': {
|
||||
const dayStart = getDayStart(current);
|
||||
return {
|
||||
start: dayStart.minus({ days: 29 }),
|
||||
end: getDayEnd(current)
|
||||
};
|
||||
}
|
||||
case 'last90days': {
|
||||
const dayStart = getDayStart(current);
|
||||
return {
|
||||
start: dayStart.minus({ days: 89 }),
|
||||
end: getDayEnd(current)
|
||||
};
|
||||
}
|
||||
case 'previous7days': {
|
||||
const currentPeriodStart = getDayStart(current).minus({ days: 6 });
|
||||
const previousEndDay = currentPeriodStart.minus({ days: 1 });
|
||||
const previousStartDay = previousEndDay.minus({ days: 6 });
|
||||
return {
|
||||
start: getDayStart(previousStartDay),
|
||||
end: getDayEnd(previousEndDay)
|
||||
};
|
||||
}
|
||||
case 'previous30days': {
|
||||
const currentPeriodStart = getDayStart(current).minus({ days: 29 });
|
||||
const previousEndDay = currentPeriodStart.minus({ days: 1 });
|
||||
const previousStartDay = previousEndDay.minus({ days: 29 });
|
||||
return {
|
||||
start: getDayStart(previousStartDay),
|
||||
end: getDayEnd(previousEndDay)
|
||||
};
|
||||
}
|
||||
case 'previous90days': {
|
||||
const currentPeriodStart = getDayStart(current).minus({ days: 89 });
|
||||
const previousEndDay = currentPeriodStart.minus({ days: 1 });
|
||||
const previousStartDay = previousEndDay.minus({ days: 89 });
|
||||
return {
|
||||
start: getDayStart(previousStartDay),
|
||||
end: getDayEnd(previousEndDay)
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown time range: ${timeRange}`);
|
||||
}
|
||||
};
|
||||
|
||||
const getTimeRangeConditions = (timeRange, startDate, endDate) => {
|
||||
if (timeRange === 'custom' && startDate && endDate) {
|
||||
// Custom date range
|
||||
const start = new Date(startDate);
|
||||
const end = new Date(endDate);
|
||||
|
||||
// Convert to UTC-5 (Eastern time)
|
||||
const startUTC5 = new Date(start.getTime() - (5 * 60 * 60 * 1000));
|
||||
const endUTC5 = new Date(end.getTime() - (5 * 60 * 60 * 1000));
|
||||
|
||||
return {
|
||||
whereClause: 'date_placed >= ? AND date_placed <= ?',
|
||||
params: [
|
||||
startUTC5.toISOString().slice(0, 19).replace('T', ' '),
|
||||
endUTC5.toISOString().slice(0, 19).replace('T', ' ')
|
||||
],
|
||||
dateRange: {
|
||||
start: startDate,
|
||||
end: endDate,
|
||||
label: `${formatBusinessDate(start)} - ${formatBusinessDate(end)}`
|
||||
const toDatabaseSqlString = (dt) => {
|
||||
const normalized = ensureDateTime(dt);
|
||||
if (!normalized || !normalized.isValid) {
|
||||
throw new Error('Invalid datetime provided for SQL conversion');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (!timeRange) {
|
||||
timeRange = 'today';
|
||||
}
|
||||
|
||||
const { start, end } = getBusinessDayBounds(timeRange);
|
||||
|
||||
// Convert to MySQL datetime format (UTC-5)
|
||||
const startStr = start.toISOString().slice(0, 19).replace('T', ' ');
|
||||
const endStr = end.toISOString().slice(0, 19).replace('T', ' ');
|
||||
|
||||
return {
|
||||
whereClause: 'date_placed >= ? AND date_placed <= ?',
|
||||
params: [startStr, endStr],
|
||||
dateRange: {
|
||||
start: start.toISOString(),
|
||||
end: end.toISOString(),
|
||||
label: getTimeRangeLabel(timeRange)
|
||||
}
|
||||
};
|
||||
const dbTime = normalized.setZone(DB_TIMEZONE, { keepLocalTime: true });
|
||||
return dbTime.toFormat(DB_DATETIME_FORMAT);
|
||||
};
|
||||
|
||||
const formatBusinessDate = (date) => {
|
||||
return date.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric'
|
||||
});
|
||||
const formatBusinessDate = (input) => {
|
||||
const dt = ensureDateTime(input);
|
||||
if (!dt || !dt.isValid) return '';
|
||||
return dt.setZone(TIMEZONE).toFormat('LLL d, yyyy');
|
||||
};
|
||||
|
||||
const getTimeRangeLabel = (timeRange) => {
|
||||
const labels = {
|
||||
today: 'Today',
|
||||
yesterday: 'Yesterday',
|
||||
twoDaysAgo: 'Two Days Ago',
|
||||
thisWeek: 'This Week',
|
||||
lastWeek: 'Last Week',
|
||||
thisMonth: 'This Month',
|
||||
@@ -221,32 +223,75 @@ const getTimeRangeLabel = (timeRange) => {
|
||||
last90days: 'Last 90 Days',
|
||||
previous7days: 'Previous 7 Days',
|
||||
previous30days: 'Previous 30 Days',
|
||||
previous90days: 'Previous 90 Days',
|
||||
twoDaysAgo: 'Two Days Ago'
|
||||
previous90days: 'Previous 90 Days'
|
||||
};
|
||||
|
||||
return labels[timeRange] || timeRange;
|
||||
};
|
||||
|
||||
// Helper to convert MySQL datetime to JavaScript Date
|
||||
const getTimeRangeConditions = (timeRange, startDate, endDate) => {
|
||||
if (timeRange === 'custom' && startDate && endDate) {
|
||||
const start = ensureDateTime(startDate);
|
||||
const end = ensureDateTime(endDate);
|
||||
|
||||
if (!start || !start.isValid || !end || !end.isValid) {
|
||||
throw new Error('Invalid custom date range provided');
|
||||
}
|
||||
|
||||
return {
|
||||
whereClause: 'date_placed >= ? AND date_placed <= ?',
|
||||
params: [toDatabaseSqlString(start), toDatabaseSqlString(end)],
|
||||
dateRange: {
|
||||
start: start.toUTC().toISO(),
|
||||
end: end.toUTC().toISO(),
|
||||
label: `${formatBusinessDate(start)} - ${formatBusinessDate(end)}`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const normalizedRange = timeRange || 'today';
|
||||
const range = getRangeForTimeRange(normalizedRange);
|
||||
|
||||
return {
|
||||
whereClause: 'date_placed >= ? AND date_placed <= ?',
|
||||
params: [toDatabaseSqlString(range.start), toDatabaseSqlString(range.end)],
|
||||
dateRange: {
|
||||
start: range.start.toUTC().toISO(),
|
||||
end: range.end.toUTC().toISO(),
|
||||
label: getTimeRangeLabel(normalizedRange)
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const getBusinessDayBounds = (timeRange) => {
|
||||
const range = getRangeForTimeRange(timeRange);
|
||||
return {
|
||||
start: range.start.toJSDate(),
|
||||
end: range.end.toJSDate()
|
||||
};
|
||||
};
|
||||
|
||||
const parseBusinessDate = (mysqlDatetime) => {
|
||||
if (!mysqlDatetime || mysqlDatetime === '0000-00-00 00:00:00') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// MySQL datetime is stored in UTC-5, so we need to add 5 hours to get UTC
|
||||
const date = new Date(mysqlDatetime + ' UTC');
|
||||
date.setHours(date.getHours() + 5);
|
||||
return date;
|
||||
const dt = DateTime.fromSQL(mysqlDatetime, { zone: DB_TIMEZONE });
|
||||
if (!dt.isValid) {
|
||||
console.error('[timeUtils] Failed to parse MySQL datetime:', mysqlDatetime, dt.invalidExplanation);
|
||||
return null;
|
||||
}
|
||||
|
||||
return dt.toUTC().toJSDate();
|
||||
};
|
||||
|
||||
// Helper to format date for MySQL queries
|
||||
const formatMySQLDate = (date) => {
|
||||
if (!date) return null;
|
||||
const formatMySQLDate = (input) => {
|
||||
if (!input) return null;
|
||||
|
||||
// Convert to UTC-5 for storage
|
||||
const utc5Date = new Date(date.getTime() - (5 * 60 * 60 * 1000));
|
||||
return utc5Date.toISOString().slice(0, 19).replace('T', ' ');
|
||||
const dt = ensureDateTime(input, { zone: 'utc' });
|
||||
if (!dt || !dt.isValid) return null;
|
||||
|
||||
return dt.setZone(DB_TIMEZONE).toFormat(DB_DATETIME_FORMAT);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
@@ -255,5 +300,13 @@ module.exports = {
|
||||
formatBusinessDate,
|
||||
getTimeRangeLabel,
|
||||
parseBusinessDate,
|
||||
formatMySQLDate
|
||||
formatMySQLDate,
|
||||
// Expose helpers for tests or advanced consumers
|
||||
_internal: {
|
||||
getDayStart,
|
||||
getDayEnd,
|
||||
getWeekStart,
|
||||
getRangeForTimeRange,
|
||||
BUSINESS_DAY_START_HOUR
|
||||
}
|
||||
};
|
||||
@@ -186,7 +186,6 @@ export const AnalyticsDashboard = () => {
|
||||
const result = await response.json();
|
||||
|
||||
if (!result?.data?.rows) {
|
||||
console.log("No result data received");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -761,7 +761,6 @@ const FinancialOverview = () => {
|
||||
}
|
||||
|
||||
const grossSalesValue = toNumber((point as { grossSales?: number }).grossSales);
|
||||
const refundsValue = toNumber((point as { refunds?: number }).refunds);
|
||||
const shippingFeesValue = toNumber((point as { shippingFees?: number }).shippingFees);
|
||||
const taxCollectedValue = toNumber((point as { taxCollected?: number }).taxCollected);
|
||||
const discountsValue = toNumber((point as { discounts?: number }).discounts);
|
||||
|
||||
@@ -225,13 +225,6 @@ const GorgiasOverview = () => {
|
||||
.then(res => res.data?.data?.data?.data || []),
|
||||
]);
|
||||
|
||||
console.log('Raw API responses:', {
|
||||
overview,
|
||||
channelStats,
|
||||
agentStats,
|
||||
satisfaction,
|
||||
});
|
||||
|
||||
setData({
|
||||
overview,
|
||||
channels: channelStats,
|
||||
@@ -270,8 +263,6 @@ const GorgiasOverview = () => {
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
console.log('Processed stats:', stats);
|
||||
|
||||
// Process satisfaction data
|
||||
const satisfactionStats = (data.satisfaction || []).reduce((acc, item) => {
|
||||
if (item.name !== 'response_distribution') {
|
||||
@@ -285,8 +276,6 @@ const GorgiasOverview = () => {
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
console.log('Processed satisfaction stats:', satisfactionStats);
|
||||
|
||||
// Process channel data
|
||||
const channels = data.channels?.map(line => ({
|
||||
name: line[0]?.value || '',
|
||||
@@ -295,8 +284,6 @@ const GorgiasOverview = () => {
|
||||
delta: line[3]?.value || 0
|
||||
})) || [];
|
||||
|
||||
console.log('Processed channels:', channels);
|
||||
|
||||
// Process agent data
|
||||
const agents = data.agents?.map(line => ({
|
||||
name: line[0]?.value || '',
|
||||
@@ -306,8 +293,6 @@ const GorgiasOverview = () => {
|
||||
delta: line[4]?.value || 0
|
||||
})) || [];
|
||||
|
||||
console.log('Processed agents:', agents);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Card className="h-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
|
||||
|
||||
@@ -256,16 +256,6 @@ const MiniStatCards = ({
|
||||
: stats.revenue;
|
||||
const prevRevenue = stats.prevPeriodRevenue; // Previous period's total revenue
|
||||
|
||||
console.log('[MiniStatCards RevenueTrend Debug]', {
|
||||
periodProgress: stats.periodProgress,
|
||||
currentRevenue,
|
||||
smartProjection: projection?.projectedRevenue,
|
||||
simpleProjection: stats.projectedRevenue,
|
||||
actualRevenue: stats.revenue,
|
||||
prevRevenue,
|
||||
isProjected: stats.periodProgress < 100
|
||||
});
|
||||
|
||||
if (!currentRevenue || !prevRevenue) return null;
|
||||
|
||||
// Calculate absolute difference percentage
|
||||
@@ -273,12 +263,6 @@ const MiniStatCards = ({
|
||||
const diff = Math.abs(currentRevenue - prevRevenue);
|
||||
const percentage = (diff / prevRevenue) * 100;
|
||||
|
||||
console.log('[MiniStatCards RevenueTrend Result]', {
|
||||
trend,
|
||||
percentage,
|
||||
calculation: `(|${currentRevenue} - ${prevRevenue}| / ${prevRevenue}) * 100 = ${percentage}%`
|
||||
});
|
||||
|
||||
return {
|
||||
trend,
|
||||
value: percentage,
|
||||
|
||||
@@ -1397,12 +1397,10 @@ const StatCards = ({
|
||||
const cachedData = getCacheData(detailTimeRange, metric);
|
||||
|
||||
if (cachedData) {
|
||||
console.log(`Using cached data for ${metric}`);
|
||||
setDetailData((prev) => ({ ...prev, [metric]: cachedData }));
|
||||
return cachedData;
|
||||
}
|
||||
|
||||
console.log(`Fetching detail data for ${metric}`);
|
||||
setDetailDataLoading((prev) => ({ ...prev, [metric]: true }));
|
||||
|
||||
try {
|
||||
@@ -1481,7 +1479,6 @@ const StatCards = ({
|
||||
eventType: "PLACED_ORDER",
|
||||
});
|
||||
const data = response.stats;
|
||||
console.log("Fetched order range data:", data);
|
||||
setCacheData(detailTimeRange, metric, data);
|
||||
setDetailData((prev) => ({ ...prev, [metric]: data }));
|
||||
setError(null);
|
||||
@@ -1570,16 +1567,6 @@ const StatCards = ({
|
||||
: stats.revenue;
|
||||
const prevRevenue = stats.prevPeriodRevenue; // Previous period's total revenue
|
||||
|
||||
console.log('[RevenueTrend Debug]', {
|
||||
periodProgress: stats.periodProgress,
|
||||
currentRevenue,
|
||||
smartProjection: projection?.projectedRevenue,
|
||||
simpleProjection: stats.projectedRevenue,
|
||||
actualRevenue: stats.revenue,
|
||||
prevRevenue,
|
||||
isProjected: stats.periodProgress < 100
|
||||
});
|
||||
|
||||
if (!currentRevenue || !prevRevenue) return null;
|
||||
|
||||
// Calculate absolute difference percentage
|
||||
@@ -1587,12 +1574,6 @@ const StatCards = ({
|
||||
const diff = Math.abs(currentRevenue - prevRevenue);
|
||||
const percentage = (diff / prevRevenue) * 100;
|
||||
|
||||
console.log('[RevenueTrend Result]', {
|
||||
trend,
|
||||
percentage,
|
||||
calculation: `(|${currentRevenue} - ${prevRevenue}| / ${prevRevenue}) * 100 = ${percentage}%`
|
||||
});
|
||||
|
||||
return {
|
||||
trend,
|
||||
value: percentage,
|
||||
|
||||
@@ -86,7 +86,6 @@ export const UserBehaviorDashboard = () => {
|
||||
|
||||
const processPageData = (data) => {
|
||||
if (!data?.rows) {
|
||||
console.log("No rows in page data");
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -101,7 +100,6 @@ export const UserBehaviorDashboard = () => {
|
||||
|
||||
const processDeviceData = (data) => {
|
||||
if (!data?.rows) {
|
||||
console.log("No rows in device data");
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -123,7 +121,6 @@ export const UserBehaviorDashboard = () => {
|
||||
|
||||
const processSourceData = (data) => {
|
||||
if (!data?.rows) {
|
||||
console.log("No rows in source data");
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -150,7 +147,6 @@ export const UserBehaviorDashboard = () => {
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log("Raw user behavior response:", result);
|
||||
|
||||
if (!result?.success) {
|
||||
throw new Error("Invalid response structure");
|
||||
@@ -164,12 +160,6 @@ export const UserBehaviorDashboard = () => {
|
||||
const deviceResponse = rawData?.deviceResponse || rawData?.reports?.[1];
|
||||
const sourceResponse = rawData?.sourceResponse || rawData?.reports?.[2];
|
||||
|
||||
console.log("Extracted responses:", {
|
||||
pageResponse,
|
||||
deviceResponse,
|
||||
sourceResponse,
|
||||
});
|
||||
|
||||
const processed = {
|
||||
success: true,
|
||||
data: {
|
||||
@@ -181,7 +171,6 @@ export const UserBehaviorDashboard = () => {
|
||||
},
|
||||
};
|
||||
|
||||
console.log("Final processed data:", processed);
|
||||
setData(processed);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch behavior data:", error);
|
||||
|
||||
@@ -40,14 +40,12 @@ export function ForecastMetrics() {
|
||||
startDate: dateRange.from?.toISOString() || "",
|
||||
endDate: dateRange.to?.toISOString() || "",
|
||||
});
|
||||
console.log('Fetching forecast metrics with params:', params.toString());
|
||||
const response = await fetch(`${config.apiUrl}/dashboard/forecast/metrics?${params}`)
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(`Failed to fetch forecast metrics: ${text}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log('Forecast metrics response:', data);
|
||||
return data;
|
||||
},
|
||||
})
|
||||
|
||||
@@ -103,7 +103,6 @@ export function PurchaseMetrics() {
|
||||
const { data, error, isLoading } = useQuery<PurchaseMetricsData>({
|
||||
queryKey: ["purchase-metrics"],
|
||||
queryFn: async () => {
|
||||
console.log('Fetching from:', `${config.apiUrl}/dashboard/purchase/metrics`);
|
||||
const response = await fetch(`${config.apiUrl}/dashboard/purchase/metrics`)
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
@@ -111,7 +110,6 @@ export function PurchaseMetrics() {
|
||||
throw new Error(`Failed to fetch purchase metrics: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log('API Response:', data);
|
||||
return data;
|
||||
},
|
||||
})
|
||||
|
||||
@@ -25,7 +25,6 @@ export function ReplenishmentMetrics() {
|
||||
const { data, error, isLoading } = useQuery<ReplenishmentMetricsData>({
|
||||
queryKey: ["replenishment-metrics"],
|
||||
queryFn: async () => {
|
||||
console.log('Fetching from:', `${config.apiUrl}/dashboard/replenishment/metrics`);
|
||||
const response = await fetch(`${config.apiUrl}/dashboard/replenishment/metrics`)
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
@@ -33,7 +32,6 @@ export function ReplenishmentMetrics() {
|
||||
throw new Error(`Failed to fetch replenishment metrics: ${response.status} ${response.statusText} - ${text}`)
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log('API Response:', data);
|
||||
return data;
|
||||
},
|
||||
})
|
||||
|
||||
@@ -103,7 +103,6 @@ export function StockMetrics() {
|
||||
const { data, error, isLoading } = useQuery<StockMetricsData>({
|
||||
queryKey: ["stock-metrics"],
|
||||
queryFn: async () => {
|
||||
console.log('Fetching from:', `${config.apiUrl}/dashboard/stock/metrics`);
|
||||
const response = await fetch(`${config.apiUrl}/dashboard/stock/metrics`);
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
@@ -111,7 +110,6 @@ export function StockMetrics() {
|
||||
throw new Error(`Failed to fetch stock metrics: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log('API Response:', data);
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1029,7 +1029,6 @@ export const MatchColumnsStep = React.memo(<T extends string>({
|
||||
);
|
||||
|
||||
if (matchingOption) {
|
||||
console.log(`Auto-matched "${entryValue}" to "${matchingOption.label}" (${matchingOption.value})`);
|
||||
return {
|
||||
...option,
|
||||
value: matchingOption.value
|
||||
@@ -1094,7 +1093,6 @@ export const MatchColumnsStep = React.memo(<T extends string>({
|
||||
);
|
||||
|
||||
if (matchingOption) {
|
||||
console.log(`Auto-matched "${entryValue}" to "${matchingOption.label}" (${matchingOption.value})`);
|
||||
return {
|
||||
...option,
|
||||
value: matchingOption.value
|
||||
@@ -1159,18 +1157,6 @@ export const MatchColumnsStep = React.memo(<T extends string>({
|
||||
// Convert the fields to the expected type
|
||||
const fieldsArray = Array.isArray(fields) ? fields : [fields];
|
||||
|
||||
// Log the fields for debugging
|
||||
console.log("All fields:", fieldsArray);
|
||||
|
||||
// Log validation rules for each field
|
||||
fieldsArray.forEach(field => {
|
||||
if (field.validations && Array.isArray(field.validations)) {
|
||||
console.log(`Field ${field.key} validations:`, field.validations.map((v: any) => v.rule));
|
||||
} else {
|
||||
console.log(`Field ${field.key} has no validations`);
|
||||
}
|
||||
});
|
||||
|
||||
// Check for required fields based on validations
|
||||
const required = fieldsArray.filter(field => {
|
||||
// Check if the field has validations
|
||||
@@ -1183,12 +1169,10 @@ export const MatchColumnsStep = React.memo(<T extends string>({
|
||||
(v: any) => v.rule === 'required' || v.required === true
|
||||
);
|
||||
|
||||
console.log(`Field ${field.key} required:`, isRequired, field.validations);
|
||||
|
||||
return isRequired;
|
||||
});
|
||||
|
||||
console.log("Required fields:", required);
|
||||
return required;
|
||||
}, [fields]);
|
||||
|
||||
|
||||
@@ -62,14 +62,8 @@ export const SelectHeaderStep = ({ data, onContinue, onBack }: SelectHeaderProps
|
||||
const [selectedRowIndex] = selectedRows;
|
||||
const selectedHeaderRow = localData[selectedRowIndex];
|
||||
|
||||
// Debug: Log the selected header row
|
||||
console.log("Selected header row:", selectedHeaderRow);
|
||||
|
||||
const normalizedHeaderRow = normalizeRowForComparison(selectedHeaderRow);
|
||||
|
||||
// Debug: Log the normalized header row
|
||||
console.log("Normalized header row:", normalizedHeaderRow);
|
||||
|
||||
const selectedHeaderStr = JSON.stringify(Object.entries(normalizedHeaderRow).sort());
|
||||
|
||||
// Filter out empty rows, rows with single values (if we have multi-value rows),
|
||||
@@ -97,11 +91,6 @@ export const SelectHeaderStep = ({ data, onContinue, onBack }: SelectHeaderProps
|
||||
// Check if it's a duplicate (case-insensitive)
|
||||
const normalizedRow = normalizeRowForComparison(row);
|
||||
|
||||
// Debug: If this row might be a duplicate of the header, log it
|
||||
if (index < 5 || index === selectedRowIndex + 1 || index === selectedRowIndex - 1) {
|
||||
console.log(`Row ${index} normalized:`, normalizedRow);
|
||||
}
|
||||
|
||||
const rowStr = JSON.stringify(Object.entries(normalizedRow).sort());
|
||||
|
||||
if (seen.has(rowStr)) {
|
||||
@@ -117,9 +106,6 @@ export const SelectHeaderStep = ({ data, onContinue, onBack }: SelectHeaderProps
|
||||
return true;
|
||||
});
|
||||
|
||||
// Debug: Log removed rows
|
||||
console.log("Removed rows:", removedRows);
|
||||
|
||||
// Only update if we actually removed any rows
|
||||
if (filteredRows.length < localData.length) {
|
||||
// Adjust the selected row index if needed
|
||||
@@ -127,9 +113,6 @@ export const SelectHeaderStep = ({ data, onContinue, onBack }: SelectHeaderProps
|
||||
JSON.stringify(Object.entries(normalizeRowForComparison(row)).sort()) === selectedHeaderStr
|
||||
);
|
||||
|
||||
// Debug: Log the new selected index
|
||||
console.log("New selected index:", newSelectedIndex);
|
||||
|
||||
setLocalData(filteredRows);
|
||||
setSelectedRows(new Set([newSelectedIndex]));
|
||||
|
||||
|
||||
@@ -184,11 +184,9 @@ const ValidationContainer = <T extends string>({
|
||||
const fetchFieldOptions = useCallback(async () => {
|
||||
try {
|
||||
const response = await axios.get('/api/import/field-options');
|
||||
console.log('Field options from API:', response.data);
|
||||
|
||||
// Check if suppliers are included in the response
|
||||
if (response.data && response.data.suppliers) {
|
||||
console.log('Suppliers available:', response.data.suppliers.length);
|
||||
} else {
|
||||
console.warn('No suppliers found in field options response');
|
||||
}
|
||||
@@ -282,24 +280,6 @@ const ValidationContainer = <T extends string>({
|
||||
try {
|
||||
const options = await fetchFieldOptions();
|
||||
if (options && options.suppliers) {
|
||||
console.log(`Loaded ${options.suppliers.length} suppliers for template form`);
|
||||
|
||||
// Log if we can find a match for our supplier
|
||||
if (templateData.supplier !== undefined) {
|
||||
// Need to compare numeric values since supplier options have numeric values
|
||||
const supplierMatch = options.suppliers.find((s: { value: string | number }) =>
|
||||
s.value === templateData.supplier ||
|
||||
Number(s.value) === Number(templateData.supplier)
|
||||
);
|
||||
|
||||
console.log('Found supplier match:', supplierMatch ? 'Yes' : 'No',
|
||||
'For supplier value:', templateData.supplier,
|
||||
'Type:', typeof templateData.supplier);
|
||||
|
||||
if (supplierMatch) {
|
||||
console.log('Matched supplier:', supplierMatch.label);
|
||||
}
|
||||
}
|
||||
|
||||
setIsTemplateFormOpen(true);
|
||||
} else {
|
||||
@@ -344,7 +324,6 @@ const ValidationContainer = <T extends string>({
|
||||
.filter(([_, selected]) => selected === true)
|
||||
.map(([key, _]) => key);
|
||||
|
||||
console.log('Selected row keys for deletion:', selectedKeys);
|
||||
|
||||
if (selectedKeys.length === 0) {
|
||||
toast.error("No rows selected");
|
||||
@@ -361,7 +340,6 @@ const ValidationContainer = <T extends string>({
|
||||
return index;
|
||||
}).filter(index => index !== -1); // Filter out any not found
|
||||
|
||||
console.log('Mapped row indices for deletion:', selectedIndices);
|
||||
|
||||
if (selectedIndices.length === 0) {
|
||||
toast.error('Could not find selected rows');
|
||||
@@ -567,7 +545,6 @@ const ValidationContainer = <T extends string>({
|
||||
const upcValue = (data[rowIndex] as any)?.upc || (data[rowIndex] as any)?.barcode;
|
||||
|
||||
if (upcValue) {
|
||||
console.log(`Validating UPC: rowIndex=${rowIndex}, supplier=${value}, upc=${upcValue}`);
|
||||
|
||||
// Mark the item_number cell as being validated
|
||||
const cellKey = `${rowIndex}-item_number`;
|
||||
@@ -581,7 +558,6 @@ const ValidationContainer = <T extends string>({
|
||||
upcValidation.validateUpc(rowIndex, value.toString(), upcValue.toString())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
console.log(`UPC validation successful for row ${rowIndex}`);
|
||||
upcValidation.applyItemNumbersToData();
|
||||
|
||||
// Mark for revalidation after item numbers are updated
|
||||
@@ -606,14 +582,12 @@ const ValidationContainer = <T extends string>({
|
||||
|
||||
// Handle line change - clear subline and fetch sublines
|
||||
if (key === 'line' && value) {
|
||||
console.log(`Line changed to ${value} for row ${rowIndex}, updating sublines`);
|
||||
|
||||
// Clear any existing subline value
|
||||
setData(prevData => {
|
||||
const newData = [...prevData];
|
||||
const idx = newData.findIndex(item => item.__index === rowId);
|
||||
if (idx >= 0) {
|
||||
console.log(`Clearing subline values for row with ID ${rowId}`);
|
||||
newData[idx] = {
|
||||
...newData[idx],
|
||||
subline: undefined
|
||||
@@ -628,9 +602,6 @@ const ValidationContainer = <T extends string>({
|
||||
if (rowId && value !== undefined) {
|
||||
const lineId = value.toString();
|
||||
|
||||
// Force immediate fetch for better UX
|
||||
console.log(`Immediately fetching sublines for line ${lineId} for row ${rowId}`);
|
||||
|
||||
// Set loading state first
|
||||
setValidatingCells(prev => {
|
||||
const newSet = new Set(prev);
|
||||
@@ -639,8 +610,7 @@ const ValidationContainer = <T extends string>({
|
||||
});
|
||||
|
||||
fetchSublines(rowId, lineId)
|
||||
.then(sublines => {
|
||||
console.log(`Successfully loaded ${sublines.length} sublines for line ${lineId}`);
|
||||
.then(() => {
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(`Error fetching sublines for line ${lineId}:`, err);
|
||||
@@ -664,7 +634,6 @@ const ValidationContainer = <T extends string>({
|
||||
const supplier = (data[rowIndex] as any)?.supplier;
|
||||
|
||||
if (supplier) {
|
||||
console.log(`Validating UPC from UPC change: rowIndex=${rowIndex}, supplier=${supplier}, upc=${value}`);
|
||||
|
||||
// Mark the item_number cell as being validated
|
||||
const cellKey = `${rowIndex}-item_number`;
|
||||
@@ -678,7 +647,6 @@ const ValidationContainer = <T extends string>({
|
||||
upcValidation.validateUpc(rowIndex, supplier.toString(), value.toString())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
console.log(`UPC validation successful for row ${rowIndex}`);
|
||||
upcValidation.applyItemNumbersToData();
|
||||
|
||||
// Mark for revalidation after item numbers are updated
|
||||
@@ -818,7 +786,6 @@ const ValidationContainer = <T extends string>({
|
||||
|
||||
// Run validations in parallel but limit the batch size
|
||||
if (validationsToRun.length > 0) {
|
||||
console.log(`Running ${validationsToRun.length} UPC validations for copyDown`);
|
||||
|
||||
// Mark all cells as validating
|
||||
validationsToRun.forEach(({ rowIndex }) => {
|
||||
@@ -877,7 +844,6 @@ const ValidationContainer = <T extends string>({
|
||||
// Add a small delay between batches to prevent UI freezing
|
||||
setTimeout(() => processBatch(endIdx), 100);
|
||||
} else {
|
||||
console.log(`Completed all ${validationsToRun.length} UPC validations`);
|
||||
// Final application of all item numbers if not done by individual batches
|
||||
upcValidation.applyItemNumbersToData(updatedRowIds => {
|
||||
// Mark these rows for revalidation after a delay
|
||||
@@ -1112,8 +1078,6 @@ const ValidationContainer = <T extends string>({
|
||||
size="sm"
|
||||
className="h-8 shadow-xs"
|
||||
onClick={() => {
|
||||
console.log('Delete/Discard button clicked');
|
||||
console.log('Row selection state:', rowSelection);
|
||||
deleteSelectedRows();
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -23,7 +23,6 @@ export default defineConfig(({ mode }) => {
|
||||
await fs.ensureDir(path.dirname(targetPath));
|
||||
await fs.remove(targetPath);
|
||||
await fs.copy(sourcePath, targetPath);
|
||||
console.log('Build files copied successfully to server directory!');
|
||||
} catch (error) {
|
||||
console.error('Error copying build files:', error);
|
||||
process.exit(1);
|
||||
@@ -105,7 +104,7 @@ export default defineConfig(({ mode }) => {
|
||||
rewrite: (path) => path.replace(/^\/api/, "/api"),
|
||||
configure: (proxy, _options) => {
|
||||
proxy.on("error", (err, req, res) => {
|
||||
console.log("API proxy error:", err)
|
||||
console.error("API proxy error:", err)
|
||||
res.writeHead(500, {
|
||||
"Content-Type": "application/json",
|
||||
})
|
||||
@@ -113,20 +112,6 @@ export default defineConfig(({ mode }) => {
|
||||
JSON.stringify({ error: "Proxy Error", message: err.message })
|
||||
)
|
||||
})
|
||||
proxy.on("proxyReq", (proxyReq, req, _res) => {
|
||||
console.log("Outgoing request to API:", {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
headers: proxyReq.getHeaders(),
|
||||
})
|
||||
})
|
||||
proxy.on("proxyRes", (proxyRes, req, _res) => {
|
||||
console.log("API Proxy response:", {
|
||||
statusCode: proxyRes.statusCode,
|
||||
url: req.url,
|
||||
headers: proxyRes.headers,
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
"/dashboard-auth": {
|
||||
@@ -153,7 +138,7 @@ export default defineConfig(({ mode }) => {
|
||||
rewrite: (path) => path.replace(/^\/auth-inv/, "/auth-inv"),
|
||||
configure: (proxy, _options) => {
|
||||
proxy.on("error", (err, req, res) => {
|
||||
console.log("Auth proxy error:", err)
|
||||
console.error("Auth proxy error:", err)
|
||||
res.writeHead(500, {
|
||||
"Content-Type": "application/json",
|
||||
})
|
||||
@@ -161,20 +146,6 @@ export default defineConfig(({ mode }) => {
|
||||
JSON.stringify({ error: "Proxy Error", message: err.message })
|
||||
)
|
||||
})
|
||||
proxy.on("proxyReq", (proxyReq, req, _res) => {
|
||||
console.log("Outgoing request to Auth:", {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
headers: proxyReq.getHeaders(),
|
||||
})
|
||||
})
|
||||
proxy.on("proxyRes", (proxyRes, req, _res) => {
|
||||
console.log("Auth Proxy response:", {
|
||||
statusCode: proxyRes.statusCode,
|
||||
url: req.url,
|
||||
headers: proxyRes.headers,
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
"/chat-api": {
|
||||
@@ -188,7 +159,7 @@ export default defineConfig(({ mode }) => {
|
||||
rewrite: (path) => path,
|
||||
configure: (proxy, _options) => {
|
||||
proxy.on("error", (err, req, res) => {
|
||||
console.log("Chat API proxy error:", err)
|
||||
console.error("Chat API proxy error:", err)
|
||||
res.writeHead(500, {
|
||||
"Content-Type": "application/json",
|
||||
})
|
||||
@@ -196,20 +167,6 @@ export default defineConfig(({ mode }) => {
|
||||
JSON.stringify({ error: "Proxy Error", message: err.message })
|
||||
)
|
||||
})
|
||||
proxy.on("proxyReq", (proxyReq, req, _res) => {
|
||||
console.log("Outgoing request to Chat API:", {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
headers: proxyReq.getHeaders(),
|
||||
})
|
||||
})
|
||||
proxy.on("proxyRes", (proxyRes, req, _res) => {
|
||||
console.log("Chat API Proxy response:", {
|
||||
statusCode: proxyRes.statusCode,
|
||||
url: req.url,
|
||||
headers: proxyRes.headers,
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
"/uploads": {
|
||||
|
||||
Reference in New Issue
Block a user