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
+209
View File
@@ -0,0 +1,209 @@
"use client"
import { useState, useEffect } from "react"
// Mock data types
interface KPIs {
totalRevenue: number
revenueGrowth: number
activeProducts: number
lowStockItems: number
activeClients: number
newClientsThisMonth: number
profitMargin: number
marginImprovement: number
}
interface Transaction {
id: string
client: string
product: string
amount: number
status: "paid" | "pending" | "overdue"
date: string
}
interface Product {
id: string
name: string
category: string
unitsSold: number
revenue: number
}
interface ClientCredit {
id: string
name: string
outstandingAmount: number
daysOverdue: number
lastPayment: string
}
interface SupplierCredit {
id: string
name: string
amountOwed: number
status: "current" | "overdue"
nextPayment: string
}
// Mock data - in a real app, this would come from your database
const mockKPIs: KPIs = {
totalRevenue: 125000,
revenueGrowth: 12.5,
activeProducts: 156,
lowStockItems: 8,
activeClients: 42,
newClientsThisMonth: 6,
profitMargin: 28.5,
marginImprovement: 3.2,
}
const mockTransactions: Transaction[] = [
{
id: "1",
client: "ABC Electronics",
product: "Wireless Headphones",
amount: 2500,
status: "paid",
date: "2024-01-15",
},
{
id: "2",
client: "Tech Solutions Inc",
product: "Laptop Chargers",
amount: 1800,
status: "pending",
date: "2024-01-14",
},
{
id: "3",
client: "Mobile World",
product: "Phone Cases",
amount: 950,
status: "paid",
date: "2024-01-13",
},
{
id: "4",
client: "Digital Store",
product: "USB Cables",
amount: 650,
status: "overdue",
date: "2024-01-12",
},
{
id: "5",
client: "Gadget Hub",
product: "Power Banks",
amount: 3200,
status: "paid",
date: "2024-01-11",
},
]
const mockTopProducts: Product[] = [
{
id: "1",
name: "Wireless Headphones",
category: "Audio",
unitsSold: 145,
revenue: 14500,
},
{
id: "2",
name: "Phone Cases",
category: "Accessories",
unitsSold: 230,
revenue: 11500,
},
{
id: "3",
name: "Power Banks",
category: "Electronics",
unitsSold: 89,
revenue: 8900,
},
{
id: "4",
name: "USB Cables",
category: "Accessories",
unitsSold: 156,
revenue: 7800,
},
]
const mockClientsWithCredit: ClientCredit[] = [
{
id: "1",
name: "Tech Solutions Inc",
outstandingAmount: 4500,
daysOverdue: 15,
lastPayment: "2023-12-20",
},
{
id: "2",
name: "Digital Store",
outstandingAmount: 2800,
daysOverdue: 45,
lastPayment: "2023-11-30",
},
{
id: "3",
name: "Mobile Mart",
outstandingAmount: 1200,
daysOverdue: 8,
lastPayment: "2024-01-05",
},
]
const mockSuppliersWithCredit: SupplierCredit[] = [
{
id: "1",
name: "Electronics Wholesale Co",
amountOwed: 8500,
status: "current",
nextPayment: "2024-01-25",
},
{
id: "2",
name: "Global Tech Supplies",
amountOwed: 3200,
status: "overdue",
nextPayment: "2024-01-10",
},
{
id: "3",
name: "Premium Components Ltd",
amountOwed: 5600,
status: "current",
nextPayment: "2024-01-30",
},
]
export function useBusinessData() {
const [loading, setLoading] = useState(true)
const [kpis, setKpis] = useState<KPIs>(mockKPIs)
const [recentTransactions, setRecentTransactions] = useState<Transaction[]>(mockTransactions)
const [topProducts, setTopProducts] = useState<Product[]>(mockTopProducts)
const [clientsWithCredit, setClientsWithCredit] = useState<ClientCredit[]>(mockClientsWithCredit)
const [suppliersWithCredit, setSuppliersWithCredit] = useState<SupplierCredit[]>(mockSuppliersWithCredit)
useEffect(() => {
// Simulate loading data
const timer = setTimeout(() => {
setLoading(false)
}, 1000)
return () => clearTimeout(timer)
}, [])
return {
kpis,
recentTransactions,
topProducts,
clientsWithCredit,
suppliersWithCredit,
loading,
}
}
+147
View File
@@ -0,0 +1,147 @@
"use client"
import { useState, useEffect } from "react"
import type { Client } from "@/types/business"
// Mock clients data
const mockClients: Client[] = [
{
id: "1",
name: "ABC Electronics",
email: "orders@abcelectronics.com",
phone: "(555) 123-4567",
address: "123 Tech Street, Silicon Valley, CA 94000",
creditLimit: 10000,
outstandingAmount: 4500,
paymentTerms: "Net 30",
contactPerson: "John Smith",
businessType: "Electronics Retailer",
notes: "Large volume customer, always pays on time",
createdAt: "2024-01-01",
},
{
id: "2",
name: "Tech Solutions Inc",
email: "billing@techsolutions.com",
phone: "(555) 987-6543",
address: "456 Business Ave, Downtown, NY 10001",
creditLimit: 15000,
outstandingAmount: 2800,
paymentTerms: "Net 15",
contactPerson: "Sarah Johnson",
businessType: "IT Services",
notes: "Prefers email communication",
createdAt: "2024-01-01",
},
{
id: "3",
name: "Mobile World",
email: "contact@mobileworld.com",
phone: "(555) 456-7890",
address: "789 Mobile Plaza, Austin, TX 78701",
creditLimit: 8000,
outstandingAmount: 0,
paymentTerms: "Net 30",
contactPerson: "Mike Chen",
businessType: "Mobile Phone Store",
notes: "New customer, good payment history so far",
createdAt: "2024-01-01",
},
{
id: "4",
name: "Digital Store",
email: "admin@digitalstore.com",
phone: "(555) 321-0987",
address: "321 Digital Way, Seattle, WA 98101",
creditLimit: 5000,
outstandingAmount: 5200,
paymentTerms: "Net 45",
contactPerson: "Lisa Wong",
businessType: "Online Retailer",
notes: "Currently over credit limit - monitor closely",
createdAt: "2024-01-01",
},
]
export function useClients() {
const [clients, setClients] = useState<Client[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
// Simulate loading from localStorage or API
const savedClients = localStorage.getItem("wholesale-clients")
if (savedClients) {
setClients(JSON.parse(savedClients))
} else {
setClients(mockClients)
localStorage.setItem("wholesale-clients", JSON.stringify(mockClients))
}
setLoading(false)
}, [])
const addClient = (clientData: Omit<Client, "id" | "createdAt">) => {
const newClient: Client = {
...clientData,
id: Date.now().toString(),
createdAt: new Date().toISOString(),
}
const updatedClients = [...clients, newClient]
setClients(updatedClients)
localStorage.setItem("wholesale-clients", JSON.stringify(updatedClients))
}
const updateClient = (clientId: string, clientData: Omit<Client, "id" | "createdAt">) => {
const updatedClients = clients.map((client) => (client.id === clientId ? { ...client, ...clientData } : client))
setClients(updatedClients)
localStorage.setItem("wholesale-clients", JSON.stringify(updatedClients))
}
const deleteClient = (clientId: string) => {
const updatedClients = clients.filter((client) => client.id !== clientId)
setClients(updatedClients)
localStorage.setItem("wholesale-clients", JSON.stringify(updatedClients))
}
const addPayment = (clientId: string, amount: number, notes: string, date: string) => {
const updatedClients = clients.map((client) =>
client.id === clientId
? { ...client, outstandingAmount: Math.max(0, client.outstandingAmount - amount) }
: client,
)
setClients(updatedClients)
localStorage.setItem("wholesale-clients", JSON.stringify(updatedClients))
// In a real app, you'd also save the payment record to a payments table
console.log(`Payment recorded: $${amount} from client ${clientId} on ${date}. Notes: ${notes}`)
}
const getClientById = (clientId: string) => {
return clients.find((client) => client.id === clientId)
}
const getClientsWithOutstandingCredit = () => {
return clients.filter((client) => client.outstandingAmount > 0)
}
const getClientsOverCreditLimit = () => {
return clients.filter((client) => client.outstandingAmount > client.creditLimit)
}
const getTotalOutstandingAmount = () => {
return clients.reduce((total, client) => total + client.outstandingAmount, 0)
}
return {
clients,
loading,
addClient,
updateClient,
deleteClient,
addPayment,
getClientById,
getClientsWithOutstandingCredit,
getClientsOverCreditLimit,
getTotalOutstandingAmount,
}
}
+19
View File
@@ -0,0 +1,19 @@
import * as React from "react"
const MOBILE_BREAKPOINT = 768
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
return !!isMobile
}
+131
View File
@@ -0,0 +1,131 @@
"use client"
import { useState, useEffect } from "react"
import type { Product } from "@/types/business"
// Mock products data
const mockProducts: Product[] = [
{
id: "1",
name: "Wireless Headphones",
category: "Audio",
description: "High-quality wireless headphones with noise cancellation",
buyPrice: 45.0,
sellPrice: 89.99,
stock: 25,
minStock: 5,
supplier: "Audio Tech Co",
sku: "WH-001",
createdAt: "2024-01-01",
updatedAt: "2024-01-01",
},
{
id: "2",
name: "Phone Cases",
category: "Accessories",
description: "Protective cases for various phone models",
buyPrice: 3.5,
sellPrice: 12.99,
stock: 150,
minStock: 20,
supplier: "Mobile Accessories Ltd",
sku: "PC-002",
createdAt: "2024-01-01",
updatedAt: "2024-01-01",
},
{
id: "3",
name: "Power Banks",
category: "Electronics",
description: "10000mAh portable power banks",
buyPrice: 18.0,
sellPrice: 34.99,
stock: 3,
minStock: 10,
supplier: "Power Solutions Inc",
sku: "PB-003",
createdAt: "2024-01-01",
updatedAt: "2024-01-01",
},
{
id: "4",
name: "USB Cables",
category: "Accessories",
description: "USB-C charging cables 6ft length",
buyPrice: 2.25,
sellPrice: 8.99,
stock: 0,
minStock: 15,
supplier: "Cable World",
sku: "UC-004",
createdAt: "2024-01-01",
updatedAt: "2024-01-01",
},
]
export function useProducts() {
const [products, setProducts] = useState<Product[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
// Simulate loading from localStorage or API
const savedProducts = localStorage.getItem("wholesale-products")
if (savedProducts) {
setProducts(JSON.parse(savedProducts))
} else {
setProducts(mockProducts)
localStorage.setItem("wholesale-products", JSON.stringify(mockProducts))
}
setLoading(false)
}, [])
const addProduct = (productData: Omit<Product, "id" | "createdAt" | "updatedAt">) => {
const newProduct: Product = {
...productData,
id: Date.now().toString(),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
const updatedProducts = [...products, newProduct]
setProducts(updatedProducts)
localStorage.setItem("wholesale-products", JSON.stringify(updatedProducts))
}
const updateProduct = (productId: string, productData: Omit<Product, "id" | "createdAt" | "updatedAt">) => {
const updatedProducts = products.map((product) =>
product.id === productId ? { ...product, ...productData, updatedAt: new Date().toISOString() } : product,
)
setProducts(updatedProducts)
localStorage.setItem("wholesale-products", JSON.stringify(updatedProducts))
}
const deleteProduct = (productId: string) => {
const updatedProducts = products.filter((product) => product.id !== productId)
setProducts(updatedProducts)
localStorage.setItem("wholesale-products", JSON.stringify(updatedProducts))
}
const getProductById = (productId: string) => {
return products.find((product) => product.id === productId)
}
const getLowStockProducts = () => {
return products.filter((product) => product.stock <= product.minStock)
}
const getOutOfStockProducts = () => {
return products.filter((product) => product.stock === 0)
}
return {
products,
loading,
addProduct,
updateProduct,
deleteProduct,
getProductById,
getLowStockProducts,
getOutOfStockProducts,
}
}
+164
View File
@@ -0,0 +1,164 @@
"use client"
import { useState, useEffect } from "react"
import type { Supplier } from "@/types/business"
// Mock suppliers data
const mockSuppliers: Supplier[] = [
{
id: "1",
name: "Electronics Wholesale Co",
email: "orders@electronicswholesale.com",
phone: "(555) 111-2222",
address: "100 Industrial Blvd, Manufacturing District, CA 90210",
paymentTerms: "Net 30",
amountOwed: 8500,
contactPerson: "David Wilson",
businessType: "Electronics Manufacturer",
notes: "Primary supplier for electronic components",
taxId: "12-3456789",
createdAt: "2024-01-01",
},
{
id: "2",
name: "Global Tech Supplies",
email: "billing@globaltechsupplies.com",
phone: "(555) 333-4444",
address: "250 Tech Park Ave, Innovation City, TX 75001",
paymentTerms: "Net 15",
amountOwed: 3200,
contactPerson: "Maria Rodriguez",
businessType: "Technology Distributor",
notes: "Fast shipping, good quality products",
taxId: "98-7654321",
createdAt: "2024-01-01",
},
{
id: "3",
name: "Premium Components Ltd",
email: "accounts@premiumcomponents.com",
phone: "(555) 555-6666",
address: "500 Component Way, Quality Town, NY 10001",
paymentTerms: "Net 45",
amountOwed: 0,
contactPerson: "James Thompson",
businessType: "Component Manufacturer",
notes: "High-end components, premium pricing",
taxId: "55-1122334",
createdAt: "2024-01-01",
},
{
id: "4",
name: "Budget Electronics Supply",
email: "info@budgetelectronics.com",
phone: "(555) 777-8888",
address: "75 Discount Drive, Value City, FL 33101",
paymentTerms: "COD",
amountOwed: 1250,
contactPerson: "Susan Lee",
businessType: "Discount Supplier",
notes: "Good for budget-friendly options",
taxId: "44-9988776",
createdAt: "2024-01-01",
},
]
export function useSuppliers() {
const [suppliers, setSuppliers] = useState<Supplier[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
// Simulate loading from localStorage or API
const savedSuppliers = localStorage.getItem("wholesale-suppliers")
if (savedSuppliers) {
setSuppliers(JSON.parse(savedSuppliers))
} else {
setSuppliers(mockSuppliers)
localStorage.setItem("wholesale-suppliers", JSON.stringify(mockSuppliers))
}
setLoading(false)
}, [])
const addSupplier = (supplierData: Omit<Supplier, "id" | "createdAt">) => {
const newSupplier: Supplier = {
...supplierData,
id: Date.now().toString(),
createdAt: new Date().toISOString(),
}
const updatedSuppliers = [...suppliers, newSupplier]
setSuppliers(updatedSuppliers)
localStorage.setItem("wholesale-suppliers", JSON.stringify(updatedSuppliers))
}
const updateSupplier = (supplierId: string, supplierData: Omit<Supplier, "id" | "createdAt">) => {
const updatedSuppliers = suppliers.map((supplier) =>
supplier.id === supplierId ? { ...supplier, ...supplierData } : supplier,
)
setSuppliers(updatedSuppliers)
localStorage.setItem("wholesale-suppliers", JSON.stringify(updatedSuppliers))
}
const deleteSupplier = (supplierId: string) => {
const updatedSuppliers = suppliers.filter((supplier) => supplier.id !== supplierId)
setSuppliers(updatedSuppliers)
localStorage.setItem("wholesale-suppliers", JSON.stringify(updatedSuppliers))
}
const addPurchase = (
supplierId: string,
amount: number,
description: string,
date: string,
invoiceNumber: string,
) => {
const updatedSuppliers = suppliers.map((supplier) =>
supplier.id === supplierId ? { ...supplier, amountOwed: supplier.amountOwed + amount } : supplier,
)
setSuppliers(updatedSuppliers)
localStorage.setItem("wholesale-suppliers", JSON.stringify(updatedSuppliers))
// In a real app, you'd also save the purchase record to a purchases table
console.log(
`Purchase recorded: $${amount} from supplier ${supplierId} on ${date}. Invoice: ${invoiceNumber}. Description: ${description}`,
)
}
const recordPayment = (supplierId: string, amount: number, notes: string, date: string, paymentMethod: string) => {
const updatedSuppliers = suppliers.map((supplier) =>
supplier.id === supplierId ? { ...supplier, amountOwed: Math.max(0, supplier.amountOwed - amount) } : supplier,
)
setSuppliers(updatedSuppliers)
localStorage.setItem("wholesale-suppliers", JSON.stringify(updatedSuppliers))
// In a real app, you'd also save the payment record to a payments table
console.log(
`Payment recorded: $${amount} to supplier ${supplierId} on ${date}. Method: ${paymentMethod}. Notes: ${notes}`,
)
}
const getSupplierById = (supplierId: string) => {
return suppliers.find((supplier) => supplier.id === supplierId)
}
const getSuppliersWithOutstandingBalance = () => {
return suppliers.filter((supplier) => supplier.amountOwed > 0)
}
const getTotalAmountOwed = () => {
return suppliers.reduce((total, supplier) => total + supplier.amountOwed, 0)
}
return {
suppliers,
loading,
addSupplier,
updateSupplier,
deleteSupplier,
addPurchase,
recordPayment,
getSupplierById,
getSuppliersWithOutstandingBalance,
getTotalAmountOwed,
}
}
+194
View File
@@ -0,0 +1,194 @@
"use client"
// Inspired by react-hot-toast library
import * as React from "react"
import type {
ToastActionElement,
ToastProps,
} from "@/components/ui/toast"
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000
type ToasterToast = ToastProps & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const
let count = 0
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
}
type ActionType = typeof actionTypes
type Action =
| {
type: ActionType["ADD_TOAST"]
toast: ToasterToast
}
| {
type: ActionType["UPDATE_TOAST"]
toast: Partial<ToasterToast>
}
| {
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
}
| {
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
}
interface State {
toasts: ToasterToast[]
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(toastId, timeout)
}
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
}
case "DISMISS_TOAST": {
const { toastId } = action
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t
),
}
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
}
}
const listeners: Array<(state: State) => void> = []
let memoryState: State = { toasts: [] }
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
}
type Toast = Omit<ToasterToast, "id">
function toast({ ...props }: Toast) {
const id = genId()
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
})
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
},
},
})
return {
id: id,
dismiss,
update,
}
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState)
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
}, [state])
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
}
}
export { useToast, toast }