Introduces a new GrowthAnalyticsChart component and integrates growth analytics data into the dashboard. Updates analytics-service to support growth analytics API, adds types, and exposes helper functions for fetching growth analytics with store context. The dashboard UI now includes a 'Growth' tab for visualizing store growth metrics and trends.
311 lines
7.5 KiB
TypeScript
311 lines
7.5 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 {
|
|
period: {
|
|
start: string;
|
|
end: string;
|
|
days: number;
|
|
granularity: "daily" | "weekly" | "monthly";
|
|
};
|
|
summary: {
|
|
currentPeriod: {
|
|
revenue: number;
|
|
orders: number;
|
|
avgOrderValue: number;
|
|
customers: number;
|
|
};
|
|
previousPeriod: {
|
|
revenue: number;
|
|
orders: number;
|
|
avgOrderValue: number;
|
|
customers: number;
|
|
};
|
|
growthRates: {
|
|
revenue: number;
|
|
orders: number;
|
|
avgOrderValue: number;
|
|
customers: number;
|
|
};
|
|
};
|
|
customerInsights: {
|
|
newCustomers: number;
|
|
returningCustomers: number;
|
|
totalCustomers: number;
|
|
newCustomerRate: number;
|
|
avgOrdersPerCustomer: number;
|
|
avgSpentPerCustomer: number;
|
|
};
|
|
timeSeries: Array<{
|
|
date: string;
|
|
revenue: number;
|
|
orders: number;
|
|
avgOrderValue: number;
|
|
uniqueCustomers: number;
|
|
cumulativeRevenue: number;
|
|
cumulativeOrders: number;
|
|
}>;
|
|
topGrowingProducts: Array<{
|
|
productId: string;
|
|
productName: string;
|
|
currentPeriodRevenue: number;
|
|
previousPeriodRevenue: number;
|
|
revenueGrowth: number;
|
|
currentPeriodQuantity: number;
|
|
previousPeriodQuantity: 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
|
|
* @param period Time period: "7", "30", "90", "365", or "all" (default: "30")
|
|
* @param granularity Data granularity: "daily", "weekly", "monthly" (auto-selected if not specified)
|
|
* @param storeId Optional storeId for staff users
|
|
*/
|
|
export const getGrowthAnalytics = async (
|
|
period: string = "30",
|
|
granularity?: string,
|
|
storeId?: string,
|
|
): Promise<GrowthAnalytics> => {
|
|
const params = new URLSearchParams({ period });
|
|
if (granularity) params.append("granularity", granularity);
|
|
if (storeId) params.append("storeId", storeId);
|
|
|
|
const url = `/analytics/growth?${params.toString()}`;
|
|
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 (
|
|
period: string = "30",
|
|
granularity?: string,
|
|
): Promise<GrowthAnalytics> => {
|
|
const storeId = getStoreIdForUser();
|
|
return getGrowthAnalytics(period, granularity, storeId);
|
|
};
|
|
|
|
export function formatGBP(value: number) {
|
|
return value.toLocaleString("en-GB", {
|
|
style: "currency",
|
|
currency: "GBP",
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
});
|
|
}
|