591 lines
15 KiB
TypeScript
591 lines
15 KiB
TypeScript
"use client";
|
|
|
|
import { clientFetch } from "../api-client";
|
|
|
|
// Analytics Types
|
|
export interface AnalyticsOverview {
|
|
orders: {
|
|
total: number;
|
|
completed: number;
|
|
pending: number;
|
|
completionRate: string;
|
|
};
|
|
revenue: {
|
|
total: number;
|
|
monthly: number;
|
|
weekly: number;
|
|
averageOrderValue: number;
|
|
};
|
|
products: {
|
|
total: number;
|
|
};
|
|
customers: {
|
|
unique: number;
|
|
};
|
|
userType?: "vendor" | "staff";
|
|
}
|
|
|
|
export interface RevenueData {
|
|
_id: {
|
|
year: number;
|
|
month: number;
|
|
day: number;
|
|
};
|
|
revenue: number;
|
|
orders: number;
|
|
}
|
|
|
|
export interface ProductPerformance {
|
|
productId: string;
|
|
name: string;
|
|
image: string;
|
|
unitType: string;
|
|
currentStock: number;
|
|
stockStatus: string;
|
|
totalSold: number;
|
|
totalRevenue: number;
|
|
orderCount: number;
|
|
averagePrice: number;
|
|
}
|
|
|
|
export interface CustomerInsights {
|
|
totalCustomers: number;
|
|
segments: {
|
|
new: number;
|
|
returning: number;
|
|
loyal: number;
|
|
vip: number;
|
|
};
|
|
topCustomers: Array<{
|
|
_id: string;
|
|
orderCount: number;
|
|
totalSpent: number;
|
|
averageOrderValue: number;
|
|
firstOrder: string;
|
|
lastOrder: string;
|
|
displayName?: string;
|
|
username?: string;
|
|
}>;
|
|
averageOrdersPerCustomer: string;
|
|
pagination?: {
|
|
currentPage: number;
|
|
totalPages: number;
|
|
totalCustomers: number;
|
|
limit: number;
|
|
hasNextPage: boolean;
|
|
hasPrevPage: boolean;
|
|
startIndex: number;
|
|
endIndex: number;
|
|
};
|
|
}
|
|
|
|
export interface OrderAnalytics {
|
|
statusDistribution: Array<{
|
|
_id: string;
|
|
count: number;
|
|
}>;
|
|
}
|
|
|
|
export interface GrowthAnalytics {
|
|
launchDate: string;
|
|
generatedAt: string;
|
|
daily: Array<{
|
|
date: string;
|
|
orders: number;
|
|
revenue: number;
|
|
customers: number;
|
|
avgOrderValue: number;
|
|
}>;
|
|
monthly: Array<{
|
|
month: string;
|
|
orders: number;
|
|
revenue: number;
|
|
customers: number;
|
|
avgOrderValue: number;
|
|
newCustomers: number;
|
|
}>;
|
|
customers: {
|
|
total: number;
|
|
segments: {
|
|
new: number;
|
|
returning: number;
|
|
loyal: number;
|
|
vip: number;
|
|
};
|
|
segmentDetails: {
|
|
[key: string]: {
|
|
count: number;
|
|
totalRevenue: number;
|
|
avgOrderCount: number;
|
|
avgSpent: number;
|
|
};
|
|
};
|
|
segmentPercentages: {
|
|
new: number;
|
|
returning: number;
|
|
loyal: number;
|
|
vip: number;
|
|
};
|
|
};
|
|
cumulative: {
|
|
orders: number;
|
|
revenue: number;
|
|
customers: number;
|
|
products: number;
|
|
avgOrderValue: number;
|
|
};
|
|
}
|
|
|
|
// Analytics Service Functions
|
|
|
|
/**
|
|
* Get analytics overview data
|
|
* @param storeId Optional storeId for staff users
|
|
*/
|
|
export const getAnalyticsOverview = async (
|
|
storeId?: string,
|
|
): Promise<AnalyticsOverview> => {
|
|
const url = storeId
|
|
? `/analytics/overview?storeId=${storeId}`
|
|
: "/analytics/overview";
|
|
return clientFetch<AnalyticsOverview>(url);
|
|
};
|
|
|
|
/**
|
|
* Get revenue trends data
|
|
* @param period Time period in days (7, 30, 90)
|
|
* @param storeId Optional storeId for staff users
|
|
*/
|
|
export const getRevenueTrends = async (
|
|
period: string = "30",
|
|
storeId?: string,
|
|
): Promise<RevenueData[]> => {
|
|
const params = new URLSearchParams({ period });
|
|
if (storeId) params.append("storeId", storeId);
|
|
|
|
const url = `/analytics/revenue-trends?${params.toString()}`;
|
|
return clientFetch<RevenueData[]>(url);
|
|
};
|
|
|
|
/**
|
|
* Get product performance data
|
|
* @param storeId Optional storeId for staff users
|
|
*/
|
|
export const getProductPerformance = async (
|
|
storeId?: string,
|
|
): Promise<ProductPerformance[]> => {
|
|
const url = storeId
|
|
? `/analytics/product-performance?storeId=${storeId}`
|
|
: "/analytics/product-performance";
|
|
return clientFetch<ProductPerformance[]>(url);
|
|
};
|
|
|
|
/**
|
|
* Get customer insights data
|
|
* @param storeId Optional storeId for staff users
|
|
* @param page Page number (default: 1)
|
|
* @param limit Number of customers per page (default: 10)
|
|
*/
|
|
export const getCustomerInsights = async (
|
|
storeId?: string,
|
|
page: number = 1,
|
|
limit: number = 10,
|
|
): Promise<CustomerInsights> => {
|
|
const params = new URLSearchParams({
|
|
page: page.toString(),
|
|
limit: limit.toString(),
|
|
});
|
|
if (storeId) params.append("storeId", storeId);
|
|
|
|
const url = `/analytics/customer-insights?${params.toString()}`;
|
|
return clientFetch<CustomerInsights>(url);
|
|
};
|
|
|
|
/**
|
|
* Get order analytics data
|
|
* @param period Time period in days (7, 30, 90)
|
|
* @param storeId Optional storeId for staff users
|
|
*/
|
|
export const getOrderAnalytics = async (
|
|
period: string = "30",
|
|
storeId?: string,
|
|
): Promise<OrderAnalytics> => {
|
|
const params = new URLSearchParams({ period });
|
|
if (storeId) params.append("storeId", storeId);
|
|
|
|
const url = `/analytics/order-analytics?${params.toString()}`;
|
|
return clientFetch<OrderAnalytics>(url);
|
|
};
|
|
|
|
/**
|
|
* Get growth analytics data (since first order)
|
|
* @param storeId Optional storeId for staff users
|
|
*/
|
|
export const getGrowthAnalytics = async (
|
|
storeId?: string,
|
|
): Promise<GrowthAnalytics> => {
|
|
const params = new URLSearchParams();
|
|
if (storeId) params.append("storeId", storeId);
|
|
|
|
const url = params.toString()
|
|
? `/analytics/growth?${params.toString()}`
|
|
: "/analytics/growth";
|
|
return clientFetch<GrowthAnalytics>(url);
|
|
};
|
|
|
|
// Helper function to determine if user is staff and get storeId
|
|
export const getStoreIdForUser = (): string | undefined => {
|
|
if (typeof window === "undefined") return undefined;
|
|
|
|
// Check if user is staff (you might need to adjust this based on your auth system)
|
|
const userType = localStorage.getItem("userType");
|
|
const storeId = localStorage.getItem("storeId");
|
|
|
|
if (userType === "staff" && storeId) {
|
|
return storeId;
|
|
}
|
|
|
|
return undefined;
|
|
};
|
|
|
|
// Enhanced analytics functions that automatically handle storeId for staff users
|
|
export const getAnalyticsOverviewWithStore =
|
|
async (): Promise<AnalyticsOverview> => {
|
|
const storeId = getStoreIdForUser();
|
|
return getAnalyticsOverview(storeId);
|
|
};
|
|
|
|
export const getRevenueTrendsWithStore = async (
|
|
period: string = "30",
|
|
): Promise<RevenueData[]> => {
|
|
const storeId = getStoreIdForUser();
|
|
return getRevenueTrends(period, storeId);
|
|
};
|
|
|
|
export const getProductPerformanceWithStore = async (): Promise<
|
|
ProductPerformance[]
|
|
> => {
|
|
const storeId = getStoreIdForUser();
|
|
return getProductPerformance(storeId);
|
|
};
|
|
|
|
export const getCustomerInsightsWithStore = async (
|
|
page: number = 1,
|
|
limit: number = 10,
|
|
): Promise<CustomerInsights> => {
|
|
const storeId = getStoreIdForUser();
|
|
return getCustomerInsights(storeId, page, limit);
|
|
};
|
|
|
|
export const getOrderAnalyticsWithStore = async (
|
|
period: string = "30",
|
|
): Promise<OrderAnalytics> => {
|
|
const storeId = getStoreIdForUser();
|
|
return getOrderAnalytics(period, storeId);
|
|
};
|
|
|
|
export const getGrowthAnalyticsWithStore =
|
|
async (): Promise<GrowthAnalytics> => {
|
|
const storeId = getStoreIdForUser();
|
|
return getGrowthAnalytics(storeId);
|
|
};
|
|
|
|
export function formatGBP(value: number) {
|
|
return value.toLocaleString("en-GB", {
|
|
style: "currency",
|
|
currency: "GBP",
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
});
|
|
}
|
|
|
|
// Prediction Types
|
|
export interface SalesPrediction {
|
|
predicted: number | null;
|
|
predictedOrders: number | null;
|
|
dailyPredictions?: Array<{
|
|
day: number;
|
|
predicted: number;
|
|
date: string;
|
|
}>;
|
|
confidence: "very_high" | "high" | "medium" | "low";
|
|
method: string;
|
|
methods?: {
|
|
aiNeuralNetwork?: number | null;
|
|
weightedMovingAverage?: number | null;
|
|
exponentialSmoothing?: number | null;
|
|
holtWinters?: number | null;
|
|
weightedLinearRegression?: number | null;
|
|
trendAdjusted?: number | null;
|
|
};
|
|
aiModel?: {
|
|
used: boolean;
|
|
available?: boolean;
|
|
confidence?: string;
|
|
modelAccuracy?: number;
|
|
error?: string | null;
|
|
};
|
|
trend?: {
|
|
direction: "up" | "down" | "neutral";
|
|
strength: number;
|
|
acceleration: number;
|
|
slope?: number;
|
|
};
|
|
variance?: number;
|
|
minPrediction?: number;
|
|
maxPrediction?: number;
|
|
confidenceScore?: number;
|
|
confidenceIntervals?: {
|
|
lower: number;
|
|
upper: number;
|
|
confidenceScore?: number;
|
|
modelAgreement?: number;
|
|
avgModelAccuracy?: number;
|
|
dataConsistency?: number;
|
|
};
|
|
modelPerformance?: {
|
|
[key: string]: {
|
|
mae?: number;
|
|
mape?: number;
|
|
rmse?: number;
|
|
accuracy?: number;
|
|
confidence?: string;
|
|
};
|
|
};
|
|
seasonality?: {
|
|
dayOfWeek: Record<string, number>;
|
|
month: Record<string, number>;
|
|
confidence: string;
|
|
};
|
|
historicalPeriod: number;
|
|
predictionPeriod: number;
|
|
message?: string;
|
|
}
|
|
|
|
export interface DemandPrediction {
|
|
predictedDaily: number | null;
|
|
predictedWeekly: number | null;
|
|
predictedMonthly: number | null;
|
|
confidence: "very_high" | "high" | "medium" | "low";
|
|
averageDaily?: number;
|
|
trendFactor?: number;
|
|
stdDev?: number;
|
|
confidenceIntervals?: {
|
|
lower: number;
|
|
upper: number;
|
|
confidenceScore?: number;
|
|
modelAgreement?: number;
|
|
avgModelAccuracy?: number;
|
|
dataConsistency?: number;
|
|
};
|
|
historicalPeriod: number;
|
|
predictionPeriod: number;
|
|
productId?: string | null;
|
|
message?: string;
|
|
}
|
|
|
|
export interface StockPrediction {
|
|
productId: string;
|
|
productName: string;
|
|
currentStock: number;
|
|
lowStockThreshold: number;
|
|
unitType: string;
|
|
prediction: {
|
|
daysUntilOutOfStock: number | null;
|
|
estimatedDate: string | null;
|
|
confidence: "very_high" | "high" | "medium" | "low";
|
|
averageDailySales?: number;
|
|
stdDev?: number;
|
|
optimisticDays?: number | null;
|
|
pessimisticDays?: number | null;
|
|
optimisticDate?: string | null;
|
|
pessimisticDate?: string | null;
|
|
message?: string;
|
|
};
|
|
needsRestock: boolean;
|
|
}
|
|
|
|
export interface StockPredictionsResponse {
|
|
predictions: StockPrediction[];
|
|
historicalPeriod: number;
|
|
totalProducts: number;
|
|
productsNeedingRestock: number;
|
|
}
|
|
|
|
export interface PredictionsOverview {
|
|
sales: SalesPrediction;
|
|
demand: DemandPrediction;
|
|
stock: {
|
|
totalProducts: number;
|
|
message?: string;
|
|
};
|
|
historicalPeriod: number;
|
|
predictionPeriod: number;
|
|
message?: string;
|
|
}
|
|
|
|
export interface BatchPredictionsResponse {
|
|
success: boolean;
|
|
storeId?: string;
|
|
historicalPeriod?: number;
|
|
horizons?: number[];
|
|
simulationFactors?: number[];
|
|
predictions?: {
|
|
[horizon: string]: {
|
|
[simulationFactor: string]: PredictionsOverview;
|
|
};
|
|
};
|
|
totalEntries?: number;
|
|
generatedAt?: string;
|
|
message?: string;
|
|
}
|
|
|
|
|
|
// Prediction Service Functions
|
|
|
|
/**
|
|
* Get sales/revenue predictions
|
|
* @param daysAhead Number of days to predict ahead (default: 7)
|
|
* @param period Historical period in days (default: 30)
|
|
* @param storeId Optional storeId for staff users
|
|
*/
|
|
export const getSalesPredictions = async (
|
|
daysAhead: number = 7,
|
|
period: number = 30,
|
|
storeId?: string,
|
|
): Promise<SalesPrediction> => {
|
|
const params = new URLSearchParams({
|
|
daysAhead: daysAhead.toString(),
|
|
period: period.toString(),
|
|
});
|
|
if (storeId) params.append("storeId", storeId);
|
|
|
|
const url = `/analytics/predictions/sales?${params.toString()}`;
|
|
return clientFetch<SalesPrediction>(url);
|
|
};
|
|
|
|
/**
|
|
* Get product demand predictions
|
|
* @param productId Optional product ID for specific product prediction
|
|
* @param daysAhead Number of days to predict ahead (default: 7)
|
|
* @param period Historical period in days (default: 30)
|
|
* @param storeId Optional storeId for staff users
|
|
*/
|
|
export const getDemandPredictions = async (
|
|
productId?: string,
|
|
daysAhead: number = 7,
|
|
period: number = 30,
|
|
storeId?: string,
|
|
): Promise<DemandPrediction> => {
|
|
const params = new URLSearchParams({
|
|
daysAhead: daysAhead.toString(),
|
|
period: period.toString(),
|
|
});
|
|
if (productId) params.append("productId", productId);
|
|
if (storeId) params.append("storeId", storeId);
|
|
|
|
const url = `/analytics/predictions/demand?${params.toString()}`;
|
|
return clientFetch<DemandPrediction>(url);
|
|
};
|
|
|
|
/**
|
|
* Get stock depletion predictions
|
|
* @param period Historical period in days (default: 30)
|
|
* @param storeId Optional storeId for staff users
|
|
*/
|
|
export const getStockPredictions = async (
|
|
period: number = 30,
|
|
storeId?: string,
|
|
): Promise<StockPredictionsResponse> => {
|
|
const params = new URLSearchParams({
|
|
period: period.toString(),
|
|
});
|
|
if (storeId) params.append("storeId", storeId);
|
|
|
|
const url = `/analytics/predictions/stock?${params.toString()}`;
|
|
return clientFetch<StockPredictionsResponse>(url);
|
|
};
|
|
|
|
/**
|
|
* Get comprehensive predictions overview
|
|
* @param daysAhead Number of days to predict ahead (default: 7)
|
|
* @param period Historical period in days (default: 30)
|
|
* @param storeId Optional storeId for staff users
|
|
* @param simulation Simulation factor (e.g. 0.1 for +10%)
|
|
*/
|
|
export const getPredictionsOverview = async (
|
|
daysAhead: number = 7,
|
|
period: number = 30,
|
|
storeId?: string,
|
|
simulation: number = 0,
|
|
): Promise<PredictionsOverview> => {
|
|
const params = new URLSearchParams({
|
|
daysAhead: daysAhead.toString(),
|
|
period: period.toString(),
|
|
simulation: simulation.toString(),
|
|
});
|
|
if (storeId) params.append("storeId", storeId);
|
|
|
|
const url = `/analytics/predictions/overview?${params.toString()}`;
|
|
return clientFetch<PredictionsOverview>(url);
|
|
};
|
|
|
|
// Helper functions with automatic storeId handling
|
|
export const getSalesPredictionsWithStore = async (
|
|
daysAhead: number = 7,
|
|
period: number = 30,
|
|
): Promise<SalesPrediction> => {
|
|
const storeId = getStoreIdForUser();
|
|
return getSalesPredictions(daysAhead, period, storeId);
|
|
};
|
|
|
|
export const getDemandPredictionsWithStore = async (
|
|
productId?: string,
|
|
daysAhead: number = 7,
|
|
period: number = 30,
|
|
): Promise<DemandPrediction> => {
|
|
const storeId = getStoreIdForUser();
|
|
return getDemandPredictions(productId, daysAhead, period, storeId);
|
|
};
|
|
|
|
export const getStockPredictionsWithStore = async (
|
|
period: number = 30,
|
|
): Promise<StockPredictionsResponse> => {
|
|
const storeId = getStoreIdForUser();
|
|
return getStockPredictions(period, storeId);
|
|
};
|
|
|
|
export const getPredictionsOverviewWithStore = async (
|
|
daysAhead: number = 7,
|
|
period: number = 30,
|
|
simulation: number = 0,
|
|
): Promise<PredictionsOverview> => {
|
|
const storeId = getStoreIdForUser();
|
|
return getPredictionsOverview(daysAhead, period, storeId, simulation);
|
|
};
|
|
|
|
/**
|
|
* Get all cached predictions in one request (for client-side switching)
|
|
* @param period Historical period in days (default: 90)
|
|
* @param storeId Optional storeId for staff users
|
|
*/
|
|
export const getBatchPredictions = async (
|
|
period: number = 90,
|
|
storeId?: string,
|
|
): Promise<BatchPredictionsResponse> => {
|
|
const params = new URLSearchParams({
|
|
period: period.toString(),
|
|
});
|
|
if (storeId) params.append("storeId", storeId);
|
|
|
|
const url = `/analytics/predictions/batch?${params.toString()}`;
|
|
return clientFetch<BatchPredictionsResponse>(url);
|
|
};
|
|
|
|
export const getBatchPredictionsWithStore = async (
|
|
period: number = 90,
|
|
): Promise<BatchPredictionsResponse> => {
|
|
const storeId = getStoreIdForUser();
|
|
return getBatchPredictions(period, storeId);
|
|
};
|