feat: initial admin panel

This commit is contained in:
2026-02-19 22:13:11 +03:30
parent 70efff0dfd
commit 9aff48019d
50 changed files with 9604 additions and 1 deletions
+217
View File
@@ -0,0 +1,217 @@
'use client';
import { useState, useEffect } from 'react';
import { useAuth } from '@/contexts/AuthContext';
import { packagesApi } from '@/lib/api/packages';
import { productsApi } from '@/lib/api/products';
import ProductList from '@/components/admin/products/ProductList';
import ProductForm from '@/components/admin/products/ProductForm';
import { PackageName } from '@/types/package';
import { Product, CreateProductData, UpdateProductData } from '@/types/product';
import { PlusIcon } from '@heroicons/react/24/outline';
export default function ProductsPage() {
const { token } = useAuth();
const [packages, setPackages] = useState<PackageName[]>([]);
const [selectedPackage, setSelectedPackage] = useState<PackageName | null>(null);
const [products, setProducts] = useState<Product[]>([]);
const [selectedProduct, setSelectedProduct] = useState<Product | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [isFormVisible, setIsFormVisible] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
// Load packages on mount
useEffect(() => {
if (token) {
loadPackages();
}
}, [token]);
// Load products when package is selected
useEffect(() => {
if (selectedPackage && token) {
loadProducts(selectedPackage.name!);
} else {
setProducts([]);
}
}, [selectedPackage, token]);
const loadPackages = async () => {
try {
const data = await packagesApi.getAllPackages(token!);
setPackages(data);
if (data.length > 0 && !selectedPackage) {
setSelectedPackage(data[0]);
}
} catch (err) {
setError('خطا در دریافت لیست پکیج‌ها');
}
};
const loadProducts = async (packageName: string) => {
setIsLoading(true);
setError(null);
try {
const data = await productsApi.getProducts(packageName, token!);
setProducts(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'خطا در دریافت محصولات');
} finally {
setIsLoading(false);
}
};
const handlePackageChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const pkg = packages.find(p => p.name === e.target.value);
setSelectedPackage(pkg || null);
setIsFormVisible(false);
setSelectedProduct(null);
};
const handleCreate = () => {
setSelectedProduct(null);
setIsFormVisible(true);
};
const handleEdit = (product: Product) => {
setSelectedProduct(product);
setIsFormVisible(true);
};
const handleDelete = async (product: Product) => {
if (!selectedPackage) return;
if (!confirm(`آیا از حذف محصول "${product.title}" اطمینان دارید؟`)) {
return;
}
setIsLoading(true);
setError(null);
setSuccess(null);
try {
await productsApi.deleteProduct(selectedPackage.name!, product.id, token!);
setSuccess('محصول با موفقیت حذف شد');
await loadProducts(selectedPackage.name!);
} catch (err) {
setError(err instanceof Error ? err.message : 'خطا در حذف محصول');
} finally {
setIsLoading(false);
}
};
const handleSubmit = async (data: CreateProductData | UpdateProductData) => {
if (!selectedPackage) return;
setIsLoading(true);
setError(null);
setSuccess(null);
try {
if (selectedProduct) {
// Update existing product
await productsApi.updateProduct(selectedPackage.name!, selectedProduct.id, data, token!);
setSuccess('محصول با موفقیت به‌روزرسانی شد');
} else {
// Create new product
await productsApi.createProduct(selectedPackage.name!, data as CreateProductData, token!);
setSuccess('محصول با موفقیت ایجاد شد');
}
await loadProducts(selectedPackage.name!);
setIsFormVisible(false);
setSelectedProduct(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'خطا در ذخیره محصول');
} finally {
setIsLoading(false);
}
};
const handleCancel = () => {
setIsFormVisible(false);
setSelectedProduct(null);
setError(null);
setSuccess(null);
};
return (
<div className="p-6">
<div className="max-w-7xl mx-auto">
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold text-gray-900">
مدیریت محصولات
</h1>
</div>
{/* Package Selector */}
<div className="bg-white p-4 rounded-lg shadow mb-6">
<label htmlFor="package" className="block text-sm font-medium text-gray-700 mb-2">
انتخاب پکیج
</label>
<select
id="package"
value={selectedPackage?.name || ''}
onChange={handlePackageChange}
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
>
{packages.map((pkg) => (
<option key={pkg.id} value={pkg.name || ''}>
{pkg.title} ({pkg.name})
</option>
))}
</select>
</div>
{/* Messages */}
{error && (
<div className="mb-4 rounded-md bg-red-50 p-4">
<p className="text-sm text-red-800">{error}</p>
</div>
)}
{success && (
<div className="mb-4 rounded-md bg-green-50 p-4">
<p className="text-sm text-green-800">{success}</p>
</div>
)}
{/* Form or List */}
{selectedPackage && (
<>
{!isFormVisible && (
<div className="mb-4 flex justify-end">
<button
onClick={handleCreate}
className="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
<PlusIcon className="h-5 w-5 ml-2" />
محصول جدید
</button>
</div>
)}
{isFormVisible ? (
<ProductForm
product={selectedProduct}
packageName={selectedPackage.name!}
onSubmit={handleSubmit}
onCancel={handleCancel}
isLoading={isLoading}
/>
) : (
<ProductList
products={products}
packageName={selectedPackage.name!}
onEdit={handleEdit}
onDelete={handleDelete}
isLoading={isLoading}
/>
)}
</>
)}
</div>
</div>
);
}