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) =>
|
export const getGiftCardTable = async (params: TableInput) =>
|
||||||
await useAxios.post<Array<Giftcard>>('/admin/giftcard/table', params)
|
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) =>
|
export const createGiftCard = async (obj: GiftcardInput) =>
|
||||||
await useAxios.post<Giftcard>(`/admin/giftcard/generate`, obj)
|
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>
|
||||||
|
<BaseButton
|
||||||
|
v-if="!customerId && (can('usageReport') || can('codes'))"
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
icon="vuesax-linear:chart-2"
|
||||||
|
@click="usageReportVisible = true"
|
||||||
|
>
|
||||||
|
گزارش استفاده کدها
|
||||||
|
</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #action="{ column, record }">
|
<template #action="{ column, record }">
|
||||||
@@ -69,6 +78,8 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</BasicShowContext>
|
</BasicShowContext>
|
||||||
|
|
||||||
|
<GiftCardCodesUsageReportSheet v-model:visible="usageReportVisible" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -84,6 +95,7 @@ import { isMobile } from '@core/utils/is'
|
|||||||
import { isStoreApp } from '@/utils/config'
|
import { isStoreApp } from '@/utils/config'
|
||||||
import { giftCardsPageIntro } from '@/utils/adminHelpCopy'
|
import { giftCardsPageIntro } from '@/utils/adminHelpCopy'
|
||||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
import GiftCardCodesUsageReportSheet from './GiftCardCodesUsageReportSheet.vue'
|
||||||
|
|
||||||
interface Prop {
|
interface Prop {
|
||||||
customerId?: number
|
customerId?: number
|
||||||
@@ -92,6 +104,7 @@ interface Prop {
|
|||||||
const props = defineProps<Prop>()
|
const props = defineProps<Prop>()
|
||||||
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'giftcardcodes'])
|
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'giftcardcodes'])
|
||||||
const { can } = usePageActionAccess()
|
const { can } = usePageActionAccess()
|
||||||
|
const usageReportVisible = ref(false)
|
||||||
|
|
||||||
const [submit, reload] = useBaseTable()
|
const [submit, reload] = useBaseTable()
|
||||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
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