feat(gift-cards): admin usage report for gift codes
Add report sheet with summary stats, table columns, and API hook for codes usage reporting. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,6 +7,26 @@ import type {Giftcard, GiftcardInput} from "@/model/giftCard";
|
||||
export const getGiftCardTable = async (params: TableInput) =>
|
||||
await useAxios.post<Array<Giftcard>>('/admin/giftcard/table', params)
|
||||
|
||||
export interface GiftCardCodesUsageSummary {
|
||||
total_codes: number
|
||||
used_codes: number
|
||||
unused_codes: number
|
||||
active_codes: number
|
||||
expired_codes: number
|
||||
}
|
||||
|
||||
export type GiftCardCodesUsageReportResponse = {
|
||||
rows: Record<string, unknown>[]
|
||||
summary?: GiftCardCodesUsageSummary
|
||||
query?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const getGiftCardCodesUsageReport = async (params: TableInput) =>
|
||||
await useAxios.post<GiftCardCodesUsageReportResponse>(
|
||||
'/admin/giftcard/codes-usage-report',
|
||||
params,
|
||||
)
|
||||
|
||||
export const createGiftCard = async (obj: GiftcardInput) =>
|
||||
await useAxios.post<Giftcard>(`/admin/giftcard/generate`, obj)
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<BasicActionSheet
|
||||
v-model:visible="visibleProxy"
|
||||
title="گزارش استفاده کدهای هدیه"
|
||||
modal-width="92%"
|
||||
drawer-height="92vh"
|
||||
:show-button="true"
|
||||
:show-cancel="true"
|
||||
:show-ok="false"
|
||||
cancel-text="بستن"
|
||||
@cancel="close"
|
||||
@close="close"
|
||||
>
|
||||
<p class="m-0 mb-3 text-13px leading-6 text-[var(--admin-text-secondary)]">
|
||||
هر سطر یک کد است. ستون «تعداد استفاده» برای کدهای عمومی چندمصرف، تعداد مصرف کل پویش است.
|
||||
</p>
|
||||
|
||||
<div
|
||||
v-if="summary"
|
||||
class="mb-3 grid grid-cols-2 gap-2 sm:grid-cols-5"
|
||||
>
|
||||
<div class="rounded-lg border border-solid border-[var(--admin-border)] p-2.5 text-center">
|
||||
<div class="text-xs text-[var(--admin-text-secondary)]">کل کدها</div>
|
||||
<div class="text-base font-700">{{ summary.total_codes }}</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-solid border-[var(--admin-border)] p-2.5 text-center">
|
||||
<div class="text-xs text-[var(--admin-text-secondary)]">استفادهشده</div>
|
||||
<div class="text-base font-700 text-emerald-700">{{ summary.used_codes }}</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-solid border-[var(--admin-border)] p-2.5 text-center">
|
||||
<div class="text-xs text-[var(--admin-text-secondary)]">بدون مصرف</div>
|
||||
<div class="text-base font-700">{{ summary.unused_codes }}</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-solid border-[var(--admin-border)] p-2.5 text-center">
|
||||
<div class="text-xs text-[var(--admin-text-secondary)]">فعال</div>
|
||||
<div class="text-base font-700">{{ summary.active_codes }}</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-solid border-[var(--admin-border)] p-2.5 text-center col-span-2 sm:col-span-1">
|
||||
<div class="text-xs text-[var(--admin-text-secondary)]">منقضی</div>
|
||||
<div class="text-base font-700 text-red-600">{{ summary.expired_codes }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BaseTable
|
||||
class="h-[min(58vh,520px)] min-h-0 min-w-0"
|
||||
v-bind="tblProps"
|
||||
@register="submit"
|
||||
/>
|
||||
</BasicActionSheet>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import type { TableInput } from '@core/type/baseTable'
|
||||
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
|
||||
import BasicActionSheet from '@/components/BasicActionSheet.vue'
|
||||
import { getGiftCardCodesUsageReport } from '@/api/giftCard'
|
||||
import { getGiftCardUsageReportColumns } from '../hooks/usageReportColumns'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
}>()
|
||||
|
||||
const visibleProxy = computed({
|
||||
get: () => props.visible,
|
||||
set: (value: boolean) => emit('update:visible', value),
|
||||
})
|
||||
|
||||
const summary = ref<{
|
||||
total_codes: number
|
||||
used_codes: number
|
||||
unused_codes: number
|
||||
active_codes: number
|
||||
expired_codes: number
|
||||
} | null>(null)
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
|
||||
const tblProps = ref({
|
||||
columns: getGiftCardUsageReportColumns(),
|
||||
api: async (params: TableInput) => {
|
||||
const res = await getGiftCardCodesUsageReport(params)
|
||||
summary.value = res.summary ?? null
|
||||
return res
|
||||
},
|
||||
})
|
||||
|
||||
function close() {
|
||||
visibleProxy.value = false
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(open) => {
|
||||
if (open) {
|
||||
void reload()
|
||||
}
|
||||
},
|
||||
)
|
||||
</script>
|
||||
@@ -23,6 +23,15 @@
|
||||
>
|
||||
افزودن کد تخفیف
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="!customerId && (can('usageReport') || can('codes'))"
|
||||
type="default"
|
||||
size="small"
|
||||
icon="vuesax-linear:chart-2"
|
||||
@click="usageReportVisible = true"
|
||||
>
|
||||
گزارش استفاده کدها
|
||||
</BaseButton>
|
||||
</div>
|
||||
</template>
|
||||
<template #action="{ column, record }">
|
||||
@@ -69,6 +78,8 @@
|
||||
/>
|
||||
</div>
|
||||
</BasicShowContext>
|
||||
|
||||
<GiftCardCodesUsageReportSheet v-model:visible="usageReportVisible" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -84,6 +95,7 @@ import { isMobile } from '@core/utils/is'
|
||||
import { isStoreApp } from '@/utils/config'
|
||||
import { giftCardsPageIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
import GiftCardCodesUsageReportSheet from './GiftCardCodesUsageReportSheet.vue'
|
||||
|
||||
interface Prop {
|
||||
customerId?: number
|
||||
@@ -92,6 +104,7 @@ interface Prop {
|
||||
const props = defineProps<Prop>()
|
||||
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'giftcardcodes'])
|
||||
const { can } = usePageActionAccess()
|
||||
const usageReportVisible = ref(false)
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
import type { BaseTableColumn } from '@core/type/baseTable'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import CustomTag from '@/components/CustomTag.vue'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
|
||||
export function getGiftCardUsageReportColumns(): BaseTableColumn[] {
|
||||
return [
|
||||
{
|
||||
title: 'ردیف',
|
||||
dataIndex: 'id',
|
||||
key: 'row',
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
customRender: (record: { index: number }) => record.index + 1,
|
||||
},
|
||||
{
|
||||
title: 'کد',
|
||||
dataIndex: 'code',
|
||||
key: 'code',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
title: 'نام پویش',
|
||||
dataIndex: 'campaign_name',
|
||||
key: 'campaign_name',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
title: 'تعداد استفاده',
|
||||
dataIndex: 'usage_count',
|
||||
key: 'usage_count',
|
||||
type: 'Number',
|
||||
filterable: false,
|
||||
},
|
||||
{
|
||||
title: 'وضعیت کد',
|
||||
dataIndex: 'sts',
|
||||
key: 'sts',
|
||||
type: 'Number',
|
||||
customRender: ({ record }) => {
|
||||
const sts = Number(record.sts)
|
||||
if (sts === 1) {
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#ca8a04" bgColor="#fef3c7">
|
||||
فعال
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (sts === 2) {
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
|
||||
استفاده شده
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#C53030" bgColor="#FECACA">
|
||||
منقضی / غیرفعال
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'نوع',
|
||||
dataIndex: 'unit',
|
||||
key: 'unit',
|
||||
filterable: false,
|
||||
customRender: ({ record }) => {
|
||||
const unit = Number(record.unit)
|
||||
if (unit === 3) {
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#1d4ed8" bgColor="#dbeafe">
|
||||
تخفیف
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (unit === 2) {
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#C53030" bgColor="#FECACA">
|
||||
{metalLabel.goldWalletShort()}
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
|
||||
ریالی
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'مقدار',
|
||||
dataIndex: 'amount',
|
||||
key: 'amount',
|
||||
type: 'Number',
|
||||
filterable: false,
|
||||
},
|
||||
{
|
||||
title: 'مصرفکننده',
|
||||
dataIndex: 'assigned_to',
|
||||
key: 'assigned_to_name',
|
||||
filterable: false,
|
||||
customRender: ({ record }) => record.assigned_to?.name ?? '-',
|
||||
},
|
||||
{
|
||||
title: 'موبایل مصرفکننده',
|
||||
dataIndex: 'assigned_to',
|
||||
key: 'assigned_to_mobile',
|
||||
filterable: false,
|
||||
customRender: ({ record }) => (
|
||||
<span dir="ltr" class="inline-block">
|
||||
{record.assigned_to?.mobile ?? '-'}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'تاریخ استفاده',
|
||||
dataIndex: 'used_at',
|
||||
key: 'used_at',
|
||||
filterable: false,
|
||||
customRender: ({ record }) => (record.used_at ? toJalali(record.used_at) : '-'),
|
||||
},
|
||||
{
|
||||
title: 'انقضا',
|
||||
dataIndex: 'expiry_date',
|
||||
key: 'expiry_date',
|
||||
filterable: false,
|
||||
customRender: ({ record }) => (record.expiry_date ? toJalali(record.expiry_date) : '-'),
|
||||
},
|
||||
{
|
||||
title: 'کل مجاز پویش',
|
||||
dataIndex: 'campaign_count',
|
||||
key: 'campaign_count',
|
||||
type: 'Number',
|
||||
filterable: false,
|
||||
},
|
||||
{
|
||||
title: 'مصرفشده پویش',
|
||||
dataIndex: 'campaign_used_count',
|
||||
key: 'campaign_used_count',
|
||||
type: 'Number',
|
||||
filterable: false,
|
||||
},
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user