From d2d9e0d5c304fe4933bb5fd184faeed2e7ab351b Mon Sep 17 00:00:00 2001 From: Chandini Date: Tue, 20 Jan 2026 17:24:54 +0530 Subject: [PATCH] changes on updated on calcution in frontend --- src/api/endpoints.ts | 3 + src/api/services/dealer.service.ts | 59 +++++++++++++++++++ src/components/dealer/DealerSnapshot.tsx | 72 +++++++++++++++++------- src/pages/Dashboard.tsx | 57 +++++++++++++++++-- src/pages/DealerProfile.tsx | 46 ++++++++++----- src/pages/ScoreCard.tsx | 46 +++++++++++++-- 6 files changed, 239 insertions(+), 44 deletions(-) diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts index 121618c..387b656 100644 --- a/src/api/endpoints.ts +++ b/src/api/endpoints.ts @@ -15,6 +15,7 @@ export const API_ENDPOINTS = { OVERVIEW: '/v1/dealers/overview', STATES: '/v1/dealers/states', DISTRICTS: '/v1/dealers/districts', + EXPORT: '/v1/dealers/export', SNAPSHOT: (id: string | number) => `/v1/dealerdetails/dealer-snapshot/${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}`, @@ -22,5 +23,7 @@ export const API_ENDPOINTS = { CREDIT_SCORE_BREAKDOWN: (id: string | number) => `/v1/dealerdetails/credit-score-breakdown/${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_EXPORT: (id: string | number) => `/v1/dealerdetails/product-wise-score-trends/${id}/export`, + COMPARE_DEALER_BUSINESS: (id: string | number) => `/v1/dealerdetails/compare-dealer-business/${id}`, }, }; diff --git a/src/api/services/dealer.service.ts b/src/api/services/dealer.service.ts index b0f0e57..be5729d 100644 --- a/src/api/services/dealer.service.ts +++ b/src/api/services/dealer.service.ts @@ -233,4 +233,63 @@ export const dealerService = { throw error; } }, + + /** + * Export product-wise score trends to Excel. + * Downloads product-wise monthly score trends as Excel file. + */ + exportProductWiseScoreTrends: async (id: string | number): Promise => { + 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> => { + try { + const response = await axiosInstance.get>( + 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 => { + 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; + } + }, }; diff --git a/src/components/dealer/DealerSnapshot.tsx b/src/components/dealer/DealerSnapshot.tsx index 2e1e399..7161d65 100644 --- a/src/components/dealer/DealerSnapshot.tsx +++ b/src/components/dealer/DealerSnapshot.tsx @@ -45,15 +45,24 @@ DealerSnapshot.displayName = 'DealerSnapshot'; // Compare My Dealer Business variant for manufacturers interface ManufacturerData { type: string; - totalCompanies: number; - activeProducts: number; - totalSales: number; - totalPurchase: number; - avgLiquidityCycle: number; - avgAcknowledgmentCycle: number; - avgStockAge: number; - agedStock: number; - currentStock: number; + totalCompanies?: number; + total_companies_associated?: number; + activeProducts?: number; + active_products?: number; + totalSales?: number; + total_sales_6m_rolling?: number | { value: number; unit: string }; + totalPurchase?: number; + total_purchase_6m_rolling?: number | { value: number; unit: string }; + 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 { @@ -61,17 +70,42 @@ interface 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 = [ - { label: "Type", value: data.type }, - { label: "Total Companies Associated", value: data.totalCompanies }, - { label: "Active Products", value: data.activeProducts }, - { label: "Total Sales (6M Rolling)", value: `${data.totalSales} MT` }, - { label: "Total Purchase (6M Rolling)", value: `${data.totalPurchase} MT` }, - { label: "Avg. Liquidity Cycle (3M Weighted)", value: `${data.avgLiquidityCycle > 0 ? data.avgLiquidityCycle.toFixed(1) : 0} Days` }, - { label: "Avg. Acknowledgment Cycle (3M Weighted)", value: `${data.avgAcknowledgmentCycle > 0 ? data.avgAcknowledgmentCycle.toFixed(1) : 0} Days` }, - { label: "Avg. Stock Age", value: `${data.avgStockAge} Days` }, - { label: "Aged Stock (>90 Days)", value: `${data.agedStock} MT` }, - { label: "Current Stock Quantity", value: `${data.currentStock} MT` }, + { label: "Type", value: type }, + { label: "Total Companies Associated", value: totalCompanies }, + { label: "Active Products", value: activeProducts }, + { label: "Total Sales (6M Rolling)", value: `${totalSales} MT` }, + { label: "Total Purchase (6M Rolling)", value: `${totalPurchase} MT` }, + { label: "Avg. Liquidity Cycle (3M Weighted)", value: `${avgLiquidityCycle > 0 ? avgLiquidityCycle.toFixed(1) : 0} Days` }, + { label: "Avg. Acknowledgment Cycle (3M Weighted)", value: `${avgAcknowledgmentCycle > 0 ? avgAcknowledgmentCycle.toFixed(1) : 0} Days` }, + { label: "Avg. Stock Age", value: `${avgStockAge} Days` }, + { label: "Aged Stock (>90 Days)", value: `${agedStock} MT` }, + { label: "Current Stock Quantity", value: `${currentStock} MT` }, ]; return ( diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index c2de518..cbc3724 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -16,6 +16,7 @@ import { useDealerFilters } from "@/hooks/useDealerFilters"; import { useDealerOverview } from "@/hooks/useDealerOverview"; import { useDealersList } from "@/hooks/useDealersList"; +import { dealerService } from "@/api/services/dealer.service"; const ITEMS_PER_PAGE = 100; @@ -63,12 +64,56 @@ const Dashboard = () => { }, [user, loading, navigate]); // Memoized handlers - const handleDownload = useCallback(() => { - toast({ - title: "Download Started", - description: "Exporting dealer data to Excel...", - }); - }, [toast]); + const handleDownload = useCallback(async () => { + try { + toast({ + title: "Download Started", + description: "Exporting dealer data to Excel...", + }); + + // 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) => { navigate(`/dealer/${dealerId}`); diff --git a/src/pages/DealerProfile.tsx b/src/pages/DealerProfile.tsx index 8acd156..dd1a882 100644 --- a/src/pages/DealerProfile.tsx +++ b/src/pages/DealerProfile.tsx @@ -33,6 +33,7 @@ const DealerProfile = () => { const [stockAgeData, setStockAgeData] = useState([]); const [creditScoreBreakdown, setCreditScoreBreakdown] = useState([]); const [keyInsights, setKeyInsights] = useState([]); + const [compareBusinessData, setCompareBusinessData] = useState(null); const [fetchingDealer, setFetchingDealer] = useState(true); useEffect(() => { @@ -46,14 +47,15 @@ const DealerProfile = () => { try { // Fetch both details and snapshot 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.getDealerSnapshot(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.getStockAgeDistribution(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; @@ -174,6 +176,18 @@ const DealerProfile = () => { 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) { setApiDealer(mappedDealer); } else { @@ -231,19 +245,19 @@ const DealerProfile = () => { showLastActivityTimeline: userRole === 'helpdesk', }), [userRole]); - // Mock manufacturer data + // Dynamic manufacturer data from dealer snapshot (same data source as DealerSnapshot) const manufacturerData = useMemo(() => ({ type: dealer?.dealerType || 'Retailer', - totalCompanies: 1, - activeProducts: 3, - totalSales: 100, - totalPurchase: 312, - avgLiquidityCycle: 20, - avgAcknowledgmentCycle: 3, - avgStockAge: 22, - agedStock: 144, - currentStock: 344, - }), [dealer?.dealerType]); + totalCompanies: dealer?.noOfCompanies || 0, + activeProducts: dealer?.noOfProducts || 0, + totalSales: dealer?.totalSales6M || 0, + totalPurchase: dealer?.totalPurchase6M || 0, + avgLiquidityCycle: dealer?.avgLiquidityCycle || 0, + avgAcknowledgmentCycle: dealer?.avgAcknowledgmentCycle || 0, + avgStockAge: dealer?.stockAge || 0, + agedStock: dealer?.agedStock || 0, + currentStock: dealer?.currentStock || 0, + }), [dealer]); // Navigation handlers const handleBack = useCallback(() => navigate("/"), [navigate]); @@ -318,7 +332,11 @@ const DealerProfile = () => { )} {/* Compare My Dealer Business (only for manufacturers) */} - {isManufacturingCustomer && ( + {isManufacturingCustomer && compareBusinessData && ( + + )} + {/* Fallback to manufacturerData if API data not available */} + {isManufacturingCustomer && !compareBusinessData && ( )} diff --git a/src/pages/ScoreCard.tsx b/src/pages/ScoreCard.tsx index 0155d29..962dad1 100644 --- a/src/pages/ScoreCard.tsx +++ b/src/pages/ScoreCard.tsx @@ -84,11 +84,47 @@ const ScoreCard = () => { } }, [user, userRole, loading, navigate, id, toast]); - const handleDownload = () => { - toast({ - title: "Download Started", - description: "Exporting score card as Excel...", - }); + const handleDownload = async () => { + if (!id) { + toast({ + 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) => {