Fix portfolio list sorting UX and add name/category filters.
Newest portfolios show first with corrected move-up, category no longer mixes into the date, and website link generation can filter by tags. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
2477936bce
commit
1e9f6481b0
@@ -13,14 +13,17 @@ import type { ProductCategory } from '../types/category'
|
||||
import {
|
||||
WEBSITE_LINK_KINDS,
|
||||
buildWebsiteCategoryUrl,
|
||||
buildWebsiteTagsUrl,
|
||||
type WebsiteLinkFilter,
|
||||
type WebsiteLinkKind,
|
||||
} from '../utils/websiteLinks'
|
||||
import { CategoryTreeSelect } from './CategoryTreeSelect'
|
||||
import { TagInput } from './TagInput'
|
||||
import modalStyles from './CategoryModal.module.css'
|
||||
import styles from './GenerateWebsiteLinkModal.module.css'
|
||||
|
||||
export type { WebsiteLinkKind }
|
||||
export { buildWebsiteCategoryUrl }
|
||||
export { buildWebsiteCategoryUrl, buildWebsiteTagsUrl }
|
||||
|
||||
const KIND_LABEL: Record<WebsiteLinkKind, BusinessMessageKey> = {
|
||||
product: 'website.linkGenerator.types.product',
|
||||
@@ -91,8 +94,10 @@ export function GenerateWebsiteLinkModal({ open, onClose, onPick }: GenerateWebs
|
||||
const [mounted, setMounted] = useState(open)
|
||||
const [closing, setClosing] = useState(false)
|
||||
const [kind, setKind] = useState<WebsiteLinkKind>('product')
|
||||
const [filterBy, setFilterBy] = useState<WebsiteLinkFilter>('category')
|
||||
const [categories, setCategories] = useState<ProductCategory[]>([])
|
||||
const [categoryId, setCategoryId] = useState('')
|
||||
const [tags, setTags] = useState<string[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
@@ -101,8 +106,10 @@ export function GenerateWebsiteLinkModal({ open, onClose, onPick }: GenerateWebs
|
||||
setMounted(true)
|
||||
setClosing(false)
|
||||
setKind('product')
|
||||
setFilterBy('category')
|
||||
setCategories([])
|
||||
setCategoryId('')
|
||||
setTags([])
|
||||
setError('')
|
||||
} else if (mounted) {
|
||||
setClosing(true)
|
||||
@@ -127,7 +134,7 @@ export function GenerateWebsiteLinkModal({ open, onClose, onPick }: GenerateWebs
|
||||
}, [mounted, closing, onClose])
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
if (!open || filterBy !== 'category') return
|
||||
|
||||
const controller = new AbortController()
|
||||
setLoading(true)
|
||||
@@ -152,7 +159,7 @@ export function GenerateWebsiteLinkModal({ open, onClose, onPick }: GenerateWebs
|
||||
})
|
||||
|
||||
return () => controller.abort()
|
||||
}, [open, kind, t])
|
||||
}, [open, kind, filterBy, t])
|
||||
|
||||
const uiCategories = useMemo(
|
||||
() => categories.map(mapProductCategoryToUi),
|
||||
@@ -160,7 +167,12 @@ export function GenerateWebsiteLinkModal({ open, onClose, onPick }: GenerateWebs
|
||||
)
|
||||
|
||||
const selected = categories.find((category) => category.id === categoryId)
|
||||
const preview = selected ? buildWebsiteCategoryUrl(kind, selected) : ''
|
||||
const preview =
|
||||
filterBy === 'category'
|
||||
? selected
|
||||
? buildWebsiteCategoryUrl(kind, selected)
|
||||
: ''
|
||||
: buildWebsiteTagsUrl(kind, tags)
|
||||
|
||||
if (!mounted) return null
|
||||
|
||||
@@ -171,6 +183,16 @@ export function GenerateWebsiteLinkModal({ open, onClose, onPick }: GenerateWebs
|
||||
onPick(preview)
|
||||
}
|
||||
|
||||
function handleFilterChange(next: WebsiteLinkFilter) {
|
||||
setFilterBy(next)
|
||||
setError('')
|
||||
if (next === 'tag') {
|
||||
setCategoryId('')
|
||||
} else {
|
||||
setTags([])
|
||||
}
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
className={`${modalStyles.overlay} ${modalStyles.overlayStacked} ${closing ? modalStyles.overlayOut : modalStyles.overlayIn}`}
|
||||
@@ -222,23 +244,47 @@ export function GenerateWebsiteLinkModal({ open, onClose, onPick }: GenerateWebs
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className={`${modalStyles.field} ${styles.treeField}`}>
|
||||
<label htmlFor="generate-link-category">{t('website.linkGenerator.category')}</label>
|
||||
{loading ? (
|
||||
<p className={styles.status}>{t('website.linkGenerator.loading')}</p>
|
||||
) : uiCategories.length === 0 ? (
|
||||
<p className={styles.status}>{t('website.linkGenerator.empty')}</p>
|
||||
) : (
|
||||
<CategoryTreeSelect
|
||||
id="generate-link-category"
|
||||
categories={uiCategories}
|
||||
value={categoryId}
|
||||
onChange={setCategoryId}
|
||||
placeholder={t('website.linkGenerator.pickCategory')}
|
||||
/>
|
||||
)}
|
||||
<div className={modalStyles.field}>
|
||||
<label htmlFor="generate-link-filter">{t('website.linkGenerator.filterBy')}</label>
|
||||
<select
|
||||
id="generate-link-filter"
|
||||
value={filterBy}
|
||||
onChange={(e) => handleFilterChange(e.target.value as WebsiteLinkFilter)}
|
||||
>
|
||||
<option value="category">{t('website.linkGenerator.category')}</option>
|
||||
<option value="tag">{t('website.linkGenerator.tag')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{filterBy === 'category' ? (
|
||||
<div className={`${modalStyles.field} ${styles.treeField}`}>
|
||||
<label htmlFor="generate-link-category">{t('website.linkGenerator.category')}</label>
|
||||
{loading ? (
|
||||
<p className={styles.status}>{t('website.linkGenerator.loading')}</p>
|
||||
) : uiCategories.length === 0 ? (
|
||||
<p className={styles.status}>{t('website.linkGenerator.empty')}</p>
|
||||
) : (
|
||||
<CategoryTreeSelect
|
||||
id="generate-link-category"
|
||||
categories={uiCategories}
|
||||
value={categoryId}
|
||||
onChange={setCategoryId}
|
||||
placeholder={t('website.linkGenerator.pickCategory')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className={modalStyles.field}>
|
||||
<label htmlFor="generate-link-tags">{t('website.linkGenerator.tags')}</label>
|
||||
<TagInput
|
||||
tags={tags}
|
||||
onChange={setTags}
|
||||
placeholder={t('website.linkGenerator.tagsPlaceholder')}
|
||||
/>
|
||||
<p className={styles.hint}>{t('website.linkGenerator.tagsHint')}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{preview ? (
|
||||
<p className={styles.preview} dir="ltr">
|
||||
{preview}
|
||||
|
||||
Reference in New Issue
Block a user