unzip frontend

This commit is contained in:
2025-08-16 14:41:12 +02:00
parent b60af66732
commit 598774bca6
87 changed files with 9676 additions and 0 deletions
@@ -0,0 +1,229 @@
"use client"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { TrendingUp, TrendingDown, DollarSign, AlertTriangle, Clock, CheckCircle } from "lucide-react"
import type { Client, Supplier } from "@/types/business"
interface CreditOverviewProps {
clients: Client[]
suppliers: Supplier[]
}
export function CreditOverview({ clients, suppliers }: CreditOverviewProps) {
// Calculate receivables (money owed to us by clients)
const totalReceivables = clients.reduce((sum, client) => sum + client.outstandingAmount, 0)
const clientsWithCredit = clients.filter((client) => client.outstandingAmount > 0)
const overLimitClients = clients.filter((client) => client.outstandingAmount > client.creditLimit)
const totalCreditLimit = clients.reduce((sum, client) => sum + client.creditLimit, 0)
const creditUtilization = totalCreditLimit > 0 ? (totalReceivables / totalCreditLimit) * 100 : 0
// Calculate payables (money we owe to suppliers)
const totalPayables = suppliers.reduce((sum, supplier) => sum + supplier.amountOwed, 0)
const suppliersWithBalance = suppliers.filter((supplier) => supplier.amountOwed > 0)
// Net credit position (positive means we're owed more than we owe)
const netCreditPosition = totalReceivables - totalPayables
// Aging analysis (simplified - in real app would use actual dates)
const currentReceivables = totalReceivables * 0.6
const thirtyDayReceivables = totalReceivables * 0.25
const sixtyDayReceivables = totalReceivables * 0.1
const ninetyPlusReceivables = totalReceivables * 0.05
return (
<div className="space-y-6">
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Receivables</CardTitle>
<TrendingUp className="h-4 w-4 text-green-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-600">${totalReceivables.toLocaleString()}</div>
<p className="text-xs text-muted-foreground">
{clientsWithCredit.length} clients with outstanding balances
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Payables</CardTitle>
<TrendingDown className="h-4 w-4 text-red-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-red-600">${totalPayables.toLocaleString()}</div>
<p className="text-xs text-muted-foreground">
{suppliersWithBalance.length} suppliers with outstanding balances
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Net Position</CardTitle>
<DollarSign className={`h-4 w-4 ${netCreditPosition >= 0 ? "text-green-600" : "text-red-600"}`} />
</CardHeader>
<CardContent>
<div className={`text-2xl font-bold ${netCreditPosition >= 0 ? "text-green-600" : "text-red-600"}`}>
${Math.abs(netCreditPosition).toLocaleString()}
</div>
<p className="text-xs text-muted-foreground">
{netCreditPosition >= 0 ? "Net receivable position" : "Net payable position"}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Credit Utilization</CardTitle>
<AlertTriangle className={`h-4 w-4 ${creditUtilization > 80 ? "text-red-600" : "text-yellow-600"}`} />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{creditUtilization.toFixed(1)}%</div>
<Progress value={creditUtilization} className="mt-2" />
<p className="text-xs text-muted-foreground mt-1">
${totalReceivables.toLocaleString()} / ${totalCreditLimit.toLocaleString()} limit
</p>
</CardContent>
</Card>
</div>
{/* Alerts */}
{(overLimitClients.length > 0 || creditUtilization > 90) && (
<Card className="border-red-200 bg-red-50 dark:bg-red-900/20">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-red-800 dark:text-red-200">
<AlertTriangle className="h-5 w-5" />
Credit Alerts
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
{overLimitClients.length > 0 && (
<p className="text-sm text-red-700 dark:text-red-300">
<strong>{overLimitClients.length}</strong> clients are over their credit limit
</p>
)}
{creditUtilization > 90 && (
<p className="text-sm text-red-700 dark:text-red-300">
Overall credit utilization is critically high at {creditUtilization.toFixed(1)}%
</p>
)}
</CardContent>
</Card>
)}
{/* Aging Analysis */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Clock className="h-5 w-5" />
Receivables Aging
</CardTitle>
<CardDescription>Breakdown of outstanding receivables by age</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-3">
<div className="flex items-center justify-between p-3 border rounded-lg">
<div className="flex items-center gap-3">
<CheckCircle className="h-4 w-4 text-green-600" />
<span className="font-medium">Current (0-30 days)</span>
</div>
<div className="text-right">
<p className="font-medium">${currentReceivables.toFixed(0)}</p>
<Badge variant="default" className="text-xs">
60%
</Badge>
</div>
</div>
<div className="flex items-center justify-between p-3 border rounded-lg">
<div className="flex items-center gap-3">
<Clock className="h-4 w-4 text-yellow-600" />
<span className="font-medium">31-60 days</span>
</div>
<div className="text-right">
<p className="font-medium">${thirtyDayReceivables.toFixed(0)}</p>
<Badge variant="secondary" className="text-xs">
25%
</Badge>
</div>
</div>
<div className="flex items-center justify-between p-3 border rounded-lg">
<div className="flex items-center gap-3">
<AlertTriangle className="h-4 w-4 text-orange-600" />
<span className="font-medium">61-90 days</span>
</div>
<div className="text-right">
<p className="font-medium">${sixtyDayReceivables.toFixed(0)}</p>
<Badge variant="secondary" className="text-xs">
10%
</Badge>
</div>
</div>
<div className="flex items-center justify-between p-3 border rounded-lg">
<div className="flex items-center gap-3">
<AlertTriangle className="h-4 w-4 text-red-600" />
<span className="font-medium">90+ days</span>
</div>
<div className="text-right">
<p className="font-medium text-red-600">${ninetyPlusReceivables.toFixed(0)}</p>
<Badge variant="destructive" className="text-xs">
5%
</Badge>
</div>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<DollarSign className="h-5 w-5" />
Cash Flow Impact
</CardTitle>
<CardDescription>Credit impact on cash flow</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-3">
<div className="flex items-center justify-between p-3 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg">
<div>
<p className="font-medium text-green-800 dark:text-green-200">Expected Inflow</p>
<p className="text-sm text-green-600 dark:text-green-400">From client payments</p>
</div>
<p className="text-xl font-bold text-green-600">${totalReceivables.toLocaleString()}</p>
</div>
<div className="flex items-center justify-between p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
<div>
<p className="font-medium text-red-800 dark:text-red-200">Expected Outflow</p>
<p className="text-sm text-red-600 dark:text-red-400">To supplier payments</p>
</div>
<p className="text-xl font-bold text-red-600">${totalPayables.toLocaleString()}</p>
</div>
<div className="flex items-center justify-between p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
<div>
<p className="font-medium text-blue-800 dark:text-blue-200">Net Cash Impact</p>
<p className="text-sm text-blue-600 dark:text-blue-400">
{netCreditPosition >= 0 ? "Positive impact" : "Negative impact"}
</p>
</div>
<p className={`text-xl font-bold ${netCreditPosition >= 0 ? "text-green-600" : "text-red-600"}`}>
{netCreditPosition >= 0 ? "+" : "-"}${Math.abs(netCreditPosition).toLocaleString()}
</p>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
)
}
@@ -0,0 +1,272 @@
"use client"
import { useState } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Input } from "@/components/ui/input"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Search, ArrowUpRight, ArrowDownLeft, Calendar } from "lucide-react"
interface Transaction {
id: string
date: string
type: "receivable" | "payable" | "payment_received" | "payment_made"
entity: string
description: string
amount: number
status: "pending" | "completed" | "overdue"
reference?: string
}
// Mock transaction data
const mockTransactions: Transaction[] = [
{
id: "1",
date: "2024-01-15",
type: "receivable",
entity: "ABC Electronics",
description: "Invoice #INV-001 - Wireless Headphones",
amount: 2500,
status: "completed",
reference: "INV-001",
},
{
id: "2",
date: "2024-01-14",
type: "payment_received",
entity: "Tech Solutions Inc",
description: "Payment received for Invoice #INV-002",
amount: 1800,
status: "completed",
reference: "PAY-001",
},
{
id: "3",
date: "2024-01-13",
type: "payable",
entity: "Electronics Wholesale Co",
description: "Purchase Order #PO-001 - Components",
amount: 3200,
status: "pending",
reference: "PO-001",
},
{
id: "4",
date: "2024-01-12",
type: "payment_made",
entity: "Global Tech Supplies",
description: "Payment for Invoice #SUP-001",
amount: 1500,
status: "completed",
reference: "PAY-002",
},
{
id: "5",
date: "2024-01-11",
type: "receivable",
entity: "Mobile World",
description: "Invoice #INV-003 - Phone Cases",
amount: 950,
status: "overdue",
reference: "INV-003",
},
{
id: "6",
date: "2024-01-10",
type: "payable",
entity: "Premium Components Ltd",
description: "Purchase Order #PO-002 - Premium Parts",
amount: 4500,
status: "pending",
reference: "PO-002",
},
]
export function TransactionHistory() {
const [searchTerm, setSearchTerm] = useState("")
const [typeFilter, setTypeFilter] = useState<string>("all")
const [statusFilter, setStatusFilter] = useState<string>("all")
const filteredTransactions = mockTransactions.filter((transaction) => {
const matchesSearch =
transaction.entity.toLowerCase().includes(searchTerm.toLowerCase()) ||
transaction.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
(transaction.reference && transaction.reference.toLowerCase().includes(searchTerm.toLowerCase()))
const matchesType = typeFilter === "all" || transaction.type === typeFilter
const matchesStatus = statusFilter === "all" || transaction.status === statusFilter
return matchesSearch && matchesType && matchesStatus
})
const getTransactionIcon = (type: string) => {
switch (type) {
case "receivable":
case "payment_received":
return <ArrowUpRight className="h-4 w-4 text-green-600" />
case "payable":
case "payment_made":
return <ArrowDownLeft className="h-4 w-4 text-red-600" />
default:
return <Calendar className="h-4 w-4 text-muted-foreground" />
}
}
const getTransactionColor = (type: string) => {
switch (type) {
case "receivable":
case "payment_received":
return "text-green-600"
case "payable":
case "payment_made":
return "text-red-600"
default:
return "text-foreground"
}
}
const getStatusBadge = (status: string) => {
switch (status) {
case "completed":
return (
<Badge variant="default" className="text-xs">
Completed
</Badge>
)
case "pending":
return (
<Badge variant="secondary" className="text-xs">
Pending
</Badge>
)
case "overdue":
return (
<Badge variant="destructive" className="text-xs">
Overdue
</Badge>
)
default:
return (
<Badge variant="outline" className="text-xs">
{status}
</Badge>
)
}
}
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Calendar className="h-5 w-5" />
Transaction History
</CardTitle>
<CardDescription>Complete history of all credit-related transactions</CardDescription>
</CardHeader>
<CardContent>
{/* Filters */}
<div className="flex flex-col sm:flex-row gap-4 mb-6">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" />
<Input
placeholder="Search by entity, description, or reference..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
<Select value={typeFilter} onValueChange={setTypeFilter}>
<SelectTrigger className="w-full sm:w-[180px]">
<SelectValue placeholder="Filter by type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Types</SelectItem>
<SelectItem value="receivable">Receivables</SelectItem>
<SelectItem value="payable">Payables</SelectItem>
<SelectItem value="payment_received">Payments Received</SelectItem>
<SelectItem value="payment_made">Payments Made</SelectItem>
</SelectContent>
</Select>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-full sm:w-[180px]">
<SelectValue placeholder="Filter by status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Status</SelectItem>
<SelectItem value="completed">Completed</SelectItem>
<SelectItem value="pending">Pending</SelectItem>
<SelectItem value="overdue">Overdue</SelectItem>
</SelectContent>
</Select>
</div>
{/* Transaction Table */}
<div className="border rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableHead>Date</TableHead>
<TableHead>Type</TableHead>
<TableHead>Entity</TableHead>
<TableHead>Description</TableHead>
<TableHead>Amount</TableHead>
<TableHead>Status</TableHead>
<TableHead>Reference</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredTransactions.length === 0 ? (
<TableRow>
<TableCell colSpan={7} className="text-center py-8 text-muted-foreground">
{searchTerm || typeFilter !== "all" || statusFilter !== "all"
? "No transactions found matching your filters."
: "No transactions recorded yet."}
</TableCell>
</TableRow>
) : (
filteredTransactions.map((transaction) => (
<TableRow key={transaction.id}>
<TableCell>
<div className="font-medium">{new Date(transaction.date).toLocaleDateString()}</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
{getTransactionIcon(transaction.type)}
<span className="capitalize text-sm">{transaction.type.replace("_", " ")}</span>
</div>
</TableCell>
<TableCell>
<div className="font-medium">{transaction.entity}</div>
</TableCell>
<TableCell>
<div className="max-w-[300px] truncate" title={transaction.description}>
{transaction.description}
</div>
</TableCell>
<TableCell>
<div className={`font-medium ${getTransactionColor(transaction.type)}`}>
{transaction.type.includes("receivable") || transaction.type.includes("payment_received")
? "+"
: "-"}
${transaction.amount.toLocaleString()}
</div>
</TableCell>
<TableCell>{getStatusBadge(transaction.status)}</TableCell>
<TableCell>
{transaction.reference && (
<code className="text-xs bg-muted px-2 py-1 rounded">{transaction.reference}</code>
)}
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
)
}