changes on updated on calcution in frontend

This commit is contained in:
Chandini 2026-01-20 17:24:54 +05:30
parent 4da7f519e8
commit d2d9e0d5c3
6 changed files with 239 additions and 44 deletions

View File

@ -15,6 +15,7 @@ export const API_ENDPOINTS = {
OVERVIEW: '/v1/dealers/overview', OVERVIEW: '/v1/dealers/overview',
STATES: '/v1/dealers/states', STATES: '/v1/dealers/states',
DISTRICTS: '/v1/dealers/districts', DISTRICTS: '/v1/dealers/districts',
EXPORT: '/v1/dealers/export',
SNAPSHOT: (id: string | number) => `/v1/dealerdetails/dealer-snapshot/${id}`, SNAPSHOT: (id: string | number) => `/v1/dealerdetails/dealer-snapshot/${id}`,
CREDIT_SCORE_TREND: (id: string | number) => `/v1/dealerdetails/credit-score-trend/${id}`, CREDIT_SCORE_TREND: (id: string | number) => `/v1/dealerdetails/credit-score-trend/${id}`,
SALES_VS_PURCHASE: (id: string | number) => `/v1/dealerdetails/sales-vs-purchase/${id}`, SALES_VS_PURCHASE: (id: string | number) => `/v1/dealerdetails/sales-vs-purchase/${id}`,
@ -22,5 +23,7 @@ export const API_ENDPOINTS = {
CREDIT_SCORE_BREAKDOWN: (id: string | number) => `/v1/dealerdetails/credit-score-breakdown/${id}`, CREDIT_SCORE_BREAKDOWN: (id: string | number) => `/v1/dealerdetails/credit-score-breakdown/${id}`,
KEY_INSIGHTS: (id: string | number) => `/v1/dealerdetails/key-insights/${id}`, KEY_INSIGHTS: (id: string | number) => `/v1/dealerdetails/key-insights/${id}`,
PRODUCT_WISE_SCORE_TRENDS: (id: string | number) => `/v1/dealerdetails/product-wise-score-trends/${id}`, PRODUCT_WISE_SCORE_TRENDS: (id: string | number) => `/v1/dealerdetails/product-wise-score-trends/${id}`,
PRODUCT_WISE_SCORE_TRENDS_EXPORT: (id: string | number) => `/v1/dealerdetails/product-wise-score-trends/${id}/export`,
COMPARE_DEALER_BUSINESS: (id: string | number) => `/v1/dealerdetails/compare-dealer-business/${id}`,
}, },
}; };

View File

@ -233,4 +233,63 @@ export const dealerService = {
throw error; throw error;
} }
}, },
/**
* Export product-wise score trends to Excel.
* Downloads product-wise monthly score trends as Excel file.
*/
exportProductWiseScoreTrends: async (id: string | number): Promise<Blob> => {
try {
const response = await extendedTimeoutInstance.get(
API_ENDPOINTS.DEALERS.PRODUCT_WISE_SCORE_TRENDS_EXPORT(id),
{
responseType: 'blob', // Important: Set responseType to 'blob' for file download
}
);
return response.data;
} catch (error: any) {
// Handle AbortError gracefully (request was cancelled)
if (error.name === 'AbortError' || error.code === 'ERR_CANCELED') {
throw new Error('Request cancelled');
}
throw error;
}
},
/**
* Get compare dealer business data.
*/
getCompareDealerBusiness: async (id: string | number): Promise<ApiResponse<any>> => {
try {
const response = await axiosInstance.get<ApiResponse<any>>(
API_ENDPOINTS.DEALERS.COMPARE_DEALER_BUSINESS(id)
);
return response.data;
} catch (error) {
throw error;
}
},
/**
* Export dealers list to Excel.
* Downloads filtered and paginated dealer data as Excel file.
*/
exportDealersList: async (params: DealerListParams): Promise<Blob> => {
try {
const response = await extendedTimeoutInstance.get(
API_ENDPOINTS.DEALERS.EXPORT,
{
params,
responseType: 'blob', // Important: Set responseType to 'blob' for file download
}
);
return response.data;
} catch (error: any) {
// Handle AbortError gracefully (request was cancelled)
if (error.name === 'AbortError' || error.code === 'ERR_CANCELED') {
throw new Error('Request cancelled');
}
throw error;
}
},
}; };

View File

