added online and trigger

This commit is contained in:
2026-07-22 18:12:45 +03:30
parent a759cf7251
commit 090dbbe239
21 changed files with 1463 additions and 286 deletions
+60 -88
View File
@@ -118,6 +118,7 @@
<div class="admin-stat-muted rounded-lg w-full flex flex-col gap-3 p-3 md:flex-row md:justify-between">
<div>
<div class="font-bold">{{ userInfo.name }}</div>
<CustomerPresenceBadge :last-seen-at="userInfo?.last_seen_at" />
<div class="mt-1">{{ userInfo?.mobile }}</div>
</div>
<div class="flex flex-col justify-center items-center pl-3">
@@ -178,7 +179,6 @@
title="حداکثر معرفی"
:value="userInfo?.add_user_limit ? `${userInfo?.add_user_limit} نفر` : '_'"
/>
<BasicInfoItem title="آخرین بازدید" :value="toJalali(userInfo?.last_seen_at)" />
<BasicInfoItem title="وضعیت سفارش دهی" class="col-span-2 md:col-span-1">
<CustomTag
:textColor="userInfo?.trade_active === 1 ? '#25855A' : '#C53030'"
@@ -188,41 +188,6 @@
{{ userInfo?.trade_active === 1 ? 'مجاز' : 'غیر مجاز' }}
</CustomTag>
</BasicInfoItem>
<template v-if="conditionalOrderEnabled">
<BasicInfoItem title="سفارش شرطی" class="col-span-2 md:col-span-1">
<div class="flex flex-wrap items-center gap-2">
<CustomTag
:textColor="userInfo?.conditional_order_active ? '#25855A' : '#C53030'"
:bgColor="userInfo?.conditional_order_active ? '#E3F4E9' : '#FECACA'"
class="font-bold px-4"
>
{{ userInfo?.conditional_order_active ? 'مجاز' : 'غیر مجاز' }}
</CustomTag>
<BaseButton
size="small"
type="default"
:loading="conditionalOrderLoading"
@click="toggleConditionalOrderActive"
>
{{ userInfo?.conditional_order_active ? 'محدود کردن' : 'مجاز کردن' }}
</BaseButton>
</div>
</BasicInfoItem>
<BasicInfoItem title="سقف سفارش شرطی" class="col-span-2 md:col-span-1">
<div class="flex flex-wrap items-center gap-2">
<span class="font-bold">
{{
userInfo?.conditional_order_limit
? `${userInfo.conditional_order_limit} عدد`
: 'نامحدود'
}}
</span>
<BaseButton size="small" type="default" @click="editConditionalOrderLimit">
ویرایش سقف
</BaseButton>
</div>
</BasicInfoItem>
</template>
</div>
<div v-if="userInfo?.id && isMobile" class="mt-2 space-y-2">
@@ -278,6 +243,19 @@
<a-tab-pane key="قلک ها" tab="قلک ها">
<FundsTable ref="fundTable" :customerId="userInfo?.id" />
</a-tab-pane>
<a-tab-pane
v-if="conditionalOrderEnabled"
key="سفارش شرطی"
tab="سفارش شرطی"
>
<CustomerConditionalOrderTab
v-if="userInfo?.id"
:customer-id="userInfo.id"
:conditional-order-active="userInfo.conditional_order_active"
:conditional-order-limit="userInfo.conditional_order_limit"
@updated="onConditionalOrderUpdated"
/>
</a-tab-pane>
</a-tabs>
</BasicActionSheet>
<WalletChargeModal
@@ -314,15 +292,17 @@ import { confirmDeleteItem } from '@/utils/confirmModal'
import { cloneDeep } from 'lodash'
import type { Item } from '@/model/Item'
import type { Customer } from '@/model/customer'
import { createCustomer, deleteCustomer, updateCustomer, changeCustomerConditionalOrderActive, updateCustomerConditionalOrderLimit } from '@/api/customer'
import { createCustomer, deleteCustomer, getCustomer, updateCustomer } from '@/api/customer'
import customers from '@/router/routes/customers'
import { usePrice } from '@core/utils/usePrice'
import jalaliday from 'jalaliday'
import { useRoute, useRouter } from 'vue-router'
import ordersTbl from '@/pages/menuPages/orders/components/table.vue'
import transactionsTbl from '@/pages/menuPages/transactions/components/table.vue'
import smsTbl from '@/pages/menuPages/sms/components/table.vue'
import FundsTable from '@/pages/menuPages/customers/base/components/fundsTable.vue'
import ReferralsTable from '@/pages/menuPages/customers/base/components/referralsTable.vue'
import CustomerConditionalOrderTab from '@/pages/menuPages/customers/base/components/CustomerConditionalOrderTab.vue'
import type { Order } from '@/model/order'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import { isMobile } from '@core/utils/is'
@@ -340,10 +320,9 @@ import {
initWalletSettingsFromApi
} from '@/utils/config'
import { displayAccountingCode, isValidAccountingCode } from '@/utils/accountingCode'
import CustomerPresenceBadge from '@/pages/menuPages/customers/base/components/CustomerPresenceBadge.vue'
import { syncCustomerWalletFromPargar, type PargarSyncWalletResult } from '@/api/pargar'
import PargarWalletSyncAction from './components/PargarWalletSyncAction.vue'
import { MODAL_ABOVE_ACTION_SHEET_Z_INDEX } from '@/constants/overlayZIndex'
import { Modal } from 'ant-design-vue'
const fnameHelp: DashboardHelp = {
description: 'نام کوچک مشتری که در اپ و پنل نمایش داده می‌شود.',
@@ -576,12 +555,42 @@ const resetData = () => {
const deleteItem = (id: number) => confirmDeleteItem('کاربر', id, deleteCustomer, refreshTable)
//define selects
const route = useRoute()
const router = useRouter()
async function openInfoFromQuery() {
const raw = route.query.infoId
const id = Number(Array.isArray(raw) ? raw[0] : raw)
if (!Number.isFinite(id) || id <= 0) return
try {
loading.value = true
const customer = await getCustomer(id)
if (customer) await info(customer)
} catch (e) {
console.error(e)
message.error('بارگذاری مشخصات مشتری ممکن نیست')
} finally {
loading.value = false
const nextQuery = { ...route.query }
delete nextQuery.infoId
await router.replace({ query: nextQuery })
}
}
onMounted(async () => {
loading.value = true
await Promise.allSettled([initAccountingSettingsFromApi(), initWalletSettingsFromApi()])
loading.value = false
})
watch(
() => route.query.infoId,
() => {
void openInfoFromQuery()
},
{ immediate: true }
)
//add project modal
const addUserVisible = ref<boolean>(false)
const userInfoVisible = ref<boolean>(false)
@@ -603,57 +612,20 @@ const accountingCodeVisible = computed(() => {
return provider === 'pargar' || provider === 'tahesab'
})
const conditionalOrderEnabled = conditionalOrderEnabledSetting
const conditionalOrderLoading = ref(false)
const conditionalOrderEnabled = computed(() => conditionalOrderEnabledSetting.value)
async function toggleConditionalOrderActive() {
if (!userInfo.value?.id || conditionalOrderLoading.value) return
conditionalOrderLoading.value = true
try {
const res = await changeCustomerConditionalOrderActive(userInfo.value.id)
userInfo.value.conditional_order_active = Boolean(
res?.conditional_order_active ?? !userInfo.value.conditional_order_active
)
refreshTable()
message.success('مجوز سفارش شرطی به‌روز شد')
} catch (e) {
console.error(e)
} finally {
conditionalOrderLoading.value = false
function onConditionalOrderUpdated(payload: {
conditional_order_active?: boolean
conditional_order_limit?: number | null
}) {
if (!userInfo.value) return
if (payload.conditional_order_active !== undefined) {
userInfo.value.conditional_order_active = payload.conditional_order_active
}
}
function editConditionalOrderLimit() {
if (!userInfo.value?.id) return
const current = userInfo.value.conditional_order_limit
Modal.confirm({
title: 'سقف تعداد سفارش شرطی',
content: `برای «${userInfo.value.name || userInfo.value.id}» عدد سقف را وارد کنید (خالی = نامحدود). فعلی: ${
current ? current : 'نامحدود'
}`,
okText: 'ذخیره',
cancelText: 'انصراف',
centered: true,
zIndex: MODAL_ABOVE_ACTION_SHEET_Z_INDEX,
onOk: async () => {
const raw = window.prompt(
'سقف تعداد تریگر pending (خالی = نامحدود):',
current != null ? String(current) : ''
)
if (raw === null) return
const trimmed = raw.trim()
const limit = trimmed === '' ? null : Number(trimmed)
if (limit !== null && (!Number.isFinite(limit) || limit < 1)) {
message.error('عدد نامعتبر است')
return Promise.reject()
}
const res = await updateCustomerConditionalOrderLimit(userInfo.value!.id, limit)
userInfo.value!.conditional_order_limit =
res?.conditional_order_limit ?? limit
refreshTable()
message.success('سقف سفارش شرطی ذخیره شد')
},
})
if (payload.conditional_order_limit !== undefined) {
userInfo.value.conditional_order_limit = payload.conditional_order_limit
}
refreshTable()
}
const accountingCodeValue = computed(() => {