Improve dashboard charts, category tree selects, and product AI name fill.

Fix RTL chart ordering with Y-axis and hover values; aggregate website views; refine category picker tree borders and spacing; add one-click FA→EN name translation on add product.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-28 20:39:43 +03:30
co-authored by Cursor
parent 7b2ed7c7b0
commit a412cb82fc
31 changed files with 1222 additions and 226 deletions
+53 -14
View File
@@ -26,7 +26,6 @@ import {
} from '../services/productService'
import {
fillProductFieldByAi,
type ProductAiFillField,
} from '../services/productAiService'
import type { Category, FlatCategory } from '../types/category'
import { flattenCategories } from '../utils/categories'
@@ -58,7 +57,7 @@ export function AddNewProductPage() {
const [loaded, setLoaded] = useState(!isEdit)
const [isSubmitting, setIsSubmitting] = useState(false)
const [error, setError] = useState('')
const [aiField, setAiField] = useState<ProductAiFillField | null>(null)
const [aiField, setAiField] = useState<'summary' | 'description' | null>(null)
const [isAiRunning, setIsAiRunning] = useState(false)
const categoryOptions = flattenCategories(categories)
@@ -69,8 +68,9 @@ export function AddNewProductPage() {
}, [categoryOptions, categoryId])
const canUseAiFill = Boolean(nameFa.trim() || nameEn.trim()) && !isSubmitting
const canTranslateNameEn = Boolean(nameFa.trim()) && !isSubmitting && !isAiRunning
function openAiFill(field: ProductAiFillField) {
function openAiFill(field: 'summary' | 'description') {
if (!nameFa.trim() && !nameEn.trim()) {
setError(t('products.form.aiFillNeedName'))
return
@@ -103,6 +103,30 @@ export function AddNewProductPage() {
}
}
async function translateNameEnByAi() {
if (!nameFa.trim()) {
setError(t('products.form.aiTranslateNameNeedFa'))
return
}
setError('')
setIsAiRunning(true)
try {
const result = await fillProductFieldByAi({
field: 'nameEn',
nameFa: nameFa.trim(),
categoryId: categoryId || undefined,
})
if (result.field === 'nameEn') {
setNameEn(result.text)
showToast(t('products.form.aiNameEnFilled'), 'success')
}
} catch (err) {
setError(err instanceof ApiError ? err.message : t('products.form.aiFillError'))
} finally {
setIsAiRunning(false)
}
}
useEffect(() => {
const controller = new AbortController()
@@ -302,17 +326,32 @@ export function AddNewProductPage() {
<div className={`${styles.field} ${styles.fieldHalf}`}>
<label htmlFor="nameEn">{t('products.form.nameEn')}</label>
<input
id="nameEn"
name="nameEn"
type="text"
dir="ltr"
placeholder={t('products.form.nameEnPlaceholder')}
value={nameEn}
onChange={(e) => setNameEn(e.target.value)}
required
disabled={isSubmitting}
/>
<div className={`${styles.fieldWithAi} ${styles.nameEnField}`}>
<input
id="nameEn"
name="nameEn"
type="text"
dir="ltr"
placeholder={t('products.form.nameEnPlaceholder')}
value={nameEn}
onChange={(e) => setNameEn(e.target.value)}
required
disabled={isSubmitting || isAiRunning}
/>
<span className={styles.aiFillAnchorInline}>
<Tooltip label={t('products.form.aiTranslateName')}>
<button
type="button"
className={`${aiStyles.aiBtn} ${styles.aiFillBtn}`}
onClick={() => void translateNameEnByAi()}
disabled={!canTranslateNameEn}
aria-label={t('products.form.aiTranslateName')}
>
<Sparkles size={14} />
</button>
</Tooltip>
</span>
</div>
</div>
<div className={`${styles.field} ${styles.fieldFull} ${styles.summaryField}`}>