MichaelEdou
Set default branch to Montreal for unclassified Interac transactions
fe203ef
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Loader2 } from 'lucide-react';
import { cn } from '@/lib/utils';
interface BranchData {
branch: string;
count: number;
amount: number;
}
interface BranchChartProps {
branches: BranchData[];
isLoading?: boolean;
}
const barColors = [
{ gradient: 'from-blue-500 to-blue-400', glow: 'shadow-[0_0_15px_rgba(59,130,246,0.3)]', hover: 'group-hover:text-blue-500' },
{ gradient: 'from-purple-500 to-purple-400', glow: 'shadow-[0_0_15px_rgba(139,92,246,0.3)]', hover: 'group-hover:text-purple-500' },
{ gradient: 'from-teal-500 to-teal-400', glow: 'shadow-[0_0_15px_rgba(20,184,166,0.3)]', hover: 'group-hover:text-teal-500' },
{ gradient: 'from-sky-500 to-sky-400', glow: 'shadow-[0_0_15px_rgba(14,165,233,0.3)]', hover: 'group-hover:text-sky-500' },
{ gradient: 'from-indigo-500 to-indigo-400', glow: 'shadow-[0_0_15px_rgba(99,102,241,0.3)]', hover: 'group-hover:text-indigo-500' },
{ gradient: 'from-rose-500 to-rose-400', glow: 'shadow-[0_0_15px_rgba(244,63,94,0.3)]', hover: 'group-hover:text-rose-500' },
{ gradient: 'from-amber-500 to-amber-400', glow: 'shadow-[0_0_15px_rgba(245,158,11,0.3)]', hover: 'group-hover:text-amber-500' },
{ gradient: 'from-emerald-500 to-emerald-400', glow: 'shadow-[0_0_15px_rgba(16,185,129,0.3)]', hover: 'group-hover:text-emerald-500' },
];
function formatAmount(amount: number): string {
if (amount >= 1_000_000) return `${(amount / 1_000_000).toFixed(1)}M`;
if (amount >= 1_000) return `${(amount / 1_000).toFixed(0)}k`;
return amount.toFixed(0);
}
export default function BranchChart({ branches, isLoading }: BranchChartProps) {
const { t } = useTranslation();
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const maxAmount = Math.max(...branches.map((b) => b.amount), 1);
// Generate Y-axis labels based on actual data
const yMax = Math.ceil(maxAmount / 1000) * 1000;
const yLabels = [formatAmount(yMax), formatAmount(yMax * 0.66), formatAmount(yMax * 0.33), '0'];
return (
<div className="lg:col-span-2 bg-card rounded-xl border border-border p-6 shadow-[0_10px_15px_-3px_rgba(0,0,0,0.05),0_4px_6px_-2px_rgba(0,0,0,0.025)] flex flex-col">
<div className="flex justify-between items-center mb-8">
<div>
<h3 className="text-lg font-bold text-foreground">
{t('reports.chart.title')}
</h3>
<p className="text-xs text-muted-foreground">
{t('reports.chart.subtitle')}
</p>
</div>
</div>
{isLoading ? (
<div className="flex-1 flex items-center justify-center h-64">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
) : branches.length === 0 ? (
<div className="flex-1 flex items-center justify-center h-64 text-muted-foreground text-sm">
{t('reports.chart.noData', 'Aucune donnée disponible')}
</div>
) : (
<div className="flex-1 flex items-end gap-6 md:gap-10 h-64 w-full px-4 pb-2 relative">
{/* Grid lines */}
<div className="absolute inset-0 flex flex-col justify-between pointer-events-none pb-2 pl-8">
{[...Array(3)].map((_, i) => (
<div key={i} className="w-full border-t border-slate-100 border-dashed h-0" />
))}
<div className="w-full border-t border-slate-200 h-0" />
</div>
{/* Y-axis labels */}
<div className="absolute left-0 bottom-0 top-0 w-8 flex flex-col justify-between text-[10px] font-medium text-slate-400 h-full py-2 text-right pr-2">
{yLabels.map((label, i) => (
<span key={i}>{label}</span>
))}
</div>
{/* Bars */}
{branches.map((branch, index) => {
const color = barColors[index % barColors.length];
const heightPct = Math.max(5, (branch.amount / maxAmount) * 100);
const formattedAmt = branch.amount.toLocaleString('fr-CA', {
style: 'currency',
currency: 'CAD',
minimumFractionDigits: 0,
});
return (
<div
key={branch.branch}
className="flex-1 flex flex-col justify-end group cursor-pointer h-full z-10"
onMouseEnter={() => setHoveredIndex(index)}
onMouseLeave={() => setHoveredIndex(null)}
>
<div
className="w-full bg-slate-50 rounded-t-lg relative transition-all duration-300 flex flex-col justify-end overflow-hidden"
style={{ height: `${heightPct}%` }}
>
<div
className={cn(
'w-full h-full rounded-t-lg relative transition-all duration-300 origin-bottom',
`bg-gradient-to-t ${color.gradient}`,
color.glow,
'group-hover:brightness-110 group-hover:scale-y-105'
)}
>
{/* Tooltip */}
<div
className={cn(
'absolute -top-14 left-1/2 -translate-x-1/2 bg-slate-800 text-white text-xs font-medium rounded-lg py-1.5 px-3 pointer-events-none whitespace-nowrap z-20 shadow-xl border border-slate-700 transition-opacity',
hoveredIndex === index ? 'opacity-100' : 'opacity-0'
)}
>
<div className="font-bold text-blue-200">{formattedAmt}</div>
<div className="text-[10px] text-slate-300">
{branch.count} Transactions
</div>
<div className="absolute bottom-[-4px] left-1/2 -translate-x-1/2 w-2 h-2 bg-slate-800 rotate-45 border-r border-b border-slate-700" />
</div>
</div>
</div>
<span
className={cn(
'text-xs text-center mt-3 text-muted-foreground font-semibold truncate transition-colors',
color.hover
)}
>
{branch.branch || 'Montreal'}
</span>
</div>
);
})}
</div>
)}
</div>
);
}