Generated by Spark: Add export functionality to remaining views (Reports, Purchase Orders, Audit Trail)

This commit is contained in:
2026-02-05 16:05:26 +00:00
committed by GitHub
parent c1d5bdde97
commit 4585c0c3b6
2 changed files with 132 additions and 15 deletions

View File

@@ -2,6 +2,7 @@ import { useState, useEffect, useMemo } from 'react'
import { usePurchaseOrdersCrud } from '@/hooks/use-purchase-orders-crud'
import { useInvoicesCrud } from '@/hooks/use-invoices-crud'
import { useTranslation } from '@/hooks/use-translation'
import { useDataExport } from '@/hooks/use-data-export'
import {
FileText,
Plus,
@@ -18,7 +19,8 @@ import {
Trash,
PencilSimple,
Eye,
X
X,
Download
} from '@phosphor-icons/react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
@@ -37,6 +39,7 @@ export function PurchaseOrderTracking() {
const { t } = useTranslation()
const { entities: purchaseOrders, create, update, remove } = usePurchaseOrdersCrud()
const { invoices } = useInvoicesCrud()
const { exportToCSV, exportToExcel } = useDataExport()
const [searchQuery, setSearchQuery] = useState('')
const [isCreateOpen, setIsCreateOpen] = useState(false)
const [isDetailOpen, setIsDetailOpen] = useState(false)
@@ -297,6 +300,30 @@ export function PurchaseOrderTracking() {
)
}, [invoices, selectedPO])
const handleExportPOs = () => {
try {
const exportData = filteredPOs.map(po => ({
'PO Number': po.poNumber,
'Client': po.clientName,
'Status': po.status,
'Issue Date': new Date(po.issueDate).toLocaleDateString(),
'Expiry Date': po.expiryDate ? new Date(po.expiryDate).toLocaleDateString() : 'N/A',
'Currency': po.currency,
'Total Value': po.totalValue,
'Utilised Value': po.utilisedValue,
'Remaining Value': po.remainingValue,
'Utilization %': ((po.utilisedValue / po.totalValue) * 100).toFixed(2),
'Linked Invoices': po.linkedInvoices.length,
'Created By': po.createdBy,
'Created Date': new Date(po.createdDate).toLocaleDateString()
}))
exportToExcel(exportData, { filename: `purchase-orders-${new Date().toISOString().split('T')[0]}` })
toast.success(t('purchaseOrders.messages.exportSuccess') || 'Purchase orders exported successfully')
} catch (error) {
toast.error(t('purchaseOrders.messages.exportError') || 'Failed to export purchase orders')
}
}
if (isLoadingState) {
return (
<div className="flex items-center justify-center h-96">
@@ -315,10 +342,16 @@ export function PurchaseOrderTracking() {
<h2 className="text-3xl font-semibold tracking-tight">{t('purchaseOrders.title')}</h2>
<p className="text-muted-foreground mt-1">{t('purchaseOrders.subtitle')}</p>
</div>
<Button onClick={() => setIsCreateOpen(true)}>
<Plus size={18} className="mr-2" />
{t('purchaseOrders.createPO')}
</Button>
<div className="flex gap-2">
<Button variant="outline" onClick={handleExportPOs} disabled={filteredPOs.length === 0}>
<Download size={18} className="mr-2" />
{t('purchaseOrders.export') || 'Export'}
</Button>
<Button onClick={() => setIsCreateOpen(true)}>
<Plus size={18} className="mr-2" />
{t('purchaseOrders.createPO')}
</Button>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">

View File

@@ -18,8 +18,10 @@ import {
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')
@@ -28,6 +30,7 @@ export function ReportsView() {
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']
@@ -111,6 +114,71 @@ export function ReportsView() {
...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">
@@ -130,7 +198,7 @@ export function ReportsView() {
<SelectItem value="2023">2023</SelectItem>
</SelectContent>
</Select>
<Button variant="outline">
<Button variant="outline" onClick={handleExportAll}>
<Download size={18} className="mr-2" />
{t('reports.exportReport')}
</Button>
@@ -214,8 +282,16 @@ export function ReportsView() {
<TabsContent value="margin-analysis" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>{t('reports.marginAnalysisTitle')}</CardTitle>
<CardDescription>{t('reports.marginAnalysisDescription', { year: selectedYear })}</CardDescription>
<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">
@@ -305,13 +381,21 @@ export function ReportsView() {
<TabsContent value="forecasting" className="space-y-4">
<Card>
<CardHeader>
<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 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 ? (