New enterprise feature
This commit is contained in:
@@ -102,6 +102,9 @@ class ApiClient {
|
||||
// Only add auth token for non-public endpoints
|
||||
if (this.authToken && !isPublicEndpoint) {
|
||||
config.headers.Authorization = `Bearer ${this.authToken}`;
|
||||
console.log('🔑 [API Client] Adding Authorization header for:', config.url);
|
||||
} else if (!isPublicEndpoint) {
|
||||
console.warn('⚠️ [API Client] No auth token available for:', config.url, 'authToken:', this.authToken ? 'exists' : 'missing');
|
||||
}
|
||||
|
||||
// Add tenant ID only for endpoints that require it
|
||||
@@ -343,7 +346,9 @@ class ApiClient {
|
||||
|
||||
// Configuration methods
|
||||
setAuthToken(token: string | null) {
|
||||
console.log('🔧 [API Client] setAuthToken called:', token ? `${token.substring(0, 20)}...` : 'null');
|
||||
this.authToken = token;
|
||||
console.log('✅ [API Client] authToken is now:', this.authToken ? 'set' : 'null');
|
||||
}
|
||||
|
||||
setRefreshToken(token: string | null) {
|
||||
|
||||
89
frontend/src/api/hooks/enterprise.ts
Normal file
89
frontend/src/api/hooks/enterprise.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
|
||||
import { enterpriseService, NetworkSummary, ChildPerformance, DistributionOverview, ForecastSummary, NetworkPerformance } from '../services/enterprise';
|
||||
import { ApiError } from '../client';
|
||||
|
||||
// Query Keys
|
||||
export const enterpriseKeys = {
|
||||
all: ['enterprise'] as const,
|
||||
networkSummary: (tenantId: string) => [...enterpriseKeys.all, 'network-summary', tenantId] as const,
|
||||
childrenPerformance: (tenantId: string, metric: string, period: number) =>
|
||||
[...enterpriseKeys.all, 'children-performance', tenantId, metric, period] as const,
|
||||
distributionOverview: (tenantId: string, date?: string) =>
|
||||
[...enterpriseKeys.all, 'distribution-overview', tenantId, date] as const,
|
||||
forecastSummary: (tenantId: string, days: number) =>
|
||||
[...enterpriseKeys.all, 'forecast-summary', tenantId, days] as const,
|
||||
networkPerformance: (tenantId: string, startDate?: string, endDate?: string) =>
|
||||
[...enterpriseKeys.all, 'network-performance', tenantId, startDate, endDate] as const,
|
||||
} as const;
|
||||
|
||||
// Hooks
|
||||
|
||||
export const useNetworkSummary = (
|
||||
tenantId: string,
|
||||
options?: Omit<UseQueryOptions<NetworkSummary, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<NetworkSummary, ApiError>({
|
||||
queryKey: enterpriseKeys.networkSummary(tenantId),
|
||||
queryFn: () => enterpriseService.getNetworkSummary(tenantId),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 30000, // 30 seconds
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useChildrenPerformance = (
|
||||
tenantId: string,
|
||||
metric: string,
|
||||
period: number,
|
||||
options?: Omit<UseQueryOptions<ChildPerformance, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<ChildPerformance, ApiError>({
|
||||
queryKey: enterpriseKeys.childrenPerformance(tenantId, metric, period),
|
||||
queryFn: () => enterpriseService.getChildrenPerformance(tenantId, metric, period),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 60000, // 1 minute
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useDistributionOverview = (
|
||||
tenantId: string,
|
||||
targetDate?: string,
|
||||
options?: Omit<UseQueryOptions<DistributionOverview, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<DistributionOverview, ApiError>({
|
||||
queryKey: enterpriseKeys.distributionOverview(tenantId, targetDate),
|
||||
queryFn: () => enterpriseService.getDistributionOverview(tenantId, targetDate),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 30000, // 30 seconds
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useForecastSummary = (
|
||||
tenantId: string,
|
||||
daysAhead: number = 7,
|
||||
options?: Omit<UseQueryOptions<ForecastSummary, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<ForecastSummary, ApiError>({
|
||||
queryKey: enterpriseKeys.forecastSummary(tenantId, daysAhead),
|
||||
queryFn: () => enterpriseService.getForecastSummary(tenantId, daysAhead),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 120000, // 2 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useNetworkPerformance = (
|
||||
tenantId: string,
|
||||
startDate?: string,
|
||||
endDate?: string,
|
||||
options?: Omit<UseQueryOptions<NetworkPerformance, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<NetworkPerformance, ApiError>({
|
||||
queryKey: enterpriseKeys.networkPerformance(tenantId, startDate, endDate),
|
||||
queryFn: () => enterpriseService.getNetworkPerformance(tenantId, startDate, endDate),
|
||||
enabled: !!tenantId,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
* Subscription hook for checking plan features and limits
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { subscriptionService } from '../services/subscription';
|
||||
import {
|
||||
SUBSCRIPTION_TIERS,
|
||||
@@ -41,7 +41,7 @@ export const useSubscription = () => {
|
||||
loading: true,
|
||||
});
|
||||
|
||||
const currentTenant = useCurrentTenant();
|
||||
const currentTenant = useCurrentTenant();
|
||||
const user = useAuthUser();
|
||||
const tenantId = currentTenant?.id || user?.tenant_id;
|
||||
const { notifySubscriptionChanged, subscriptionVersion } = useSubscriptionEvents();
|
||||
@@ -72,7 +72,7 @@ export const useSubscription = () => {
|
||||
error: 'Failed to load subscription data'
|
||||
}));
|
||||
}
|
||||
}, [tenantId]); // Removed notifySubscriptionChanged - it's now stable from context
|
||||
}, [tenantId]);
|
||||
|
||||
useEffect(() => {
|
||||
loadSubscriptionData();
|
||||
@@ -99,7 +99,7 @@ export const useSubscription = () => {
|
||||
|
||||
// Check analytics access level
|
||||
const getAnalyticsAccess = useCallback((): { hasAccess: boolean; level: string; reason?: string } => {
|
||||
const { plan } = subscriptionInfo;
|
||||
const plan = subscriptionInfo.plan;
|
||||
|
||||
// Convert plan string to typed SubscriptionTier
|
||||
let tierKey: SubscriptionTier | undefined;
|
||||
|
||||
104
frontend/src/api/services/enterprise.ts
Normal file
104
frontend/src/api/services/enterprise.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { apiClient } from '../client';
|
||||
|
||||
export interface NetworkSummary {
|
||||
parent_tenant_id: string;
|
||||
total_tenants: number;
|
||||
child_tenant_count: number;
|
||||
total_revenue: number;
|
||||
network_sales_30d: number;
|
||||
active_alerts: number;
|
||||
efficiency_score: number;
|
||||
growth_rate: number;
|
||||
production_volume_30d: number;
|
||||
pending_internal_transfers_count: number;
|
||||
active_shipments_count: number;
|
||||
last_updated: string;
|
||||
}
|
||||
|
||||
export interface ChildPerformance {
|
||||
rankings: Array<{
|
||||
tenant_id: string;
|
||||
name: string;
|
||||
anonymized_name: string;
|
||||
metric_value: number;
|
||||
rank: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface DistributionOverview {
|
||||
route_sequences: any[];
|
||||
status_counts: {
|
||||
pending: number;
|
||||
in_transit: number;
|
||||
delivered: number;
|
||||
failed: number;
|
||||
[key: string]: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ForecastSummary {
|
||||
aggregated_forecasts: Record<string, any>;
|
||||
days_forecast: number;
|
||||
last_updated: string;
|
||||
}
|
||||
|
||||
export interface NetworkPerformance {
|
||||
metrics: Record<string, any>;
|
||||
}
|
||||
|
||||
export class EnterpriseService {
|
||||
private readonly baseUrl = '/tenants';
|
||||
|
||||
async getNetworkSummary(tenantId: string): Promise<NetworkSummary> {
|
||||
return apiClient.get<NetworkSummary>(`${this.baseUrl}/${tenantId}/enterprise/network-summary`);
|
||||
}
|
||||
|
||||
async getChildrenPerformance(
|
||||
tenantId: string,
|
||||
metric: string = 'sales',
|
||||
periodDays: number = 30
|
||||
): Promise<ChildPerformance> {
|
||||
const queryParams = new URLSearchParams({
|
||||
metric,
|
||||
period_days: periodDays.toString()
|
||||
});
|
||||
return apiClient.get<ChildPerformance>(
|
||||
`${this.baseUrl}/${tenantId}/enterprise/children-performance?${queryParams.toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
async getDistributionOverview(tenantId: string, targetDate?: string): Promise<DistributionOverview> {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (targetDate) {
|
||||
queryParams.append('target_date', targetDate);
|
||||
}
|
||||
return apiClient.get<DistributionOverview>(
|
||||
`${this.baseUrl}/${tenantId}/enterprise/distribution-overview?${queryParams.toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
async getForecastSummary(tenantId: string, daysAhead: number = 7): Promise<ForecastSummary> {
|
||||
const queryParams = new URLSearchParams({
|
||||
days_ahead: daysAhead.toString()
|
||||
});
|
||||
return apiClient.get<ForecastSummary>(
|
||||
`${this.baseUrl}/${tenantId}/enterprise/forecast-summary?${queryParams.toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
async getNetworkPerformance(
|
||||
tenantId: string,
|
||||
startDate?: string,
|
||||
endDate?: string
|
||||
): Promise<NetworkPerformance> {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (startDate) queryParams.append('start_date', startDate);
|
||||
if (endDate) queryParams.append('end_date', endDate);
|
||||
|
||||
return apiClient.get<NetworkPerformance>(
|
||||
`${this.baseUrl}/${tenantId}/enterprise/network-performance?${queryParams.toString()}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const enterpriseService = new EnterpriseService();
|
||||
@@ -23,10 +23,11 @@ import {
|
||||
} from '../types/subscription';
|
||||
|
||||
// Map plan tiers to analytics levels based on backend data
|
||||
const TIER_TO_ANALYTICS_LEVEL: Record<SubscriptionTier, AnalyticsLevel> = {
|
||||
const TIER_TO_ANALYTICS_LEVEL: Record<SubscriptionTier | string, AnalyticsLevel> = {
|
||||
[SUBSCRIPTION_TIERS.STARTER]: ANALYTICS_LEVELS.BASIC,
|
||||
[SUBSCRIPTION_TIERS.PROFESSIONAL]: ANALYTICS_LEVELS.ADVANCED,
|
||||
[SUBSCRIPTION_TIERS.ENTERPRISE]: ANALYTICS_LEVELS.PREDICTIVE
|
||||
[SUBSCRIPTION_TIERS.ENTERPRISE]: ANALYTICS_LEVELS.PREDICTIVE,
|
||||
'demo': ANALYTICS_LEVELS.ADVANCED, // Treat demo tier same as professional for analytics access
|
||||
};
|
||||
|
||||
// Cache for available plans
|
||||
|
||||
Reference in New Issue
Block a user