changes on updated on calcution in frontend
This commit is contained in:
parent
4da7f519e8
commit
d2d9e0d5c3
@ -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}`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 (
|
||||||
|
|||||||
@ -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 () => {
|
||||||
|
try {
|
||||||
toast({
|
toast({
|
||||||
title: "Download Started",
|
title: "Download Started",
|
||||||
description: "Exporting dealer data to Excel...",
|
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}`);
|
||||||
|
|||||||
@ -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} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -84,11 +84,47 @@ const ScoreCard = () => {
|
|||||||
}
|
}
|
||||||
}, [user, userRole, loading, navigate, id, toast]);
|
}, [user, userRole, loading, navigate, id, toast]);
|
||||||
|
|
||||||
const handleDownload = () => {
|
const handleDownload = async () => {
|
||||||
|
if (!id) {
|
||||||
|
toast({
|
||||||
|
title: "Error",
|
||||||
|
description: "Dealer ID not found",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
toast({
|
toast({
|
||||||
title: "Download Started",
|
title: "Download Started",
|
||||||
description: "Exporting score card as Excel...",
|
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) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user