import { useEffect, useRef, useState } from 'react' import { Link, useNavigate, useSearchParams } from 'react-router-dom' import { Globe, Package, Plus, RotateCcw, Search, Sparkles } from 'lucide-react' import { EmptyState, ListEmptyState } from '../components/EmptyState' import { ProductCard } from '../components/ProductCard' import { AddProductByAiModal } from '../components/AddProductByAiModal' import { Pagination } from '../components/Pagination' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { ProductVariantsModal } from '../components/ProductVariantsModal' import { ProductTechnicalInfoModal } from '../components/ProductTechnicalInfoModal' import { ProductCommentsModal } from '../components/ProductCommentsModal' import { ProductSeoModal } from '../components/ProductSeoModal' import { TranslationModal } from '../components/TranslationModal' import { TranslateAllLanguageModal } from '../components/TranslateAllLanguageModal' import { Breadcrumbs } from '../components/Breadcrumbs' import { PageTitle } from '../components/PageTitle' import { useToast } from '../context/ToastContext' import { useTenantBranding } from '../context/TenantBrandingContext' import { useT } from '../i18n/useT' import { ApiError } from '../lib/api' import { translateAllProducts } from '../services/translationService' import type { TranslationLocale } from '../types/translation' import { deleteProduct, listProducts, PRODUCTS_PER_PAGE, } from '../services/productService' import type { Product } from '../types/product' import { hasBusinessModule } from '../utils/businessModules' import pageStyles from '../components/PageContent.module.css' import filterStyles from '../components/ListFiltersPanel.module.css' import styles from './MyProductsPage.module.css' import aiStyles from '../styles/ai.module.css' function readListQuery(searchParams: URLSearchParams) { const name = searchParams.get('q')?.trim() ?? '' const pageRaw = Number(searchParams.get('page')) const page = Number.isFinite(pageRaw) && pageRaw >= 1 ? Math.floor(pageRaw) : 1 return { name, page } } export function MyProductsPage() { const t = useT() const navigate = useNavigate() const { showToast } = useToast() const { enabledModules } = useTenantBranding() const isMultiLanguageEnabled = hasBusinessModule(enabledModules, 'multilanguage_data') const [searchParams, setSearchParams] = useSearchParams() const { name: appliedName, page: currentPage } = readListQuery(searchParams) const [products, setProducts] = useState([]) const [totalProducts, setTotalProducts] = useState(0) const [isLoading, setIsLoading] = useState(true) const [isDeleting, setIsDeleting] = useState(false) const [error, setError] = useState('') const [variantCounts, setVariantCounts] = useState>({}) const [commentCounts, setCommentCounts] = useState>({}) const [deleteTarget, setDeleteTarget] = useState<{ id: string; name: string } | null>(null) const [variantsTarget, setVariantsTarget] = useState<{ id: string name: string categoryId: string } | null>(null) const [commentsTarget, setCommentsTarget] = useState<{ id: string; name: string } | null>(null) const [seoTarget, setSeoTarget] = useState<{ id: string; name: string } | null>(null) const [technicalTarget, setTechnicalTarget] = useState<{ id: string name: string categoryId: string } | null>(null) const [aiModalOpen, setAiModalOpen] = useState(false) const [translatingAll, setTranslatingAll] = useState(false) const [translateAllOpen, setTranslateAllOpen] = useState(false) const [aiMenuOpen, setAiMenuOpen] = useState(false) const aiFabGroupRef = useRef(null) const aiMenuCloseTimerRef = useRef(null) const [translateTarget, setTranslateTarget] = useState(null) const [draftName, setDraftName] = useState(appliedName) const totalPages = Math.max(1, Math.ceil(totalProducts / PRODUCTS_PER_PAGE)) useEffect(() => { setDraftName(appliedName) }, [appliedName]) useEffect(() => { const controller = new AbortController() void loadProducts(currentPage, controller.signal) return () => controller.abort() }, [currentPage, appliedName]) useEffect(() => { if (!isMultiLanguageEnabled) setTranslateTarget(null) }, [isMultiLanguageEnabled]) useEffect(() => { if (!aiMenuOpen) return const onPointerDown = (event: PointerEvent) => { if (!aiFabGroupRef.current?.contains(event.target as Node)) { setAiMenuOpen(false) } } document.addEventListener('pointerdown', onPointerDown) return () => document.removeEventListener('pointerdown', onPointerDown) }, [aiMenuOpen]) useEffect(() => { return () => { if (aiMenuCloseTimerRef.current !== null) { window.clearTimeout(aiMenuCloseTimerRef.current) } } }, []) function openAiFabMenu() { if (aiMenuCloseTimerRef.current !== null) { window.clearTimeout(aiMenuCloseTimerRef.current) aiMenuCloseTimerRef.current = null } setAiMenuOpen(true) } function scheduleCloseAiFabMenu() { if (aiMenuCloseTimerRef.current !== null) { window.clearTimeout(aiMenuCloseTimerRef.current) } aiMenuCloseTimerRef.current = window.setTimeout(() => { setAiMenuOpen(false) aiMenuCloseTimerRef.current = null }, 160) } function writeListQuery(name: string, page: number) { const next = new URLSearchParams() if (name) next.set('q', name) if (page > 1) next.set('page', String(page)) setSearchParams(next, { replace: true }) } async function loadProducts(page: number, signal?: AbortSignal) { setIsLoading(true) setError('') try { const data = await listProducts( page, PRODUCTS_PER_PAGE, signal, appliedName ? { name: appliedName } : undefined, ) setProducts(data.items) setTotalProducts(data.total) setVariantCounts( Object.fromEntries(data.items.map((product) => [product.id, product.variantCount ?? 0])), ) } catch (err) { if (err instanceof DOMException && err.name === 'AbortError') return if (err instanceof ApiError) { setError(err.message) } else { setError(t('products.list.errorLoad')) } } finally { setIsLoading(false) } } function handleEdit(id: string) { navigate(`/products/edit/${id}`) } function handleTranslate(id: string) { if (!isMultiLanguageEnabled) return const product = products.find((p) => p.id === id) if (product) setTranslateTarget(product) } async function handleTranslateAllProducts(locale: TranslationLocale) { if (translatingAll || products.length === 0) return setTranslatingAll(true) setError('') try { const result = await translateAllProducts(locale) await loadProducts(currentPage) const langLabel = t(locale === 'en' ? 'translations.lang.en' : 'translations.lang.ar') if (result.translated === 0 && result.failed > 0) { showToast(t('products.list.translateAll.error'), 'error') } else if (result.translated === 0) { showToast(t('products.list.translateAll.none'), 'info') } else { showToast( t('products.list.translateAll.done', { translated: result.translated, lang: langLabel, }), 'success', ) } setTranslateAllOpen(false) } catch (err) { if (err instanceof ApiError) { setError(err.message) } else { setError(t('products.list.translateAll.error')) } } finally { setTranslatingAll(false) } } function handleComments(id: string) { const product = products.find((p) => p.id === id) if (product) { setCommentsTarget({ id, name: product.nameFa || product.nameEn }) } } function handleSeo(id: string) { const product = products.find((p) => p.id === id) if (product) { setSeoTarget({ id, name: product.nameFa || product.nameEn }) } } function handleQuickInfo(id: string) { navigate(`/products/detail/${id}`) } function handleVariations(id: string) { const product = products.find((p) => p.id === id) if (product) { setVariantsTarget({ id, name: product.nameFa || product.nameEn, categoryId: product.categoryId, }) } } function handleTechnicalInfo(id: string) { const product = products.find((p) => p.id === id) if (product) { setTechnicalTarget({ id, name: product.nameFa || product.nameEn, categoryId: product.categoryId, }) } } function handleRemoveRequest(id: string) { const product = products.find((p) => p.id === id) if (product) { setDeleteTarget({ id, name: product.nameFa || product.nameEn }) } } async function confirmDelete() { if (!deleteTarget) return setIsDeleting(true) setError('') try { await deleteProduct(deleteTarget.id) const nextTotal = totalProducts - 1 const nextTotalPages = Math.max(1, Math.ceil(nextTotal / PRODUCTS_PER_PAGE)) const nextPage = currentPage > nextTotalPages ? nextTotalPages : currentPage setDeleteTarget(null) writeListQuery(appliedName, nextPage) await loadProducts(nextPage) } catch (err) { if (err instanceof ApiError) { setError(err.message) } else { setError(t('products.list.errorDelete')) } } finally { setIsDeleting(false) } } function handlePageChange(page: number) { writeListQuery(appliedName, page) window.scrollTo({ top: 0, behavior: 'smooth' }) } function applyFilters() { writeListQuery(draftName.trim(), 1) } function clearFilters() { setDraftName('') writeListQuery('', 1) } function handleCommentCountChange(count: number) { if (!commentsTarget) return setCommentCounts((prev) => ({ ...prev, [commentsTarget.id]: count, })) } return (
{t('title.myProducts')}

{t('products.list.subtitle', { count: totalProducts })}

{t('products.list.filters')}
{ e.preventDefault(); applyFilters() }}>
setDraftName(e.target.value)} placeholder={t('products.list.filterNamePlaceholder')} aria-label={t('products.list.filterName')} disabled={isLoading} />
{error && (
{error}
)} {isLoading ? (

{t('products.list.loading')}

) : products.length === 0 ? ( appliedName ? ( ) : ( ) ) : ( <>
{products.map((product) => ( ))}
{t('products.list.pagination', { page: currentPage, totalPages, perPage: PRODUCTS_PER_PAGE, total: totalProducts, })}
)} !isDeleting && setDeleteTarget(null)} /> {variantsTarget && ( setVariantsTarget(null)} onVariantsChange={(count) => { setVariantCounts((prev) => ({ ...prev, [variantsTarget.id]: count, })) }} /> )} {commentsTarget && ( setCommentsTarget(null)} onCountChange={handleCommentCountChange} /> )} {seoTarget && ( setSeoTarget(null)} /> )} {technicalTarget && ( setTechnicalTarget(null)} /> )} setAiModalOpen(false)} onCreated={(productId) => navigate(`/products/edit/${productId}`)} /> {translateTarget && ( setTranslateTarget(null)} onChanged={(count) => { const targetId = translateTarget.id setProducts((prev) => prev.map((product) => product.id === targetId ? { ...product, translationCount: count } : product, ), ) }} /> )} !translatingAll && setTranslateAllOpen(false)} onConfirm={(locale) => void handleTranslateAllProducts(locale)} />
{isMultiLanguageEnabled ? ( ) : null}
) }