feat(products): add list and TV visibility icons in operations

Clarify tooltips for product-list vs TV board, keep the same switches in the editor, and show separate status columns.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-05 10:16:37 +03:30
co-authored by Cursor
parent 7c95f1f9d6
commit dec36cc31e
7 changed files with 105 additions and 31 deletions
+3
View File
@@ -52,6 +52,9 @@ export const changeSellActiveProduct = async (id?: number) => {
export const changeActiveProduct = async (id: number) => {
return await useAxios.get(`/admin/product/active/${id}`)
}
export const changeShowOnTvProduct = async (id: number) => {
return await useAxios.get(`/admin/product/show_on_tv/${id}`)
}
export const changeVisibilityProduct = async (id: number) => {
return await useAxios.get(`/admin/product/hide/${id}`)
}
@@ -496,11 +496,19 @@
<div class="grid gap-4 md:grid-cols-2">
<div class="product-visibility-panel">
<div class="product-visibility-row">
<span class="text-sm">نمایش در لیست محصولات</span>
<DashboardLabelWithHelp
label="نمایش در لیست محصولات"
:help="showInListHelp"
size="sm"
/>
<a-switch v-model:checked="form.show_in_list" />
</div>
<div class="product-visibility-row">
<span class="text-sm">نمایش در تابلو TV</span>
<DashboardLabelWithHelp
label="نمایش در تابلو TV"
:help="showOnTvHelp"
size="sm"
/>
<a-switch v-model:checked="form.show_on_tv" />
</div>
</div>
@@ -770,11 +778,13 @@ const cashFeeSellHelp: DashboardHelp = {
}
const showOnTvHelp: DashboardHelp = {
description: 'روشن: این محصول در صفحه تابلو قیمت مشتری (/tv) نمایش داده می‌شود.',
description:
'مستقل از لیست محصولات است. روشن: این محصول در صفحه تابلو قیمت مشتری (/tv) نمایش داده می‌شود؛ حتی اگر در لیست محصولات خاموش باشد.',
}
const showInListHelp: DashboardHelp = {
description: 'روشن: این محصول در صفحه لیست محصولات اپ مشتری نمایش داده می‌شود.',
description:
'مستقل از تابلو TV است. روشن: این محصول در صفحه لیست محصولات اپ مشتری نمایش داده می‌شود. همان آیکون تیک/ضربدر در ستون عملیات همین وضعیت را عوض می‌کند.',
}
const autoOpenHelp: DashboardHelp = {
@@ -24,6 +24,7 @@
@toggleBuy="$emit('toggleBuy', $event)"
@toggleSell="$emit('toggleSell', $event)"
@toggleActive="$emit('toggleActive', $event)"
@toggleShowOnTv="$emit('toggleShowOnTv', $event)"
@edit="$emit('edit', $event)"
@archive="$emit('archive', $event)"
/>
@@ -43,6 +44,7 @@ defineEmits<{
(e: 'toggleBuy', item: Product): void
(e: 'toggleSell', item: Product): void
(e: 'toggleActive', item: Product): void
(e: 'toggleShowOnTv', item: Product): void
(e: 'archive', item: Product): void
}>()
@@ -24,12 +24,20 @@
/>
<MobileIconButton
plain
:key="`active-${row.id}-${row.active}`"
:icon="row.active ? 'vuesax-linear:tick-circle' : 'vuesax-linear:close-circle'"
:color="row.active ? '#16A34A' : '#DC2626'"
:key="`list-${row.id}-${row.show_in_list ?? row.active}`"
:icon="(row.show_in_list ?? row.active) ? 'vuesax-linear:tick-circle' : 'vuesax-linear:close-circle'"
:color="(row.show_in_list ?? row.active) ? '#16A34A' : '#DC2626'"
:size="22"
@click="$emit('toggleActive', row)"
/>
<MobileIconButton
plain
:key="`tv-${row.id}-${row.show_on_tv}`"
icon="vuesax-linear:monitor"
:color="row.show_on_tv ? '#16A34A' : '#DC2626'"
:size="22"
@click="$emit('toggleShowOnTv', row)"
/>
</div>
</div>
</template>
@@ -103,6 +111,7 @@ const emit = defineEmits<{
(e: 'toggleBuy', item: Product): void
(e: 'toggleSell', item: Product): void
(e: 'toggleActive', item: Product): void
(e: 'toggleShowOnTv', item: Product): void
(e: 'edit', item: Product): void
(e: 'archive', item: Product): void
}>()
@@ -36,7 +36,9 @@ defineEmits<{
(e: 'edit', item: Product): void
}>()
const statusDotClass = computed(() => (props.item.active ? 'bg-green-500' : 'bg-red-400'))
const statusDotClass = computed(() =>
(props.item.show_in_list ?? props.item.active) ? 'bg-green-500' : 'bg-red-400',
)
const priceLabel = computed(() => (props.item.price ? usePrice(props.item.price) : '-'))
</script>
@@ -75,20 +75,37 @@
@click="activeSell(record)"
/>
</a-tooltip>
<a-tooltip class="cursor-pointer" :key="`active-${record.id}-${record.active}`">
<template v-if="!record.active" #title><span>فعال کردن محصول</span></template>
<template v-else #title><span>غیر فعال کردن محصول</span></template>
<a-tooltip class="cursor-pointer" :key="`list-${record.id}-${record.show_in_list ?? record.active}`">
<template v-if="!(record.show_in_list ?? record.active)" #title>
<span>فعال کردن نمایش در لیست محصولات</span>
</template>
<template v-else #title>
<span>غیرفعال کردن نمایش در لیست محصولات</span>
</template>
<BaseIcon
v-if="!record.active"
v-if="!(record.show_in_list ?? record.active)"
icon="vuesax-linear:close-circle"
color="#DC2626"
@click="activeProduct(record)"
@click="toggleShowInList(record)"
/>
<BaseIcon
v-else
icon="vuesax-linear:tick-circle"
color="#16A34A"
@click="activeProduct(record)"
@click="toggleShowInList(record)"
/>
</a-tooltip>
<a-tooltip class="cursor-pointer" :key="`tv-${record.id}-${record.show_on_tv}`">
<template v-if="!record.show_on_tv" #title>
<span>فعال کردن نمایش در تابلو TV</span>
</template>
<template v-else #title>
<span>غیرفعال کردن نمایش در تابلو TV</span>
</template>
<BaseIcon
icon="vuesax-linear:monitor"
:color="record.show_on_tv ? '#16A34A' : '#DC2626'"
@click="toggleShowOnTv(record)"
/>
</a-tooltip>
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
@@ -118,7 +135,8 @@
@edit="emits('edit', $event)"
@toggleBuy="activeBuy"
@toggleSell="activeSell"
@toggleActive="activeProduct"
@toggleActive="toggleShowInList"
@toggleShowOnTv="toggleShowOnTv"
@archive="handleArchive"
/>
</div>
@@ -137,6 +155,7 @@ import {
changeActiveProduct,
changeBuyActiveProduct,
changeSellActiveProduct,
changeShowOnTvProduct,
getCustomerTable,
setActiveTime
} from '@/api/product'
@@ -232,12 +251,18 @@ const activeBuy = (product: Product) =>
await fetch()
})
const activeProduct = (product: Product) =>
confirmToggle('آیا مطمئن به انجام این عملیات هستید؟', async () => {
const toggleShowInList = (product: Product) =>
confirmToggle('نمایش این محصول در لیست محصولات تغییر می‌کند. آیا مطمئن هستید؟', async () => {
await changeActiveProduct(product.id)
await fetch()
})
const toggleShowOnTv = (product: Product) =>
confirmToggle('نمایش این محصول در تابلو TV تغییر می‌کند. آیا مطمئن هستید؟', async () => {
await changeShowOnTvProduct(product.id)
await fetch()
})
function handleArchive(product: Product) {
confirmToggle(
'آیا این محصول بایگانی شود؟ نمایش، خرید و فروش آن غیرفعال می‌شود.',
@@ -104,21 +104,44 @@ export function getProductColumns(): BaseTableColumn[] {
},
...(isStoreApp() ? [inventoryColumn] : []),
{
title: 'وضعیت محصول',
dataIndex: 'active',
key: 'active',
title: 'وضعیت لیست',
dataIndex: 'show_in_list',
key: 'show_in_list',
type: 'Boolean',
customRender: (record) => (
<div class="flex justify-center">
<CustomTag
class="px-3 font-bold py-1"
textColor={record?.record.active ? '#65a30d' : '#dc2626'}
bgColor={record?.record.active ? '#ecfccb' : '#fee2e2'}
>
{record?.record.active ? 'باز' : 'بسته'}
</CustomTag>
</div>
)
customRender: (record) => {
const on = record?.record.show_in_list ?? record?.record.active
return (
<div class="flex justify-center">
<CustomTag
class="px-3 font-bold py-1"
textColor={on ? '#65a30d' : '#dc2626'}
bgColor={on ? '#ecfccb' : '#fee2e2'}
>
{on ? 'باز' : 'بسته'}
</CustomTag>
</div>
)
}
},
{
title: 'تابلو TV',
dataIndex: 'show_on_tv',
key: 'show_on_tv',
type: 'Boolean',
customRender: (record) => {
const on = !!record?.record.show_on_tv
return (
<div class="flex justify-center">
<CustomTag
class="px-3 font-bold py-1"
textColor={on ? '#65a30d' : '#dc2626'}
bgColor={on ? '#ecfccb' : '#fee2e2'}
>
{on ? 'روشن' : 'خاموش'}
</CustomTag>
</div>
)
}
},
{
title: 'عملیات',