@ -45,15 +45,24 @@ DealerSnapshot.displayName = 'DealerSnapshot';
// Compare My Dealer Business variant for manufacturers // Compare My Dealer Business variant for manufacturers
interface ManufacturerData { interface ManufacturerData {
type: string; type: string;
totalCompanies: number; totalCompanies?: number;
activeProducts: number; total_companies_associated?: number;
totalSales: number; activeProducts?: number;
totalPurchase: number; active_products?: number;
avgLiquidityCycle: number; totalSales?: number;
avgAcknowledgmentCycle: number; total_sales_6m_rolling?: number | { value: number; unit: string };
avgStockAge: number; totalPurchase?: number;
agedStock: number; total_purchase_6m_rolling?: number | { value: number; unit: string };
currentStock: number; avgLiquidityCycle?: number;
avg_liquidity_cycle_3m_weighted?: number | { value: number; unit: string };
avgAcknowledgmentCycle?: number;
avg_acknowledgment_cycle_3m_weighted?: number | { value: number; unit: string };
avgStockAge?: number;
avg_stock_age?: number | { value: number; unit: string };
agedStock?: number;
aged_stock_over_90_days?: number | { value: number; unit: string };
currentStock?: number;
current_stock_quantity?: number | { value: number; unit: string };
} }
interface CompareBusinessSnapshotProps { interface CompareBusinessSnapshotProps {
@ -61,17 +70,42 @@ interface CompareBusinessSnapshotProps {
} }
export const CompareBusinessSnapshot = memo(({ data }: CompareBusinessSnapshotProps) => { export const CompareBusinessSnapshot = memo(({ data }: CompareBusinessSnapshotProps) => {
// Helper function to extract value from API response (handles both formats)
const getValue = (value: number | { value: number; unit: string } | undefined, defaultVal: number = 0): number => {
if (value === undefined) return defaultVal;
if (typeof value === 'number') return value;
return value.value || defaultVal;
};
// Helper function to get unit from API response
const getUnit = (value: number | { value: number; unit: string } | undefined, defaultUnit: string = ''): string => {
if (value === undefined || typeof value === 'number') return defaultUnit;
return value.unit || defaultUnit;
};
// Extract values from API response format or fallback to direct values
const type = data.type || 'N/A';
const totalCompanies = data.total_companies_associated ?? data.totalCompanies ?? 0;
const activeProducts = data.active_products ?? data.activeProducts ?? 0;
const totalSales = getValue(data.total_sales_6m_rolling ?? data.totalSales, 0);
const totalPurchase = getValue(data.total_purchase_6m_rolling ?? data.totalPurchase, 0);
const avgLiquidityCycle = getValue(data.avg_liquidity_cycle_3m_weighted ?? data.avgLiquidityCycle, 0);
const avgAcknowledgmentCycle = getValue(data.avg_acknowledgment_cycle_3m_weighted ?? data.avgAcknowledgmentCycle, 0);
const avgStockAge = getValue(data.avg_stock_age ?? data.avgStockAge, 0);
const agedStock = getValue(data.aged_stock_over_90_days ?? data.agedStock, 0);
const currentStock = getValue(data.current_stock_quantity ?? data.currentStock, 0);
const snapshotItems = [ const snapshotItems = [
{ label: "Type", value: data.type }, { label: "Type", value: type },
{ label: "Total Companies Associated", value: data.totalCompanies }, { label: "Total Companies Associated", value: totalCompanies },
{ label: "Active Products", value: data.activeProducts }, { label: "Active Products", value: activeProducts },
{ label: "Total Sales (6M Rolling)", value: `${data.totalSales} MT` }, { label: "Total Sales (6M Rolling)", value: `${totalSales} MT` },
{ label: "Total Purchase (6M Rolling)", value: `${data.totalPurchase} MT` }, { label: "Total Purchase (6M Rolling)", value: `${totalPurchase} MT` },
{ label: "Avg. Liquidity Cycle (3M Weighted)", value: `${data.avgLiquidityCycle > 0 ? data.avgLiquidityCycle.toFixed(1) : 0} Days` }, { label: "Avg. Liquidity Cycle (3M Weighted)", value: `${avgLiquidityCycle > 0 ? avgLiquidityCycle.toFixed(1) : 0} Days` },
{ label: "Avg. Acknowledgment Cycle (3M Weighted)", value: `${data.avgAcknowledgmentCycle > 0 ? data.avgAcknowledgmentCycle.toFixed(1) : 0} Days` }, { label: "Avg. Acknowledgment Cycle (3M Weighted)", value: `${avgAcknowledgmentCycle > 0 ? avgAcknowledgmentCycle.toFixed(1) : 0} Days` },
{ label: "Avg. Stock Age", value: `${data.avgStockAge} Days` }, { label: "Avg. Stock Age", value: `${avgStockAge} Days` },
{ label: "Aged Stock (>90 Days)", value: `${data.agedStock} MT` }, { label: "Aged Stock (>90 Days)", value: `${agedStock} MT` },
{ label: "Current Stock Quantity", value: `${data.currentStock} MT` }, { label: "Current Stock Quantity", value: `${currentStock} MT` },
]; ];
return ( return (

View File

@ -16,6 +16,7 @@ import { useDealerFilters } from "@/hooks/useDealerFilters";
import { useDealerOverview } from "@/hooks/useDealerOverview"; import { useDealerOverview } from "@/hooks/useDealerOverview";
import { useDealersList } from "@/hooks/useDealersList"; import { useDealersList } from "@/hooks/useDealersList";
import { dealerService } from "@/api/services/dealer.service";
const ITEMS_PER_PAGE = 100; const ITEMS_PER_PAGE = 100;
@ -63,12 +64,56 @@ const Dashboard = () => {
}, [user, loading, navigate]); }, [user, loading, navigate]);
// Memoized handlers // Memoized handlers
const handleDownload = useCallback(() => { const handleDownload = useCallback(async () => {
toast({ try {
title: "Download Started", toast({
description: "Exporting dealer data to Excel...", title: "Download Started",
}); description: "Exporting dealer data to Excel...",
}, [toast]); });
// Prepare export parameters with current filters and pagination
const exportParams = {
state: filters.states.length > 0 ? filters.states.join(',') : undefined,
district: filters.districts.length > 0 ? filters.districts.join(',') : undefined,
dealer_type: filters.dealerType === 'all' ? undefined : filters.dealerType,
credit_score_min: filters.minCreditScore > 0 ? filters.minCreditScore : undefined,
credit_score_max: filters.maxCreditScore < 1000 ? filters.maxCreditScore : undefined,
search: searchQuery.trim() || undefined,
page: currentPage,
limit: ITEMS_PER_PAGE,
};
// Remove undefined values from params
Object.keys(exportParams).forEach(key =>
exportParams[key as keyof typeof exportParams] === undefined && delete exportParams[key as keyof typeof exportParams]
);
// Call export API
const blob = await dealerService.exportDealersList(exportParams);
// Create download link
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `dealers_export_${new Date().toISOString().split('T')[0]}.xlsx`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
toast({
title: "Download Complete",
description: "Dealer data exported successfully",
});
} catch (error: any) {
console.error('Error exporting dealers:', error);
toast({
title: "Export Failed",
description: error.message || "Failed to export dealer data. Please try again.",
variant: "destructive",
});
}
}, [toast, filters, searchQuery, currentPage]);
const handleRowClick = useCallback((dealerId: string) => { const handleRowClick = useCallback((dealerId: string) => {
navigate(`/dealer/${dealerId}`); navigate(`/dealer/${dealerId}`);

View File

@ -33,6 +33,7 @@ const DealerProfile = () => {
const [stockAgeData, setStockAgeData] = useState<any[]>([]); const [stockAgeData, setStockAgeData] = useState<any[]>([]);
const [creditScoreBreakdown, setCreditScoreBreakdown] = useState<ScoreParameter[]>([]); const [creditScoreBreakdown, setCreditScoreBreakdown] = useState<ScoreParameter[]>([]);
const [keyInsights, setKeyInsights] = useState<Insight[]>([]); const [keyInsights, setKeyInsights] = useState<Insight[]>([]);
const [compareBusinessData, setCompareBusinessData] = useState<any>(null);
const [fetchingDealer, setFetchingDealer] = useState(true); const [fetchingDealer, setFetchingDealer] = useState(true);
useEffect(() => { useEffect(() => {
@ -46,14 +47,15 @@ const DealerProfile = () => {
try { try {
// Fetch both details and snapshot in parallel // Fetch both details and snapshot in parallel
// Fetch details, snapshot, trend, sales vs purchase, and stock age distribution in parallel // Fetch details, snapshot, trend, sales vs purchase, and stock age distribution in parallel
const [detailsRes, snapshotRes, trendRes, salesRes, stockRes, breakdownRes, insightsRes] = await Promise.all([ const [detailsRes, snapshotRes, trendRes, salesRes, stockRes, breakdownRes, insightsRes, compareRes] = await Promise.all([
dealerService.getDealerDetails(id).catch(e => ({ success: false, data: null })), dealerService.getDealerDetails(id).catch(e => ({ success: false, data: null })),
dealerService.getDealerSnapshot(id).catch(e => ({ success: false, data: null })), dealerService.getDealerSnapshot(id).catch(e => ({ success: false, data: null })),
dealerService.getCreditScoreTrend(id).catch(e => ({ success: false, data: null })), dealerService.getCreditScoreTrend(id).catch(e => ({ success: false, data: null })),
dealerService.getSalesVsPurchase(id).catch(e => ({ success: false, data: null })), dealerService.getSalesVsPurchase(id).catch(e => ({ success: false, data: null })),
dealerService.getStockAgeDistribution(id).catch(e => ({ success: false, data: null })), dealerService.getStockAgeDistribution(id).catch(e => ({ success: false, data: null })),
dealerService.getCreditScoreBreakdown(id).catch(e => ({ success: false, data: null })), dealerService.getCreditScoreBreakdown(id).catch(e => ({ success: false, data: null })),
dealerService.getKeyInsights(id).catch(e => ({ success: false, data: null })) dealerService.getKeyInsights(id).catch(e => ({ success: false, data: null })),
dealerService.getCompareDealerBusiness(id).catch(e => ({ success: false, data: null }))
]); ]);
let mappedDealer: Dealer | null = null; let mappedDealer: Dealer | null = null;
@ -174,6 +176,18 @@ const DealerProfile = () => {
setKeyInsights(insights); setKeyInsights(insights);
} }
// Process Compare Dealer Business Response
console.log('=== FRONTEND: Compare Dealer Business Response ===');
console.log('compareRes.success:', compareRes.success);
console.log('compareRes.data:', compareRes.data);
if (compareRes.success && compareRes.data) {
console.log('Setting compareBusinessData from API');
setCompareBusinessData(compareRes.data);
} else {
console.log('Compare API failed or no data, will use manufacturerData fallback');
console.log('manufacturerData:', manufacturerData);
}
if (mappedDealer) { if (mappedDealer) {
setApiDealer(mappedDealer); setApiDealer(mappedDealer);
} else { } else {
@ -231,19 +245,19 @@ const DealerProfile = () => {
showLastActivityTimeline: userRole === 'helpdesk', showLastActivityTimeline: userRole === 'helpdesk',
}), [userRole]); }), [userRole]);
// Mock manufacturer data // Dynamic manufacturer data from dealer snapshot (same data source as DealerSnapshot)
const manufacturerData = useMemo(() => ({ const manufacturerData = useMemo(() => ({
type: dealer?.dealerType || 'Retailer', type: dealer?.dealerType || 'Retailer',
totalCompanies: 1, totalCompanies: dealer?.noOfCompanies || 0,
activeProducts: 3, activeProducts: dealer?.noOfProducts || 0,
totalSales: 100, totalSales: dealer?.totalSales6M || 0,
totalPurchase: 312, totalPurchase: dealer?.totalPurchase6M || 0,
avgLiquidityCycle: 20, avgLiquidityCycle: dealer?.avgLiquidityCycle || 0,
avgAcknowledgmentCycle: 3, avgAcknowledgmentCycle: dealer?.avgAcknowledgmentCycle || 0,
avgStockAge: 22, avgStockAge: dealer?.stockAge || 0,
agedStock: 144, agedStock: dealer?.agedStock || 0,
currentStock: 344, currentStock: dealer?.currentStock || 0,
}), [dealer?.dealerType]); }), [dealer]);
// Navigation handlers // Navigation handlers
const handleBack = useCallback(() => navigate("/"), [navigate]); const handleBack = useCallback(() => navigate("/"), [navigate]);
@ -318,7 +332,11 @@ const DealerProfile = () => {
)} )}
{/* Compare My Dealer Business (only for manufacturers) */} {/* Compare My Dealer Business (only for manufacturers) */}
{isManufacturingCustomer && ( {isManufacturingCustomer && compareBusinessData && (
<CompareBusinessSnapshot data={compareBusinessData} />
)}
{/* Fallback to manufacturerData if API data not available */}
{isManufacturingCustomer && !compareBusinessData && (
<CompareBusinessSnapshot data={manufacturerData} /> <CompareBusinessSnapshot data={manufacturerData} />
)} )}

View File

@ -84,11 +84,47 @@ const ScoreCard = () => {
} }
}, [user, userRole, loading, navigate, id, toast]); }, [user, userRole, loading, navigate, id, toast]);
const handleDownload = () => { const handleDownload = async () => {
toast({ if (!id) {
title: "Download Started", toast({
description: "Exporting score card as Excel...", title: "Error",
}); description: "Dealer ID not found",
variant: "destructive",
});
return;
}
try {
toast({
title: "Download Started",
description: "Exporting score card as Excel...",
});
// Call export API
const blob = await dealerService.exportProductWiseScoreTrends(id);
// Create download link
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `product_score_trends_${id}_${new Date().toISOString().split('T')[0]}.xlsx`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
toast({
title: "Download Complete",
description: "Score card exported successfully",
});
} catch (error: any) {
console.error('Error exporting score card:', error);
toast({
title: "Export Failed",
description: error.message || "Failed to export score card. Please try again.",
variant: "destructive",
});
}
}; };
const getScoreColor = (score: number) => { const getScoreColor = (score: number) => {