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