mirror of
https://git.meshkee.com/Meshkee/dashboards.git
synced 2026-08-11 22:30:58 +04:30
Polish category edit, product image placeholder, and menu blur.
Add a clear pencil edit on category rows, show a proper no-image placeholder on product cards, and strengthen overflow menu glass blur. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
12ab1f8006
commit
5eaf7fb046
@@ -1,4 +1,4 @@
|
|||||||
import { FolderPlus, Layers, ListTree, Trash2, ChevronRight, ClipboardList } from 'lucide-react'
|
import { FolderPlus, Layers, Trash2, ChevronRight, ClipboardList, Pencil } from 'lucide-react'
|
||||||
import { useLocale } from '@meshkee/dashboard-ui'
|
import { useLocale } from '@meshkee/dashboard-ui'
|
||||||
import type { Category } from '../types/category'
|
import type { Category } from '../types/category'
|
||||||
import { useT } from '../i18n/useT'
|
import { useT } from '../i18n/useT'
|
||||||
@@ -16,7 +16,7 @@ interface CategoryRowProps {
|
|||||||
onAddSub: (id: string) => void
|
onAddSub: (id: string) => void
|
||||||
onVariations: (id: string) => void
|
onVariations: (id: string) => void
|
||||||
onTechnicalForm: (id: string) => void
|
onTechnicalForm: (id: string) => void
|
||||||
onOptions: (id: string) => void
|
onEdit: (id: string) => void
|
||||||
onRemove: (id: string) => void
|
onRemove: (id: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ export function CategoryRow({
|
|||||||
onAddSub,
|
onAddSub,
|
||||||
onVariations,
|
onVariations,
|
||||||
onTechnicalForm,
|
onTechnicalForm,
|
||||||
onOptions,
|
onEdit,
|
||||||
onRemove,
|
onRemove,
|
||||||
}: CategoryRowProps) {
|
}: CategoryRowProps) {
|
||||||
const t = useT()
|
const t = useT()
|
||||||
@@ -121,13 +121,13 @@ export function CategoryRow({
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label={t('categories.tooltip.options')}>
|
<Tooltip label={t('categories.tooltip.edit')}>
|
||||||
<button
|
<button
|
||||||
className={styles.controlBtn}
|
className={styles.controlBtn}
|
||||||
onClick={() => onOptions(category.id)}
|
onClick={() => onEdit(category.id)}
|
||||||
aria-label={t('categories.tooltip.options')}
|
aria-label={t('categories.tooltip.edit')}
|
||||||
>
|
>
|
||||||
<ListTree size={17} />
|
<Pencil size={17} />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label={t('categories.tooltip.remove')}>
|
<Tooltip label={t('categories.tooltip.remove')}>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ interface CategoryTreeProps {
|
|||||||
onAddSub: (id: string) => void
|
onAddSub: (id: string) => void
|
||||||
onVariations: (id: string) => void
|
onVariations: (id: string) => void
|
||||||
onTechnicalForm: (id: string) => void
|
onTechnicalForm: (id: string) => void
|
||||||
onOptions: (id: string) => void
|
onEdit: (id: string) => void
|
||||||
onRemove: (id: string) => void
|
onRemove: (id: string) => void
|
||||||
parentId?: string | null
|
parentId?: string | null
|
||||||
depth?: number
|
depth?: number
|
||||||
@@ -27,7 +27,7 @@ export function CategoryTree({
|
|||||||
onAddSub,
|
onAddSub,
|
||||||
onVariations,
|
onVariations,
|
||||||
onTechnicalForm,
|
onTechnicalForm,
|
||||||
onOptions,
|
onEdit,
|
||||||
onRemove,
|
onRemove,
|
||||||
parentId = null,
|
parentId = null,
|
||||||
depth = 0,
|
depth = 0,
|
||||||
@@ -53,7 +53,7 @@ export function CategoryTree({
|
|||||||
onAddSub={onAddSub}
|
onAddSub={onAddSub}
|
||||||
onVariations={onVariations}
|
onVariations={onVariations}
|
||||||
onTechnicalForm={onTechnicalForm}
|
onTechnicalForm={onTechnicalForm}
|
||||||
onOptions={onOptions}
|
onEdit={onEdit}
|
||||||
onRemove={onRemove}
|
onRemove={onRemove}
|
||||||
/>
|
/>
|
||||||
{childCount && (
|
{childCount && (
|
||||||
@@ -68,7 +68,7 @@ export function CategoryTree({
|
|||||||
onAddSub={onAddSub}
|
onAddSub={onAddSub}
|
||||||
onVariations={onVariations}
|
onVariations={onVariations}
|
||||||
onTechnicalForm={onTechnicalForm}
|
onTechnicalForm={onTechnicalForm}
|
||||||
onOptions={onOptions}
|
onEdit={onEdit}
|
||||||
onRemove={onRemove}
|
onRemove={onRemove}
|
||||||
parentId={category.id}
|
parentId={category.id}
|
||||||
depth={depth + 1}
|
depth={depth + 1}
|
||||||
|
|||||||
@@ -46,6 +46,20 @@
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.imagePlaceholder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: rgba(148, 163, 184, 0.75);
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(148, 163, 184, 0.14) 0%,
|
||||||
|
rgba(148, 163, 184, 0.05) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { Pencil, Info, Trash2, Layers, MessageSquare, ClipboardList } from 'lucide-react'
|
import {
|
||||||
|
Pencil,
|
||||||
|
Info,
|
||||||
|
Trash2,
|
||||||
|
Layers,
|
||||||
|
MessageSquare,
|
||||||
|
ClipboardList,
|
||||||
|
ImageOff,
|
||||||
|
} from 'lucide-react'
|
||||||
import { useLocale } from '@meshkee/dashboard-ui'
|
import { useLocale } from '@meshkee/dashboard-ui'
|
||||||
import type { Product } from '../types/product'
|
import type { Product } from '../types/product'
|
||||||
import { useT } from '../i18n/useT'
|
import { useT } from '../i18n/useT'
|
||||||
@@ -39,17 +48,31 @@ export function ProductCard({
|
|||||||
: ''
|
: ''
|
||||||
: product.nameFa
|
: product.nameFa
|
||||||
const categoryLabel = isFa ? product.categoryFa || product.category : product.category
|
const categoryLabel = isFa ? product.categoryFa || product.category : product.category
|
||||||
|
const imageSrc = product.image?.trim() || ''
|
||||||
|
const [imageFailed, setImageFailed] = useState(false)
|
||||||
|
const showImage = Boolean(imageSrc) && !imageFailed
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setImageFailed(false)
|
||||||
|
}, [imageSrc])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article className={styles.card}>
|
<article className={styles.card}>
|
||||||
<Link to={`/products/detail/${product.id}`} className={styles.clickable}>
|
<Link to={`/products/detail/${product.id}`} className={styles.clickable}>
|
||||||
<div className={styles.imageWrap}>
|
<div className={styles.imageWrap}>
|
||||||
<img
|
{showImage ? (
|
||||||
src={product.image}
|
<img
|
||||||
alt={primaryName}
|
src={imageSrc}
|
||||||
className={styles.image}
|
alt={primaryName}
|
||||||
loading="lazy"
|
className={styles.image}
|
||||||
/>
|
loading="lazy"
|
||||||
|
onError={() => setImageFailed(true)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className={styles.imagePlaceholder} aria-hidden="true">
|
||||||
|
<ImageOff size={28} strokeWidth={1.5} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{product.status === 'draft' && (
|
{product.status === 'draft' && (
|
||||||
<span className={styles.badge}>{t('products.card.draft')}</span>
|
<span className={styles.badge}>{t('products.card.draft')}</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -453,7 +453,7 @@ export function CategoriesPage() {
|
|||||||
onAddSub={(id) => openCreateModal(id, t('categories.addSub'))}
|
onAddSub={(id) => openCreateModal(id, t('categories.addSub'))}
|
||||||
onVariations={openVariations}
|
onVariations={openVariations}
|
||||||
onTechnicalForm={openTechnicalForm}
|
onTechnicalForm={openTechnicalForm}
|
||||||
onOptions={(id) => {
|
onEdit={(id) => {
|
||||||
const category = categories.find((item) => item.id === id)
|
const category = categories.find((item) => item.id === id)
|
||||||
if (category) openEditModal(category)
|
if (category) openEditModal(category)
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -31,11 +31,14 @@
|
|||||||
min-width: 220px;
|
min-width: 220px;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.85);
|
border: 1px solid rgba(255, 255, 255, 0.92);
|
||||||
background: rgba(255, 255, 255, 0.78);
|
background: rgba(255, 255, 255, 0.58);
|
||||||
backdrop-filter: blur(72px) saturate(1.35);
|
backdrop-filter: blur(160px) saturate(1.85);
|
||||||
-webkit-backdrop-filter: blur(72px) saturate(1.35);
|
-webkit-backdrop-filter: blur(160px) saturate(1.85);
|
||||||
box-shadow: 0 12px 40px rgba(15, 23, 42, 0.16);
|
box-shadow:
|
||||||
|
0 12px 40px rgba(15, 23, 42, 0.16),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.65);
|
||||||
|
isolation: isolate;
|
||||||
animation: menuIn 0.14s ease;
|
animation: menuIn 0.14s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user