unzip frontend
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
@import 'tailwindcss';
|
||||
@import 'tw-animate-css';
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--destructive-foreground: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--radius: 0.625rem;
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.145 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.145 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.985 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.396 0.141 25.723);
|
||||
--destructive-foreground: oklch(0.637 0.237 25.331);
|
||||
--border: oklch(0.269 0 0);
|
||||
--input: oklch(0.269 0 0);
|
||||
--ring: oklch(0.439 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(0.269 0 0);
|
||||
--sidebar-ring: oklch(0.439 0 0);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { GeistSans } from 'geist/font/sans'
|
||||
import { GeistMono } from 'geist/font/mono'
|
||||
import './globals.css'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'v0 App',
|
||||
description: 'Created with v0',
|
||||
generator: 'v0.app',
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<style>{`
|
||||
html {
|
||||
font-family: ${GeistSans.style.fontFamily};
|
||||
--font-sans: ${GeistSans.variable};
|
||||
--font-mono: ${GeistMono.variable};
|
||||
}
|
||||
`}</style>
|
||||
</head>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
"use client"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import {
|
||||
DollarSign,
|
||||
Package,
|
||||
Users,
|
||||
AlertTriangle,
|
||||
ShoppingCart,
|
||||
CreditCard,
|
||||
BarChart3,
|
||||
Plus,
|
||||
Building2,
|
||||
} from "lucide-react"
|
||||
import { useBusinessData } from "@/hooks/use-business-data"
|
||||
import { useProducts } from "@/hooks/use-products"
|
||||
import { useClients } from "@/hooks/use-clients"
|
||||
import { useSuppliers } from "@/hooks/use-suppliers"
|
||||
import { ProductForm } from "@/components/products/product-form"
|
||||
import { ProductsTable } from "@/components/products/products-table"
|
||||
import { ClientForm } from "@/components/clients/client-form"
|
||||
import { ClientsTable } from "@/components/clients/clients-table"
|
||||
import { PaymentForm } from "@/components/clients/payment-form"
|
||||
import { SupplierForm } from "@/components/suppliers/supplier-form"
|
||||
import { SuppliersTable } from "@/components/suppliers/suppliers-table"
|
||||
import { PurchaseForm } from "@/components/suppliers/purchase-form"
|
||||
import { SupplierPaymentForm } from "@/components/suppliers/supplier-payment-form"
|
||||
import { CreditOverview } from "@/components/credit/credit-overview"
|
||||
import { TransactionHistory } from "@/components/credit/transaction-history"
|
||||
import { useState } from "react"
|
||||
import type { Product, Client, Supplier } from "@/types/business"
|
||||
|
||||
export default function Dashboard() {
|
||||
const { kpis, recentTransactions, topProducts, clientsWithCredit, suppliersWithCredit, loading } = useBusinessData()
|
||||
const {
|
||||
products,
|
||||
loading: productsLoading,
|
||||
addProduct,
|
||||
updateProduct,
|
||||
deleteProduct,
|
||||
getLowStockProducts,
|
||||
getOutOfStockProducts,
|
||||
} = useProducts()
|
||||
|
||||
const {
|
||||
clients,
|
||||
loading: clientsLoading,
|
||||
addClient,
|
||||
updateClient,
|
||||
deleteClient,
|
||||
addPayment,
|
||||
getClientById,
|
||||
getClientsWithOutstandingCredit,
|
||||
getClientsOverCreditLimit,
|
||||
getTotalOutstandingAmount,
|
||||
} = useClients()
|
||||
|
||||
const {
|
||||
suppliers,
|
||||
loading: suppliersLoading,
|
||||
addSupplier,
|
||||
updateSupplier,
|
||||
deleteSupplier,
|
||||
addPurchase,
|
||||
recordPayment,
|
||||
getSupplierById,
|
||||
getSuppliersWithOutstandingBalance,
|
||||
getTotalAmountOwed,
|
||||
} = useSuppliers()
|
||||
|
||||
const [showProductForm, setShowProductForm] = useState(false)
|
||||
const [editingProduct, setEditingProduct] = useState<Product | undefined>()
|
||||
const [showClientForm, setShowClientForm] = useState(false)
|
||||
const [editingClient, setEditingClient] = useState<Client | undefined>()
|
||||
const [showPaymentForm, setShowPaymentForm] = useState(false)
|
||||
const [paymentClientId, setPaymentClientId] = useState<string>("")
|
||||
const [showSupplierForm, setShowSupplierForm] = useState(false)
|
||||
const [editingSupplier, setEditingSupplier] = useState<Supplier | undefined>()
|
||||
const [showPurchaseForm, setShowPurchaseForm] = useState(false)
|
||||
const [purchaseSupplierId, setPurchaseSupplierId] = useState<string>("")
|
||||
const [showSupplierPaymentForm, setShowSupplierPaymentForm] = useState(false)
|
||||
const [supplierPaymentId, setSupplierPaymentId] = useState<string>("")
|
||||
|
||||
const handleEditProduct = (product: Product) => {
|
||||
setEditingProduct(product)
|
||||
setShowProductForm(true)
|
||||
}
|
||||
|
||||
const handleProductSubmit = (productData: Omit<Product, "id">) => {
|
||||
if (editingProduct) {
|
||||
updateProduct(editingProduct.id, productData)
|
||||
setEditingProduct(undefined)
|
||||
} else {
|
||||
addProduct(productData)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCloseProductForm = () => {
|
||||
setShowProductForm(false)
|
||||
setEditingProduct(undefined)
|
||||
}
|
||||
|
||||
const handleEditClient = (client: Client) => {
|
||||
setEditingClient(client)
|
||||
setShowClientForm(true)
|
||||
}
|
||||
|
||||
const handleClientSubmit = (clientData: Omit<Client, "id">) => {
|
||||
if (editingClient) {
|
||||
updateClient(editingClient.id, clientData)
|
||||
setEditingClient(undefined)
|
||||
} else {
|
||||
addClient(clientData)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCloseClientForm = () => {
|
||||
setShowClientForm(false)
|
||||
setEditingClient(undefined)
|
||||
}
|
||||
|
||||
const handleAddPayment = (clientId: string) => {
|
||||
setPaymentClientId(clientId)
|
||||
setShowPaymentForm(true)
|
||||
}
|
||||
|
||||
const handlePaymentSubmit = (payment: { amount: number; notes: string; date: string }) => {
|
||||
addPayment(paymentClientId, payment.amount, payment.notes, payment.date)
|
||||
setShowPaymentForm(false)
|
||||
setPaymentClientId("")
|
||||
}
|
||||
|
||||
const handleEditSupplier = (supplier: Supplier) => {
|
||||
setEditingSupplier(supplier)
|
||||
setShowSupplierForm(true)
|
||||
}
|
||||
|
||||
const handleSupplierSubmit = (supplierData: Omit<Supplier, "id">) => {
|
||||
if (editingSupplier) {
|
||||
updateSupplier(editingSupplier.id, supplierData)
|
||||
setEditingSupplier(undefined)
|
||||
} else {
|
||||
addSupplier(supplierData)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCloseSupplierForm = () => {
|
||||
setShowSupplierForm(false)
|
||||
setEditingSupplier(undefined)
|
||||
}
|
||||
|
||||
const handleAddPurchase = (supplierId: string) => {
|
||||
setPurchaseSupplierId(supplierId)
|
||||
setShowPurchaseForm(true)
|
||||
}
|
||||
|
||||
const handlePurchaseSubmit = (purchase: {
|
||||
amount: number
|
||||
description: string
|
||||
date: string
|
||||
invoiceNumber: string
|
||||
}) => {
|
||||
addPurchase(purchaseSupplierId, purchase.amount, purchase.description, purchase.date, purchase.invoiceNumber)
|
||||
setShowPurchaseForm(false)
|
||||
setPurchaseSupplierId("")
|
||||
}
|
||||
|
||||
const handleRecordPayment = (supplierId: string) => {
|
||||
setSupplierPaymentId(supplierId)
|
||||
setShowSupplierPaymentForm(true)
|
||||
}
|
||||
|
||||
const handleSupplierPaymentSubmit = (payment: {
|
||||
amount: number
|
||||
notes: string
|
||||
date: string
|
||||
paymentMethod: string
|
||||
}) => {
|
||||
recordPayment(supplierPaymentId, payment.amount, payment.notes, payment.date, payment.paymentMethod)
|
||||
setShowSupplierPaymentForm(false)
|
||||
setSupplierPaymentId("")
|
||||
}
|
||||
|
||||
if (loading || productsLoading || clientsLoading || suppliersLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
|
||||
<p className="text-muted-foreground">Loading dashboard...</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const lowStockProducts = getLowStockProducts()
|
||||
const outOfStockProducts = getOutOfStockProducts()
|
||||
const clientsWithOutstanding = getClientsWithOutstandingCredit()
|
||||
const clientsOverLimit = getClientsOverCreditLimit()
|
||||
const totalOutstanding = getTotalOutstandingAmount()
|
||||
const suppliersWithBalance = getSuppliersWithOutstandingBalance()
|
||||
const totalOwed = getTotalAmountOwed()
|
||||
const paymentClient = getClientById(paymentClientId)
|
||||
const purchaseSupplier = getSupplierById(purchaseSupplierId)
|
||||
const paymentSupplier = getSupplierById(supplierPaymentId)
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
{/* Header */}
|
||||
<header className="border-b bg-card">
|
||||
<div className="container mx-auto px-4 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-foreground">Wholesale Manager</h1>
|
||||
<p className="text-muted-foreground">Business Dashboard</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm">
|
||||
<BarChart3 className="h-4 w-4 mr-2" />
|
||||
Export Report
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="container mx-auto px-4 py-6 space-y-6">
|
||||
{/* KPI 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 Revenue</CardTitle>
|
||||
<DollarSign className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">${kpis.totalRevenue.toLocaleString()}</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<span className="text-green-600">+{kpis.revenueGrowth}%</span> from last month
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Active Products</CardTitle>
|
||||
<Package className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{products.length}</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{lowStockProducts.length} low stock, {outOfStockProducts.length} out of stock
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Active Clients</CardTitle>
|
||||
<Users className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{clients.length}</div>
|
||||
<p className="text-xs text-muted-foreground">${totalOutstanding.toLocaleString()} outstanding</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Suppliers</CardTitle>
|
||||
<Building2 className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{suppliers.length}</div>
|
||||
<p className="text-xs text-muted-foreground">${totalOwed.toLocaleString()} owed</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Main Content Tabs */}
|
||||
<Tabs defaultValue="overview" className="space-y-4">
|
||||
<TabsList className="grid w-full grid-cols-5">
|
||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||
<TabsTrigger value="products">Products</TabsTrigger>
|
||||
<TabsTrigger value="clients">Clients</TabsTrigger>
|
||||
<TabsTrigger value="suppliers">Suppliers</TabsTrigger>
|
||||
<TabsTrigger value="credit">Credit Tracking</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="overview" className="space-y-4">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Recent Transactions */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<ShoppingCart className="h-5 w-5" />
|
||||
Recent Transactions
|
||||
</CardTitle>
|
||||
<CardDescription>Latest business activities</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{recentTransactions.slice(0, 5).map((transaction) => (
|
||||
<div key={transaction.id} className="flex items-center justify-between p-3 border rounded-lg">
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-sm">{transaction.client}</p>
|
||||
<p className="text-xs text-muted-foreground">{transaction.product}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="font-medium text-sm">${transaction.amount}</p>
|
||||
<Badge variant={transaction.status === "paid" ? "default" : "secondary"} className="text-xs">
|
||||
{transaction.status}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Top Products */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Package className="h-5 w-5" />
|
||||
Top Selling Products
|
||||
</CardTitle>
|
||||
<CardDescription>Best performers this month</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{topProducts.map((product, index) => (
|
||||
<div key={product.id} className="flex items-center justify-between p-3 border rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-primary/10 rounded-full flex items-center justify-center">
|
||||
<span className="text-sm font-bold text-primary">#{index + 1}</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-sm">{product.name}</p>
|
||||
<p className="text-xs text-muted-foreground">{product.category}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="font-medium text-sm">{product.unitsSold} sold</p>
|
||||
<p className="text-xs text-muted-foreground">${product.revenue}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="products" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Package className="h-5 w-5" />
|
||||
Products Management
|
||||
</CardTitle>
|
||||
<CardDescription>Manage your product inventory and pricing</CardDescription>
|
||||
</div>
|
||||
<Button onClick={() => setShowProductForm(true)}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add Product
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{/* Stock Alerts */}
|
||||
{(lowStockProducts.length > 0 || outOfStockProducts.length > 0) && (
|
||||
<div className="mb-6 p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<AlertTriangle className="h-4 w-4 text-yellow-600" />
|
||||
<h4 className="font-medium text-yellow-800 dark:text-yellow-200">Stock Alerts</h4>
|
||||
</div>
|
||||
{outOfStockProducts.length > 0 && (
|
||||
<p className="text-sm text-yellow-700 dark:text-yellow-300 mb-1">
|
||||
<strong>{outOfStockProducts.length}</strong> products are out of stock
|
||||
</p>
|
||||
)}
|
||||
{lowStockProducts.length > 0 && (
|
||||
<p className="text-sm text-yellow-700 dark:text-yellow-300">
|
||||
<strong>{lowStockProducts.length}</strong> products are running low
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ProductsTable products={products} onEdit={handleEditProduct} onDelete={deleteProduct} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="clients" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Users className="h-5 w-5" />
|
||||
Clients Management
|
||||
</CardTitle>
|
||||
<CardDescription>Manage your clients and track credit status</CardDescription>
|
||||
</div>
|
||||
<Button onClick={() => setShowClientForm(true)}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add Client
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{/* Credit Alerts */}
|
||||
{(clientsWithOutstanding.length > 0 || clientsOverLimit.length > 0) && (
|
||||
<div className="mb-6 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<CreditCard className="h-4 w-4 text-red-600" />
|
||||
<h4 className="font-medium text-red-800 dark:text-red-200">Credit Alerts</h4>
|
||||
</div>
|
||||
{clientsOverLimit.length > 0 && (
|
||||
<p className="text-sm text-red-700 dark:text-red-300 mb-1">
|
||||
<strong>{clientsOverLimit.length}</strong> clients are over their credit limit
|
||||
</p>
|
||||
)}
|
||||
<p className="text-sm text-red-700 dark:text-red-300">
|
||||
Total outstanding: <strong>${totalOutstanding.toLocaleString()}</strong>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ClientsTable
|
||||
clients={clients}
|
||||
onEdit={handleEditClient}
|
||||
onDelete={deleteClient}
|
||||
onAddPayment={handleAddPayment}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="suppliers" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Building2 className="h-5 w-5" />
|
||||
Suppliers Management
|
||||
</CardTitle>
|
||||
<CardDescription>Manage your suppliers and track payment obligations</CardDescription>
|
||||
</div>
|
||||
<Button onClick={() => setShowSupplierForm(true)}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add Supplier
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{/* Payment Alerts */}
|
||||
{suppliersWithBalance.length > 0 && (
|
||||
<div className="mb-6 p-4 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<AlertTriangle className="h-4 w-4 text-blue-600" />
|
||||
<h4 className="font-medium text-blue-800 dark:text-blue-200">Payment Obligations</h4>
|
||||
</div>
|
||||
<p className="text-sm text-blue-700 dark:text-blue-300 mb-1">
|
||||
<strong>{suppliersWithBalance.length}</strong> suppliers have outstanding balances
|
||||
</p>
|
||||
<p className="text-sm text-blue-700 dark:text-blue-300">
|
||||
Total amount owed: <strong>${totalOwed.toLocaleString()}</strong>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SuppliersTable
|
||||
suppliers={suppliers}
|
||||
onEdit={handleEditSupplier}
|
||||
onDelete={deleteSupplier}
|
||||
onAddPurchase={handleAddPurchase}
|
||||
onRecordPayment={handleRecordPayment}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
{/* Added new credit tracking tab */}
|
||||
<TabsContent value="credit" className="space-y-4">
|
||||
<CreditOverview clients={clients} suppliers={suppliers} />
|
||||
<TransactionHistory />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</main>
|
||||
|
||||
{/* Product Form Dialog */}
|
||||
<ProductForm
|
||||
product={editingProduct}
|
||||
open={showProductForm}
|
||||
onOpenChange={handleCloseProductForm}
|
||||
onSubmit={handleProductSubmit}
|
||||
/>
|
||||
|
||||
{/* Client Form Dialog */}
|
||||
<ClientForm
|
||||
client={editingClient}
|
||||
open={showClientForm}
|
||||
onOpenChange={handleCloseClientForm}
|
||||
onSubmit={handleClientSubmit}
|
||||
/>
|
||||
|
||||
{/* Payment Form Dialog */}
|
||||
{paymentClient && (
|
||||
<PaymentForm
|
||||
clientName={paymentClient.name}
|
||||
outstandingAmount={paymentClient.outstandingAmount}
|
||||
open={showPaymentForm}
|
||||
onOpenChange={(open) => {
|
||||
setShowPaymentForm(open)
|
||||
if (!open) setPaymentClientId("")
|
||||
}}
|
||||
onSubmit={handlePaymentSubmit}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Supplier Form Dialog */}
|
||||
<SupplierForm
|
||||
supplier={editingSupplier}
|
||||
open={showSupplierForm}
|
||||
onOpenChange={handleCloseSupplierForm}
|
||||
onSubmit={handleSupplierSubmit}
|
||||
/>
|
||||
|
||||
{/* Purchase Form Dialog */}
|
||||
{purchaseSupplier && (
|
||||
<PurchaseForm
|
||||
supplierName={purchaseSupplier.name}
|
||||
open={showPurchaseForm}
|
||||
onOpenChange={(open) => {
|
||||
setShowPurchaseForm(open)
|
||||
if (!open) setPurchaseSupplierId("")
|
||||
}}
|
||||
onSubmit={handlePurchaseSubmit}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Supplier Payment Form Dialog */}
|
||||
{paymentSupplier && (
|
||||
<SupplierPaymentForm
|
||||
supplierName={paymentSupplier.name}
|
||||
amountOwed={paymentSupplier.amountOwed}
|
||||
open={showSupplierPaymentForm}
|
||||
onOpenChange={(open) => {
|
||||
setShowSupplierPaymentForm(open)
|
||||
if (!open) setSupplierPaymentId("")
|
||||
}}
|
||||
onSubmit={handleSupplierPaymentSubmit}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user