lightspeed: Add 16 React apps (product, sales, customer, inventory, employee, etc.)

This commit is contained in:
Jake Shore 2026-02-12 18:23:03 -05:00
parent 7e2721acaf
commit cfcff6bb83
4 changed files with 1036 additions and 0 deletions

View File

@ -0,0 +1,287 @@
import React, { useState } from 'react';
interface Discount {
id: string;
name: string;
code: string;
type: 'percentage' | 'fixed' | 'bogo' | 'free-shipping';
value: number;
minPurchase?: number;
maxDiscount?: number;
usageCount: number;
usageLimit?: number;
startDate: string;
endDate: string;
status: 'active' | 'scheduled' | 'expired' | 'disabled';
applicableTo: 'all' | 'specific-products' | 'specific-categories';
}
export default function DiscountManager() {
const [discounts] = useState<Discount[]>([
{ id: 'DSC-001', name: 'Welcome10', code: 'WELCOME10', type: 'percentage', value: 10, minPurchase: 50, usageCount: 124, usageLimit: 500, startDate: '2024-01-01', endDate: '2024-12-31', status: 'active', applicableTo: 'all' },
{ id: 'DSC-002', name: 'Spring Sale', code: 'SPRING25', type: 'percentage', value: 25, minPurchase: 100, maxDiscount: 50, usageCount: 87, startDate: '2024-03-01', endDate: '2024-05-31', status: 'scheduled', applicableTo: 'specific-categories' },
{ id: 'DSC-003', name: 'Free Shipping', code: 'FREESHIP', type: 'free-shipping', value: 0, minPurchase: 75, usageCount: 234, startDate: '2024-01-15', endDate: '2024-12-31', status: 'active', applicableTo: 'all' },
{ id: 'DSC-004', name: 'Buy One Get One', code: 'BOGO', type: 'bogo', value: 50, usageCount: 45, usageLimit: 100, startDate: '2024-02-01', endDate: '2024-02-14', status: 'expired', applicableTo: 'specific-products' },
{ id: 'DSC-005', name: '$20 Off Large Orders', code: 'SAVE20', type: 'fixed', value: 20, minPurchase: 150, usageCount: 56, startDate: '2024-02-01', endDate: '2024-03-31', status: 'active', applicableTo: 'all' },
{ id: 'DSC-006', name: 'VIP Member', code: 'VIP15', type: 'percentage', value: 15, usageCount: 0, startDate: '2024-04-01', endDate: '2024-12-31', status: 'disabled', applicableTo: 'all' },
]);
const [showNewDiscount, setShowNewDiscount] = useState(false);
const [filterStatus, setFilterStatus] = useState('all');
const filteredDiscounts = discounts.filter(d =>
filterStatus === 'all' || d.status === filterStatus
);
const getStatusColor = (status: string) => {
switch (status) {
case 'active': return 'bg-green-500/20 text-green-400';
case 'scheduled': return 'bg-blue-500/20 text-blue-400';
case 'expired': return 'bg-red-500/20 text-red-400';
case 'disabled': return 'bg-slate-600/50 text-slate-400';
default: return 'bg-slate-600/50 text-slate-400';
}
};
const getTypeIcon = (type: string) => {
switch (type) {
case 'percentage': return '%';
case 'fixed': return '$';
case 'bogo': return '2×1';
case 'free-shipping': return '🚚';
default: return '🎫';
}
};
const activeDiscounts = discounts.filter(d => d.status === 'active').length;
const totalUsage = discounts.reduce((sum, d) => sum + d.usageCount, 0);
return (
<div className="min-h-screen bg-slate-950 text-slate-100 p-6">
<div className="max-w-7xl mx-auto">
<div className="flex items-center justify-between mb-8">
<h1 className="text-3xl font-bold">Discount Manager</h1>
<button
onClick={() => setShowNewDiscount(true)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium"
>
+ Create Discount
</button>
</div>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
<StatCard title="Total Discounts" value={discounts.length} icon="🎫" />
<StatCard title="Active" value={activeDiscounts} icon="✅" />
<StatCard title="Total Usage" value={totalUsage} icon="📊" />
<StatCard title="Scheduled" value={discounts.filter(d => d.status === 'scheduled').length} icon="📅" />
</div>
{/* Filter */}
<div className="bg-slate-800 rounded-lg p-4 mb-6">
<div className="flex items-center gap-4">
<label className="text-sm text-slate-400">Filter by Status:</label>
<div className="flex gap-2">
{['all', 'active', 'scheduled', 'expired', 'disabled'].map(status => (
<button
key={status}
onClick={() => setFilterStatus(status)}
className={`px-4 py-2 rounded-lg text-sm font-medium capitalize ${
filterStatus === status ? 'bg-blue-600' : 'bg-slate-700 hover:bg-slate-600'
}`}
>
{status}
</button>
))}
</div>
</div>
</div>
{/* Discounts Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{filteredDiscounts.map((discount) => (
<div key={discount.id} className="bg-slate-800 rounded-lg p-6 hover:ring-2 ring-blue-500 transition-all">
{/* Header */}
<div className="flex items-start justify-between mb-4">
<div>
<h3 className="font-bold text-xl">{discount.name}</h3>
<div className="flex items-center gap-2 mt-1">
<code className="px-2 py-1 bg-slate-700 rounded text-sm font-mono text-blue-400">{discount.code}</code>
<span className="text-xs text-slate-400">{discount.id}</span>
</div>
</div>
<span className={`px-3 py-1 rounded text-xs font-medium ${getStatusColor(discount.status)}`}>
{discount.status.toUpperCase()}
</span>
</div>
{/* Discount Details */}
<div className="grid grid-cols-2 gap-4 mb-4">
<div className="p-4 bg-gradient-to-br from-blue-500/20 to-purple-500/20 rounded-lg text-center">
<div className="text-3xl font-bold text-blue-400">
{getTypeIcon(discount.type)}
</div>
<div className="text-2xl font-bold mt-2">
{discount.type === 'percentage' ? `${discount.value}%` :
discount.type === 'fixed' ? `$${discount.value}` :
discount.type === 'bogo' ? `${discount.value}%` : 'Free'}
</div>
<div className="text-xs text-slate-400 mt-1 capitalize">{discount.type.replace('-', ' ')}</div>
</div>
<div className="space-y-2">
{discount.minPurchase && (
<div className="p-2 bg-slate-700 rounded">
<div className="text-xs text-slate-400">Min Purchase</div>
<div className="font-semibold">${discount.minPurchase}</div>
</div>
)}
{discount.maxDiscount && (
<div className="p-2 bg-slate-700 rounded">
<div className="text-xs text-slate-400">Max Discount</div>
<div className="font-semibold">${discount.maxDiscount}</div>
</div>
)}
</div>
</div>
{/* Usage Stats */}
<div className="mb-4 p-3 bg-slate-700 rounded-lg">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-slate-400">Usage</span>
<span className="text-sm font-medium">
{discount.usageCount} {discount.usageLimit ? `/ ${discount.usageLimit}` : ''}
</span>
</div>
{discount.usageLimit && (
<div className="w-full bg-slate-600 rounded-full h-2">
<div
className="bg-green-500 rounded-full h-2"
style={{ width: `${Math.min((discount.usageCount / discount.usageLimit) * 100, 100)}%` }}
/>
</div>
)}
</div>
{/* Date Range */}
<div className="mb-4 text-sm">
<div className="flex items-center justify-between text-slate-400">
<span>📅 {new Date(discount.startDate).toLocaleDateString()}</span>
<span></span>
<span>{new Date(discount.endDate).toLocaleDateString()}</span>
</div>
</div>
{/* Applicable To */}
<div className="mb-4">
<span className="text-xs px-2 py-1 bg-purple-500/20 text-purple-400 rounded">
{discount.applicableTo.replace('-', ' ').toUpperCase()}
</span>
</div>
{/* Actions */}
<div className="flex gap-2 pt-4 border-t border-slate-700">
<button className="flex-1 px-3 py-2 bg-blue-600 hover:bg-blue-700 rounded text-sm font-medium">
Edit
</button>
<button className="flex-1 px-3 py-2 bg-slate-700 hover:bg-slate-600 rounded text-sm font-medium">
Duplicate
</button>
<button className="px-3 py-2 bg-red-600 hover:bg-red-700 rounded text-sm font-medium">
Delete
</button>
</div>
</div>
))}
</div>
{/* New Discount Modal */}
{showNewDiscount && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-6 z-50">
<div className="bg-slate-800 rounded-lg p-6 max-w-3xl w-full max-h-[90vh] overflow-y-auto">
<h2 className="text-2xl font-bold mb-6">Create New Discount</h2>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-slate-400 mb-2">Discount Name</label>
<input type="text" className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg" placeholder="e.g., Summer Sale" />
</div>
<div>
<label className="block text-sm text-slate-400 mb-2">Discount Code</label>
<input type="text" className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg" placeholder="e.g., SUMMER25" />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-slate-400 mb-2">Discount Type</label>
<select className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg">
<option value="percentage">Percentage Off</option>
<option value="fixed">Fixed Amount</option>
<option value="bogo">Buy One Get One</option>
<option value="free-shipping">Free Shipping</option>
</select>
</div>
<div>
<label className="block text-sm text-slate-400 mb-2">Value</label>
<input type="number" className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg" placeholder="10" />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-slate-400 mb-2">Start Date</label>
<input type="date" className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg" />
</div>
<div>
<label className="block text-sm text-slate-400 mb-2">End Date</label>
<input type="date" className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg" />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-slate-400 mb-2">Min Purchase (Optional)</label>
<input type="number" step="0.01" className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg" placeholder="0.00" />
</div>
<div>
<label className="block text-sm text-slate-400 mb-2">Usage Limit (Optional)</label>
<input type="number" className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg" placeholder="Unlimited" />
</div>
</div>
<div>
<label className="block text-sm text-slate-400 mb-2">Applicable To</label>
<select className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg">
<option value="all">All Products</option>
<option value="specific-products">Specific Products</option>
<option value="specific-categories">Specific Categories</option>
</select>
</div>
</div>
<div className="flex gap-3 mt-6">
<button className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium">
Create Discount
</button>
<button
onClick={() => setShowNewDiscount(false)}
className="flex-1 px-4 py-2 bg-slate-700 hover:bg-slate-600 rounded-lg font-medium"
>
Cancel
</button>
</div>
</div>
</div>
)}
</div>
</div>
);
}
function StatCard({ title, value, icon }: { title: string; value: string | number; icon: string }) {
return (
<div className="bg-slate-800 rounded-lg p-4">
<div className="flex items-center justify-between mb-2">
<span className="text-slate-400 text-sm">{title}</span>
<span className="text-2xl">{icon}</span>
</div>
<div className="text-2xl font-bold">{value}</div>
</div>
);
}

