mirror of
https://github.com/johndoe6345789/workforce-pay-bill-p.git
synced 2026-04-24 13:24:57 +00:00
540 lines
25 KiB
TypeScript
540 lines
25 KiB
TypeScript
import { useState } from 'react'
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
|
import {
|
|
TrendUp,
|
|
TrendDown,
|
|
ChartBar,
|
|
Download,
|
|
Calendar,
|
|
CurrencyDollar,
|
|
ArrowUp,
|
|
ArrowDown,
|
|
ChartLine,
|
|
Lightning
|
|
} from '@phosphor-icons/react'
|
|
import { useInvoicesCrud } from '@/hooks/use-invoices-crud'
|
|
import { usePayrollCrud } from '@/hooks/use-payroll-crud'
|
|
import { useTranslation } from '@/hooks/use-translation'
|
|
import { useDataExport } from '@/hooks/use-data-export'
|
|
import type { MarginAnalysis, ForecastData } from '@/lib/types'
|
|
import { cn } from '@/lib/utils'
|
|
import { toast } from 'sonner'
|
|
|
|
export function ReportsView() {
|
|
const [selectedPeriod, setSelectedPeriod] = useState<'week' | 'month' | 'quarter' | 'year'>('month')
|
|
const [selectedYear, setSelectedYear] = useState('2025')
|
|
const { t } = useTranslation()
|
|
|
|
const { invoices } = useInvoicesCrud()
|
|
const { payrollRuns } = usePayrollCrud()
|
|
const { exportToCSV, exportToExcel } = useDataExport()
|
|
|
|
const calculateMarginAnalysis = (): MarginAnalysis[] => {
|
|
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
|
const currentMonth = new Date().getMonth()
|
|
|
|
return months.slice(0, currentMonth + 1).map((month, index) => {
|
|
const monthRevenue = invoices
|
|
.filter(inv => {
|
|
const invDate = new Date(inv.issueDate)
|
|
return invDate.getMonth() === index && invDate.getFullYear() === 2025
|
|
})
|
|
.reduce((sum, inv) => sum + inv.amount, 0)
|
|
|
|
const monthCosts = payrollRuns
|
|
.filter(pr => {
|
|
const prDate = new Date(pr.periodEnding)
|
|
return prDate.getMonth() === index && prDate.getFullYear() === 2025
|
|
})
|
|
.reduce((sum, pr) => sum + pr.totalAmount, 0)
|
|
|
|
const margin = monthRevenue - monthCosts
|
|
const marginPercentage = monthRevenue > 0 ? (margin / monthRevenue) * 100 : 0
|
|
|
|
return {
|
|
period: month,
|
|
revenue: monthRevenue,
|
|
costs: monthCosts,
|
|
margin,
|
|
marginPercentage
|
|
}
|
|
})
|
|
}
|
|
|
|
const generateForecast = (): ForecastData[] => {
|
|
const historicalData = calculateMarginAnalysis()
|
|
if (historicalData.length < 2) return []
|
|
|
|
const avgRevenue = historicalData.reduce((sum, d) => sum + d.revenue, 0) / historicalData.length
|
|
const avgCosts = historicalData.reduce((sum, d) => sum + d.costs, 0) / historicalData.length
|
|
|
|
const revenueGrowthRate = historicalData.length > 1
|
|
? (historicalData[historicalData.length - 1].revenue - historicalData[0].revenue) / historicalData[0].revenue / historicalData.length
|
|
: 0.05
|
|
|
|
const months = ['Feb', 'Mar', 'Apr', 'May', 'Jun']
|
|
const currentMonth = new Date().getMonth()
|
|
const futureMonths = months.slice(currentMonth + 1, currentMonth + 4)
|
|
|
|
return futureMonths.map((month, index) => {
|
|
const predictedRevenue = avgRevenue * (1 + revenueGrowthRate * (index + 1))
|
|
const predictedCosts = avgCosts * (1 + revenueGrowthRate * 0.7 * (index + 1))
|
|
const predictedMargin = predictedRevenue - predictedCosts
|
|
const confidence = Math.max(60, 95 - (index * 10))
|
|
|
|
return {
|
|
period: month,
|
|
predictedRevenue,
|
|
predictedCosts,
|
|
predictedMargin,
|
|
confidence
|
|
}
|
|
})
|
|
}
|
|
|
|
const marginAnalysis = calculateMarginAnalysis()
|
|
const forecast = generateForecast()
|
|
|
|
const totalRevenue = marginAnalysis.reduce((sum, m) => sum + m.revenue, 0)
|
|
const totalCosts = marginAnalysis.reduce((sum, m) => sum + m.costs, 0)
|
|
const totalMargin = totalRevenue - totalCosts
|
|
const avgMarginPercentage = totalRevenue > 0 ? (totalMargin / totalRevenue) * 100 : 0
|
|
|
|
const lastMonth = marginAnalysis[marginAnalysis.length - 1]
|
|
const prevMonth = marginAnalysis[marginAnalysis.length - 2]
|
|
const monthOverMonthChange = prevMonth
|
|
? ((lastMonth.marginPercentage - prevMonth.marginPercentage) / Math.abs(prevMonth.marginPercentage)) * 100
|
|
: 0
|
|
|
|
const maxValue = Math.max(
|
|
...marginAnalysis.map(m => Math.max(m.revenue, m.costs)),
|
|
...forecast.map(f => Math.max(f.predictedRevenue, f.predictedCosts))
|
|
)
|
|
|
|
const handleExportMarginAnalysis = () => {
|
|
try {
|
|
const exportData = marginAnalysis.map(item => ({
|
|
Period: item.period,
|
|
Year: selectedYear,
|
|
Revenue: item.revenue,
|
|
Costs: item.costs,
|
|
Margin: item.margin,
|
|
'Margin %': item.marginPercentage.toFixed(2)
|
|
}))
|
|
exportToCSV(exportData, { filename: `margin-analysis-${selectedYear}` })
|
|
toast.success(t('reports.exportSuccess') || 'Margin analysis exported successfully')
|
|
} catch (error) {
|
|
toast.error(t('reports.exportError') || 'Failed to export margin analysis')
|
|
}
|
|
}
|
|
|
|
const handleExportForecast = () => {
|
|
try {
|
|
const exportData = forecast.map(item => ({
|
|
Period: item.period,
|
|
Year: selectedYear,
|
|
'Predicted Revenue': item.predictedRevenue,
|
|
'Predicted Costs': item.predictedCosts,
|
|
'Predicted Margin': item.predictedMargin,
|
|
'Confidence %': item.confidence
|
|
}))
|
|
exportToCSV(exportData, { filename: `forecast-${selectedYear}` })
|
|
toast.success(t('reports.exportSuccess') || 'Forecast data exported successfully')
|
|
} catch (error) {
|
|
toast.error(t('reports.exportError') || 'Failed to export forecast data')
|
|
}
|
|
}
|
|
|
|
const handleExportAll = () => {
|
|
try {
|
|
const combinedData = [
|
|
...marginAnalysis.map(item => ({
|
|
Type: 'Actual',
|
|
Period: item.period,
|
|
Year: selectedYear,
|
|
Revenue: item.revenue,
|
|
Costs: item.costs,
|
|
Margin: item.margin,
|
|
'Margin %': item.marginPercentage.toFixed(2),
|
|
Confidence: 100
|
|
})),
|
|
...forecast.map(item => ({
|
|
Type: 'Forecast',
|
|
Period: item.period,
|
|
Year: selectedYear,
|
|
Revenue: item.predictedRevenue,
|
|
Costs: item.predictedCosts,
|
|
Margin: item.predictedMargin,
|
|
'Margin %': ((item.predictedMargin / item.predictedRevenue) * 100).toFixed(2),
|
|
Confidence: item.confidence
|
|
}))
|
|
]
|
|
exportToExcel(combinedData, { filename: `financial-report-${selectedYear}` })
|
|
toast.success(t('reports.exportSuccess') || 'Complete report exported successfully')
|
|
} catch (error) {
|
|
toast.error(t('reports.exportError') || 'Failed to export complete report')
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h2 className="text-3xl font-semibold tracking-tight">{t('reports.title')}</h2>
|
|
<p className="text-muted-foreground mt-1">{t('reports.subtitle')}</p>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Select value={selectedYear} onValueChange={setSelectedYear}>
|
|
<SelectTrigger className="w-32">
|
|
<Calendar size={16} className="mr-2" />
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="2025">2025</SelectItem>
|
|
<SelectItem value="2024">2024</SelectItem>
|
|
<SelectItem value="2023">2023</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<Button variant="outline" onClick={handleExportAll}>
|
|
<Download size={18} className="mr-2" />
|
|
{t('reports.exportReport')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<Card className="border-l-4 border-success/20">
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm text-muted-foreground">{t('reports.totalRevenueYTD')}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-semibold font-mono">£{totalRevenue.toLocaleString()}</div>
|
|
<div className="flex items-center gap-1 mt-1 text-xs text-success">
|
|
<TrendUp size={14} weight="bold" />
|
|
<span>{t('reports.yearToDate')}</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="border-l-4 border-warning/20">
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm text-muted-foreground">{t('reports.totalCostsYTD')}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-semibold font-mono">£{totalCosts.toLocaleString()}</div>
|
|
<div className="flex items-center gap-1 mt-1 text-xs text-muted-foreground">
|
|
<TrendUp size={14} weight="bold" />
|
|
<span>{t('reports.yearToDate')}</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="border-l-4 border-accent/20">
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm text-muted-foreground">{t('reports.grossMarginYTD')}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-semibold font-mono">£{totalMargin.toLocaleString()}</div>
|
|
<div className="flex items-center gap-1 mt-1 text-xs text-accent">
|
|
<span className="font-medium">{avgMarginPercentage.toFixed(1)}%</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="border-l-4 border-primary/20">
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm text-muted-foreground">{t('reports.momChange')}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className={cn(
|
|
"text-2xl font-semibold font-mono",
|
|
monthOverMonthChange >= 0 ? "text-success" : "text-destructive"
|
|
)}>
|
|
{monthOverMonthChange >= 0 ? '+' : ''}{monthOverMonthChange.toFixed(1)}%
|
|
</div>
|
|
<div className="flex items-center gap-1 mt-1 text-xs text-muted-foreground">
|
|
{monthOverMonthChange >= 0 ? (
|
|
<TrendUp size={14} weight="bold" className="text-success" />
|
|
) : (
|
|
<TrendDown size={14} weight="bold" className="text-destructive" />
|
|
)}
|
|
<span>{t('reports.vsLastMonth')}</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
<Tabs defaultValue="margin-analysis" className="space-y-4">
|
|
<TabsList>
|
|
<TabsTrigger value="margin-analysis">
|
|
<ChartBar size={16} className="mr-2" />
|
|
{t('reports.tabs.marginAnalysis')}
|
|
</TabsTrigger>
|
|
<TabsTrigger value="forecasting">
|
|
<ChartLine size={16} className="mr-2" />
|
|
{t('reports.tabs.forecasting')}
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="margin-analysis" className="space-y-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<CardTitle>{t('reports.marginAnalysisTitle')}</CardTitle>
|
|
<CardDescription>{t('reports.marginAnalysisDescription', { year: selectedYear })}</CardDescription>
|
|
</div>
|
|
<Button variant="outline" size="sm" onClick={handleExportMarginAnalysis}>
|
|
<Download size={16} className="mr-2" />
|
|
Export CSV
|
|
</Button>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-6">
|
|
<div className="h-64 flex items-end justify-between gap-2">
|
|
{marginAnalysis.map((data, index) => {
|
|
const revenueHeight = (data.revenue / maxValue) * 100
|
|
const costsHeight = (data.costs / maxValue) * 100
|
|
|
|
return (
|
|
<div key={data.period} className="flex-1 flex flex-col items-center gap-2">
|
|
<div className="w-full flex justify-center gap-1" style={{ height: '100%' }}>
|
|
<div className="flex flex-col justify-end w-full max-w-12">
|
|
<div
|
|
className="bg-accent rounded-t transition-all hover:opacity-80 cursor-pointer relative group"
|
|
style={{ height: `${revenueHeight}%` }}
|
|
>
|
|
<div className="absolute -top-8 left-1/2 -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity bg-popover border border-border rounded px-2 py-1 text-xs whitespace-nowrap pointer-events-none z-10">
|
|
£{data.revenue.toLocaleString()}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col justify-end w-full max-w-12">
|
|
<div
|
|
className="bg-warning/60 rounded-t transition-all hover:opacity-80 cursor-pointer relative group"
|
|
style={{ height: `${costsHeight}%` }}
|
|
>
|
|
<div className="absolute -top-8 left-1/2 -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity bg-popover border border-border rounded px-2 py-1 text-xs whitespace-nowrap pointer-events-none z-10">
|
|
£{data.costs.toLocaleString()}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="text-xs font-medium text-muted-foreground">{data.period}</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
<div className="flex items-center justify-center gap-6 pt-4 border-t border-border">
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-4 h-4 bg-accent rounded" />
|
|
<span className="text-sm">{t('reports.revenue')}</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-4 h-4 bg-warning/60 rounded" />
|
|
<span className="text-sm">{t('reports.costs')}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 pt-4 border-t border-border">
|
|
{marginAnalysis.slice(-3).map((data) => (
|
|
<Card key={data.period}>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm">{data.period} {selectedYear}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-2">
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-muted-foreground">{t('reports.revenue')}</span>
|
|
<span className="font-mono font-medium">£{data.revenue.toLocaleString()}</span>
|
|
</div>
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-muted-foreground">{t('reports.costs')}</span>
|
|
<span className="font-mono font-medium">£{data.costs.toLocaleString()}</span>
|
|
</div>
|
|
<div className="flex justify-between text-sm pt-2 border-t border-border">
|
|
<span className="text-muted-foreground font-medium">{t('reports.margin')}</span>
|
|
<div className="text-right">
|
|
<div className="font-mono font-semibold">£{data.margin.toLocaleString()}</div>
|
|
<div className={cn(
|
|
"text-xs font-medium",
|
|
data.marginPercentage >= 25 ? "text-success" :
|
|
data.marginPercentage >= 15 ? "text-warning" : "text-destructive"
|
|
)}>
|
|
{data.marginPercentage.toFixed(1)}%
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="forecasting" className="space-y-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Lightning size={20} weight="fill" className="text-accent" />
|
|
{t('reports.forecastingTitle')}
|
|
</CardTitle>
|
|
<CardDescription>
|
|
{t('reports.forecastingDescription')}
|
|
</CardDescription>
|
|
</div>
|
|
<Button variant="outline" size="sm" onClick={handleExportForecast} disabled={forecast.length === 0}>
|
|
<Download size={16} className="mr-2" />
|
|
Export CSV
|
|
</Button>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{forecast.length === 0 ? (
|
|
<div className="text-center py-12 text-muted-foreground">
|
|
<ChartLine size={48} className="mx-auto mb-4 opacity-50" />
|
|
<p>{t('reports.notEnoughData')}</p>
|
|
<p className="text-sm mt-2">{t('reports.notEnoughDataDescription')}</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-6">
|
|
<div className="h-64 flex items-end justify-between gap-2">
|
|
{[...marginAnalysis.slice(-2), ...forecast].map((data, index) => {
|
|
const isHistorical = index < 2
|
|
const revenue = isHistorical ? (data as MarginAnalysis).revenue : (data as ForecastData).predictedRevenue
|
|
const costs = isHistorical ? (data as MarginAnalysis).costs : (data as ForecastData).predictedCosts
|
|
|
|
const revenueHeight = (revenue / maxValue) * 100
|
|
const costsHeight = (costs / maxValue) * 100
|
|
|
|
return (
|
|
<div key={data.period} className="flex-1 flex flex-col items-center gap-2">
|
|
<div className="w-full flex justify-center gap-1" style={{ height: '100%' }}>
|
|
<div className="flex flex-col justify-end w-full max-w-12">
|
|
<div
|
|
className={cn(
|
|
"rounded-t transition-all hover:opacity-80 cursor-pointer relative group",
|
|
isHistorical ? "bg-accent" : "bg-accent/40 border-2 border-dashed border-accent"
|
|
)}
|
|
style={{ height: `${revenueHeight}%` }}
|
|
>
|
|
<div className="absolute -top-8 left-1/2 -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity bg-popover border border-border rounded px-2 py-1 text-xs whitespace-nowrap pointer-events-none z-10">
|
|
£{revenue.toLocaleString()}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col justify-end w-full max-w-12">
|
|
<div
|
|
className={cn(
|
|
"rounded-t transition-all hover:opacity-80 cursor-pointer relative group",
|
|
isHistorical ? "bg-warning/60" : "bg-warning/20 border-2 border-dashed border-warning"
|
|
)}
|
|
style={{ height: `${costsHeight}%` }}
|
|
>
|
|
<div className="absolute -top-8 left-1/2 -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity bg-popover border border-border rounded px-2 py-1 text-xs whitespace-nowrap pointer-events-none z-10">
|
|
£{costs.toLocaleString()}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="text-xs font-medium text-muted-foreground">{data.period}</div>
|
|
{!isHistorical && (
|
|
<div className="text-xs text-accent font-medium">
|
|
{(data as ForecastData).confidence}%
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
<div className="flex items-center justify-center gap-6 pt-4 border-t border-border">
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-4 h-4 bg-accent rounded" />
|
|
<span className="text-sm">{t('reports.actualRevenue')}</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-4 h-4 bg-accent/40 border-2 border-dashed border-accent rounded" />
|
|
<span className="text-sm">{t('reports.predictedRevenue')}</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-4 h-4 bg-warning/60 rounded" />
|
|
<span className="text-sm">{t('reports.actualCosts')}</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-4 h-4 bg-warning/20 border-2 border-dashed border-warning rounded" />
|
|
<span className="text-sm">{t('reports.predictedCosts')}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 pt-4 border-t border-border">
|
|
{forecast.map((data) => {
|
|
const margin = data.predictedRevenue - data.predictedCosts
|
|
const marginPercentage = (margin / data.predictedRevenue) * 100
|
|
|
|
return (
|
|
<Card key={data.period} className="border-dashed border-2">
|
|
<CardHeader className="pb-2">
|
|
<div className="flex items-center justify-between">
|
|
<CardTitle className="text-sm">{data.period} {selectedYear}</CardTitle>
|
|
<div className="flex items-center gap-1 text-xs text-accent font-medium">
|
|
<Lightning size={12} weight="fill" />
|
|
{data.confidence}%
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="space-y-2">
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-muted-foreground">{t('reports.predictedRevenue')}</span>
|
|
<span className="font-mono font-medium">£{data.predictedRevenue.toLocaleString()}</span>
|
|
</div>
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-muted-foreground">{t('reports.predictedCosts')}</span>
|
|
<span className="font-mono font-medium">£{data.predictedCosts.toLocaleString()}</span>
|
|
</div>
|
|
<div className="flex justify-between text-sm pt-2 border-t border-border">
|
|
<span className="text-muted-foreground font-medium">{t('reports.estMargin')}</span>
|
|
<div className="text-right">
|
|
<div className="font-mono font-semibold">£{margin.toLocaleString()}</div>
|
|
<div className="text-xs font-medium text-accent">
|
|
{marginPercentage.toFixed(1)}%
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
<Card className="bg-muted/50">
|
|
<CardContent className="p-4">
|
|
<div className="flex items-start gap-3">
|
|
<Lightning size={20} className="text-accent mt-0.5" weight="fill" />
|
|
<div className="flex-1 text-sm">
|
|
<p className="font-medium mb-1">{t('reports.forecastMethodology')}</p>
|
|
<p className="text-muted-foreground">
|
|
{t('reports.forecastMethodologyDescription')}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
)
|
|
}
|