import {
useCallback,
useEffect,
useRef,
useState
} from 'react';
import { useOutletContext } from 'react-router-dom';
import moment from 'moment';
// components
import BarCharts from '../../common/charting/BarCharts';
import LineCharts from '../../common/charting/LineCharts';
// hooks
import useDebounceLogic from '../useDebounceLogic';
import useLoadingMessages from '../useLoadingMessages';
// functions
import apiLoader from '../../../services/utilities/apiLoader';
// constants
const errorNumber = 367;
const nowRunning = 'hooks/inventory/useSkuHistory.jsx';
function useSkuHistory({ sku, inStock }) {
const [state, setState] = useState({
allItems: {}, // All items from the API
filteredItems: {}, // Filtered items based on `inStock`
averagesByYear: {}, // Averages by year for all items
durations: [],
chartLabel: '',
average: 0,
overallAverage: 0,
tooltipContent: [], // Tooltip content for BarCharts
});
const {
handleError,
level,
setLoadingMessages
} = useOutletContext();
const {
addLoadingMessage,
removeLoadingMessage
} = useLoadingMessages(setLoadingMessages);
const previousSkuRef = useRef(null);
const noAccess = useCallback(() => null, []);
// Calculate averages by year (independent of inStock filtering).
const calculateAveragesByYear = useCallback(() => {
const context = `${nowRunning}.calculateAveragesByYear`;
try {
const yearGroups = {};
Object.values(state.allItems).forEach((item) => {
const stockedYear = moment(item.stocked).year();
const stockedDate = moment(item.stocked);
let endDate;
if (item.condition < 11) {
endDate = moment(); // Still in stock.
} else if (item.condition === 99 && item.orders.length > 0) {
endDate = moment(
Math.max(...item.orders.map((order) => new Date(order.orderTime).getTime()))
); // Sold.
} else {
endDate = moment(item.verified); // Otherwise, verified date.
}
const timeInStock = endDate.diff(stockedDate, 'days');
if (!yearGroups[stockedYear]) {
yearGroups[stockedYear] = { totalDays: 0, itemCount: 0 };
}
yearGroups[stockedYear].totalDays += timeInStock;
yearGroups[stockedYear].itemCount += 1;
});
const averagesByYear = Object.keys(yearGroups).reduce((acc, year) => {
const { totalDays, itemCount } = yearGroups[year];
acc[year] = totalDays / itemCount;
return acc;
}, {});
setState((prevState) => ({
...prevState,
averagesByYear,
}));
} catch (error) {
handleError(error, context, errorNumber);
}
},
[
handleError,
state.allItems
]);
// Filter items based on `inStock` and calculate durations.
const filterItems = useCallback(() => {
try {
const filteredItems = Object.values(state.allItems).filter((item) =>
inStock ? item.condition < 11 : true
);
const durations = [];
let sum = 0;
let count = 0;
const tooltipContent = filteredItems.map((item) => {
const days = item.duration || 0;
const sunkCost = item.sunkCost || 0; // Default to 0 if `sunkCost` is not defined.
durations.push({ key: `#${item.itemNumber}`, days });
sum += days;
count += 1;
return `Item ${item.itemNumber}, in stock ${days} day${days !== 1 ? 's' : ''}, Sunk Cost: $${sunkCost.toFixed(2)}`;
});
const average = count > 0 ? +(sum / count).toFixed(0) : 0;
const chartLabel = `days in stock (average: ${average})`;
setState((prevState) => ({
...prevState,
filteredItems: filteredItems.reduce((acc, item) => {
acc[item.itemId] = item;
return acc;
}, {}),
durations,
average,
chartLabel,
tooltipContent,
}));
} catch (error) {
handleError(error, `${nowRunning}.filterItems`, errorNumber);
}
},
[
handleError,
inStock,
state.allItems
]);
// Fetch data from the API.
const runSearch = useCallback(async () => {
const context = `${nowRunning}.runSearch`;
const loadingMessage = 'Loading SKU history...';
try {
if (!sku || sku.length < 5 || sku === previousSkuRef.current) { return; }
previousSkuRef.current = sku;
addLoadingMessage(loadingMessage);
// Get SKU history data first.
let api = 'analysis/inventory/sku-history';
let payload = { sku };
const { data } = await apiLoader({ api, payload });
const {
failure,
items,
success
} = data;
if (!success) {
handleError(failure, context, errorNumber, true);
return;
}
if (Object.keys(items).length === 0) {
return;
} else {
const api = 'analysis/cost_sources/unrecovered-costs';
const payload = {};
const { data } = await apiLoader({ api, payload });
const {
failure,
success,
unrecoveredItems
} = data;
if (!success) {
handleError(failure, context, errorNumber, true);
return;
}
Object.entries(unrecoveredItems).forEach(([itemId, details]) => {
if (items[itemId]) { items[itemId].sunkCost = details.cost; }
});
setState((prevState) => ({
...prevState,
allItems: items,
}));
}
} catch (error) {
handleError(error, context, errorNumber);
} finally {
removeLoadingMessage(loadingMessage);
}
},
[
addLoadingMessage,
handleError,
removeLoadingMessage,
sku
]);
const debouncedRunSearch = useDebounceLogic(runSearch, 500);
// Update charts and filtered items whenever state changes.
useEffect(() => {
calculateAveragesByYear();
}, [state.allItems, calculateAveragesByYear]);
useEffect(() => {
filterItems();
}, [inStock, state.allItems, filterItems]);
useEffect(() => {
if (sku && sku.length >= 5) {
debouncedRunSearch();
}
}, [sku, debouncedRunSearch]);
if (level < 5) {
return {
debouncedRunSearch: noAccess,
filteredItems: {},
renderItemsChart: noAccess,
renderLineChart: noAccess
};
}
try {
return {
debouncedRunSearch,
filteredItems: state.filteredItems,
renderItemsChart: () => {
const {
chartLabel,
durations,
tooltipContent
} = state;
if (!durations.length) return null;
return (
);
},
renderLineChart: () => {
const dataset = Object.entries(state.averagesByYear).map(([year, average]) => ({
year,
average,
}));
if (!dataset.length) return null;
return (
);
}
};
} catch (error) {
handleError(error, `${nowRunning}.return`, errorNumber);
}
}
export default useSkuHistory;