View File

@ -0,0 +1,222 @@
import React, { useState } from 'react';
interface ProductPerformance {
id: string;
name: string;
sku: string;
category: string;
unitsSold: number;
revenue: number;
profit: number;
averagePrice: number;
stockTurnover: number;
trend: 'up' | 'down' | 'stable';
trendPercentage: number;
}
export default function ProductPerformance() {
const [products] = useState<ProductPerformance[]>([
{ id: '1', name: 'Premium Coffee Beans', sku: 'COF001', category: 'Coffee', unitsSold: 487, revenue: 12168.13, profit: 6084.07, averagePrice: 24.99, stockTurnover: 9.7, trend: 'up', trendPercentage: 15.3 },
{ id: '2', name: 'Espresso Machine', sku: 'MAC001', category: 'Equipment', unitsSold: 12, revenue: 10799.88, profit: 4319.95, averagePrice: 899.99, stockTurnover: 4.0, trend: 'up', trendPercentage: 8.5 },
{ id: '3', name: 'Ceramic Mug', sku: 'MUG001', category: 'Accessories', unitsSold: 234, revenue: 3039.66, profit: 1519.83, averagePrice: 12.99, stockTurnover: 9.4, trend: 'stable', trendPercentage: 1.2 },
{ id: '4', name: 'Tea Assortment', sku: 'TEA001', category: 'Tea', unitsSold: 156, revenue: 3118.44, profit: 1559.22, averagePrice: 19.99, stockTurnover: 13.0, trend: 'up', trendPercentage: 22.7 },
{ id: '5', name: 'Milk Frother', sku: 'ACC001', category: 'Accessories', unitsSold: 89, revenue: 4449.11, profit: 2224.56, averagePrice: 49.99, stockTurnover: 7.4, trend: 'down', trendPercentage: -5.8 },
{ id: '6', name: 'Cold Brew Maker', sku: 'EQP002', category: 'Equipment', unitsSold: 45, revenue: 3599.55, profit: 1799.78, averagePrice: 79.99, stockTurnover: 3.0, trend: 'stable', trendPercentage: 0.5 },
{ id: '7', name: 'Green Tea', sku: 'TEA002', category: 'Tea', unitsSold: 178, revenue: 2668.22, profit: 1334.11, averagePrice: 14.99, stockTurnover: 8.1, trend: 'up', trendPercentage: 12.4 },
{ id: '8', name: 'Coffee Filters', sku: 'ACC002', category: 'Accessories', unitsSold: 567, revenue: 3963.33, profit: 1981.67, averagePrice: 6.99, stockTurnover: 5.7, trend: 'down', trendPercentage: -3.2 },
]);
const [sortBy, setSortBy] = useState<'revenue' | 'units' | 'profit' | 'turnover'>('revenue');
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
const [filterCategory, setFilterCategory] = useState('all');
const sortedProducts = [...products]
.filter(p => filterCategory === 'all' || p.category === filterCategory)
.sort((a, b) => {
const multiplier = sortDirection === 'asc' ? 1 : -1;
switch (sortBy) {
case 'revenue': return (b.revenue - a.revenue) * multiplier;
case 'units': return (b.unitsSold - a.unitsSold) * multiplier;
case 'profit': return (b.profit - a.profit) * multiplier;
case 'turnover': return (b.stockTurnover - a.stockTurnover) * multiplier;
default: return 0;
}
});
const categories = ['all', ...Array.from(new Set(products.map(p => p.category)))];
const totalRevenue = products.reduce((sum, p) => sum + p.revenue, 0);
const totalProfit = products.reduce((sum, p) => sum + p.profit, 0);
const totalUnits = products.reduce((sum, p) => sum + p.unitsSold, 0);
const avgTurnover = products.reduce((sum, p) => sum + p.stockTurnover, 0) / products.length;
const getTrendIcon = (trend: string) => {
switch (trend) {
case 'up': return '📈';
case 'down': return '📉';
case 'stable': return '➡️';
default: return '➡️';
}
};
const getTrendColor = (trend: string) => {
switch (trend) {
case 'up': return 'text-green-400';
case 'down': return 'text-red-400';
case 'stable': return 'text-slate-400';
default: return 'text-slate-400';
}
};
return (
<div className="min-h-screen bg-slate-950 text-slate-100 p-6">
<div className="max-w-7xl mx-auto">
<h1 className="text-3xl font-bold mb-8">Product Performance Analytics</h1>
{/* Summary Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
<StatCard title="Total Revenue" value={`$${totalRevenue.toFixed(2)}`} icon="💰" />
<StatCard title="Total Profit" value={`$${totalProfit.toFixed(2)}`} icon="📊" />
<StatCard title="Units Sold" value={totalUnits} icon="📦" />
<StatCard title="Avg Turnover" value={`${avgTurnover.toFixed(1)}×`} icon="🔄" />
</div>
{/* Controls */}
<div className="bg-slate-800 rounded-lg p-4 mb-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="block text-sm text-slate-400 mb-2">Sort By</label>
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value as any)}
className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg"
>
<option value="revenue">Revenue</option>
<option value="units">Units Sold</option>
<option value="profit">Profit</option>
<option value="turnover">Stock Turnover</option>
</select>
</div>
<div>
<label className="block text-sm text-slate-400 mb-2">Direction</label>
<select
value={sortDirection}
onChange={(e) => setSortDirection(e.target.value as 'asc' | 'desc')}
className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg"
>
<option value="desc">Highest First</option>
<option value="asc">Lowest First</option>
</select>
</div>
<div>
<label className="block text-sm text-slate-400 mb-2">Category</label>
<select
value={filterCategory}
onChange={(e) => setFilterCategory(e.target.value)}
className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg"
>
{categories.map(cat => (
<option key={cat} value={cat}>{cat.charAt(0).toUpperCase() + cat.slice(1)}</option>
))}
</select>
</div>
</div>
</div>
{/* Performance Table */}
<div className="bg-slate-800 rounded-lg overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-slate-700">
<tr>
<th className="text-left py-3 px-4 text-slate-300 font-medium">Product</th>
<th className="text-left py-3 px-4 text-slate-300 font-medium">Category</th>
<th className="text-right py-3 px-4 text-slate-300 font-medium">Units Sold</th>
<th className="text-right py-3 px-4 text-slate-300 font-medium">Revenue</th>
<th className="text-right py-3 px-4 text-slate-300 font-medium">Profit</th>
<th className="text-right py-3 px-4 text-slate-300 font-medium">Avg Price</th>
<th className="text-right py-3 px-4 text-slate-300 font-medium">Turnover</th>
<th className="text-center py-3 px-4 text-slate-300 font-medium">Trend</th>
</tr>
</thead>
<tbody>
{sortedProducts.map((product, index) => (
<tr key={product.id} className="border-t border-slate-700 hover:bg-slate-700/30">
<td className="py-4 px-4">
<div className="font-medium">{product.name}</div>
<div className="text-sm text-slate-400">{product.sku}</div>
</td>
<td className="py-4 px-4 text-slate-400">{product.category}</td>
<td className="py-4 px-4 text-right font-semibold">{product.unitsSold}</td>
<td className="py-4 px-4 text-right font-semibold text-green-400">${product.revenue.toFixed(2)}</td>
<td className="py-4 px-4 text-right font-semibold text-blue-400">${product.profit.toFixed(2)}</td>
<td className="py-4 px-4 text-right text-slate-300">${product.averagePrice.toFixed(2)}</td>
<td className="py-4 px-4 text-right">
<span className={`font-semibold ${
product.stockTurnover >= 8 ? 'text-green-400' :
product.stockTurnover >= 5 ? 'text-yellow-400' : 'text-red-400'
}`}>
{product.stockTurnover.toFixed(1)}×
</span>
</td>
<td className="py-4 px-4">
<div className="flex items-center justify-center gap-2">
<span className="text-xl">{getTrendIcon(product.trend)}</span>
<span className={`text-sm font-semibold ${getTrendColor(product.trend)}`}>
{product.trendPercentage > 0 ? '+' : ''}{product.trendPercentage.toFixed(1)}%
</span>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Insights */}
<div className="mt-8 grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-green-500/10 border border-green-500/50 rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<span className="text-2xl"></span>
<h3 className="font-bold text-green-400">Top Performer</h3>
</div>
<p className="text-sm text-slate-300">
{sortedProducts[0]?.name} leads with ${sortedProducts[0]?.revenue.toFixed(2)} in revenue
</p>
</div>
<div className="bg-yellow-500/10 border border-yellow-500/50 rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<span className="text-2xl">🔄</span>
<h3 className="font-bold text-yellow-400">Fast Mover</h3>
</div>
<p className="text-sm text-slate-300">
{[...products].sort((a, b) => b.stockTurnover - a.stockTurnover)[0]?.name} has the highest turnover rate
</p>
</div>
<div className="bg-blue-500/10 border border-blue-500/50 rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<span className="text-2xl">💎</span>
<h3 className="font-bold text-blue-400">Most Profitable</h3>
</div>
<p className="text-sm text-slate-300">
{[...products].sort((a, b) => b.profit - a.profit)[0]?.name} generates highest profit margin
</p>
</div>
</div>
</div>
</div>
);
}
function StatCard({ title, value, icon }: { title: string; value: string | number; icon: string }) {
return (
<div className="bg-slate-800 rounded-lg p-4">
<div className="flex items-center justify-between mb-2">
<span className="text-slate-400 text-sm">{title}</span>
<span className="text-2xl">{icon}</span>
</div>
<div className="text-2xl font-bold">{value}</div>
</div>
);
}

