Files
approagency-admin-panel/components/admin/users/UserProducts.tsx
T
2026-07-10 16:01:27 +03:30

181 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { UserProduct } from '@/types/user';
import { formatDate, formatPrice, getProductTypeLabel } from '@/lib/utils';
import { CheckCircleIcon, XCircleIcon, PlusCircleIcon } from '@heroicons/react/24/outline';
import { useState } from 'react';
interface UserProductsProps {
products: UserProduct[];
onSubscribe: (productId: number) => void;
onUnsubscribe: (productId: number) => void;
isLoading: boolean;
availableProducts: { id: number; title: string | null; packageName?: string }[];
selectedPackage?: string | null;
}
export default function UserProducts({
products,
onSubscribe,
onUnsubscribe,
isLoading,
availableProducts,
selectedPackage
}: UserProductsProps) {
const [selectedProductId, setSelectedProductId] = useState<number | null>(null);
const getSubscriptionStatus = (product: UserProduct) => {
if (!product.pivot?.expire_at) {
return {
isActive: false,
text: 'نامشخص',
color: 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400',
dotColor: 'bg-gray-400',
};
}
const expireDate = new Date(product.pivot.expire_at);
const now = new Date();
const isActive = expireDate > now;
return {
isActive,
text: isActive ? 'فعال' : 'منقضی شده',
color: isActive
? 'bg-green-50 dark:bg-green-500/10 text-green-700 dark:text-green-400'
: 'bg-red-50 dark:bg-red-500/10 text-red-700 dark:text-red-400',
dotColor: isActive ? 'bg-green-500' : 'bg-red-500',
};
};
const getSubscriptionCount = (productId: number): number => {
return products.filter(p => p.id === productId).length;
};
const productsByPackage = products.reduce((acc, product) => {
const packageName = product.package_name?.name || 'نامشخص';
if (!acc[packageName]) {
acc[packageName] = [];
}
acc[packageName].push(product);
return acc;
}, {} as Record<string, UserProduct[]>);
const handleSubscribeClick = (productId: number) => {
setSelectedProductId(productId);
onSubscribe(productId);
};
return (
<div className="bg-white dark:bg-gray-900 rounded-2xl ring-1 ring-gray-200 dark:ring-gray-800 shadow-sm overflow-hidden">
<div className="px-5 py-4 border-b border-gray-200 dark:border-gray-800">
<h3 className="text-base font-semibold text-gray-900 dark:text-gray-100">
اشتراک‌های کاربر
</h3>
</div>
{/* Current Subscriptions */}
<div className="divide-y divide-gray-200 dark:divide-gray-800">
{Object.keys(productsByPackage).length > 0 ? (
Object.entries(productsByPackage).map(([packageName, packageProducts]) => (
<div key={packageName}>
<div className="px-5 py-2.5 bg-gray-50 dark:bg-gray-800/60">
<span className="text-xs font-medium text-gray-500 dark:text-gray-400">
پکیج: {packageName}
</span>
</div>
<ul className="divide-y divide-gray-100 dark:divide-gray-800/60">
{packageProducts.map((product) => {
const status = getSubscriptionStatus(product);
const subscriptionCount = getSubscriptionCount(product.id);
return (
<li key={`${product.id}-${product.pivot?.purchase_token || Math.random()}`} className="px-5 py-4">
<div className="flex items-start justify-between gap-4">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<p className="text-sm font-semibold text-indigo-600 dark:text-indigo-400">
{product.title || 'محصول'}
</p>
{subscriptionCount > 1 && (
<span className="px-2 py-0.5 text-xs font-medium bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 rounded-lg">
×{subscriptionCount}
</span>
)}
<span className={`inline-flex items-center gap-1.5 px-2.5 py-0.5 text-xs font-medium rounded-lg ${status.color}`}>
<span className={`h-1.5 w-1.5 rounded-full ${status.dotColor}`} />
{status.text}
</span>
</div>
<div className="mt-2 flex flex-wrap gap-x-5 gap-y-1 text-xs text-gray-500 dark:text-gray-400">
<span>قیمت: {formatPrice(product.price)} تومان</span>
<span>نوع: {getProductTypeLabel(product.type)}</span>
{product.pivot?.expire_at && (
<span>انقضا: {formatDate(product.pivot.expire_at)}</span>
)}
</div>
</div>
<button
onClick={() => onUnsubscribe(product.id)}
disabled={isLoading}
className="flex-shrink-0 px-3 py-1.5 text-xs font-medium text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-500/10 hover:bg-red-100 dark:hover:bg-red-500/20 rounded-lg disabled:opacity-50 transition-colors"
>
لغو اشتراک
</button>
</div>
</li>
);
})}
</ul>
</div>
))
) : (
<div className="px-5 py-10 text-center">
<div className="mx-auto h-10 w-10 rounded-xl bg-gray-100 dark:bg-gray-800 flex items-center justify-center mb-2">
<XCircleIcon className="h-5 w-5 text-gray-400" />
</div>
<p className="text-sm text-gray-500 dark:text-gray-400">کاربر هیچ اشتراکی ندارد</p>
</div>
)}
</div>
{/* Available Products for Subscription */}
{availableProducts && availableProducts.length > 0 && selectedPackage && (
<div className="border-t border-gray-200 dark:border-gray-800 px-5 py-5">
<h4 className="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-3">
افزودن اشتراک جدید
</h4>
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3">
{availableProducts.map((product) => {
const subscriptionCount = getSubscriptionCount(product.id);
return (
<button
key={product.id}
onClick={() => handleSubscribeClick(product.id)}
disabled={isLoading}
className="flex items-center gap-3 p-3.5 border border-dashed border-indigo-200 dark:border-indigo-800/60 rounded-xl text-right hover:border-indigo-400 hover:bg-indigo-50 dark:hover:border-indigo-600 dark:hover:bg-indigo-500/5 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-150 group"
>
<div className="h-8 w-8 rounded-lg bg-indigo-100 dark:bg-indigo-500/15 flex items-center justify-center flex-shrink-0 group-hover:bg-indigo-200 dark:group-hover:bg-indigo-500/25 transition-colors">
<PlusCircleIcon className="h-4 w-4 text-indigo-600 dark:text-indigo-400" />
</div>
<div className="min-w-0">
<p className="text-sm font-medium text-gray-900 dark:text-gray-100 truncate">
{product.title || 'محصول'}
</p>
{subscriptionCount > 0 && (
<p className="text-xs text-amber-600 dark:text-amber-400">
{subscriptionCount} اشتراک فعال
</p>
)}
</div>
</button>
);
})}
</div>
</div>
)}
</div>
);
}