File size: 8,791 Bytes
6b154f5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
import React, { useState, useRef } from 'react';
import { Upload, Download, FileSpreadsheet, RefreshCw, Package } from 'lucide-react';
import { Product, Language, languages } from './types';
import { readExcelFile, downloadExcelFile, createSampleData } from './utils/excelUtils';
import { ExcelTable } from './components/ExcelTable';
import { LanguageToggle } from './components/LanguageToggle';
import { ProductCatalog } from './components/ProductCatalog';
import { Navigation } from './components/Navigation';
function App() {
const [products, setProducts] = useState<Product[]>([]);
const [language, setLanguage] = useState<Language>(languages[0]);
const [currentPage, setCurrentPage] = useState<'inventory' | 'catalog'>('inventory');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string>('');
const fileInputRef = useRef<HTMLInputElement>(null);
const isArabic = language.code === 'ar';
const labels = {
ar: {
title: 'إدارة المخزون',
subtitle: 'نظام إدارة المخزون باستخدام Excel',
uploadFile: 'تحميل ملف Excel',
downloadFile: 'تنزيل Excel محدث',
loadSample: 'تحميل بيانات تجريبية',
uploadHint: 'اسحب ملف Excel هنا أو انقر للتحديد',
supportedFormats: 'يدعم ملفات .xlsx',
loading: 'جارٍ المعالجة...',
error: 'خطأ',
success: 'تم بنجاح'
},
en: {
title: 'Inventory Manager',
subtitle: 'Excel-based Inventory Management System',
uploadFile: 'Upload Excel File',
downloadFile: 'Download Updated Excel',
loadSample: 'Load Sample Data',
uploadHint: 'Drag Excel file here or click to select',
supportedFormats: 'Supports .xlsx files',
loading: 'Processing...',
error: 'Error',
success: 'Success'
}
};
const t = labels[language.code];
const handleFileUpload = async (file: File) => {
if (!file.name.endsWith('.xlsx')) {
setError(isArabic ? 'يرجى اختيار ملف Excel صالح (.xlsx)' : 'Please select a valid Excel file (.xlsx)');
return;
}
setIsLoading(true);
setError('');
try {
const parsedProducts = await readExcelFile(file);
setProducts(parsedProducts);
} catch (err) {
setError(isArabic ? 'فشل في قراءة الملف' : 'Failed to read file');
console.error('File reading error:', err);
} finally {
setIsLoading(false);
}
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
const files = Array.from(e.dataTransfer.files);
if (files.length > 0) {
handleFileUpload(files[0]);
}
};
const handleProductUpdate = (updatedProduct: Product) => {
setProducts(products.map(p => p.id === updatedProduct.id ? updatedProduct : p));
};
const handleProductDelete = (productId: string) => {
setProducts(products.filter(p => p.id !== productId));
};
const handleProductAdd = (newProduct: Product) => {
setProducts([...products, newProduct]);
};
const handleQuantityChange = (productId: string, change: number) => {
setProducts(products.map(product =>
product.id === productId
? { ...product, quantity: Math.max(0, product.quantity + change) }
: product
));
};
const handleDownloadExcel = () => {
if (products.length === 0) {
setError(isArabic ? 'لا توجد بيانات للتنزيل' : 'No data to download');
return;
}
downloadExcelFile(products, 'inventory');
};
const loadSampleData = () => {
setProducts(createSampleData());
};
return (
<div className={`min-h-screen bg-gray-50 ${language.dir === 'rtl' ? 'rtl' : 'ltr'}`} dir={language.dir}>
{/* Header */}
<header className="bg-white shadow-sm border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
<div className="flex items-center gap-4">
<div className="p-2 bg-blue-100 rounded-lg">
{currentPage === 'inventory' ? (
<FileSpreadsheet className="w-8 h-8 text-blue-600" />
) : (
<Package className="w-8 h-8 text-blue-600" />
)}
</div>
<div>
<h1 className="text-2xl font-bold text-gray-900">
{currentPage === 'inventory' ? t.title : (isArabic ? 'كتالوج المنتجات' : 'Product Catalog')}
</h1>
<p className="text-sm text-gray-600">
{currentPage === 'inventory' ? t.subtitle : (isArabic ? 'تصفح مجموعتنا من المعدات الكهربائية' : 'Browse our electrical equipment collection')}
</p>
</div>
</div>
<LanguageToggle currentLang={language} onLanguageChange={setLanguage} />
</div>
</div>
</header>
{/* Navigation */}
<Navigation
currentPage={currentPage}
onPageChange={setCurrentPage}
language={language}
/>
{currentPage === 'catalog' ? (
<ProductCatalog language={language} />
) : (
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* File Upload Area */}
<div className="mb-8">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* Upload Section */}
<div
onDrop={handleDrop}
onDragOver={(e) => e.preventDefault()}
className="md:col-span-2 border-2 border-dashed border-gray-300 rounded-xl p-8 text-center hover:border-blue-400 transition-colors cursor-pointer bg-white"
onClick={() => fileInputRef.current?.click()}
>
<div className="flex flex-col items-center">
<div className="p-4 bg-blue-50 rounded-full mb-4">
<Upload className="w-8 h-8 text-blue-500" />
</div>
<p className="text-lg font-medium text-gray-700 mb-2">{t.uploadHint}</p>
<p className="text-sm text-gray-500">{t.supportedFormats}</p>
</div>
</div>
{/* Action Buttons */}
<div className="space-y-3">
<button
onClick={handleDownloadExcel}
disabled={products.length === 0}
className="w-full bg-emerald-600 hover:bg-emerald-700 disabled:bg-gray-300 disabled:cursor-not-allowed text-white px-4 py-3 rounded-lg font-medium transition-colors flex items-center justify-center gap-2"
>
<Download className="w-5 h-5" />
{t.downloadFile}
</button>
<button
onClick={loadSampleData}
className="w-full bg-amber-600 hover:bg-amber-700 text-white px-4 py-3 rounded-lg font-medium transition-colors flex items-center justify-center gap-2"
>
<RefreshCw className="w-5 h-5" />
{t.loadSample}
</button>
</div>
</div>
<input
ref={fileInputRef}
type="file"
accept=".xlsx"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) handleFileUpload(file);
}}
className="hidden"
/>
</div>
{/* Loading State */}
{isLoading && (
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
<div className="flex items-center gap-3">
<RefreshCw className="w-5 h-5 text-blue-600 animate-spin" />
<span className="text-blue-700 font-medium">{t.loading}</span>
</div>
</div>
)}
{/* Error State */}
{error && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
<div className="flex items-center gap-3">
<div className="w-5 h-5 bg-red-500 rounded-full flex items-center justify-center">
<span className="text-white text-xs">!</span>
</div>
<span className="text-red-700 font-medium">{error}</span>
</div>
</div>
)}
{/* Products Table */}
<ExcelTable
products={products}
language={language}
onProductUpdate={handleProductUpdate}
onProductDelete={handleProductDelete}
onProductAdd={handleProductAdd}
onQuantityChange={handleQuantityChange}
/>
</main>
)}
</div>
);
}
export default App; |