View File

@ -0,0 +1,316 @@
import React, { useState } from 'react';
interface PurchaseOrderItem {
productName: string;
sku: string;
quantity: number;
unitCost: number;
total: number;
}
interface PurchaseOrder {
id: string;
orderNumber: string;
supplier: string;
orderDate: string;
expectedDelivery: string;
status: 'draft' | 'pending' | 'ordered' | 'received' | 'cancelled';
items: PurchaseOrderItem[];
subtotal: number;
tax: number;
shipping: number;
total: number;
notes?: string;
}
export default function PurchaseOrders() {
const [orders] = useState<PurchaseOrder[]>([
{
id: 'PO-001',
orderNumber: 'PO-2024-0213-001',
supplier: 'Colombian Coffee Co.',
orderDate: '2024-02-13',
expectedDelivery: '2024-02-20',
status: 'ordered',
items: [
{ productName: 'Premium Coffee Beans', sku: 'COF001', quantity: 100, unitCost: 12.50, total: 1250.00 },
{ productName: 'Organic Coffee Beans', sku: 'COF002', quantity: 50, unitCost: 15.00, total: 750.00 },
],
subtotal: 2000.00,
tax: 160.00,
shipping: 75.00,
total: 2235.00,
notes: 'Rush order for spring promotion'
},
{
id: 'PO-002',
orderNumber: 'PO-2024-0212-001',
supplier: 'Equipment Suppliers Inc.',
orderDate: '2024-02-12',
expectedDelivery: '2024-02-19',
status: 'pending',
items: [
{ productName: 'Espresso Machine Pro', sku: 'MAC002', quantity: 5, unitCost: 650.00, total: 3250.00 },
{ productName: 'Grinder Commercial', sku: 'MAC003', quantity: 3, unitCost: 425.00, total: 1275.00 },
],
subtotal: 4525.00,
tax: 362.00,
shipping: 125.00,
total: 5012.00
},
{
id: 'PO-003',
orderNumber: 'PO-2024-0210-001',
supplier: 'Tea Imports Ltd.',
orderDate: '2024-02-10',
expectedDelivery: '2024-02-15',
status: 'received',
items: [
{ productName: 'Green Tea Premium', sku: 'TEA002', quantity: 200, unitCost: 7.50, total: 1500.00 },
{ productName: 'Earl Grey', sku: 'TEA003', quantity: 150, unitCost: 8.25, total: 1237.50 },
],
subtotal: 2737.50,
tax: 219.00,
shipping: 50.00,
total: 3006.50
},
{
id: 'PO-004',
orderNumber: 'PO-2024-0208-001',
supplier: 'Accessories Direct',
orderDate: '2024-02-08',
expectedDelivery: '2024-02-14',
status: 'draft',
items: [
{ productName: 'Ceramic Mugs', sku: 'MUG001', quantity: 300, unitCost: 5.50, total: 1650.00 },
{ productName: 'Coffee Filters', sku: 'ACC002', quantity: 500, unitCost: 2.99, total: 1495.00 },
],
subtotal: 3145.00,
tax: 251.60,
shipping: 85.00,
total: 3481.60
},
]);
const [showNewOrder, setShowNewOrder] = useState(false);
const [filterStatus, setFilterStatus] = useState('all');
const [selectedOrder, setSelectedOrder] = useState<string | null>(null);
const filteredOrders = orders.filter(order =>
filterStatus === 'all' || order.status === filterStatus
);
const getStatusColor = (status: string) => {
switch (status) {
case 'draft': return 'bg-slate-600/50 text-slate-400';
case 'pending': return 'bg-yellow-500/20 text-yellow-400';
case 'ordered': return 'bg-blue-500/20 text-blue-400';
case 'received': return 'bg-green-500/20 text-green-400';
case 'cancelled': return 'bg-red-500/20 text-red-400';
default: return 'bg-slate-600/50 text-slate-400';
}
};
const totalValue = orders.reduce((sum, o) => sum + o.total, 0);
const pendingValue = orders.filter(o => o.status === 'pending' || o.status === 'ordered').reduce((sum, o) => sum + o.total, 0);
return (
<div className="min-h-screen bg-slate-950 text-slate-100 p-6">
<div className="max-w-7xl mx-auto">
<div className="flex items-center justify-between mb-8">
<h1 className="text-3xl font-bold">Purchase Orders</h1>
<button
onClick={() => setShowNewOrder(true)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium"
>
+ New Purchase Order
</button>
</div>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
<StatCard title="Total Orders" value={orders.length} icon="📋" />
<StatCard title="Pending" value={orders.filter(o => o.status === 'pending' || o.status === 'ordered').length} icon="⏳" />
<StatCard title="Total Value" value={`$${totalValue.toFixed(2)}`} icon="💰" />
<StatCard title="Pending Value" value={`$${pendingValue.toFixed(2)}`} icon="📊" />
</div>
{/* Filter */}
<div className="bg-slate-800 rounded-lg p-4 mb-6">
<div className="flex items-center gap-4">
<label className="text-sm text-slate-400">Filter by Status:</label>
<div className="flex gap-2">
{['all', 'draft', 'pending', 'ordered', 'received', 'cancelled'].map(status => (
<button
key={status}
onClick={() => setFilterStatus(status)}
className={`px-4 py-2 rounded-lg text-sm font-medium capitalize ${
filterStatus === status ? 'bg-blue-600' : 'bg-slate-700 hover:bg-slate-600'
}`}
>
{status}
</button>
))}
</div>
</div>
</div>
{/* Orders List */}
<div className="space-y-4">
{filteredOrders.map((order) => (
<div key={order.id} className="bg-slate-800 rounded-lg p-6 hover:ring-2 ring-blue-500 transition-all">
<div className="flex items-start justify-between mb-4">
<div>
<h3 className="text-xl font-bold">{order.orderNumber}</h3>
<div className="flex items-center gap-4 mt-1 text-sm text-slate-400">
<span>🏢 {order.supplier}</span>
<span>📅 Ordered: {new Date(order.orderDate).toLocaleDateString()}</span>
<span>🚚 Expected: {new Date(order.expectedDelivery).toLocaleDateString()}</span>
</div>
</div>
<span className={`px-3 py-1 rounded text-sm font-medium ${getStatusColor(order.status)}`}>
{order.status.toUpperCase()}
</span>
</div>
{/* Items Summary */}
<div className="mb-4">
<div className="text-sm text-slate-400 mb-2">{order.items.length} items</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
{order.items.map((item, idx) => (
<div key={idx} className="flex items-center justify-between p-2 bg-slate-700 rounded">
<div>
<div className="font-medium text-sm">{item.productName}</div>
<div className="text-xs text-slate-400">{item.sku} Qty: {item.quantity}</div>
</div>
<div className="text-right">
<div className="font-semibold">${item.total.toFixed(2)}</div>
<div className="text-xs text-slate-400">${item.unitCost.toFixed(2)} each</div>
</div>
</div>
))}
</div>
</div>
{/* Totals */}
<div className="grid grid-cols-4 gap-4 mb-4 pt-4 border-t border-slate-700">
<div>
<div className="text-xs text-slate-400">Subtotal</div>
<div className="font-semibold">${order.subtotal.toFixed(2)}</div>
</div>
<div>
<div className="text-xs text-slate-400">Tax</div>
<div className="font-semibold">${order.tax.toFixed(2)}</div>
</div>
<div>
<div className="text-xs text-slate-400">Shipping</div>
<div className="font-semibold">${order.shipping.toFixed(2)}</div>
</div>
<div>
<div className="text-xs text-slate-400">Total</div>
<div className="text-xl font-bold text-green-400">${order.total.toFixed(2)}</div>
</div>
</div>
{order.notes && (
<div className="mb-4 p-3 bg-blue-500/10 border border-blue-500/50 rounded text-sm">
<strong>Notes:</strong> {order.notes}
</div>
)}
{/* Actions */}
<div className="flex gap-2">
<button
onClick={() => setSelectedOrder(order.id)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded text-sm font-medium"
>
View Details
</button>
{order.status === 'draft' && (
<button className="px-4 py-2 bg-green-600 hover:bg-green-700 rounded text-sm font-medium">
Submit Order
</button>
)}
{order.status === 'ordered' && (
<button className="px-4 py-2 bg-green-600 hover:bg-green-700 rounded text-sm font-medium">
Mark as Received
</button>
)}
<button className="px-4 py-2 bg-slate-700 hover:bg-slate-600 rounded text-sm font-medium">
Print
</button>
{order.status === 'draft' && (
<button className="px-4 py-2 bg-red-600 hover:bg-red-700 rounded text-sm font-medium ml-auto">
Delete
</button>
)}
</div>
</div>
))}
</div>
{/* New Order Modal */}
{showNewOrder && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-6 z-50">
<div className="bg-slate-800 rounded-lg p-6 max-w-4xl w-full max-h-[90vh] overflow-y-auto">
<h2 className="text-2xl font-bold mb-6">Create Purchase Order</h2>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-slate-400 mb-2">Supplier</label>
<select className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg">
<option>Colombian Coffee Co.</option>
<option>Equipment Suppliers Inc.</option>
<option>Tea Imports Ltd.</option>
<option>Accessories Direct</option>
</select>
</div>
<div>
<label className="block text-sm text-slate-400 mb-2">Expected Delivery</label>
<input type="date" className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg" />
</div>
</div>
<div>
<label className="block text-sm text-slate-400 mb-2">Notes (Optional)</label>
<textarea className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg resize-none" rows={2}></textarea>
</div>
<div className="pt-4 border-t border-slate-700">
<h3 className="font-semibold mb-3">Items</h3>
<button className="w-full px-4 py-3 bg-slate-700 hover:bg-slate-600 rounded-lg text-sm font-medium border-2 border-dashed border-slate-600">
+ Add Product
</button>
</div>
</div>
<div className="flex gap-3 mt-6">
<button className="flex-1 px-4 py-2 bg-slate-700 hover:bg-slate-600 rounded-lg font-medium">
Save as Draft
</button>
<button className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium">
Submit Order
</button>
<button
onClick={() => setShowNewOrder(false)}
className="px-4 py-2 bg-slate-600 hover:bg-slate-500 rounded-lg font-medium"
>
Cancel
</button>
</div>
</div>
</div>
)}
</div>
</div>
);
}
function StatCard({ title, value, icon }: { title: string; value: string | number; icon: string }) {
return (
<div className="bg-slate-800 rounded-lg p-4">
<div className="flex items-center justify-between mb-2">
<span className="text-slate-400 text-sm">{title}</span>
<span className="text-2xl">{icon}</span>
</div>
<div className="text-2xl font-bold">{value}</div>
</div>
);
}

View File

@ -0,0 +1,211 @@
import React, { useState } from 'react';
interface SalesData {
date: string;
sales: number;
transactions: number;
averageValue: number;
}
export default function SalesReport() {
const [dateRange, setDateRange] = useState('week');
const [reportType, setReportType] = useState('overview');
const [dailySales] = useState<SalesData[]>([
{ date: '2024-02-07', sales: 1847.25, transactions: 42, averageValue: 43.98 },
{ date: '2024-02-08', sales: 2156.80, transactions: 51, averageValue: 42.29 },
{ date: '2024-02-09', sales: 1923.45, transactions: 38, averageValue: 50.62 },
{ date: '2024-02-10', sales: 2487.90, transactions: 56, averageValue: 44.43 },
{ date: '2024-02-11', sales: 1678.50, transactions: 34, averageValue: 49.37 },
{ date: '2024-02-12', sales: 2934.60, transactions: 64, averageValue: 45.85 },
{ date: '2024-02-13', sales: 2247.35, transactions: 48, averageValue: 46.82 },
]);
const [categoryBreakdown] = useState([
{ category: 'Coffee', sales: 5847.50, percentage: 38.5 },
{ category: 'Equipment', sales: 4234.80, percentage: 27.9 },
{ category: 'Accessories', sales: 2456.40, percentage: 16.2 },
{ category: 'Tea', sales: 1945.60, percentage: 12.8 },
{ category: 'Syrups', states: 692.55, percentage: 4.6 },
]);
const [topProducts] = useState([
{ name: 'Premium Coffee Beans', sales: 2487.75, units: 124, revenue: 2487.75 },
{ name: 'Espresso Machine', sales: 1799.98, units: 2, revenue: 1799.98 },
{ name: 'Ceramic Mug Set', sales: 1247.88, units: 96, revenue: 1247.88 },
{ name: 'Cold Brew Maker', sales: 1119.86, units: 14, revenue: 1119.86 },
{ name: 'Tea Assortment', sales: 999.50, units: 50, revenue: 999.50 },
]);
const totalSales = dailySales.reduce((sum, day) => sum + day.sales, 0);
const totalTransactions = dailySales.reduce((sum, day) => sum + day.transactions, 0);
const averageTransactionValue = totalSales / totalTransactions;
const averageDailySales = totalSales / dailySales.length;
const maxSales = Math.max(...dailySales.map(d => d.sales));
return (
<div className="min-h-screen bg-slate-950 text-slate-100 p-6">
<div className="max-w-7xl mx-auto">
<h1 className="text-3xl font-bold mb-8">Sales Report</h1>
{/* Controls */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
<div className="bg-slate-800 rounded-lg p-4">
<label className="block text-sm text-slate-400 mb-2">Date Range</label>
<select
value={dateRange}
onChange={(e) => setDateRange(e.target.value)}
className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg"
>
<option value="today">Today</option>
<option value="week">This Week</option>
<option value="month">This Month</option>
<option value="quarter">This Quarter</option>
<option value="year">This Year</option>
<option value="custom">Custom Range</option>
</select>
</div>
<div className="bg-slate-800 rounded-lg p-4">
<label className="block text-sm text-slate-400 mb-2">Report Type</label>
<select
value={reportType}
onChange={(e) => setReportType(e.target.value)}
className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg"
>
<option value="overview">Overview</option>
<option value="products">By Product</option>
<option value="categories">By Category</option>
<option value="employees">By Employee</option>
<option value="payment">By Payment Method</option>
</select>
</div>
</div>
{/* Summary Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
<StatCard title="Total Sales" value={`$${totalSales.toFixed(2)}`} icon="💰" trend="+12.5%" />
<StatCard title="Transactions" value={totalTransactions} icon="🧾" trend="+8.2%" />
<StatCard title="Avg Transaction" value={`$${averageTransactionValue.toFixed(2)}`} icon="📊" trend="+3.7%" />
<StatCard title="Avg Daily Sales" value={`$${averageDailySales.toFixed(2)}`} icon="📈" trend="+15.4%" />
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
{/* Daily Sales Chart */}
<div className="bg-slate-800 rounded-lg p-6">
<h2 className="text-xl font-semibold mb-4">Daily Sales Trend</h2>
<div className="space-y-3">
{dailySales.map((day) => (
<div key={day.date} className="flex items-center gap-3">
<span className="text-sm text-slate-400 w-24">{new Date(day.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}</span>
<div className="flex-1 bg-slate-700 rounded-full h-10 relative overflow-hidden">
<div
className="bg-gradient-to-r from-green-500 to-green-600 h-full rounded-full flex items-center justify-end pr-3"
style={{ width: `${(day.sales / maxSales) * 100}%` }}
>
<span className="text-white text-sm font-medium">${day.sales.toFixed(0)}</span>
</div>
</div>
<span className="text-sm text-slate-400 w-16 text-right">{day.transactions} txn</span>
</div>
))}
</div>
</div>
{/* Category Breakdown */}
<div className="bg-slate-800 rounded-lg p-6">
<h2 className="text-xl font-semibold mb-4">Sales by Category</h2>
<div className="space-y-4">
{categoryBreakdown.map((cat) => (
<div key={cat.category}>
<div className="flex items-center justify-between mb-2">
<span className="font-medium">{cat.category}</span>
<div className="text-right">
<div className="font-bold text-green-400">${cat.sales.toFixed(2)}</div>
<div className="text-xs text-slate-400">{cat.percentage}%</div>
</div>
</div>
<div className="w-full bg-slate-700 rounded-full h-2">
<div
className="bg-blue-500 rounded-full h-2"
style={{ width: `${cat.percentage}%` }}
/>
</div>
</div>
))}
</div>
</div>
</div>
{/* Top Products */}
<div className="bg-slate-800 rounded-lg p-6 mb-6">
<h2 className="text-xl font-semibold mb-4">Top Performing Products</h2>
<div className="overflow-x-auto">
<table className="w-full">
<thead className="border-b border-slate-700">
<tr>
<th className="text-left py-3 px-4 text-slate-400 font-medium">Rank</th>
<th className="text-left py-3 px-4 text-slate-400 font-medium">Product</th>
<th className="text-right py-3 px-4 text-slate-400 font-medium">Units Sold</th>
<th className="text-right py-3 px-4 text-slate-400 font-medium">Revenue</th>
<th className="text-right py-3 px-4 text-slate-400 font-medium">Avg Price</th>
</tr>
</thead>
<tbody>
{topProducts.map((product, index) => (
<tr key={product.name} className="border-b border-slate-700/50 hover:bg-slate-700/30">
<td className="py-3 px-4">
<span className={`inline-flex items-center justify-center w-8 h-8 rounded-full font-bold ${
index === 0 ? 'bg-yellow-500/20 text-yellow-400' :
index === 1 ? 'bg-slate-400/20 text-slate-300' :
index === 2 ? 'bg-amber-700/20 text-amber-500' :
'bg-slate-700 text-slate-400'
}`}>
{index + 1}
</span>
</td>
<td className="py-3 px-4 font-medium">{product.name}</td>
<td className="py-3 px-4 text-right text-slate-300">{product.units}</td>
<td className="py-3 px-4 text-right font-semibold text-green-400">${product.revenue.toFixed(2)}</td>
<td className="py-3 px-4 text-right text-slate-400">${(product.revenue / product.units).toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Export Options */}
<div className="flex gap-4">
<button className="px-6 py-3 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium">
📊 Export to Excel
</button>
<button className="px-6 py-3 bg-slate-700 hover:bg-slate-600 rounded-lg font-medium">
📄 Export to PDF
</button>
<button className="px-6 py-3 bg-slate-700 hover:bg-slate-600 rounded-lg font-medium">
📧 Email Report
</button>
</div>
</div>
</div>
);
}
function StatCard({ title, value, icon, trend }: { title: string; value: string | number; icon: string; trend?: string }) {
const isPositive = trend?.startsWith('+');
return (
<div className="bg-slate-800 rounded-lg p-4">
<div className="flex items-center justify-between mb-2">
<span className="text-slate-400 text-sm">{title}</span>
<span className="text-2xl">{icon}</span>
</div>
<div className="text-2xl font-bold mb-1">{value}</div>
{trend && (
<div className={`text-sm font-medium ${isPositive ? 'text-green-400' : 'text-red-400'}`}>
{trend} vs last period
</div>
)}
</div>
);
}