init project

This commit is contained in:
Ybehrooz
2025-10-11 20:29:31 +03:30
commit 8f73fac2db
25 changed files with 24492 additions and 0 deletions

184
.gitignore vendored Normal file
View File

@@ -0,0 +1,184 @@
# Dependencies
/node_modules
/.pnp
.pnp.js
# Testing
/coverage
# Production
/build
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Temporary files
*.tmp
*.temp
# Backup files
*.bak
*.backup
# Local development files
.env.local
.env.development.local
.env.test.local
.env.production.local
# Package manager lock files (uncomment if you want to ignore them)
# package-lock.json
# yarn.lock
# Build artifacts
dist/
build/
out/
# Logs
logs
*.log
# Database files (if you add database support later)
*.db
*.sqlite
*.sqlite3
# PDF output directory (if you want to ignore generated PDFs)
/pdfs/
/invoices/
# Custom application specific
/config/local.js
/public/uploads/

98
README.md Normal file
View File

@@ -0,0 +1,98 @@
# نرم‌افزار حسابداری هلو-مانند
یک نرم‌افزار حسابداری کامل با ویژگی‌های مشابه هلو، با رابط کاربری فارسی و طراحی مدرن.
## ویژگی‌های اصلی
### مدیریت پایه
- **تعریف شخص**: مدیریت مشتریان و تامین‌کنندگان
- **تعریف کالا**: مدیریت کالاها و محصولات با کد و قیمت
- **خرید**: ثبت و مدیریت خریدها با وضعیت‌های مختلف
- **فروش**: ثبت و مدیریت فروش‌ها با محاسبه درآمد
- **انبارداری**: مدیریت موجودی، تنظیم موجودی و هشدار کمبود
### ویژگی‌های پیشرفته (مشابه هلو)
- **نمودار حساب‌ها**: سیستم کامل حساب‌های کل و تفصیلی
- **گزارش‌های مالی**: ترازنامه، صورت سود و زیان، تراز آزمایشی
- **سیستم فاکتور**: ایجاد و مدیریت فاکتورها با محاسبه مالیات
- **پشتیبانی از چندین نوع حساب**: دارایی، بدهی، حقوق صاحبان سهام، درآمد، هزینه
## تکنولوژی‌های استفاده شده
- React 18
- React Router DOM
- Tailwind CSS
- فونت فارسی Vazir
## نصب و راه‌اندازی
1. نصب وابستگی‌ها:
```bash
npm install
```
2. اجرای پروژه:
```bash
npm start
```
3. باز کردن مرورگر در آدرس:
```
http://localhost:3000
```
## ساختار پروژه
```
src/
├── components/
│ ├── Dashboard.js # داشبورد اصلی
│ ├── PersonManagement.js # مدیریت اشخاص
│ ├── ProductManagement.js # مدیریت کالاها
│ ├── PurchaseManagement.js # مدیریت خرید
│ ├── SalesManagement.js # مدیریت فروش
│ ├── InventoryManagement.js # مدیریت انبار
│ ├── ChartOfAccounts.js # نمودار حساب‌ها
│ ├── FinancialReports.js # گزارش‌های مالی
│ └── InvoiceSystem.js # سیستم فاکتور
├── App.js # کامپوننت اصلی
├── App.css # استایل‌های سفارشی
├── index.js # نقطه ورود
└── index.css # استایل‌های اصلی
```
## ویژگی‌های رابط کاربری
- طراحی ریسپانسیو (Responsive)
- پشتیبانی کامل از زبان فارسی
- جهت متن راست به چپ (RTL)
- استفاده از فونت فارسی Vazir
- طراحی مدرن با Tailwind CSS
## نحوه استفاده
### بخش‌های اصلی
1. **داشبورد**: نمای کلی از آمار و عملیات سریع
2. **تعریف شخص**: افزودن، ویرایش و حذف مشتریان و تامین‌کنندگان
3. **تعریف کالا**: مدیریت کالاها با قیمت، موجودی و کد
4. **خرید**: ثبت خریدهای جدید و مدیریت وضعیت آن‌ها
5. **فروش**: ثبت فروش‌ها و محاسبه درآمد
6. **انبارداری**: نظارت بر موجودی و تنظیم آن
### ویژگی‌های پیشرفته (مشابه هلو)
7. **نمودار حساب‌ها**: ایجاد و مدیریت ساختار حساب‌های کل و تفصیلی
8. **گزارش‌های مالی**:
- ترازنامه با بررسی تعادل
- صورت سود و زیان با محاسبه سود/زیان خالص
- تراز آزمایشی با مانده‌های بدهکار و بستانکار
9. **سیستم فاکتور**:
- ایجاد فاکتورهای چندقلمی
- محاسبه خودکار مالیات
- مدیریت وضعیت پرداخت
## نکات مهم
- تمام داده‌ها در حافظه مرورگر ذخیره می‌شوند
- برای ذخیره دائمی داده‌ها، نیاز به اتصال به پایگاه داده است
- قیمت‌ها به ریال نمایش داده می‌شوند
- تاریخ‌ها به تقویم شمسی نمایش داده می‌شوند

20646
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

45
package.json Normal file
View File

@@ -0,0 +1,45 @@
{
"name": "farsi-accounting-app",
"version": "1.0.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.5.0",
"jspdf": "^2.5.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"autoprefixer": "^10.4.7",
"postcss": "^8.4.14",
"tailwindcss": "^3.1.6"
}
}

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

21
public/index.html Normal file
View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="نرم‌افزار حسابداری ساده"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link href="https://cdn.jsdelivr.net/gh/rastikerdar/vazir-font@v30.1.0/dist/font-face.css" rel="stylesheet" type="text/css" />
<title>نرم‌افزار حسابداری</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

15
public/manifest.json Normal file
View File

@@ -0,0 +1,15 @@
{
"short_name": "حسابداری",
"name": "نرم‌افزار حسابداری ساده",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

32
src/App.css Normal file
View File

@@ -0,0 +1,32 @@
/* Additional custom styles for the app */
.nav-link {
transition: all 0.3s ease;
}
.nav-link:hover {
background-color: rgba(255, 255, 255, 0.1);
}
/* Animation for form elements */
.form-input:focus {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}

82
src/App.js Normal file
View File

@@ -0,0 +1,82 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import './App.css';
import Dashboard from './components/Dashboard';
import PersonManagement from './components/PersonManagement';
import ProductManagement from './components/ProductManagement';
import PurchaseManagement from './components/PurchaseManagement';
import SalesManagement from './components/SalesManagement';
import InventoryManagement from './components/InventoryManagement';
import ChartOfAccounts from './components/ChartOfAccounts';
import FinancialReports from './components/FinancialReports';
import InvoiceSystem from './components/InvoiceSystem';
import InvoiceDemo from './components/InvoiceDemo';
function App() {
return (
<Router>
<div className="min-h-screen bg-gray-100 farsi-text">
{/* Navigation */}
<nav className="bg-blue-600 shadow-lg">
<div className="max-w-7xl mx-auto px-4">
<div className="flex justify-between h-16">
<div className="flex items-center">
<h1 className="text-white text-xl font-bold">نرمافزار حسابداری</h1>
</div>
<div className="flex items-center space-x-4 space-x-reverse">
<Link to="/" className="text-white hover:text-gray-200 px-3 py-2 rounded-md text-sm font-medium">
داشبورد
</Link>
<Link to="/persons" className="text-white hover:text-gray-200 px-3 py-2 rounded-md text-sm font-medium">
تعریف شخص
</Link>
<Link to="/products" className="text-white hover:text-gray-200 px-3 py-2 rounded-md text-sm font-medium">
تعریف کالا
</Link>
<Link to="/purchases" className="text-white hover:text-gray-200 px-3 py-2 rounded-md text-sm font-medium">
خرید
</Link>
<Link to="/sales" className="text-white hover:text-gray-200 px-3 py-2 rounded-md text-sm font-medium">
فروش
</Link>
<Link to="/inventory" className="text-white hover:text-gray-200 px-3 py-2 rounded-md text-sm font-medium">
انبارداری
</Link>
<Link to="/chart-of-accounts" className="text-white hover:text-gray-200 px-3 py-2 rounded-md text-sm font-medium">
نمودار حسابها
</Link>
<Link to="/financial-reports" className="text-white hover:text-gray-200 px-3 py-2 rounded-md text-sm font-medium">
گزارشهای مالی
</Link>
<Link to="/invoices" className="text-white hover:text-gray-200 px-3 py-2 rounded-md text-sm font-medium">
فاکتور
</Link>
<Link to="/invoice-demo" className="text-white hover:text-gray-200 px-3 py-2 rounded-md text-sm font-medium">
نمونه PDF
</Link>
</div>
</div>
</div>
</nav>
{/* Main Content */}
<main className="max-w-7xl mx-auto py-6 px-4">
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/persons" element={<PersonManagement />} />
<Route path="/products" element={<ProductManagement />} />
<Route path="/purchases" element={<PurchaseManagement />} />
<Route path="/sales" element={<SalesManagement />} />
<Route path="/inventory" element={<InventoryManagement />} />
<Route path="/chart-of-accounts" element={<ChartOfAccounts />} />
<Route path="/financial-reports" element={<FinancialReports />} />
<Route path="/invoices" element={<InvoiceSystem />} />
<Route path="/invoice-demo" element={<InvoiceDemo />} />
</Routes>
</main>
</div>
</Router>
);
}
export default App;

View File

@@ -0,0 +1,276 @@
import React, { useState } from 'react';
const ChartOfAccounts = () => {
const [accounts, setAccounts] = useState([
{ id: 1, code: '1000', name: 'دارایی‌ها', type: 'دارایی', parentId: null, level: 1 },
{ id: 2, code: '1100', name: 'دارایی‌های جاری', type: 'دارایی', parentId: 1, level: 2 },
{ id: 3, code: '1110', name: 'موجودی نقد', type: 'دارایی', parentId: 2, level: 3 },
{ id: 4, code: '1111', name: 'صندوق', type: 'دارایی', parentId: 3, level: 4, balance: 5000000 },
{ id: 5, code: '1112', name: 'بانک ملی', type: 'دارایی', parentId: 3, level: 4, balance: 25000000 },
{ id: 6, code: '1120', name: 'حساب‌های دریافتنی', type: 'دارایی', parentId: 2, level: 3 },
{ id: 7, code: '1121', name: 'مشتریان', type: 'دارایی', parentId: 6, level: 4, balance: 15000000 },
{ id: 8, code: '1200', name: 'موجودی کالا', type: 'دارایی', parentId: 2, level: 3, balance: 30000000 },
{ id: 9, code: '2000', name: 'بدهی‌ها', type: 'بدهی', parentId: null, level: 1 },
{ id: 10, code: '2100', name: 'بدهی‌های جاری', type: 'بدهی', parentId: 9, level: 2 },
{ id: 11, code: '2110', name: 'حساب‌های پرداختنی', type: 'بدهی', parentId: 10, level: 3 },
{ id: 12, code: '2111', name: 'تامین‌کنندگان', type: 'بدهی', parentId: 11, level: 4, balance: 8000000 },
{ id: 13, code: '3000', name: 'حقوق صاحبان سهام', type: 'حقوق صاحبان سهام', parentId: null, level: 1 },
{ id: 14, code: '3100', name: 'سرمایه', type: 'حقوق صاحبان سهام', parentId: 13, level: 2, balance: 100000000 },
{ id: 15, code: '4000', name: 'درآمدها', type: 'درآمد', parentId: null, level: 1 },
{ id: 16, code: '4100', name: 'فروش کالا', type: 'درآمد', parentId: 15, level: 2, balance: 50000000 },
{ id: 17, code: '5000', name: 'هزینه‌ها', type: 'هزینه', parentId: null, level: 1 },
{ id: 18, code: '5100', name: 'هزینه خرید کالا', type: 'هزینه', parentId: 17, level: 2, balance: 30000000 },
{ id: 19, code: '5200', name: 'هزینه‌های عملیاتی', type: 'هزینه', parentId: 17, level: 2 },
{ id: 20, code: '5210', name: 'اجاره', type: 'هزینه', parentId: 19, level: 3, balance: 2000000 },
{ id: 21, code: '5220', name: 'حقوق و دستمزد', type: 'هزینه', parentId: 19, level: 3, balance: 5000000 }
]);
const [showForm, setShowForm] = useState(false);
const [editingAccount, setEditingAccount] = useState(null);
const [formData, setFormData] = useState({
code: '',
name: '',
type: 'دارایی',
parentId: null
});
const accountTypes = ['دارایی', 'بدهی', 'حقوق صاحبان سهام', 'درآمد', 'هزینه'];
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
if (editingAccount) {
// Update existing account
setAccounts(accounts.map(account =>
account.id === editingAccount.id
? {
...account,
...formData,
level: formData.parentId ? accounts.find(a => a.id === parseInt(formData.parentId))?.level + 1 || 1 : 1
}
: account
));
setEditingAccount(null);
} else {
// Add new account
const newAccount = {
id: Date.now(),
...formData,
parentId: formData.parentId ? parseInt(formData.parentId) : null,
level: formData.parentId ? accounts.find(a => a.id === parseInt(formData.parentId))?.level + 1 || 1 : 1,
balance: 0
};
setAccounts([...accounts, newAccount]);
}
setFormData({ code: '', name: '', type: 'دارایی', parentId: null });
setShowForm(false);
};
const handleEdit = (account) => {
setEditingAccount(account);
setFormData({
code: account.code,
name: account.name,
type: account.type,
parentId: account.parentId
});
setShowForm(true);
};
const handleDelete = (id) => {
if (window.confirm('آیا از حذف این حساب اطمینان دارید؟')) {
// Also delete child accounts
const deleteAccountAndChildren = (accountId) => {
const children = accounts.filter(acc => acc.parentId === accountId);
children.forEach(child => deleteAccountAndChildren(child.id));
setAccounts(accounts.filter(account => account.id !== accountId));
};
deleteAccountAndChildren(id);
}
};
const handleCancel = () => {
setShowForm(false);
setEditingAccount(null);
setFormData({ code: '', name: '', type: 'دارایی', parentId: null });
};
const formatPrice = (price) => {
return new Intl.NumberFormat('fa-IR').format(price || 0) + ' ریال';
};
const getAccountTypeColor = (type) => {
switch (type) {
case 'دارایی':
return 'bg-blue-100 text-blue-800';
case 'بدهی':
return 'bg-red-100 text-red-800';
case 'حقوق صاحبان سهام':
return 'bg-green-100 text-green-800';
case 'درآمد':
return 'bg-purple-100 text-purple-800';
case 'هزینه':
return 'bg-yellow-100 text-yellow-800';
default:
return 'bg-gray-100 text-gray-800';
}
};
const renderAccountTree = (parentId = null, level = 0) => {
const children = accounts.filter(acc => acc.parentId === parentId);
return children.map(account => (
<React.Fragment key={account.id}>
<tr className={`${level > 0 ? 'bg-gray-50' : ''}`}>
<td className="pr-4" style={{ paddingRight: `${level * 20}px` }}>
{account.code}
</td>
<td style={{ paddingRight: `${level * 20}px` }}>
{level > 0 && '└─ '}{account.name}
</td>
<td>
<span className={`px-2 py-1 rounded-full text-xs ${getAccountTypeColor(account.type)}`}>
{account.type}
</span>
</td>
<td className="text-left font-bold">
{formatPrice(account.balance)}
</td>
<td>
<div className="flex space-x-2 space-x-reverse">
<button
onClick={() => handleEdit(account)}
className="text-blue-600 hover:text-blue-800 text-sm"
>
ویرایش
</button>
<button
onClick={() => handleDelete(account.id)}
className="text-red-600 hover:text-red-800 text-sm"
>
حذف
</button>
</div>
</td>
</tr>
{renderAccountTree(account.id, level + 1)}
</React.Fragment>
));
};
return (
<div className="farsi-text">
<div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold text-gray-900">نمودار حسابها</h1>
<button
onClick={() => setShowForm(true)}
className="btn-primary"
>
افزودن حساب جدید
</button>
</div>
{/* Form Modal */}
{showForm && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 w-full max-w-md">
<h2 className="text-xl font-bold mb-4">
{editingAccount ? 'ویرایش حساب' : 'افزودن حساب جدید'}
</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="form-label">کد حساب</label>
<input
type="text"
name="code"
value={formData.code}
onChange={handleInputChange}
className="form-input"
required
placeholder="مثال: 1111"
/>
</div>
<div>
<label className="form-label">نام حساب</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleInputChange}
className="form-input"
required
/>
</div>
<div>
<label className="form-label">نوع حساب</label>
<select
name="type"
value={formData.type}
onChange={handleInputChange}
className="form-input"
>
{accountTypes.map(type => (
<option key={type} value={type}>{type}</option>
))}
</select>
</div>
<div>
<label className="form-label">حساب والد (اختیاری)</label>
<select
name="parentId"
value={formData.parentId || ''}
onChange={handleInputChange}
className="form-input"
>
<option value="">بدون والد</option>
{accounts.map(account => (
<option key={account.id} value={account.id}>
{account.code} - {account.name}
</option>
))}
</select>
</div>
<div className="flex space-x-2 space-x-reverse">
<button type="submit" className="btn-success flex-1">
{editingAccount ? 'ویرایش' : 'افزودن'}
</button>
<button type="button" onClick={handleCancel} className="btn-secondary flex-1">
انصراف
</button>
</div>
</form>
</div>
</div>
)}
{/* Accounts Tree Table */}
<div className="card">
<div className="overflow-x-auto">
<table className="table">
<thead>
<tr>
<th>کد حساب</th>
<th>نام حساب</th>
<th>نوع</th>
<th>مانده</th>
<th>عملیات</th>
</tr>
</thead>
<tbody>
{renderAccountTree()}
</tbody>
</table>
</div>
</div>
</div>
);
};
export default ChartOfAccounts;

