Add growth analytics chart and service integration
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.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { clientFetch } from '../api-client';
|
||||
import { clientFetch } from "../api-client";
|
||||
|
||||
// Analytics Types
|
||||
export interface AnalyticsOverview {
|
||||
@@ -22,7 +22,7 @@ export interface AnalyticsOverview {
|
||||
customers: {
|
||||
unique: number;
|
||||
};
|
||||
userType?: 'vendor' | 'staff';
|
||||
userType?: "vendor" | "staff";
|
||||
}
|
||||
|
||||
export interface RevenueData {
|
||||
@@ -86,14 +86,73 @@ export interface OrderAnalytics {
|
||||
}>;
|
||||
}
|
||||
|
||||
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';
|
||||
export const getAnalyticsOverview = async (
|
||||
storeId?: string,
|
||||
): Promise<AnalyticsOverview> => {
|
||||
const url = storeId
|
||||
? `/analytics/overview?storeId=${storeId}`
|
||||
: "/analytics/overview";
|
||||
return clientFetch<AnalyticsOverview>(url);
|
||||
};
|
||||
|
||||
@@ -102,10 +161,13 @@ export const getAnalyticsOverview = async (storeId?: string): Promise<AnalyticsO
|
||||
* @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[]> => {
|
||||
export const getRevenueTrends = async (
|
||||
period: string = "30",
|
||||
storeId?: string,
|
||||
): Promise<RevenueData[]> => {
|
||||
const params = new URLSearchParams({ period });
|
||||
if (storeId) params.append('storeId', storeId);
|
||||
|
||||
if (storeId) params.append("storeId", storeId);
|
||||
|
||||
const url = `/analytics/revenue-trends?${params.toString()}`;
|
||||
return clientFetch<RevenueData[]>(url);
|
||||
};
|
||||
@@ -114,8 +176,12 @@ export const getRevenueTrends = async (period: string = '30', storeId?: string):
|
||||
* 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';
|
||||
export const getProductPerformance = async (
|
||||
storeId?: string,
|
||||
): Promise<ProductPerformance[]> => {
|
||||
const url = storeId
|
||||
? `/analytics/product-performance?storeId=${storeId}`
|
||||
: "/analytics/product-performance";
|
||||
return clientFetch<ProductPerformance[]>(url);
|
||||
};
|
||||
|
||||
@@ -125,13 +191,17 @@ export const getProductPerformance = async (storeId?: string): Promise<ProductPe
|
||||
* @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()
|
||||
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);
|
||||
|
||||
if (storeId) params.append("storeId", storeId);
|
||||
|
||||
const url = `/analytics/customer-insights?${params.toString()}`;
|
||||
return clientFetch<CustomerInsights>(url);
|
||||
};
|
||||
@@ -141,60 +211,100 @@ export const getCustomerInsights = async (storeId?: string, page: number = 1, li
|
||||
* @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> => {
|
||||
export const getOrderAnalytics = async (
|
||||
period: string = "30",
|
||||
storeId?: string,
|
||||
): Promise<OrderAnalytics> => {
|
||||
const params = new URLSearchParams({ period });
|
||||
if (storeId) params.append('storeId', storeId);
|
||||
|
||||
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;
|
||||
|
||||
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) {
|
||||
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 getAnalyticsOverviewWithStore =
|
||||
async (): Promise<AnalyticsOverview> => {
|
||||
const storeId = getStoreIdForUser();
|
||||
return getAnalyticsOverview(storeId);
|
||||
};
|
||||
|
||||
export const getRevenueTrendsWithStore = async (period: string = '30'): Promise<RevenueData[]> => {
|
||||
export const getRevenueTrendsWithStore = async (
|
||||
period: string = "30",
|
||||
): Promise<RevenueData[]> => {
|
||||
const storeId = getStoreIdForUser();
|
||||
return getRevenueTrends(period, storeId);
|
||||
};
|
||||
|
||||
export const getProductPerformanceWithStore = async (): Promise<ProductPerformance[]> => {
|
||||
export const getProductPerformanceWithStore = async (): Promise<
|
||||
ProductPerformance[]
|
||||
> => {
|
||||
const storeId = getStoreIdForUser();
|
||||
return getProductPerformance(storeId);
|
||||
};
|
||||
|
||||
export const getCustomerInsightsWithStore = async (page: number = 1, limit: number = 10): Promise<CustomerInsights> => {
|
||||
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> => {
|
||||
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',
|
||||
return value.toLocaleString("en-GB", {
|
||||
style: "currency",
|
||||
currency: "GBP",
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user