feat: add shayegan inbound charges tab and floating alert host

This commit is contained in:
2026-11-05 17:16:02 +03:30
parent 29904adf02
commit c9b7876734
7 changed files with 688 additions and 2 deletions
+1
View File
@@ -92,6 +92,7 @@ declare module '@vue/runtime-core' {
PargarRetryModal: typeof import('./src/components/PargarRetryModal.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
ShayeganChargeAlertHost: typeof import('./src/components/ShayeganChargeAlertHost.vue')['default']
ShayeganCustomerRegisterModal: typeof import('./src/components/ShayeganCustomerRegisterModal.vue')['default']
SmsCreditWidget: typeof import('./src/components/SmsCreditWidget.vue')['default']
SmsNegativeBalanceHost: typeof import('./src/components/SmsNegativeBalanceHost.vue')['default']
+2
View File
@@ -4,6 +4,7 @@
<CustomerImportProgress />
<TahesabDocSyncProgress />
<GroupSmsSendProgress />
<ShayeganChargeAlertHost />
</AConfigProvider>
</template>
@@ -15,6 +16,7 @@ import { initDarkmode } from '@core/stores/darkmode'
import CustomerImportProgress from '@/components/CustomerImportProgress.vue'
import TahesabDocSyncProgress from '@/components/TahesabDocSyncProgress.vue'
import GroupSmsSendProgress from '@/components/GroupSmsSendProgress.vue'
import ShayeganChargeAlertHost from '@/components/ShayeganChargeAlertHost.vue'
import { useUserStore } from '@/store/userStore'
import { useTahesabDocSyncStore } from '@/store/tahesabDocSyncStore'
import { bootstrapAdminChat, useAdminChatService } from '@/utils/adminChat'
+64
View File
@@ -4,6 +4,14 @@ import type { TableInput, TableOutput } from '@core/type/baseTable'
export interface ShayeganRecord {
id?: number | null
customer_id?: number | null
customer?: {
id?: number
name?: string
fname?: string
lname?: string
mobile?: string
moshtari_code?: string
} | null
order_id?: number | null
withdraw_id?: number | null
amount?: number | null
@@ -42,3 +50,59 @@ export const registerCustomerInShayegan = async (customerId: number) =>
await useAxios.post<{ ok: boolean; message: string; moshtari_code?: string }>(
`/admin/customer/${customerId}/shayegan/register`
)
export interface ShayeganInboundChargeRecord {
id: number
customer_id?: number | null
customer_tafsil?: string | null
shayegan_number?: string | null
amount: number
description?: string | null
source?: string
status: 'pending' | 'approved' | 'rejected'
approved_at?: string | null
rejected_at?: string | null
reject_reason?: string | null
customer?: {
id: number
name?: string
fname?: string
lname?: string
mobile?: string
n_code?: string
moshtari_code?: string
} | null
approver?: {
id: number
name?: string
} | null
created_at?: string | null
}
export const getPendingShayeganCharges = async () =>
await useAxios.get<{ ok: boolean; charges: ShayeganInboundChargeRecord[] }>(
'/admin/shayegan/charges/pending'
)
export const getShayeganChargesTable = async (params: TableInput & { status?: string }) =>
await useAxios.post<TableOutput<ShayeganInboundChargeRecord>>(
'/admin/shayegan/charges/table',
params
)
export const approveShayeganCharge = async (id: number | string) =>
await useAxios.post<{ ok: boolean; message: string; charge?: ShayeganInboundChargeRecord }>(
`/admin/shayegan/charges/${id}/approve`
)
export const rejectShayeganCharge = async (id: number | string, reason?: string) =>
await useAxios.post<{ ok: boolean; message: string; charge?: ShayeganInboundChargeRecord }>(
`/admin/shayegan/charges/${id}/reject`,
{ reason }
)
export const syncShayeganCharges = async () =>
await useAxios.post<{ ok: boolean; message: string; new_count?: number }>(
'/admin/shayegan/charges/sync'
)
+243
View File
@@ -0,0 +1,243 @@
<template>
<div v-if="activeCharges.length > 0" class="shayegan-alert-container">
<transition-group name="slide-down" tag="div" class="flex flex-col gap-2">
<div
v-for="charge in activeCharges"
:key="charge.id"
class="shayegan-charge-card shadow-lg"
>
<div class="flex items-start justify-between gap-3">
<div class="flex items-center gap-2">
<div class="bell-icon-pulse flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-amber-100 text-amber-600">
<Icon icon="vuesax-bold:notification-bing" size="20" />
</div>
<div>
<div class="font-bold text-gray-800 text-sm">
واریزی جدید از سیستم حسابداری شایگان
</div>
<div class="text-xs text-gray-500 mt-0.5">
سند شایگان: {{ charge.shayegan_number || '-' }} · تفصیلی: {{ charge.customer_tafsil || charge.customer?.moshtari_code || '-' }}
</div>
</div>
</div>
<button
type="button"
class="text-gray-400 hover:text-gray-600 p-1 text-sm rounded cursor-pointer border-0 bg-transparent"
title="بستن موقت"
@click="dismissCharge(charge.id)"
>
✕
</button>
</div>
<div class="my-2.5 rounded-md bg-amber-50/80 p-2.5 text-sm text-amber-900 border border-amber-200">
حساب شخص <b>{{ charge.customer?.name || 'نامشخص' }}</b> از طریق پنل حسابداری مقدار <b>{{ formatPrice(charge.amount) }} ریال</b> شارژ شد
</div>
<div class="flex items-center justify-between gap-2 pt-1 border-t border-gray-100">
<div class="text-[11px] text-gray-400">
{{ formatTime(charge.created_at) }}
</div>
<div class="flex items-center gap-2">
<a-button
size="small"
type="primary"
class="!bg-emerald-600 !border-emerald-600 hover:!bg-emerald-700 text-white font-medium"
:loading="approvingId === charge.id"
@click="handleApprove(charge)"
>
تأیید و شارژ کیف پول
</a-button>
<a-button
size="small"
danger
:disabled="approvingId === charge.id"
@click="handleReject(charge)"
>
رد
</a-button>
<a-button
size="small"
type="link"
class="text-xs text-gray-500"
@click="goToAccountingTab"
>
لیست کامل
</a-button>
</div>
</div>
</div>
</transition-group>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import { useRouter } from 'vue-router'
import { message, Modal } from 'ant-design-vue'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import { usePrice } from '@core/utils/usePrice'
import { toJalali } from '@core/utils/useDataTime'
import { useUserStore } from '@/store/userStore'
import {
getPendingShayeganCharges,
approveShayeganCharge,
rejectShayeganCharge,
type ShayeganInboundChargeRecord
} from '@/api/shayegan'
const router = useRouter()
const userStore = useUserStore()
const activeCharges = ref<ShayeganInboundChargeRecord[]>([])
const dismissedIds = ref<number[]>([])
const approvingId = ref<number | null>(null)
let pollTimer: any = null
function formatPrice(val: number) {
return usePrice(val, true, '')
}
function formatTime(dt?: string | null) {
return dt ? toJalali(dt) : ''
}
function dismissCharge(id: number) {
if (!dismissedIds.value.includes(id)) {
dismissedIds.value.push(id)
}
activeCharges.value = activeCharges.value.filter(c => c.id !== id)
}
async function fetchPending() {
if (!userStore.userInfo?.id) return
try {
const res = await getPendingShayeganCharges()
if (res && res.ok && Array.isArray(res.charges)) {
// فقط مواردی که هنوز dismiss نشده‌اند یا تازه آمده‌اند
activeCharges.value = res.charges.filter(c => !dismissedIds.value.includes(c.id))
}
} catch {
// خطای شبکه را بی‌صدا نادیده می‌گیریم تا مزاحم کاربر نشود
}
}
async function handleApprove(charge: ShayeganInboundChargeRecord) {
approvingId.value = charge.id
try {
const res = await approveShayeganCharge(charge.id)
if (res && res.ok) {
message.success(res.message || `کیف پول شخص ${charge.customer?.name} با موفقیت شارژ شد.`)
activeCharges.value = activeCharges.value.filter(c => c.id !== charge.id)
} else {
message.error(res?.message || 'خطا در تایید شارژ')
}
} catch (e: any) {
message.error(e?.response?.data?.message || 'خطا در تایید شارژ و افزایش کیف پول')
} finally {
approvingId.value = null
}
}
function handleReject(charge: ShayeganInboundChargeRecord) {
Modal.confirm({
title: 'رد واریزی شایگان',
content: `آیا از رد واریزی ${formatPrice(charge.amount)} ریالی برای ${charge.customer?.name || 'مشتری'} اطمینان دارید؟`,
okText: 'بله، رد شود',
cancelText: 'انصراف',
okType: 'danger',
async onOk() {
try {
const res = await rejectShayeganCharge(charge.id)
if (res && res.ok) {
message.info(res.message || 'واریزی رد شد.')
activeCharges.value = activeCharges.value.filter(c => c.id !== charge.id)
}
} catch (e: any) {
message.error(e?.response?.data?.message || 'خطا در رد شارژ')
}
}
})
}
function goToAccountingTab() {
router.push({
path: '/menu/accounting',
query: { tab: 'shayegan-charges' }
})
}
onMounted(() => {
void fetchPending()
// چک کردن خودکار هر ۳۰ ثانیه
pollTimer = setInterval(fetchPending, 30000)
})
onBeforeUnmount(() => {
if (pollTimer) clearInterval(pollTimer)
})
watch(
() => userStore.userInfo?.id,
(id) => {
if (id) void fetchPending()
}
)
</script>
<style scoped>
.shayegan-alert-container {
position: fixed;
top: 1.5rem;
left: 1.5rem;
z-index: 9999;
max-width: 440px;
width: calc(100vw - 3rem);
pointer-events: none;
}
.shayegan-charge-card {
pointer-events: auto;
background: #ffffff;
border: 1px solid #e5e7eb;
border-right: 4px solid #f59e0b;
border-radius: 0.75rem;
padding: 1rem;
transition: all 0.3s ease;
}
.bell-icon-pulse {
animation: pulse-ring 2s infinite;
}
@keyframes pulse-ring {
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.4);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 8px rgba(245, 158, 11, 0);
}
100% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
}
}
.slide-down-enter-active,
.slide-down-leave-active {
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.slide-down-enter-from {
opacity: 0;
transform: translateY(-20px);
}
.slide-down-leave-to {
opacity: 0;
transform: translateY(-20px);
}
</style>
@@ -0,0 +1,346 @@
<template>
<div class="min-w-0 w-full max-w-full overflow-x-hidden">
<BasicShowContext title="واریزی‌های شایگان" :help="help">
<template #extra>
<div class="flex items-center gap-2">
<a-select
v-model:value="statusFilter"
size="small"
style="width: 140px"
placeholder="فیلتر وضعیت"
@change="reloadTable"
>
<a-select-option value="">همه وضعیت‌ها</a-select-option>
<a-select-option value="pending">در انتظار تایید</a-select-option>
<a-select-option value="approved">تایید شده</a-select-option>
<a-select-option value="rejected">رد شده</a-select-option>
</a-select>
<a-button
size="small"
type="primary"
:loading="syncLoading"
@click="runSyncFromShayegan"
>
استعلام از شایگان
</a-button>
<a-button size="small" @click="reloadTable">
بروزرسانی
</a-button>
</div>
</template>
<a-alert
v-if="syncMessage"
:type="syncOk ? 'success' : 'error'"
:message="syncMessage"
show-icon
closable
class="mb-3"
@close="syncMessage = ''"
/>
<div class="min-w-0 w-full max-w-full overflow-x-auto">
<BaseTable v-bind="tblProps" @register="submit">
<template #customer="{ record }">
<div class="flex flex-col">
<span class="font-medium text-gray-800">{{ customerLabel(record) }}</span>
<span class="text-xs text-gray-400" v-if="record.customer?.mobile">{{ record.customer.mobile }}</span>
<span class="text-[11px] text-gray-400" v-if="record.customer_tafsil || record.customer?.moshtari_code">
تفصیلی: {{ record.customer_tafsil || record.customer?.moshtari_code }}
</span>
</div>
</template>
<template #status="{ record }">
<a-tag :color="statusColor(record.status)">{{ statusLabel(record.status) }}</a-tag>
</template>
<template #ops="{ record }">
<div class="flex items-center justify-center gap-1.5" v-if="record.status === 'pending'">
<a-tooltip placement="bottom">
<template #title><span>تأیید و شارژ کیف پول در بورس مس</span></template>
<a-button
type="primary"
size="small"
class="!bg-emerald-600 !border-emerald-600 hover:!bg-emerald-700 !px-2"
:loading="actionLoadingId === record.id"
@click="handleApprove(record)"
>
تأیید واریزی
</a-button>
</a-tooltip>
<a-tooltip placement="bottom">
<template #title><span>رد درخواست</span></template>
<a-button
danger
size="small"
class="!px-2"
:disabled="actionLoadingId === record.id"
@click="handleReject(record)"
>
رد
</a-button>
</a-tooltip>
</div>
<div v-else class="text-xs text-gray-400 text-center">
<span v-if="record.status === 'approved'">
واریز شد
<div v-if="record.approver?.name" class="text-[11px] text-gray-500">
توسط {{ record.approver.name }}
</div>
</span>
<span v-else-if="record.status === 'rejected'">
رد شده
<div v-if="record.reject_reason" class="text-[11px] text-red-500">
{{ record.reject_reason }}
</div>
</span>
</div>
</template>
</BaseTable>
</div>
</BasicShowContext>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { message, Modal } from 'ant-design-vue'
import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
import type { TableInput } from '@core/type/baseTable'
import { toJalali } from '@core/utils/useDataTime'
import { usePrice } from '@core/utils/usePrice'
import {
getShayeganChargesTable,
approveShayeganCharge,
rejectShayeganCharge,
syncShayeganCharges,
type ShayeganInboundChargeRecord
} from '@/api/shayegan'
const help =
'واریزی‌های ثبت شده در پنل حسابداری شایگان. با زدن دکمه «تأیید واریزی»، کیف پول ریالی مشتری در بورس مس شارژ خواهد شد.'
const [submit, reload] = useBaseTable()
const statusFilter = ref('')
const syncLoading = ref(false)
const syncOk = ref(false)
const syncMessage = ref('')
const actionLoadingId = ref<number | null>(null)
const columns = [
{ title: 'شناسه', dataIndex: 'id', key: 'id', width: 70, type: 'Number', filterable: false, sortable: false },
{
title: 'مشتری',
dataIndex: 'customer',
key: 'customer',
slot: 'customer',
type: 'Text'
},
{
title: 'مبلغ واریزی (ریال)',
dataIndex: 'amount',
key: 'amount',
type: 'Number',
filterable: false,
customRender: (r: any) => usePrice(r.record?.amount || 0, true, ' ریال')
},
{ title: 'شماره سند شایگان', dataIndex: 'shayegan_number', key: 'shayegan_number', type: 'Text' },
{ title: 'توضیحات', dataIndex: 'description', key: 'description', type: 'Text' },
{
title: 'منبع',
dataIndex: 'source',
key: 'source',
type: 'Text',
width: 90,
customRender: (r: any) => (r.record?.source === 'sync' ? 'استعلام' : 'وب‌هوک')
},
{
title: 'وضعیت',
dataIndex: 'status',
key: 'status',
slot: 'status',
type: 'Text',
width: 120
},
{
title: 'تاریخ دریافت',
dataIndex: 'created_at',
key: 'created_at',
type: 'DateTime',
filterable: false,
customRender: (r: any) => (r.record?.created_at ? toJalali(r.record.created_at) : '-')
},
{
title: 'عملیات',
dataIndex: 'ops',
key: 'ops',
slot: 'ops',
width: 150,
filterable: false,
sortable: false
}
]
function asTable(res: any, params: TableInput) {
if (res?.query) {
return {
rows: Array.isArray(res.rows) ? res.rows : [],
query: {
...params,
...res.query,
filters: res.query.filters ?? []
}
}
}
const rows = Array.isArray(res)
? res
: Array.isArray(res?.data)
? res.data
: Array.isArray(res?.rows)
? res.rows
: []
return {
rows,
query: {
page: Number(res?.current_page ?? params.page ?? 1),
limit: Number(res?.per_page ?? params.limit ?? 20),
total: Number(res?.total ?? rows.length),
filters: params.filters ?? [],
search: params.search ?? '',
select: params.select ?? []
}
}
}
const tblProps = {
columns,
defaultPageLimit: 20,
api: async (params: TableInput) => {
try {
const mergedParams = {
...params,
status: statusFilter.value || undefined
}
return asTable(await getShayeganChargesTable(mergedParams), params)
} catch (e: any) {
message.error(e?.message || 'خطا در دریافت لیست واریزی‌های شایگان')
throw e
}
}
}
function customerLabel(record?: ShayeganInboundChargeRecord) {
if (record?.customer?.name?.trim()) return record.customer.name.trim()
if (record?.customer?.fname || record?.customer?.lname) {
const fullName = `${record?.customer?.fname || ''} ${record?.customer?.lname || ''}`.trim()
if (fullName) return fullName
}
return '-'
}
function statusLabel(status?: string) {
switch (status) {
case 'approved':
return 'تأیید شده'
case 'rejected':
return 'رد شده'
case 'pending':
default:
return 'در انتظار تأیید'
}
}
function statusColor(status?: string) {
switch (status) {
case 'approved':
return 'green'
case 'rejected':
return 'red'
case 'pending':
default:
return 'orange'
}
}
function reloadTable() {
reload()
}
async function runSyncFromShayegan() {
syncLoading.value = true
syncMessage.value = ''
try {
const res = await syncShayeganCharges()
syncOk.value = !!res?.ok
syncMessage.value = res?.message || 'استعلام با موفقیت انجام شد.'
if (res?.ok) {
message.success(syncMessage.value)
reloadTable()
} else {
message.error(syncMessage.value)
}
} catch (e: any) {
syncOk.value = false
syncMessage.value = e?.response?.data?.message || e?.message || 'خطا در استعلام از شایگان'
message.error(syncMessage.value)
} finally {
syncLoading.value = false
}
}
async function handleApprove(record: ShayeganInboundChargeRecord) {
Modal.confirm({
title: 'تأیید واریزی شایگان',
content: `آیا تأیید می‌کنید مبلغ ${usePrice(record.amount, true, ' ریال')} به کیف پول ${customerLabel(record)} در بورس مس واریز شود؟`,
okText: 'تأیید و شارژ',
cancelText: 'انصراف',
async onOk() {
actionLoadingId.value = record.id
try {
const res = await approveShayeganCharge(record.id)
if (res && res.ok) {
message.success(res.message || 'کیف پول با موفقیت شارژ شد.')
reloadTable()
} else {
message.error(res?.message || 'خطا در شارژ کیف پول')
}
} catch (e: any) {
message.error(e?.response?.data?.message || 'خطا در شارژ کیف پول مشتری')
} finally {
actionLoadingId.value = null
}
}
})
}
function handleReject(record: ShayeganInboundChargeRecord) {
let reasonInput = ''
Modal.confirm({
title: 'رد واریزی شایگان',
content: `آیا از رد واریزی ${usePrice(record.amount, true, ' ریال')} برای ${customerLabel(record)} اطمینان دارید؟`,
okText: 'رد درخواست',
cancelText: 'انصراف',
okType: 'danger',
async onOk() {
actionLoadingId.value = record.id
try {
const res = await rejectShayeganCharge(record.id, reasonInput)
if (res && res.ok) {
message.info(res.message || 'درخواست رد شد.')
reloadTable()
} else {
message.error(res?.message || 'خطا در رد درخواست')
}
} catch (e: any) {
message.error(e?.response?.data?.message || 'خطا در رد درخواست')
} finally {
actionLoadingId.value = null
}
}
})
}
</script>
@@ -73,6 +73,13 @@ const healthMessage = ref('')
const columns = [
{ title: 'شناسه', dataIndex: 'id', key: 'id', width: 80, type: 'Number', filterable: false, sortable: false },
{
title: 'مشتری',
dataIndex: 'customer.name',
key: 'customer.name',
type: 'Text',
customRender: (r: any) => customerLabel(r.record)
},
{
title: 'نوع',
dataIndex: 'type',
@@ -160,6 +167,15 @@ const tblProps = {
}
}
function customerLabel(record?: any) {
if (record?.customer?.name?.trim()) return record.customer.name.trim()
if (record?.customer?.fname || record?.customer?.lname) {
const fullName = `${record?.customer?.fname || ''} ${record?.customer?.lname || ''}`.trim()
if (fullName) return fullName
}
return '-'
}
function typeLabel(type?: string | null) {
if (!type) return '-'
return TYPE_LABELS[type] ?? type
+16 -2
View File
@@ -18,6 +18,7 @@
<ATabPane v-if="pargarEnabled" key="pargar-customer-sync" tab="همگام سازی مشتریان در پرگار" />
<ATabPane v-if="pargarEnabled" key="pargar-wallet-sync" tab="مانده کیف پرگار" />
<ATabPane v-if="shayeganEnabled" key="shayegan" tab="اسناد شایگان" />
<ATabPane v-if="shayeganEnabled" key="shayegan-charges" tab="واریزی‌های شایگان" />
</ATabs>
<tbl v-if="activeTab === 'reports'" ref="table" class="min-h-0 flex-1" />
<tahesabTbl v-else-if="tahesabEnabled && activeTab === 'tahesab'" ref="tahesabTable" class="min-h-0 flex-1" />
@@ -30,6 +31,7 @@
<pargarCustomerSync v-else-if="pargarEnabled && activeTab === 'pargar-customer-sync'" class="shrink-0" />
<pargarWalletSync v-else-if="pargarEnabled && activeTab === 'pargar-wallet-sync'" class="shrink-0" />
<shayeganTbl v-else-if="shayeganEnabled && activeTab === 'shayegan'" ref="shayeganTable" class="min-h-0 flex-1" />
<shayeganChargesTab v-else-if="shayeganEnabled && activeTab === 'shayegan-charges'" class="min-h-0 flex-1" />
</div>
</BasicMainCard>
<BasicModal
@@ -156,7 +158,8 @@
// import BasicInfoModal from '@/components/BasicInfoModal'
import { inputLenRules, inputRules, inputRulesNumber } from '@/utils/inputRules'
import { useStaticApiListsStore } from '@/store/staticApisStore'
import { ref, computed } from 'vue'
import { ref, computed, watch } from 'vue'
import { useRoute } from 'vue-router'
import tbl from './components/table.vue'
import tahesabTbl from './components/tahesabTable.vue'
import customerSync from './components/TahesabCustomerSync.vue'
@@ -165,6 +168,7 @@ import zarinTbl from './components/zarinTable.vue'
import zarinCustomerSync from './components/ZarinCustomerSync.vue'
import zarinWalletSync from './components/ZarinWalletBulkSync.vue'
import shayeganTbl from './components/shayeganTable.vue'
import shayeganChargesTab from './components/ShayeganChargesTab.vue'
import pargarTbl from './components/pargarTable.vue'
import pargarCustomerSync from './components/PargarCustomerSync.vue'
import pargarWalletSync from './components/PargarWalletBulkSync.vue'
@@ -189,14 +193,24 @@ import { metalLabel } from '@/utils/adminMetalLabels'
import { initAccountingSettingsFromApi, tahesabEnabledSetting, zarinEnabledSetting, pargarEnabledSetting, shayeganEnabledSetting } from '@/utils/config'
//loading
const route = useRoute()
const loading = ref<boolean>(false)
const submitError = ref('')
const activeTab = ref<'reports' | 'tahesab' | 'customer-sync' | 'tahesab-wallet-sync' | 'zarin' | 'zarin-customer-sync' | 'zarin-wallet-sync' | 'pargar' | 'pargar-customer-sync' | 'pargar-wallet-sync' | 'shayegan'>('reports')
const activeTab = ref<'reports' | 'tahesab' | 'customer-sync' | 'tahesab-wallet-sync' | 'zarin' | 'zarin-customer-sync' | 'zarin-wallet-sync' | 'pargar' | 'pargar-customer-sync' | 'pargar-wallet-sync' | 'shayegan' | 'shayegan-charges'>((route.query.tab as any) || 'reports')
const tahesabEnabled = tahesabEnabledSetting
const zarinEnabled = zarinEnabledSetting
const pargarEnabled = pargarEnabledSetting
const shayeganEnabled = shayeganEnabledSetting
watch(
() => route.query.tab,
(tab) => {
if (tab && typeof tab === 'string') {
activeTab.value = tab as any
}
}
)
//define store
const store = useStaticApiListsStore()