150
src/components/Dashboard.js Normal file
View File

@@ -0,0 +1,150 @@
import React from 'react';
import { Link } from 'react-router-dom';
const Dashboard = () => {
const stats = [
{
title: 'کل اشخاص',
value: '25',
color: 'bg-blue-500',
icon: '👥',
link: '/persons'
},
{
title: 'کل کالاها',
value: '150',
color: 'bg-green-500',
icon: '📦',
link: '/products'
},
{
title: 'خرید امروز',
value: '5',
color: 'bg-yellow-500',
icon: '🛒',
link: '/purchases'
},
{
title: 'فروش امروز',
value: '12',
color: 'bg-purple-500',
icon: '💰',
link: '/sales'
},
{
title: 'حساب‌های فعال',
value: '21',
color: 'bg-indigo-500',
icon: '📊',
link: '/chart-of-accounts'
},
{
title: 'فاکتورهای صادر شده',
value: '8',
color: 'bg-pink-500',
icon: '📄',
link: '/invoices'
}
];
return (
<div className="farsi-text">
<h1 className="text-3xl font-bold text-gray-900 mb-8">داشبورد</h1>
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-6 mb-8">
{stats.map((stat, index) => (
<Link
key={index}
to={stat.link}
className="card hover:shadow-lg transition-shadow duration-300"
>
<div className="flex items-center">
<div className={`${stat.color} rounded-full p-3 text-white text-2xl`}>
{stat.icon}
</div>
<div className="mr-4">
<p className="text-sm font-medium text-gray-600">{stat.title}</p>
<p className="text-2xl font-bold text-gray-900">{stat.value}</p>
</div>
</div>
</Link>
))}
</div>
{/* Quick Actions */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="card">
<h2 className="text-xl font-bold text-gray-900 mb-4">عملیات سریع</h2>
<div className="space-y-3">
<Link
to="/purchases"
className="flex items-center p-3 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors"
>
<span className="text-2xl ml-3">🛒</span>
<span className="font-medium">ثبت خرید جدید</span>
</Link>
<Link
to="/sales"
className="flex items-center p-3 bg-green-50 rounded-lg hover:bg-green-100 transition-colors"
>
<span className="text-2xl ml-3">💰</span>
<span className="font-medium">ثبت فروش جدید</span>
</Link>
<Link
to="/persons"
className="flex items-center p-3 bg-purple-50 rounded-lg hover:bg-purple-100 transition-colors"
>
<span className="text-2xl ml-3">👥</span>
<span className="font-medium">افزودن شخص جدید</span>
</Link>
<Link
to="/products"
className="flex items-center p-3 bg-yellow-50 rounded-lg hover:bg-yellow-100 transition-colors"
>
<span className="text-2xl ml-3">📦</span>
<span className="font-medium">افزودن کالای جدید</span>
</Link>
<Link
to="/invoices"
className="flex items-center p-3 bg-pink-50 rounded-lg hover:bg-pink-100 transition-colors"
>
<span className="text-2xl ml-3">📄</span>
<span className="font-medium">ایجاد فاکتور جدید</span>
</Link>
<Link
to="/financial-reports"
className="flex items-center p-3 bg-indigo-50 rounded-lg hover:bg-indigo-100 transition-colors"
>
<span className="text-2xl ml-3">📊</span>
<span className="font-medium">مشاهده گزارشهای مالی</span>
</Link>
</div>
</div>
<div className="card">
<h2 className="text-xl font-bold text-gray-900 mb-4">آخرین فعالیتها</h2>
<div className="space-y-3">
<div className="flex items-center p-3 bg-gray-50 rounded-lg">
<span className="text-green-600 ml-3"></span>
<span className="text-sm">فروش کالای A به مشتری B</span>
<span className="text-xs text-gray-500 mr-auto">2 ساعت پیش</span>
</div>
<div className="flex items-center p-3 bg-gray-50 rounded-lg">
<span className="text-blue-600 ml-3">+</span>
<span className="text-sm">خرید کالای C از تامینکننده D</span>
<span className="text-xs text-gray-500 mr-auto">4 ساعت پیش</span>
</div>
<div className="flex items-center p-3 bg-gray-50 rounded-lg">
<span className="text-purple-600 ml-3">👤</span>
<span className="text-sm">افزودن مشتری جدید</span>
<span className="text-xs text-gray-500 mr-auto">6 ساعت پیش</span>
</div>
</div>
</div>
</div>
</div>
);
};
export default Dashboard;

View File

@@ -0,0 +1,335 @@
import React, { useState } from 'react';
const FinancialReports = () => {
const [selectedReport, setSelectedReport] = useState('balance-sheet');
const [reportDate, setReportDate] = useState(new Date().toLocaleDateString('fa-IR'));
// Sample data for reports
const balanceSheetData = {
assets: [
{ name: 'موجودی نقد', amount: 30000000 },
{ name: 'حساب‌های دریافتنی', amount: 15000000 },
{ name: 'موجودی کالا', amount: 30000000 },
{ name: 'دارایی‌های ثابت', amount: 50000000 }
],
liabilities: [
{ name: 'حساب‌های پرداختنی', amount: 8000000 },
{ name: 'وام‌های کوتاه‌مدت', amount: 12000000 }
],
equity: [
{ name: 'سرمایه', amount: 100000000 },
{ name: 'سود انباشته', amount: 15000000 }
]
};
const profitLossData = {
revenues: [
{ name: 'فروش کالا', amount: 50000000 },
{ name: 'درآمد خدمات', amount: 5000000 }
],
expenses: [
{ name: 'هزینه خرید کالا', amount: 30000000 },
{ name: 'هزینه‌های عملیاتی', amount: 7000000 },
{ name: 'اجاره', amount: 2000000 },
{ name: 'حقوق و دستمزد', amount: 5000000 }
]
};
const formatPrice = (price) => {
return new Intl.NumberFormat('fa-IR').format(price) + ' ریال';
};
const calculateTotal = (items) => {
return items.reduce((sum, item) => sum + item.amount, 0);
};
const totalAssets = calculateTotal(balanceSheetData.assets);
const totalLiabilities = calculateTotal(balanceSheetData.liabilities);
const totalEquity = calculateTotal(balanceSheetData.equity);
const totalRevenues = calculateTotal(profitLossData.revenues);
const totalExpenses = calculateTotal(profitLossData.expenses);
const netProfit = totalRevenues - totalExpenses;
const renderBalanceSheet = () => (
<div className="space-y-6">
<div className="text-center mb-6">
<h2 className="text-2xl font-bold">ترازنامه</h2>
<p className="text-gray-600">تاریخ: {reportDate}</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Assets */}
<div className="card">
<h3 className="text-xl font-bold mb-4 text-blue-800">داراییها</h3>
<div className="space-y-2">
{balanceSheetData.assets.map((asset, index) => (
<div key={index} className="flex justify-between items-center py-2 border-b border-gray-200">
<span>{asset.name}</span>
<span className="font-bold">{formatPrice(asset.amount)}</span>
</div>
))}
<div className="flex justify-between items-center py-2 border-t-2 border-blue-500 font-bold text-lg">
<span>مجموع داراییها</span>
<span className="text-blue-600">{formatPrice(totalAssets)}</span>
</div>
</div>
</div>
{/* Liabilities & Equity */}
<div className="space-y-6">
<div className="card">
<h3 className="text-xl font-bold mb-4 text-red-800">بدهیها</h3>
<div className="space-y-2">
{balanceSheetData.liabilities.map((liability, index) => (
<div key={index} className="flex justify-between items-center py-2 border-b border-gray-200">
<span>{liability.name}</span>
<span className="font-bold">{formatPrice(liability.amount)}</span>
</div>
))}
<div className="flex justify-between items-center py-2 border-t-2 border-red-500 font-bold">
<span>مجموع بدهیها</span>
<span className="text-red-600">{formatPrice(totalLiabilities)}</span>
</div>
</div>
</div>
<div className="card">
<h3 className="text-xl font-bold mb-4 text-green-800">حقوق صاحبان سهام</h3>
<div className="space-y-2">
{balanceSheetData.equity.map((equity, index) => (
<div key={index} className="flex justify-between items-center py-2 border-b border-gray-200">
<span>{equity.name}</span>
<span className="font-bold">{formatPrice(equity.amount)}</span>
</div>
))}
<div className="flex justify-between items-center py-2 border-t-2 border-green-500 font-bold">
<span>مجموع حقوق صاحبان سهام</span>
<span className="text-green-600">{formatPrice(totalEquity)}</span>
</div>
</div>
</div>
</div>
</div>
{/* Balance Check */}
<div className="card bg-gray-50">
<div className="text-center">
<h3 className="text-lg font-bold mb-2">بررسی تراز</h3>
<p className={`text-lg ${totalAssets === (totalLiabilities + totalEquity) ? 'text-green-600' : 'text-red-600'}`}>
{totalAssets === (totalLiabilities + totalEquity) ? '✓ ترازنامه متعادل است' : '✗ ترازنامه نامتعادل است'}
</p>
<p className="text-sm text-gray-600 mt-2">
داراییها: {formatPrice(totalAssets)} | بدهیها + حقوق صاحبان سهام: {formatPrice(totalLiabilities + totalEquity)}
</p>
</div>
</div>
</div>
);
const renderProfitLoss = () => (
<div className="space-y-6">
<div className="text-center mb-6">
<h2 className="text-2xl font-bold">صورت سود و زیان</h2>
<p className="text-gray-600">تاریخ: {reportDate}</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Revenues */}
<div className="card">
<h3 className="text-xl font-bold mb-4 text-green-800">درآمدها</h3>
<div className="space-y-2">
{profitLossData.revenues.map((revenue, index) => (
<div key={index} className="flex justify-between items-center py-2 border-b border-gray-200">
<span>{revenue.name}</span>
<span className="font-bold">{formatPrice(revenue.amount)}</span>
</div>
))}
<div className="flex justify-between items-center py-2 border-t-2 border-green-500 font-bold text-lg">
<span>مجموع درآمدها</span>
<span className="text-green-600">{formatPrice(totalRevenues)}</span>
</div>
</div>
</div>
{/* Expenses */}
<div className="card">
<h3 className="text-xl font-bold mb-4 text-red-800">هزینهها</h3>
<div className="space-y-2">
{profitLossData.expenses.map((expense, index) => (
<div key={index} className="flex justify-between items-center py-2 border-b border-gray-200">
<span>{expense.name}</span>
<span className="font-bold">{formatPrice(expense.amount)}</span>
</div>
))}
<div className="flex justify-between items-center py-2 border-t-2 border-red-500 font-bold text-lg">
<span>مجموع هزینهها</span>
<span className="text-red-600">{formatPrice(totalExpenses)}</span>
</div>
</div>
</div>
</div>
{/* Net Profit/Loss */}
<div className="card">
<div className="text-center">
<h3 className="text-xl font-bold mb-4">نتیجه فعالیت</h3>
<div className={`text-3xl font-bold ${netProfit >= 0 ? 'text-green-600' : 'text-red-600'}`}>
{netProfit >= 0 ? 'سود خالص' : 'زیان خالص'}: {formatPrice(Math.abs(netProfit))}
</div>
<div className="mt-4 grid grid-cols-2 gap-4 text-sm">
<div className="bg-green-50 p-3 rounded">
<p className="font-bold text-green-800">درآمد کل</p>
<p className="text-green-600">{formatPrice(totalRevenues)}</p>
</div>
<div className="bg-red-50 p-3 rounded">
<p className="font-bold text-red-800">هزینه کل</p>
<p className="text-red-600">{formatPrice(totalExpenses)}</p>
</div>
</div>
</div>
</div>
</div>
);
const renderTrialBalance = () => (
<div className="space-y-6">
<div className="text-center mb-6">
<h2 className="text-2xl font-bold">تراز آزمایشی</h2>
<p className="text-gray-600">تاریخ: {reportDate}</p>
</div>
<div className="card">
<div className="overflow-x-auto">
<table className="table">
<thead>
<tr>
<th>کد حساب</th>
<th>نام حساب</th>
<th>بدهکار</th>
<th>بستانکار</th>
</tr>
</thead>
<tbody>
<tr>
<td>1111</td>
<td>صندوق</td>
<td className="text-left">{formatPrice(5000000)}</td>
<td className="text-left">-</td>
</tr>
<tr>
<td>1112</td>
<td>بانک ملی</td>
<td className="text-left">{formatPrice(25000000)}</td>
<td className="text-left">-</td>
</tr>
<tr>
<td>1121</td>
<td>مشتریان</td>
<td className="text-left">{formatPrice(15000000)}</td>
<td className="text-left">-</td>
</tr>
<tr>
<td>1200</td>
<td>موجودی کالا</td>
<td className="text-left">{formatPrice(30000000)}</td>
<td className="text-left">-</td>
</tr>
<tr>
<td>2111</td>
<td>تامینکنندگان</td>
<td className="text-left">-</td>
<td className="text-left">{formatPrice(8000000)}</td>
</tr>
<tr>
<td>3100</td>
<td>سرمایه</td>
<td className="text-left">-</td>
<td className="text-left">{formatPrice(100000000)}</td>
</tr>
<tr>
<td>4100</td>
<td>فروش کالا</td>
<td className="text-left">-</td>
<td className="text-left">{formatPrice(50000000)}</td>
</tr>
<tr>
<td>5100</td>
<td>هزینه خرید کالا</td>
<td className="text-left">{formatPrice(30000000)}</td>
<td className="text-left">-</td>
</tr>
<tr className="border-t-2 border-gray-400 font-bold">
<td colSpan="2">مجموع</td>
<td className="text-left">{formatPrice(75000000)}</td>
<td className="text-left">{formatPrice(158000000)}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
);
return (
<div className="farsi-text">
<div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold text-gray-900">گزارشهای مالی</h1>
<div className="flex space-x-2 space-x-reverse">
<input
type="text"
value={reportDate}
onChange={(e) => setReportDate(e.target.value)}
className="form-input w-40"
placeholder="تاریخ گزارش"
/>
<button className="btn-primary">
چاپ گزارش
</button>
</div>
</div>
{/* Report Type Selector */}
<div className="card mb-6">
<div className="flex space-x-4 space-x-reverse">
<button
onClick={() => setSelectedReport('balance-sheet')}
className={`px-4 py-2 rounded-lg font-medium ${
selectedReport === 'balance-sheet'
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
ترازنامه
</button>
<button
onClick={() => setSelectedReport('profit-loss')}
className={`px-4 py-2 rounded-lg font-medium ${
selectedReport === 'profit-loss'
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
صورت سود و زیان
</button>
<button
onClick={() => setSelectedReport('trial-balance')}
className={`px-4 py-2 rounded-lg font-medium ${
selectedReport === 'trial-balance'
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
تراز آزمایشی
</button>
</div>
</div>
{/* Report Content */}
{selectedReport === 'balance-sheet' && renderBalanceSheet()}
{selectedReport === 'profit-loss' && renderProfitLoss()}
{selectedReport === 'trial-balance' && renderTrialBalance()}
</div>
);
};
export default FinancialReports;

View File

@@ -0,0 +1,299 @@
import React, { useState } from 'react';
const InventoryManagement = () => {
const [inventory, setInventory] = useState([
{
id: 1,
product: 'لپ‌تاپ ایسوس',
code: 'LAP001',
currentStock: 5,
minStock: 2,
maxStock: 20,
unit: 'عدد',
lastUpdate: '1403/01/15'
},
{
id: 2,
product: 'موبایل سامسونگ',
code: 'MOB002',
currentStock: 12,
minStock: 5,
maxStock: 50,
unit: 'عدد',
lastUpdate: '1403/01/14'
},
{
id: 3,
product: 'کتاب برنامه‌نویسی',
code: 'BOK003',
currentStock: 25,
minStock: 10,
maxStock: 100,
unit: 'جلد',
lastUpdate: '1403/01/13'
}
]);
const [showAdjustmentForm, setShowAdjustmentForm] = useState(false);
const [selectedProduct, setSelectedProduct] = useState(null);
const [adjustmentData, setAdjustmentData] = useState({
type: 'افزایش',
quantity: '',
reason: ''
});
const handleAdjustmentChange = (e) => {
const { name, value } = e.target;
setAdjustmentData(prev => ({
...prev,
[name]: value
}));
};
const handleStockAdjustment = (e) => {
e.preventDefault();
const adjustmentQuantity = parseInt(adjustmentData.quantity);
const newStock = adjustmentData.type === 'افزایش'
? selectedProduct.currentStock + adjustmentQuantity
: selectedProduct.currentStock - adjustmentQuantity;
if (newStock < 0) {
alert('موجودی نمی‌تواند منفی باشد!');
return;
}
setInventory(inventory.map(item =>
item.id === selectedProduct.id
? {
...item,
currentStock: newStock,
lastUpdate: new Date().toLocaleDateString('fa-IR')
}
: item
));
setShowAdjustmentForm(false);
setSelectedProduct(null);
setAdjustmentData({ type: 'افزایش', quantity: '', reason: '' });
};
const handleCancelAdjustment = () => {
setShowAdjustmentForm(false);
setSelectedProduct(null);
setAdjustmentData({ type: 'افزایش', quantity: '', reason: '' });
};
const getStockStatus = (current, min, max) => {
if (current <= min) return { status: 'کم', color: 'bg-red-100 text-red-800' };
if (current >= max) return { status: 'زیاد', color: 'bg-yellow-100 text-yellow-800' };
return { status: 'مناسب', color: 'bg-green-100 text-green-800' };
};
const lowStockItems = inventory.filter(item => item.currentStock <= item.minStock);
const totalValue = inventory.reduce((sum, item) => {
// Assuming average price for calculation
const avgPrice = item.product === 'لپ‌تاپ ایسوس' ? 15000000 :
item.product === 'موبایل سامسونگ' ? 8000000 : 150000;
return sum + (item.currentStock * avgPrice);
}, 0);
const formatPrice = (price) => {
return new Intl.NumberFormat('fa-IR').format(price) + ' ریال';
};
return (
<div className="farsi-text">
<div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold text-gray-900">مدیریت انبار</h1>
<button
onClick={() => setShowAdjustmentForm(true)}
className="btn-primary"
>
تنظیم موجودی
</button>
</div>
{/* Inventory Summary */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
<div className="card">
<div className="flex items-center">
<div className="bg-blue-500 rounded-full p-3 text-white text-2xl ml-4">
📦
</div>
<div>
<p className="text-sm font-medium text-gray-600">کل کالاها</p>
<p className="text-2xl font-bold text-gray-900">{inventory.length}</p>
</div>
</div>
</div>
<div className="card">
<div className="flex items-center">
<div className="bg-red-500 rounded-full p-3 text-white text-2xl ml-4">
</div>
<div>
<p className="text-sm font-medium text-gray-600">کمبود موجودی</p>
<p className="text-2xl font-bold text-gray-900">{lowStockItems.length}</p>
</div>
</div>
</div>
<div className="card">
<div className="flex items-center">
<div className="bg-green-500 rounded-full p-3 text-white text-2xl ml-4">
💰
</div>
<div>
<p className="text-sm font-medium text-gray-600">ارزش کل انبار</p>
<p className="text-lg font-bold text-gray-900">{formatPrice(totalValue)}</p>
</div>
</div>
</div>
<div className="card">
<div className="flex items-center">
<div className="bg-purple-500 rounded-full p-3 text-white text-2xl ml-4">
📊
</div>
<div>
<p className="text-sm font-medium text-gray-600">کل موجودی</p>
<p className="text-2xl font-bold text-gray-900">
{inventory.reduce((sum, item) => sum + item.currentStock, 0)}
</p>
</div>
</div>
</div>
</div>
{/* Low Stock Alert */}
{lowStockItems.length > 0 && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
<h3 className="text-red-800 font-bold mb-2">هشدار کمبود موجودی</h3>
<div className="space-y-2">
{lowStockItems.map(item => (
<div key={item.id} className="text-red-700">
{item.product} - موجودی فعلی: {item.currentStock} {item.unit} (حداقل: {item.minStock})
</div>
))}
</div>
</div>
)}
{/* Stock Adjustment Form Modal */}
{showAdjustmentForm && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 w-full max-w-md">
<h2 className="text-xl font-bold mb-4">تنظیم موجودی</h2>
<form onSubmit={handleStockAdjustment} className="space-y-4">
<div>
<label className="form-label">کالا</label>
<select
value={selectedProduct?.id || ''}
onChange={(e) => {
const product = inventory.find(item => item.id === parseInt(e.target.value));
setSelectedProduct(product);
}}
className="form-input"
required
>
<option value="">انتخاب کنید</option>
{inventory.map(item => (
<option key={item.id} value={item.id}>
{item.product} (موجودی فعلی: {item.currentStock})
</option>
))}
</select>
</div>
<div>
<label className="form-label">نوع تنظیم</label>
<select
name="type"
value={adjustmentData.type}
onChange={handleAdjustmentChange}
className="form-input"
>
<option value="افزایش">افزایش موجودی</option>
<option value="کاهش">کاهش موجودی</option>
</select>
</div>
<div>
<label className="form-label">تعداد</label>
<input
type="number"
name="quantity"
value={adjustmentData.quantity}
onChange={handleAdjustmentChange}
className="form-input"
required
min="1"
/>
</div>
<div>
<label className="form-label">دلیل</label>
<textarea
name="reason"
value={adjustmentData.reason}
onChange={handleAdjustmentChange}
className="form-input"
rows="3"
placeholder="دلیل تنظیم موجودی..."
/>
</div>
<div className="flex space-x-2 space-x-reverse">
<button type="submit" className="btn-success flex-1">
اعمال تغییرات
</button>
<button type="button" onClick={handleCancelAdjustment} className="btn-secondary flex-1">
انصراف
</button>
</div>
</form>
</div>
</div>
)}
{/* Inventory Table */}
<div className="card">
<div className="overflow-x-auto">
<table className="table">
<thead>
<tr>
<th>نام کالا</th>
<th>کد کالا</th>
<th>موجودی فعلی</th>
<th>حداقل</th>
<th>حداکثر</th>
<th>وضعیت</th>
<th>آخرین بروزرسانی</th>
</tr>
</thead>
<tbody>
{inventory.map(item => {
const stockStatus = getStockStatus(item.currentStock, item.minStock, item.maxStock);
return (
<tr key={item.id}>
<td>{item.product}</td>
<td>
<span className="bg-gray-100 px-2 py-1 rounded text-sm">
{item.code}
</span>
</td>
<td className="font-bold">{item.currentStock} {item.unit}</td>
<td>{item.minStock} {item.unit}</td>
<td>{item.maxStock} {item.unit}</td>
<td>
<span className={`px-2 py-1 rounded-full text-xs ${stockStatus.color}`}>
{stockStatus.status}
</span>
</td>
<td>{item.lastUpdate}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</div>
);
};
export default InventoryManagement;

View File

@@ -0,0 +1,136 @@
import React from 'react';
import InvoicePDF from './InvoicePDF';
import { sampleInvoice, sampleInvoice2 } from '../data/sampleInvoice';
const InvoiceDemo = () => {
const handleGenerateSamplePDF = (invoice) => {
InvoicePDF.generatePDF(invoice);
};
const handleGenerateSampleHTML = (invoice) => {
const invoiceHTML = InvoicePDF.generateHTMLInvoice(invoice);
const printWindow = window.open('', '_blank');
printWindow.document.write(invoiceHTML);
printWindow.document.close();
printWindow.focus();
printWindow.print();
};
return (
<div className="farsi-text p-8">
<h1 className="text-3xl font-bold text-gray-900 mb-8">نمونه فاکتورهای PDF</h1>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{/* Sample Invoice 1 */}
<div className="card">
<h2 className="text-xl font-bold mb-4">فاکتور نمونه 1 - فروش تجهیزات کامپیوتری</h2>
<div className="space-y-2 mb-4">
<p><strong>شماره فاکتور:</strong> {sampleInvoice.invoiceNumber}</p>
<p><strong>مشتری:</strong> {sampleInvoice.customer}</p>
<p><strong>تعداد اقلام:</strong> {sampleInvoice.items.length}</p>
<p><strong>مبلغ کل:</strong> {new Intl.NumberFormat('fa-IR').format(sampleInvoice.total)} ریال</p>
</div>
<div className="flex space-x-2 space-x-reverse">
<button
onClick={() => handleGenerateSamplePDF(sampleInvoice)}
className="btn-success flex-1"
>
دانلود PDF
</button>
<button
onClick={() => handleGenerateSampleHTML(sampleInvoice)}
className="btn-primary flex-1"
>
چاپ HTML
</button>
</div>
</div>
{/* Sample Invoice 2 */}
<div className="card">
<h2 className="text-xl font-bold mb-4">فاکتور نمونه 2 - فروش تجهیزات شبکه</h2>
<div className="space-y-2 mb-4">
<p><strong>شماره فاکتور:</strong> {sampleInvoice2.invoiceNumber}</p>
<p><strong>مشتری:</strong> {sampleInvoice2.customer}</p>
<p><strong>تعداد اقلام:</strong> {sampleInvoice2.items.length}</p>
<p><strong>مبلغ کل:</strong> {new Intl.NumberFormat('fa-IR').format(sampleInvoice2.total)} ریال</p>
</div>
<div className="flex space-x-2 space-x-reverse">
<button
onClick={() => handleGenerateSamplePDF(sampleInvoice2)}
className="btn-success flex-1"
>
دانلود PDF
</button>
<button
onClick={() => handleGenerateSampleHTML(sampleInvoice2)}
className="btn-primary flex-1"
>
چاپ HTML
</button>
</div>
</div>
</div>
{/* Instructions */}
<div className="card mt-8">
<h2 className="text-xl font-bold mb-4">راهنمای استفاده</h2>
<div className="space-y-3">
<div className="flex items-start space-x-3 space-x-reverse">
<span className="bg-green-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm font-bold">1</span>
<div>
<p className="font-medium">دانلود PDF:</p>
<p className="text-gray-600">فایل PDF فاکتور را مستقیماً دانلود میکند</p>
</div>
</div>
<div className="flex items-start space-x-3 space-x-reverse">
<span className="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm font-bold">2</span>
<div>
<p className="font-medium">چاپ HTML:</p>
<p className="text-gray-600">فاکتور را در پنجره جدید باز کرده و امکان چاپ فراهم میکند</p>
</div>
</div>
<div className="flex items-start space-x-3 space-x-reverse">
<span className="bg-purple-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm font-bold">3</span>
<div>
<p className="font-medium">پیشنمایش:</p>
<p className="text-gray-600">در بخش فاکتورها، دکمه "پیش‌نمایش" را کلیک کنید</p>
</div>
</div>
</div>
</div>
{/* Features */}
<div className="card mt-8">
<h2 className="text-xl font-bold mb-4">ویژگیهای فاکتور PDF</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<h3 className="font-bold text-green-600"> ویژگیهای موجود:</h3>
<ul className="space-y-1 text-sm">
<li> طراحی حرفهای و زیبا</li>
<li> پشتیبانی کامل از زبان فارسی</li>
<li> محاسبه خودکار مالیات</li>
<li> نمایش جزئیات کامل مشتری</li>
<li> جدول اقلام با قیمتگذاری</li>
<li> فرمتبندی اعداد به فارسی</li>
<li> قابلیت چاپ و ذخیره</li>
</ul>
</div>
<div className="space-y-2">
<h3 className="font-bold text-blue-600">🔧 قابلیتهای فنی:</h3>
<ul className="space-y-1 text-sm">
<li> تولید PDF با jsPDF</li>
<li> نسخه HTML برای چاپ</li>
<li> طراحی ریسپانسیو</li>
<li> پشتیبانی از چندین قلم</li>
<li> بهینهسازی برای چاپ</li>
<li> سازگار با مرورگرهای مختلف</li>
</ul>
</div>
</div>
</div>
</div>
);
};
export default InvoiceDemo;

View File

@@ -0,0 +1,257 @@
import React from 'react';
import jsPDF from 'jspdf';
const InvoicePDF = {
generatePDF: (invoice) => {
const doc = new jsPDF();
// Set font for Persian text (using a basic approach)
doc.setFont('helvetica');
// Company Header
doc.setFontSize(20);
doc.text('شرکت حسابداری نمونه', 105, 20, { align: 'center' });
doc.setFontSize(12);
doc.text('آدرس: تهران، خیابان ولیعصر، پلاک 123', 105, 30, { align: 'center' });
doc.text('تلفن: 021-12345678 | ایمیل: info@company.com', 105, 35, { align: 'center' });
// Invoice Title
doc.setFontSize(18);
doc.text('فاکتور فروش', 105, 50, { align: 'center' });
// Invoice Details
doc.setFontSize(12);
doc.text(`شماره فاکتور: ${invoice.invoiceNumber}`, 20, 65);
doc.text(`تاریخ: ${invoice.date}`, 20, 70);
doc.text(`وضعیت: ${invoice.status}`, 20, 75);
// Customer Information
doc.setFontSize(14);
doc.text('اطلاعات مشتری:', 20, 90);
doc.setFontSize(12);
doc.text(`نام: ${invoice.customer}`, 20, 100);
doc.text(`آدرس: ${invoice.customerAddress}`, 20, 105);
// Items Table Header
doc.setFontSize(12);
doc.text('شرح کالا/خدمات', 20, 125);
doc.text('تعداد', 100, 125);
doc.text('قیمت واحد', 120, 125);
doc.text('مبلغ', 160, 125);
// Draw line under header
doc.line(20, 130, 190, 130);
// Items
let yPosition = 140;
invoice.items.forEach((item, index) => {
doc.text(item.product, 20, yPosition);
doc.text(item.quantity.toString(), 100, yPosition);
doc.text(InvoicePDF.formatPrice(item.unitPrice), 120, yPosition);
doc.text(InvoicePDF.formatPrice(item.total), 160, yPosition);
yPosition += 10;
});
// Draw line under items
doc.line(20, yPosition, 190, yPosition);
// Totals
yPosition += 15;
doc.text('مجموع:', 120, yPosition);
doc.text(InvoicePDF.formatPrice(invoice.subtotal), 160, yPosition);
yPosition += 10;
doc.text('مالیات (10%):', 120, yPosition);
doc.text(InvoicePDF.formatPrice(invoice.tax), 160, yPosition);
yPosition += 10;
doc.setFontSize(14);
doc.text('مبلغ کل:', 120, yPosition);
doc.text(InvoicePDF.formatPrice(invoice.total), 160, yPosition);
// Footer
yPosition += 30;
doc.setFontSize(10);
doc.text('با تشکر از انتخاب شما', 105, yPosition, { align: 'center' });
doc.text('این فاکتور به صورت خودکار تولید شده است', 105, yPosition + 10, { align: 'center' });
// Save the PDF
doc.save(`invoice-${invoice.invoiceNumber}.pdf`);
},
formatPrice: (price) => {
return new Intl.NumberFormat('fa-IR').format(price) + ' ریال';
},
// Alternative method using HTML to PDF conversion
generateHTMLInvoice: (invoice) => {
const invoiceHTML = `
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>فاکتور ${invoice.invoiceNumber}</title>
<style>
body {
font-family: 'Vazir', Tahoma, sans-serif;
direction: rtl;
text-align: right;
margin: 0;
padding: 20px;
background: white;
}
.invoice-container {
max-width: 800px;
margin: 0 auto;
border: 2px solid #333;
padding: 30px;
}
.header {
text-align: center;
border-bottom: 2px solid #333;
padding-bottom: 20px;
margin-bottom: 30px;
}
.company-name {
font-size: 24px;
font-weight: bold;
margin-bottom: 10px;
}
.company-info {
font-size: 14px;
color: #666;
}
.invoice-title {
font-size: 20px;
font-weight: bold;
text-align: center;
margin: 20px 0;
}
.invoice-details {
display: flex;
justify-content: space-between;
margin-bottom: 30px;
}
.invoice-info, .customer-info {
flex: 1;
}
.invoice-info h3, .customer-info h3 {
margin-bottom: 10px;
color: #333;
}
.items-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.items-table th, .items-table td {
border: 1px solid #333;
padding: 10px;
text-align: center;
}
.items-table th {
background-color: #f5f5f5;
font-weight: bold;
}
.totals {
text-align: left;
margin-top: 20px;
}
.total-row {
display: flex;
justify-content: space-between;
margin: 5px 0;
padding: 5px 0;
}
.total-final {
border-top: 2px solid #333;
font-weight: bold;
font-size: 16px;
}
.footer {
text-align: center;
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid #ccc;
color: #666;
}
</style>
</head>
<body>
<div class="invoice-container">
<div class="header">
<div class="company-name">شرکت حسابداری نمونه</div>
<div class="company-info">
آدرس: تهران، خیابان ولیعصر، پلاک 123<br>
تلفن: 021-12345678 | ایمیل: info@company.com
</div>
</div>
<div class="invoice-title">فاکتور فروش</div>
<div class="invoice-details">
<div class="invoice-info">
<h3>اطلاعات فاکتور</h3>
<p><strong>شماره فاکتور:</strong> ${invoice.invoiceNumber}</p>
<p><strong>تاریخ:</strong> ${invoice.date}</p>
<p><strong>وضعیت:</strong> ${invoice.status}</p>
</div>
<div class="customer-info">
<h3>اطلاعات مشتری</h3>
<p><strong>نام:</strong> ${invoice.customer}</p>
<p><strong>آدرس:</strong> ${invoice.customerAddress}</p>
</div>
</div>
<table class="items-table">
<thead>
<tr>
<th>شرح کالا/خدمات</th>
<th>تعداد</th>
<th>قیمت واحد</th>
<th>مبلغ</th>
</tr>
</thead>
<tbody>
${invoice.items.map(item => `
<tr>
<td>${item.product}</td>
<td>${item.quantity}</td>
<td>${InvoicePDF.formatPrice(item.unitPrice)}</td>
<td>${InvoicePDF.formatPrice(item.total)}</td>
</tr>
`).join('')}
</tbody>
</table>
<div class="totals">
<div class="total-row">
<span>مجموع:</span>
<span>${InvoicePDF.formatPrice(invoice.subtotal)}</span>
</div>
<div class="total-row">
<span>مالیات (10%):</span>
<span>${InvoicePDF.formatPrice(invoice.tax)}</span>
</div>
<div class="total-row total-final">
<span>مبلغ کل:</span>
<span>${InvoicePDF.formatPrice(invoice.total)}</span>
</div>
</div>
<div class="footer">
<p>با تشکر از انتخاب شما</p>
<p>این فاکتور به صورت خودکار تولید شده است</p>
</div>
</div>
</body>
</html>
`;
return invoiceHTML;
}
};
export default InvoicePDF;

View File

@@ -0,0 +1,150 @@
import React from 'react';
const InvoicePreview = ({ invoice, onClose }) => {
const formatPrice = (price) => {
return new Intl.NumberFormat('fa-IR').format(price) + ' ریال';
};
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 overflow-y-auto">
<div className="bg-white rounded-lg p-6 w-full max-w-4xl my-8">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold">پیشنمایش فاکتور</h2>
<button
onClick={onClose}
className="text-gray-500 hover:text-gray-700 text-2xl"
>
×
</button>
</div>
{/* Invoice Preview */}
<div className="border-2 border-gray-300 p-8 bg-white" style={{ minHeight: '800px' }}>
{/* Company Header */}
<div className="text-center border-b-2 border-gray-400 pb-6 mb-8">
<h1 className="text-3xl font-bold mb-4">شرکت حسابداری نمونه</h1>
<div className="text-gray-600">
<p>آدرس: تهران، خیابان ولیعصر، پلاک 123</p>
<p>تلفن: 021-12345678 | ایمیل: info@company.com</p>
</div>
</div>
{/* Invoice Title */}
<div className="text-center mb-8">
<h2 className="text-2xl font-bold">فاکتور فروش</h2>
</div>
{/* Invoice Details */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">
<div>
<h3 className="text-lg font-bold mb-4">اطلاعات فاکتور</h3>
<div className="space-y-2">
<p><strong>شماره فاکتور:</strong> {invoice.invoiceNumber}</p>
<p><strong>تاریخ:</strong> {invoice.date}</p>
<p><strong>وضعیت:</strong>
<span className={`ml-2 px-2 py-1 rounded-full text-xs ${
invoice.status === 'پرداخت شده' ? 'bg-green-100 text-green-800' :
invoice.status === 'در انتظار پرداخت' ? 'bg-yellow-100 text-yellow-800' :
'bg-red-100 text-red-800'
}`}>
{invoice.status}
</span>
</p>
</div>
</div>
<div>
<h3 className="text-lg font-bold mb-4">اطلاعات مشتری</h3>
<div className="space-y-2">
<p><strong>نام:</strong> {invoice.customer}</p>
<p><strong>آدرس:</strong> {invoice.customerAddress}</p>
</div>
</div>
</div>
{/* Items Table */}
<div className="mb-8">
<table className="w-full border-collapse border border-gray-400">
<thead>
<tr className="bg-gray-100">
<th className="border border-gray-400 p-3 text-right">شرح کالا/خدمات</th>
<th className="border border-gray-400 p-3 text-center">تعداد</th>
<th className="border border-gray-400 p-3 text-left">قیمت واحد</th>
<th className="border border-gray-400 p-3 text-left">مبلغ</th>
</tr>
</thead>
<tbody>
{invoice.items.map((item, index) => (
<tr key={index}>
<td className="border border-gray-400 p-3">{item.product}</td>
<td className="border border-gray-400 p-3 text-center">{item.quantity}</td>
<td className="border border-gray-400 p-3 text-left">{formatPrice(item.unitPrice)}</td>
<td className="border border-gray-400 p-3 text-left font-bold">{formatPrice(item.total)}</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Totals */}
<div className="text-left">
<div className="flex justify-between items-center py-2 border-b border-gray-300">
<span className="font-medium">مجموع:</span>
<span className="font-bold">{formatPrice(invoice.subtotal)}</span>
</div>
<div className="flex justify-between items-center py-2 border-b border-gray-300">
<span className="font-medium">مالیات (10%):</span>
<span className="font-bold">{formatPrice(invoice.tax)}</span>
</div>
<div className="flex justify-between items-center py-4 border-t-2 border-gray-400">
<span className="text-xl font-bold">مبلغ کل:</span>
<span className="text-xl font-bold text-blue-600">{formatPrice(invoice.total)}</span>
</div>
</div>
{/* Footer */}
<div className="text-center mt-12 pt-6 border-t border-gray-300">
<p className="text-gray-600 mb-2">با تشکر از انتخاب شما</p>
<p className="text-sm text-gray-500">این فاکتور به صورت خودکار تولید شده است</p>
</div>
</div>
{/* Action Buttons */}
<div className="flex justify-center space-x-4 space-x-reverse mt-6">
<button
onClick={() => {
// Generate PDF
const { generatePDF } = require('./InvoicePDF');
generatePDF(invoice);
}}
className="btn-success px-6 py-2"
>
دانلود PDF
</button>
<button
onClick={() => {
// Generate HTML and print
const { generateHTMLInvoice } = require('./InvoicePDF');
const invoiceHTML = generateHTMLInvoice(invoice);
const printWindow = window.open('', '_blank');
printWindow.document.write(invoiceHTML);
printWindow.document.close();
printWindow.focus();
printWindow.print();
}}
className="btn-primary px-6 py-2"
>
چاپ فاکتور
</button>
<button
onClick={onClose}
className="btn-secondary px-6 py-2"
>
بستن
</button>
</div>
</div>
</div>
);
};
export default InvoicePreview;

View File

@@ -0,0 +1,516 @@
import React, { useState } from 'react';
import InvoicePDF from './InvoicePDF';
import InvoicePreview from './InvoicePreview';
const InvoiceSystem = () => {
const [invoices, setInvoices] = useState([
{
id: 1,
invoiceNumber: 'INV-001',
date: '1403/01/15',
customer: 'احمد محمدی',
customerAddress: 'تهران، خیابان ولیعصر',
items: [
{ product: 'لپ‌تاپ ایسوس', quantity: 1, unitPrice: 16000000, total: 16000000 },
{ product: 'ماوس بی‌سیم', quantity: 1, unitPrice: 500000, total: 500000 }
],
subtotal: 16500000,
tax: 1650000,
total: 18150000,
status: 'پرداخت شده'
},
{
id: 2,
invoiceNumber: 'INV-002',
date: '1403/01/14',
customer: 'فاطمه احمدی',
customerAddress: 'اصفهان، خیابان چهارباغ',
items: [
{ product: 'موبایل سامسونگ', quantity: 2, unitPrice: 8500000, total: 17000000 }
],
subtotal: 17000000,
tax: 1700000,
total: 18700000,
status: 'در انتظار پرداخت'
}
]);
const [showForm, setShowForm] = useState(false);
const [editingInvoice, setEditingInvoice] = useState(null);
const [previewInvoice, setPreviewInvoice] = useState(null);
const [formData, setFormData] = useState({
customer: '',
customerAddress: '',
items: [{ product: '', quantity: '', unitPrice: '', total: 0 }],
tax: 10
});
const customers = ['احمد محمدی', 'فاطمه احمدی', 'علی رضایی'];
const products = ['لپ‌تاپ ایسوس', 'موبایل سامسونگ', 'کتاب برنامه‌نویسی', 'ماوس بی‌سیم'];
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleItemChange = (index, field, value) => {
const newItems = [...formData.items];
newItems[index] = { ...newItems[index], [field]: value };
if (field === 'quantity' || field === 'unitPrice') {
const quantity = parseFloat(field === 'quantity' ? value : newItems[index].quantity) || 0;
const unitPrice = parseFloat(field === 'unitPrice' ? value : newItems[index].unitPrice) || 0;
newItems[index].total = quantity * unitPrice;
}
setFormData(prev => ({ ...prev, items: newItems }));
};
const addItem = () => {
setFormData(prev => ({
...prev,
items: [...prev.items, { product: '', quantity: '', unitPrice: '', total: 0 }]
}));
};
const removeItem = (index) => {
if (formData.items.length > 1) {
const newItems = formData.items.filter((_, i) => i !== index);
setFormData(prev => ({ ...prev, items: newItems }));
}
};
const calculateTotals = () => {
const subtotal = formData.items.reduce((sum, item) => sum + (item.total || 0), 0);
const tax = (subtotal * formData.tax) / 100;
const total = subtotal + tax;
return { subtotal, tax, total };
};
const handleSubmit = (e) => {
e.preventDefault();
const { subtotal, tax, total } = calculateTotals();
const invoiceNumber = `INV-${String(invoices.length + 1).padStart(3, '0')}`;
if (editingInvoice) {
// Update existing invoice
setInvoices(invoices.map(invoice =>
invoice.id === editingInvoice.id
? {
...invoice,
...formData,
subtotal,
tax,
total,
items: formData.items.filter(item => item.product && item.quantity && item.unitPrice)
}
: invoice
));
setEditingInvoice(null);
} else {
// Add new invoice
const newInvoice = {
id: Date.now(),
invoiceNumber,
date: new Date().toLocaleDateString('fa-IR'),
...formData,
subtotal,
tax,
total,
status: 'در انتظار پرداخت',
items: formData.items.filter(item => item.product && item.quantity && item.unitPrice)
};
setInvoices([newInvoice, ...invoices]);
}
setFormData({
customer: '',
customerAddress: '',
items: [{ product: '', quantity: '', unitPrice: '', total: 0 }],
tax: 10
});
setShowForm(false);
};
const handleEdit = (invoice) => {
setEditingInvoice(invoice);
setFormData({
customer: invoice.customer,
customerAddress: invoice.customerAddress,
items: invoice.items.length > 0 ? invoice.items : [{ product: '', quantity: '', unitPrice: '', total: 0 }],
tax: 10
});
setShowForm(true);
};
const handleDelete = (id) => {
if (window.confirm('آیا از حذف این فاکتور اطمینان دارید؟')) {
setInvoices(invoices.filter(invoice => invoice.id !== id));
}
};
const handleCancel = () => {
setShowForm(false);
setEditingInvoice(null);
setFormData({
customer: '',
customerAddress: '',
items: [{ product: '', quantity: '', unitPrice: '', total: 0 }],
tax: 10
});
};
const formatPrice = (price) => {
return new Intl.NumberFormat('fa-IR').format(price) + ' ریال';
};
const getStatusColor = (status) => {
switch (status) {
case 'پرداخت شده':
return 'bg-green-100 text-green-800';
case 'در انتظار پرداخت':
return 'bg-yellow-100 text-yellow-800';
case 'لغو شده':
return 'bg-red-100 text-red-800';
default:
return 'bg-gray-100 text-gray-800';
}
};
const handlePrintInvoice = (invoice) => {
// Generate PDF using jsPDF
InvoicePDF.generatePDF(invoice);
};
const handlePrintHTML = (invoice) => {
// Generate HTML version and open in new window for printing
const invoiceHTML = InvoicePDF.generateHTMLInvoice(invoice);
const printWindow = window.open('', '_blank');
printWindow.document.write(invoiceHTML);
printWindow.document.close();
printWindow.focus();
printWindow.print();
};
const handlePreviewInvoice = (invoice) => {
setPreviewInvoice(invoice);
};
const { subtotal, tax, total } = calculateTotals();
return (
<div className="farsi-text">
<div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold text-gray-900">سیستم فاکتور</h1>
<button
onClick={() => setShowForm(true)}
className="btn-primary"
>
ایجاد فاکتور جدید
</button>
</div>
{/* Invoice Summary */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
<div className="card">
<div className="flex items-center">
<div className="bg-blue-500 rounded-full p-3 text-white text-2xl ml-4">
📄
</div>
<div>
<p className="text-sm font-medium text-gray-600">کل فاکتورها</p>
<p className="text-2xl font-bold text-gray-900">{invoices.length}</p>
</div>
</div>
</div>
<div className="card">
<div className="flex items-center">
<div className="bg-green-500 rounded-full p-3 text-white text-2xl ml-4">
💰
</div>
<div>
<p className="text-sm font-medium text-gray-600">کل مبلغ</p>
<p className="text-lg font-bold text-gray-900">
{formatPrice(invoices.reduce((sum, inv) => sum + inv.total, 0))}
</p>
</div>
</div>
</div>
<div className="card">
<div className="flex items-center">
<div className="bg-yellow-500 rounded-full p-3 text-white text-2xl ml-4">
</div>
<div>
<p className="text-sm font-medium text-gray-600">در انتظار پرداخت</p>
<p className="text-2xl font-bold text-gray-900">
{invoices.filter(inv => inv.status === 'در انتظار پرداخت').length}
</p>
</div>
</div>
</div>
<div className="card">
<div className="flex items-center">
<div className="bg-purple-500 rounded-full p-3 text-white text-2xl ml-4">
</div>
<div>
<p className="text-sm font-medium text-gray-600">پرداخت شده</p>
<p className="text-2xl font-bold text-gray-900">
{invoices.filter(inv => inv.status === 'پرداخت شده').length}
</p>
</div>
</div>
</div>
</div>
{/* Form Modal */}
{showForm && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 overflow-y-auto">
<div className="bg-white rounded-lg p-6 w-full max-w-4xl my-8">
<h2 className="text-xl font-bold mb-4">
{editingInvoice ? 'ویرایش فاکتور' : 'ایجاد فاکتور جدید'}
</h2>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Customer Info */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="form-label">مشتری</label>
<select
name="customer"
value={formData.customer}
onChange={handleInputChange}
className="form-input"
required
>
<option value="">انتخاب کنید</option>
{customers.map(customer => (
<option key={customer} value={customer}>{customer}</option>
))}
</select>
</div>
<div>
<label className="form-label">آدرس مشتری</label>
<input
type="text"
name="customerAddress"
value={formData.customerAddress}
onChange={handleInputChange}
className="form-input"
/>
</div>
</div>
{/* Items */}
<div>
<div className="flex justify-between items-center mb-4">
<label className="form-label">اقلام فاکتور</label>
<button
type="button"
onClick={addItem}
className="btn-secondary text-sm"
>
افزودن قلم
</button>
</div>
<div className="space-y-3">
{formData.items.map((item, index) => (
<div key={index} className="grid grid-cols-12 gap-2 items-end">
<div className="col-span-5">
<select
value={item.product}
onChange={(e) => handleItemChange(index, 'product', e.target.value)}
className="form-input"
required
>
<option value="">انتخاب کالا</option>
{products.map(product => (
<option key={product} value={product}>{product}</option>
))}
</select>
</div>
<div className="col-span-2">
<input
type="number"
placeholder="تعداد"
value={item.quantity}
onChange={(e) => handleItemChange(index, 'quantity', e.target.value)}
className="form-input"
required
min="1"
/>
</div>
<div className="col-span-2">
<input
type="number"
placeholder="قیمت واحد"
value={item.unitPrice}
onChange={(e) => handleItemChange(index, 'unitPrice', e.target.value)}
className="form-input"
required
min="0"
/>
</div>
<div className="col-span-2">
<input
type="text"
value={formatPrice(item.total)}
className="form-input bg-gray-100"
readOnly
/>
</div>
<div className="col-span-1">
{formData.items.length > 1 && (
<button
type="button"
onClick={() => removeItem(index)}
className="text-red-600 hover:text-red-800"
>
حذف
</button>
)}
</div>
</div>
))}
</div>
</div>
{/* Tax */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="form-label">درصد مالیات</label>
<input
type="number"
name="tax"
value={formData.tax}
onChange={handleInputChange}
className="form-input"
min="0"
max="100"
/>
</div>
<div>
<label className="form-label">مجموع</label>
<input
type="text"
value={formatPrice(subtotal)}
className="form-input bg-gray-100"
readOnly
/>
</div>
<div>
<label className="form-label">کل با مالیات</label>
<input
type="text"
value={formatPrice(total)}
className="form-input bg-gray-100 font-bold"
readOnly
/>
</div>
</div>
<div className="flex space-x-2 space-x-reverse">
<button type="submit" className="btn-success flex-1">
{editingInvoice ? 'ویرایش' : 'ایجاد'}
</button>
<button type="button" onClick={handleCancel} className="btn-secondary flex-1">
انصراف
</button>
</div>
</form>
</div>
</div>
)}
{/* Invoices Table */}
<div className="card">
<div className="overflow-x-auto">
<table className="table">
<thead>
<tr>
<th>شماره فاکتور</th>
<th>تاریخ</th>
<th>مشتری</th>
<th>تعداد اقلام</th>
<th>مبلغ کل</th>
<th>وضعیت</th>
<th>عملیات</th>
</tr>
</thead>
<tbody>
{invoices.map(invoice => (
<tr key={invoice.id}>
<td>
<span className="bg-blue-100 px-2 py-1 rounded text-sm">
{invoice.invoiceNumber}
</span>
</td>
<td>{invoice.date}</td>
<td>{invoice.customer}</td>
<td>{invoice.items.length}</td>
<td className="font-bold">{formatPrice(invoice.total)}</td>
<td>
<span className={`px-2 py-1 rounded-full text-xs ${getStatusColor(invoice.status)}`}>
{invoice.status}
</span>
</td>
<td>
<div className="flex space-x-2 space-x-reverse">
<button
onClick={() => handleEdit(invoice)}
className="text-blue-600 hover:text-blue-800 text-sm"
>
ویرایش
</button>
<button
onClick={() => handleDelete(invoice.id)}
className="text-red-600 hover:text-red-800 text-sm"
>
حذف
</button>
<button
onClick={() => handlePreviewInvoice(invoice)}
className="text-indigo-600 hover:text-indigo-800 text-sm"
title="پیش‌نمایش"
>
پیشنمایش
</button>
<button
onClick={() => handlePrintInvoice(invoice)}
className="text-green-600 hover:text-green-800 text-sm"
title="دانلود PDF"
>
PDF
</button>
<button
onClick={() => handlePrintHTML(invoice)}
className="text-purple-600 hover:text-purple-800 text-sm"
title="چاپ"
>
چاپ
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Invoice Preview Modal */}
{previewInvoice && (
<InvoicePreview
invoice={previewInvoice}
onClose={() => setPreviewInvoice(null)}
/>
)}
</div>
);
};
export default InvoiceSystem;

View File

@@ -0,0 +1,197 @@
import React, { useState } from 'react';
const PersonManagement = () => {
const [persons, setPersons] = useState([
{ id: 1, name: 'احمد محمدی', type: 'مشتری', phone: '09123456789', address: 'تهران، خیابان ولیعصر' },
{ id: 2, name: 'شرکت پارس', type: 'تامین‌کننده', phone: '02112345678', address: 'تهران، خیابان آزادی' },
{ id: 3, name: 'فاطمه احمدی', type: 'مشتری', phone: '09187654321', address: 'اصفهان، خیابان چهارباغ' }
]);
const [showForm, setShowForm] = useState(false);
const [editingPerson, setEditingPerson] = useState(null);
const [formData, setFormData] = useState({
name: '',
type: 'مشتری',
phone: '',
address: ''
});
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
if (editingPerson) {
// Update existing person
setPersons(persons.map(person =>
person.id === editingPerson.id
? { ...person, ...formData }
: person
));
setEditingPerson(null);
} else {
// Add new person
const newPerson = {
id: Date.now(),
...formData
};
setPersons([...persons, newPerson]);
}
setFormData({ name: '', type: 'مشتری', phone: '', address: '' });
setShowForm(false);
};
const handleEdit = (person) => {
setEditingPerson(person);
setFormData(person);
setShowForm(true);
};
const handleDelete = (id) => {
if (window.confirm('آیا از حذف این شخص اطمینان دارید؟')) {
setPersons(persons.filter(person => person.id !== id));
}
};
const handleCancel = () => {
setShowForm(false);
setEditingPerson(null);
setFormData({ name: '', type: 'مشتری', phone: '', address: '' });
};
return (
<div className="farsi-text">
<div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold text-gray-900">مدیریت اشخاص</h1>
<button
onClick={() => setShowForm(true)}
className="btn-primary"
>
افزودن شخص جدید
</button>
</div>
{/* Form Modal */}
{showForm && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 w-full max-w-md">
<h2 className="text-xl font-bold mb-4">
{editingPerson ? 'ویرایش شخص' : 'افزودن شخص جدید'}
</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="form-label">نام</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleInputChange}
className="form-input"
required
/>
</div>
<div>
<label className="form-label">نوع</label>
<select
name="type"
value={formData.type}
onChange={handleInputChange}
className="form-input"
>
<option value="مشتری">مشتری</option>
<option value="تامین‌کننده">تامینکننده</option>
</select>
</div>
<div>
<label className="form-label">شماره تلفن</label>
<input
type="tel"
name="phone"
value={formData.phone}
onChange={handleInputChange}
className="form-input"
/>
</div>
<div>
<label className="form-label">آدرس</label>
<textarea
name="address"
value={formData.address}
onChange={handleInputChange}
className="form-input"
rows="3"
/>
</div>
<div className="flex space-x-2 space-x-reverse">
<button type="submit" className="btn-success flex-1">
{editingPerson ? 'ویرایش' : 'افزودن'}
</button>
<button type="button" onClick={handleCancel} className="btn-secondary flex-1">
انصراف
</button>
</div>
</form>
</div>
</div>
)}
{/* Persons Table */}
<div className="card">
<div className="overflow-x-auto">
<table className="table">
<thead>
<tr>
<th>نام</th>
<th>نوع</th>
<th>شماره تلفن</th>
<th>آدرس</th>
<th>عملیات</th>
</tr>
</thead>
<tbody>
{persons.map(person => (
<tr key={person.id}>
<td>{person.name}</td>
<td>
<span className={`px-2 py-1 rounded-full text-xs ${
person.type === 'مشتری'
? 'bg-blue-100 text-blue-800'
: 'bg-green-100 text-green-800'
}`}>
{person.type}
</span>
</td>
<td>{person.phone}</td>
<td>{person.address}</td>
<td>
<div className="flex space-x-2 space-x-reverse">
<button
onClick={() => handleEdit(person)}
className="text-blue-600 hover:text-blue-800 text-sm"
>
ویرایش
</button>
<button
onClick={() => handleDelete(person.id)}
className="text-red-600 hover:text-red-800 text-sm"
>
حذف
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
};
export default PersonManagement;

View File

@@ -0,0 +1,235 @@
import React, { useState } from 'react';
const ProductManagement = () => {
const [products, setProducts] = useState([
{ id: 1, name: 'لپ‌تاپ ایسوس', code: 'LAP001', price: 15000000, unit: 'عدد', stock: 5 },
{ id: 2, name: 'موبایل سامسونگ', code: 'MOB002', price: 8000000, unit: 'عدد', stock: 12 },
{ id: 3, name: 'کتاب برنامه‌نویسی', code: 'BOK003', price: 150000, unit: 'جلد', stock: 25 }
]);
const [showForm, setShowForm] = useState(false);
const [editingProduct, setEditingProduct] = useState(null);
const [formData, setFormData] = useState({
name: '',
code: '',
price: '',
unit: 'عدد',
stock: ''
});
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
if (editingProduct) {
// Update existing product
setProducts(products.map(product =>
product.id === editingProduct.id
? { ...product, ...formData, price: parseFloat(formData.price), stock: parseInt(formData.stock) }
: product
));
setEditingProduct(null);
} else {
// Add new product
const newProduct = {
id: Date.now(),
...formData,
price: parseFloat(formData.price),
stock: parseInt(formData.stock)
};
setProducts([...products, newProduct]);
}
setFormData({ name: '', code: '', price: '', unit: 'عدد', stock: '' });
setShowForm(false);
};
const handleEdit = (product) => {
setEditingProduct(product);
setFormData({
...product,
price: product.price.toString(),
stock: product.stock.toString()
});
setShowForm(true);
};
const handleDelete = (id) => {
if (window.confirm('آیا از حذف این کالا اطمینان دارید؟')) {
setProducts(products.filter(product => product.id !== id));
}
};
const handleCancel = () => {
setShowForm(false);
setEditingProduct(null);
setFormData({ name: '', code: '', price: '', unit: 'عدد', stock: '' });
};
const formatPrice = (price) => {
return new Intl.NumberFormat('fa-IR').format(price) + ' ریال';
};
return (
<div className="farsi-text">
<div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold text-gray-900">مدیریت کالاها</h1>
<button
onClick={() => setShowForm(true)}
className="btn-primary"
>
افزودن کالای جدید
</button>
</div>
{/* Form Modal */}
{showForm && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 w-full max-w-md">
<h2 className="text-xl font-bold mb-4">
{editingProduct ? 'ویرایش کالا' : 'افزودن کالای جدید'}
</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="form-label">نام کالا</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleInputChange}
className="form-input"
required
/>
</div>
<div>
<label className="form-label">کد کالا</label>
<input
type="text"
name="code"
value={formData.code}
onChange={handleInputChange}
className="form-input"
required
/>
</div>
<div>
<label className="form-label">قیمت (ریال)</label>
<input
type="number"
name="price"
value={formData.price}
onChange={handleInputChange}
className="form-input"
required
min="0"
/>
</div>
<div>
<label className="form-label">واحد</label>
<select
name="unit"
value={formData.unit}
onChange={handleInputChange}
className="form-input"
>
<option value="عدد">عدد</option>
<option value="کیلوگرم">کیلوگرم</option>
<option value="متر">متر</option>
<option value="لیتر">لیتر</option>
<option value="بسته">بسته</option>
<option value="کارتن">کارتن</option>
</select>
</div>
<div>
<label className="form-label">موجودی</label>
<input
type="number"
name="stock"
value={formData.stock}
onChange={handleInputChange}
className="form-input"
required
min="0"
/>
</div>
<div className="flex space-x-2 space-x-reverse">
<button type="submit" className="btn-success flex-1">
{editingProduct ? 'ویرایش' : 'افزودن'}
</button>
<button type="button" onClick={handleCancel} className="btn-secondary flex-1">
انصراف
</button>
</div>
</form>
</div>
</div>
)}
{/* Products Table */}
<div className="card">
<div className="overflow-x-auto">
<table className="table">
<thead>
<tr>
<th>نام کالا</th>
<th>کد کالا</th>
<th>قیمت</th>
<th>واحد</th>
<th>موجودی</th>
<th>عملیات</th>
</tr>
</thead>
<tbody>
{products.map(product => (
<tr key={product.id}>
<td>{product.name}</td>
<td>
<span className="bg-gray-100 px-2 py-1 rounded text-sm">
{product.code}
</span>
</td>
<td>{formatPrice(product.price)}</td>
<td>{product.unit}</td>
<td>
<span className={`px-2 py-1 rounded-full text-xs ${
product.stock > 10
? 'bg-green-100 text-green-800'
: product.stock > 0
? 'bg-yellow-100 text-yellow-800'
: 'bg-red-100 text-red-800'
}`}>
{product.stock}
</span>
</td>
<td>
<div className="flex space-x-2 space-x-reverse">
<button
onClick={() => handleEdit(product)}
className="text-blue-600 hover:text-blue-800 text-sm"
>
ویرایش
</button>
<button
onClick={() => handleDelete(product.id)}
className="text-red-600 hover:text-red-800 text-sm"
>
حذف
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
};
export default ProductManagement;

View File

@@ -0,0 +1,302 @@
import React, { useState } from 'react';
const PurchaseManagement = () => {
const [purchases, setPurchases] = useState([
{
id: 1,
date: '1403/01/15',
supplier: 'شرکت پارس',
product: 'لپ‌تاپ ایسوس',
quantity: 2,
unitPrice: 15000000,
total: 30000000,
status: 'تایید شده'
},
{
id: 2,
date: '1403/01/14',
supplier: 'تامین‌کننده الف',
product: 'موبایل سامسونگ',
quantity: 5,
unitPrice: 8000000,
total: 40000000,
status: 'در انتظار'
}
]);
const [showForm, setShowForm] = useState(false);
const [editingPurchase, setEditingPurchase] = useState(null);
const [formData, setFormData] = useState({
date: new Date().toLocaleDateString('fa-IR'),
supplier: '',
product: '',
quantity: '',
unitPrice: '',
status: 'در انتظار'
});
const suppliers = ['شرکت پارس', 'تامین‌کننده الف', 'تامین‌کننده ب'];
const products = ['لپ‌تاپ ایسوس', 'موبایل سامسونگ', 'کتاب برنامه‌نویسی'];
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
const total = parseFloat(formData.quantity) * parseFloat(formData.unitPrice);
if (editingPurchase) {
// Update existing purchase
setPurchases(purchases.map(purchase =>
purchase.id === editingPurchase.id
? {
...purchase,
...formData,
quantity: parseInt(formData.quantity),
unitPrice: parseFloat(formData.unitPrice),
total: total
}
: purchase
));
setEditingPurchase(null);
} else {
// Add new purchase
const newPurchase = {
id: Date.now(),
...formData,
quantity: parseInt(formData.quantity),
unitPrice: parseFloat(formData.unitPrice),
total: total
};
setPurchases([newPurchase, ...purchases]);
}
setFormData({
date: new Date().toLocaleDateString('fa-IR'),
supplier: '',
product: '',
quantity: '',
unitPrice: '',
status: 'در انتظار'
});
setShowForm(false);
};
const handleEdit = (purchase) => {
setEditingPurchase(purchase);
setFormData({
...purchase,
quantity: purchase.quantity.toString(),
unitPrice: purchase.unitPrice.toString()
});
setShowForm(true);
};
const handleDelete = (id) => {
if (window.confirm('آیا از حذف این خرید اطمینان دارید؟')) {
setPurchases(purchases.filter(purchase => purchase.id !== id));
}
};
const handleCancel = () => {
setShowForm(false);
setEditingPurchase(null);
setFormData({
date: new Date().toLocaleDateString('fa-IR'),
supplier: '',
product: '',
quantity: '',
unitPrice: '',
status: 'در انتظار'
});
};
const formatPrice = (price) => {
return new Intl.NumberFormat('fa-IR').format(price) + ' ریال';
};
const getStatusColor = (status) => {
switch (status) {
case 'تایید شده':
return 'bg-green-100 text-green-800';
case 'در انتظار':
return 'bg-yellow-100 text-yellow-800';
case 'لغو شده':
return 'bg-red-100 text-red-800';
default:
return 'bg-gray-100 text-gray-800';
}
};
return (
<div className="farsi-text">
<div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold text-gray-900">مدیریت خرید</h1>
<button
onClick={() => setShowForm(true)}
className="btn-primary"
>
ثبت خرید جدید
</button>
</div>
{/* Form Modal */}
{showForm && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 w-full max-w-md">
<h2 className="text-xl font-bold mb-4">
{editingPurchase ? 'ویرایش خرید' : 'ثبت خرید جدید'}
</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="form-label">تاریخ</label>
<input
type="text"
name="date"
value={formData.date}
onChange={handleInputChange}
className="form-input"
required
/>
</div>
<div>
<label className="form-label">تامینکننده</label>
<select
name="supplier"
value={formData.supplier}
onChange={handleInputChange}
className="form-input"
required
>
<option value="">انتخاب کنید</option>
{suppliers.map(supplier => (
<option key={supplier} value={supplier}>{supplier}</option>
))}
</select>
</div>
<div>
<label className="form-label">کالا</label>
<select
name="product"
value={formData.product}
onChange={handleInputChange}
className="form-input"
required
>
<option value="">انتخاب کنید</option>
{products.map(product => (
<option key={product} value={product}>{product}</option>
))}
</select>
</div>
<div>
<label className="form-label">تعداد</label>
<input
type="number"
name="quantity"
value={formData.quantity}
onChange={handleInputChange}
className="form-input"
required
min="1"
/>
</div>
<div>
<label className="form-label">قیمت واحد (ریال)</label>
<input
type="number"
name="unitPrice"
value={formData.unitPrice}
onChange={handleInputChange}
className="form-input"
required
min="0"
/>
</div>
<div>
<label className="form-label">وضعیت</label>
<select
name="status"
value={formData.status}
onChange={handleInputChange}
className="form-input"
>
<option value="در انتظار">در انتظار</option>
<option value="تایید شده">تایید شده</option>
<option value="لغو شده">لغو شده</option>
</select>
</div>
<div className="flex space-x-2 space-x-reverse">
<button type="submit" className="btn-success flex-1">
{editingPurchase ? 'ویرایش' : 'ثبت'}
</button>
<button type="button" onClick={handleCancel} className="btn-secondary flex-1">
انصراف
</button>
</div>
</form>
</div>
</div>
)}
{/* Purchases Table */}
<div className="card">
<div className="overflow-x-auto">
<table className="table">
<thead>
<tr>
<th>تاریخ</th>
<th>تامینکننده</th>
<th>کالا</th>
<th>تعداد</th>
<th>قیمت واحد</th>
<th>مجموع</th>
<th>وضعیت</th>
<th>عملیات</th>
</tr>
</thead>
<tbody>
{purchases.map(purchase => (
<tr key={purchase.id}>
<td>{purchase.date}</td>
<td>{purchase.supplier}</td>
<td>{purchase.product}</td>
<td>{purchase.quantity}</td>
<td>{formatPrice(purchase.unitPrice)}</td>
<td className="font-bold">{formatPrice(purchase.total)}</td>
<td>
<span className={`px-2 py-1 rounded-full text-xs ${getStatusColor(purchase.status)}`}>
{purchase.status}
</span>
</td>
<td>
<div className="flex space-x-2 space-x-reverse">
<button
onClick={() => handleEdit(purchase)}
className="text-blue-600 hover:text-blue-800 text-sm"
>
ویرایش
</button>
<button
onClick={() => handleDelete(purchase.id)}
className="text-red-600 hover:text-red-800 text-sm"
>
حذف
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
};
export default PurchaseManagement;

View File

@@ -0,0 +1,344 @@
import React, { useState } from 'react';
const SalesManagement = () => {
const [sales, setSales] = useState([
{
id: 1,
date: '1403/01/15',
customer: 'احمد محمدی',
product: 'لپ‌تاپ ایسوس',
quantity: 1,
unitPrice: 16000000,
total: 16000000,
status: 'تایید شده'
},
{
id: 2,
date: '1403/01/14',
customer: 'فاطمه احمدی',
product: 'موبایل سامسونگ',
quantity: 2,
unitPrice: 8500000,
total: 17000000,
status: 'در انتظار'
}
]);
const [showForm, setShowForm] = useState(false);
const [editingSale, setEditingSale] = useState(null);
const [formData, setFormData] = useState({
date: new Date().toLocaleDateString('fa-IR'),
customer: '',
product: '',
quantity: '',
unitPrice: '',
status: 'در انتظار'
});
const customers = ['احمد محمدی', 'فاطمه احمدی', 'علی رضایی'];
const products = ['لپ‌تاپ ایسوس', 'موبایل سامسونگ', 'کتاب برنامه‌نویسی'];
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
const total = parseFloat(formData.quantity) * parseFloat(formData.unitPrice);
if (editingSale) {
// Update existing sale
setSales(sales.map(sale =>
sale.id === editingSale.id
? {
...sale,
...formData,
quantity: parseInt(formData.quantity),
unitPrice: parseFloat(formData.unitPrice),
total: total
}
: sale
));
setEditingSale(null);
} else {
// Add new sale
const newSale = {
id: Date.now(),
...formData,
quantity: parseInt(formData.quantity),
unitPrice: parseFloat(formData.unitPrice),
total: total
};
setSales([newSale, ...sales]);
}
setFormData({
date: new Date().toLocaleDateString('fa-IR'),
customer: '',
product: '',
quantity: '',
unitPrice: '',
status: 'در انتظار'
});
setShowForm(false);
};
const handleEdit = (sale) => {
setEditingSale(sale);
setFormData({
...sale,
quantity: sale.quantity.toString(),
unitPrice: sale.unitPrice.toString()
});
setShowForm(true);
};
const handleDelete = (id) => {
if (window.confirm('آیا از حذف این فروش اطمینان دارید؟')) {
setSales(sales.filter(sale => sale.id !== id));
}
};
const handleCancel = () => {
setShowForm(false);
setEditingSale(null);
setFormData({
date: new Date().toLocaleDateString('fa-IR'),
customer: '',
product: '',
quantity: '',
unitPrice: '',
status: 'در انتظار'
});
};
const formatPrice = (price) => {
return new Intl.NumberFormat('fa-IR').format(price) + ' ریال';
};
const getStatusColor = (status) => {
switch (status) {
case 'تایید شده':
return 'bg-green-100 text-green-800';
case 'در انتظار':
return 'bg-yellow-100 text-yellow-800';
case 'لغو شده':
return 'bg-red-100 text-red-800';
default:
return 'bg-gray-100 text-gray-800';
}
};
// Calculate total sales
const totalSales = sales.reduce((sum, sale) => sum + sale.total, 0);
return (
<div className="farsi-text">
<div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold text-gray-900">مدیریت فروش</h1>
<button
onClick={() => setShowForm(true)}
className="btn-primary"
>
ثبت فروش جدید
</button>
</div>
{/* Sales Summary */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
<div className="card">
<div className="flex items-center">
<div className="bg-green-500 rounded-full p-3 text-white text-2xl ml-4">
💰
</div>
<div>
<p className="text-sm font-medium text-gray-600">کل فروش</p>
<p className="text-2xl font-bold text-gray-900">{formatPrice(totalSales)}</p>
</div>
</div>
</div>
<div className="card">
<div className="flex items-center">
<div className="bg-blue-500 rounded-full p-3 text-white text-2xl ml-4">
📊
</div>
<div>
<p className="text-sm font-medium text-gray-600">تعداد فروش</p>
<p className="text-2xl font-bold text-gray-900">{sales.length}</p>
</div>
</div>
</div>
<div className="card">
<div className="flex items-center">
<div className="bg-purple-500 rounded-full p-3 text-white text-2xl ml-4">
</div>
<div>
<p className="text-sm font-medium text-gray-600">فروش تایید شده</p>
<p className="text-2xl font-bold text-gray-900">
{sales.filter(sale => sale.status === 'تایید شده').length}
</p>
</div>
</div>
</div>
</div>
{/* Form Modal */}
{showForm && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 w-full max-w-md">
<h2 className="text-xl font-bold mb-4">
{editingSale ? 'ویرایش فروش' : 'ثبت فروش جدید'}
</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="form-label">تاریخ</label>
<input
type="text"
name="date"
value={formData.date}
onChange={handleInputChange}
className="form-input"
required
/>
</div>
<div>
<label className="form-label">مشتری</label>
<select
name="customer"
value={formData.customer}
onChange={handleInputChange}
className="form-input"
required
>
<option value="">انتخاب کنید</option>
{customers.map(customer => (
<option key={customer} value={customer}>{customer}</option>
))}
</select>
</div>
<div>
<label className="form-label">کالا</label>
<select
name="product"
value={formData.product}
onChange={handleInputChange}
className="form-input"
required
>
<option value="">انتخاب کنید</option>
{products.map(product => (
<option key={product} value={product}>{product}</option>
))}
</select>
</div>
<div>
<label className="form-label">تعداد</label>
<input
type="number"
name="quantity"
value={formData.quantity}
onChange={handleInputChange}
className="form-input"
required
min="1"
/>
</div>
<div>
<label className="form-label">قیمت واحد (ریال)</label>
<input
type="number"
name="unitPrice"
value={formData.unitPrice}
onChange={handleInputChange}
className="form-input"
required
min="0"
/>
</div>
<div>
<label className="form-label">وضعیت</label>
<select
name="status"
value={formData.status}
onChange={handleInputChange}
className="form-input"
>
<option value="در انتظار">در انتظار</option>
<option value="تایید شده">تایید شده</option>
<option value="لغو شده">لغو شده</option>
</select>
</div>
<div className="flex space-x-2 space-x-reverse">
<button type="submit" className="btn-success flex-1">
{editingSale ? 'ویرایش' : 'ثبت'}
</button>
<button type="button" onClick={handleCancel} className="btn-secondary flex-1">
انصراف
</button>
</div>
</form>
</div>
</div>
)}
{/* Sales Table */}
<div className="card">
<div className="overflow-x-auto">
<table className="table">
<thead>
<tr>
<th>تاریخ</th>
<th>مشتری</th>
<th>کالا</th>
<th>تعداد</th>
<th>قیمت واحد</th>
<th>مجموع</th>
<th>وضعیت</th>
<th>عملیات</th>
</tr>
</thead>
<tbody>
{sales.map(sale => (
<tr key={sale.id}>
<td>{sale.date}</td>
<td>{sale.customer}</td>
<td>{sale.product}</td>
<td>{sale.quantity}</td>
<td>{formatPrice(sale.unitPrice)}</td>
<td className="font-bold">{formatPrice(sale.total)}</td>
<td>
<span className={`px-2 py-1 rounded-full text-xs ${getStatusColor(sale.status)}`}>
{sale.status}
</span>
</td>
<td>
<div className="flex space-x-2 space-x-reverse">
<button
onClick={() => handleEdit(sale)}
className="text-blue-600 hover:text-blue-800 text-sm"
>
ویرایش
</button>
<button
onClick={() => handleDelete(sale.id)}
className="text-red-600 hover:text-red-800 text-sm"
>
حذف
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
};
export default SalesManagement;

70
src/data/sampleInvoice.js Normal file
View File

@@ -0,0 +1,70 @@
// Sample invoice data for testing PDF generation
export const sampleInvoice = {
id: 1,
invoiceNumber: 'INV-001',
date: '1403/01/15',
customer: 'احمد محمدی',
customerAddress: 'تهران، خیابان ولیعصر، پلاک 456',
items: [
{
product: 'لپ‌تاپ ایسوس ROG',
quantity: 1,
unitPrice: 25000000,
total: 25000000
},
{
product: 'ماوس بی‌سیم Logitech',
quantity: 2,
unitPrice: 800000,
total: 1600000
},
{
product: 'کیبورد مکانیکی',
quantity: 1,
unitPrice: 1200000,
total: 1200000
},
{
product: 'مانیتور 27 اینچ',
quantity: 1,
unitPrice: 8000000,
total: 8000000
}
],
subtotal: 35800000,
tax: 3580000,
total: 39380000,
status: 'پرداخت شده'
};
export const sampleInvoice2 = {
id: 2,
invoiceNumber: 'INV-002',
date: '1403/01/16',
customer: 'شرکت فناوری پارس',
customerAddress: 'اصفهان، خیابان چهارباغ، برج تجاری پارس',
items: [
{
product: 'سرور HP ProLiant',
quantity: 2,
unitPrice: 45000000,
total: 90000000
},
{
product: 'سوئیچ شبکه 24 پورت',
quantity: 3,
unitPrice: 3500000,
total: 10500000
},
{
product: 'کابل شبکه CAT6',
quantity: 100,
unitPrice: 50000,
total: 5000000
}
],
subtotal: 105500000,
tax: 10550000,
total: 116050000,
status: 'در انتظار پرداخت'
};

68
src/index.css Normal file
View File

@@ -0,0 +1,68 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
margin: 0;
font-family: 'Vazir', Tahoma, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
direction: rtl;
text-align: right;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
/* Custom styles for Farsi text */
.farsi-text {
font-family: 'Vazir', Tahoma, sans-serif;
direction: rtl;
text-align: right;
}
/* Custom button styles */
.btn-primary {
@apply bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded;
}
.btn-secondary {
@apply bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded;
}
.btn-success {
@apply bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded;
}
.btn-danger {
@apply bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded;
}
/* Form styles */
.form-input {
@apply w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent;
}
.form-label {
@apply block text-sm font-medium text-gray-700 mb-1;
}
/* Card styles */
.card {
@apply bg-white shadow-md rounded-lg p-6;
}
/* Table styles */
.table {
@apply w-full border-collapse border border-gray-300;
}
.table th {
@apply bg-gray-100 border border-gray-300 px-4 py-2 text-right;
}
.table td {
@apply border border-gray-300 px-4 py-2 text-right;
}

11
src/index.js Normal file
View File

@@ -0,0 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

17
tailwind.config.js Normal file
View File

@@ -0,0 +1,17 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {
fontFamily: {
'farsi': ['Vazir', 'Tahoma', 'sans-serif'],
},
direction: {
'rtl': 'rtl',
}
},
},
plugins: [],
}