changes of giftCard page
This commit is contained in:
@@ -10,5 +10,10 @@ export const getGiftCardTable = async (params: TableInput) =>
|
||||
export const createGiftCard = async (obj: GiftcardInput) =>
|
||||
await useAxios.post<Giftcard>(`/admin/giftcard/generate`, obj)
|
||||
|
||||
export const updateGiftCard = async (id: number, obj: GiftcardInput) =>
|
||||
await useAxios.put<Giftcard>(`/admin/giftcard/${id}`, obj)
|
||||
|
||||
export const deleteGiftCard = async (id: number) => await useAxios.delete(`/admin/giftcard/${id}`)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import { defineProps } from 'vue'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { confirmMenuAccess } from '@/utils/adminPageAccess'
|
||||
|
||||
interface Prop {
|
||||
menu: RouteRecordRaw[]
|
||||
@@ -30,7 +31,7 @@ interface Prop {
|
||||
const router = useRouter()
|
||||
|
||||
function handleClickMenu(item: RouteRecordRaw) {
|
||||
//debugger
|
||||
if (!confirmMenuAccess(item.name)) return
|
||||
router.push({ name: item.name })
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<AMenu
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
:selectedKeys="selectedKeys"
|
||||
:style="{ width: props.width }"
|
||||
:mode="props.mode"
|
||||
:theme="resolvedTheme"
|
||||
@@ -49,10 +49,13 @@ const props = withDefaults(defineProps<Prop>(), {
|
||||
})
|
||||
|
||||
const resolvedTheme = computed(() => props.theme ?? menuTheme.value)
|
||||
onMounted(() => {
|
||||
selectedKeys.value = [router.currentRoute.value.name as string]
|
||||
//emits('onChange', '1')
|
||||
})
|
||||
watch(
|
||||
() => router.currentRoute.value.name,
|
||||
(name) => {
|
||||
selectedKeys.value = name ? [String(name)] : []
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { allowsPageAction, parsePageAccess } from '@/utils/adminPageAccess'
|
||||
|
||||
export function usePageActionAccess() {
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
|
||||
function can(actionKey: string) {
|
||||
return allowsPageAction(
|
||||
parsePageAccess(userStore.userInfo?.page_access),
|
||||
userStore.hasFullPageAccess,
|
||||
route.matched.map((item) => item.name),
|
||||
actionKey
|
||||
)
|
||||
}
|
||||
|
||||
return { can }
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
<script setup lang="ts">
|
||||
import { menuIconSize } from '@root/config'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { confirmMenuAccess } from '@/utils/adminPageAccess'
|
||||
|
||||
const router = useRouter()
|
||||
interface Props {
|
||||
@@ -25,6 +26,7 @@ interface Props {
|
||||
const props = withDefaults(defineProps<Props>(), {})
|
||||
|
||||
async function handleClickMenuItem(item: RouteRecordRaw) {
|
||||
if (!confirmMenuAccess(item.name)) return
|
||||
router.push({ name: item.name })
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<AMenu
|
||||
:mode="layoutStore.sideMenu.mode"
|
||||
:theme="menuTheme"
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
:selectedKeys="selectedKeys"
|
||||
>
|
||||
<template v-for="item in menu">
|
||||
<BasicSubMenu
|
||||
@@ -26,7 +26,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import type { ComputedRef } from 'vue'
|
||||
import { sortBy } from 'lodash'
|
||||
import { usePrefix } from '@core/composable/usePrefix'
|
||||
@@ -45,29 +45,28 @@ const selectedKeys = ref<(RouteRecordName | undefined)[]>([])
|
||||
const routerStore = useRouterStore()
|
||||
const { menuTheme } = useAdminTheme()
|
||||
|
||||
const path: (RouteRecordName | undefined)[] = []
|
||||
|
||||
function walkInRoutes(routes: RouteRecordRaw[]) {
|
||||
for (const route of routes) {
|
||||
if (route.name == currentRoute.name) {
|
||||
path.push(route.name)
|
||||
return true
|
||||
}
|
||||
if (route.children && route.children.length > 0) {
|
||||
path.push(route.name)
|
||||
const res = walkInRoutes(route.children)
|
||||
if (!res) {
|
||||
path.pop()
|
||||
} else {
|
||||
function syncSelectedKeys() {
|
||||
const path: (RouteRecordName | undefined)[] = []
|
||||
function walkInRoutes(routes: RouteRecordRaw[]): boolean {
|
||||
for (const route of routes) {
|
||||
if (route.name == currentRoute.name) {
|
||||
path.push(route.name)
|
||||
return true
|
||||
}
|
||||
if (route.children && route.children.length > 0) {
|
||||
path.push(route.name)
|
||||
const res = walkInRoutes(route.children)
|
||||
if (!res) path.pop()
|
||||
else return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
walkInRoutes(routerStore.routes)
|
||||
selectedKeys.value = path
|
||||
}
|
||||
|
||||
walkInRoutes(routerStore.routes)
|
||||
selectedKeys.value = path
|
||||
watch(() => currentRoute.name, syncSelectedKeys, { immediate: true })
|
||||
|
||||
function isSupportMenuRoute(item: RouteRecordRaw): boolean {
|
||||
return item.name === 'support' || item.meta?.permissionsKey === 'support'
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
showServerUnavailableOverlay,
|
||||
} from '@/utils/serverUnavailable'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { mutationBlockMessage } from '@/utils/adminPageAccess'
|
||||
import { readAdminAccessState } from '@/utils/adminAccessState'
|
||||
//import { isToken } from '@/utils/is'
|
||||
//import router from '@/router'
|
||||
//import { getToken, removeToken } from '@/utils/useUtility'
|
||||
@@ -58,6 +60,18 @@ _axios.interceptors.request.use(
|
||||
//@ts-ignore
|
||||
config.headers['Authorization'] = 'Bearer ' + token
|
||||
}
|
||||
const routeName = readAdminAccessState().routeName
|
||||
if (routeName) {
|
||||
//@ts-ignore
|
||||
config.headers['X-Admin-Page'] = routeName
|
||||
}
|
||||
}
|
||||
const blocked = mutationBlockMessage(String(config.method || 'get'), config.url)
|
||||
if (blocked) {
|
||||
message.warning({ content: blocked, key: 'admin-page-denied' })
|
||||
const error = new Error(blocked) as Error & { pageAccessDenied?: boolean }
|
||||
error.pageAccessDenied = true
|
||||
return Promise.reject(error)
|
||||
}
|
||||
return config
|
||||
},
|
||||
@@ -77,6 +91,15 @@ _axios.interceptors.response.use(
|
||||
return response.data
|
||||
},
|
||||
async function (error) {
|
||||
const deniedText = String(error?.message || '')
|
||||
if (
|
||||
error?.pageAccessDenied ||
|
||||
deniedText === 'شما به این صفحه دسترسی ندارید' ||
|
||||
deniedText === 'شما مجوز ویرایش در این صفحه را ندارید' ||
|
||||
deniedText === 'شما مجوز حذف در این صفحه را ندارید'
|
||||
) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
const router = useRouter()
|
||||
//console.log('this is axios error', error)
|
||||
/* if (error.response.data && error.response.data.code) {
|
||||
@@ -109,7 +132,12 @@ _axios.interceptors.response.use(
|
||||
await router.push({ name: 'login' })
|
||||
} else if (error.response && error.response.status == 403) {
|
||||
if (!error.config?.meta?.silent) {
|
||||
message.error('شما دسترسی لازم برای انجام این عملیات را ندارید (403)')
|
||||
const apiMessage = error.response.data?.message
|
||||
message.error(
|
||||
typeof apiMessage === 'string' && apiMessage.trim()
|
||||
? apiMessage
|
||||
: 'شما دسترسی لازم برای انجام این عملیات را ندارید'
|
||||
)
|
||||
}
|
||||
} else if (error.response && error.response.status == 422) {
|
||||
if (!error.config?.meta?.silent) {
|
||||
|
||||
+30
-2
@@ -14,6 +14,14 @@ import { setupRoutes } from '@/router'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { setupPinia } from '@core/plugins/pinia'
|
||||
import { initStoreModeFromSettings } from '@/utils/config'
|
||||
import { message } from 'ant-design-vue'
|
||||
import {
|
||||
canOpenRoute,
|
||||
firstAllowedInBranch,
|
||||
firstAllowedRouteName,
|
||||
notifyPageDenied
|
||||
} from '@/utils/adminPageAccess'
|
||||
import { setCurrentAdminRoute } from '@/utils/adminAccessState'
|
||||
|
||||
const init = async () => {
|
||||
const app = createApp(App)
|
||||
@@ -57,9 +65,29 @@ const init = async () => {
|
||||
const token = getToken()
|
||||
if (token) {
|
||||
if (to.name == 'login') {
|
||||
return next({ name: 'dashboard' })
|
||||
return next({ name: firstAllowedRouteName() || 'dashboardMain' })
|
||||
}
|
||||
return next()
|
||||
if (canOpenRoute(to.name)) {
|
||||
setCurrentAdminRoute(String(to.name || ''))
|
||||
return next()
|
||||
}
|
||||
const redirectedFrom = to.redirectedFrom?.name
|
||||
if (redirectedFrom) {
|
||||
const branchTarget = firstAllowedInBranch(String(redirectedFrom))
|
||||
if (branchTarget && branchTarget !== String(to.name)) {
|
||||
return next({ name: branchTarget })
|
||||
}
|
||||
}
|
||||
if (from.name && String(from.name) !== 'login') {
|
||||
notifyPageDenied()
|
||||
return next(false)
|
||||
}
|
||||
const first = firstAllowedRouteName()
|
||||
if (first && first !== String(to.name)) {
|
||||
return next({ name: first })
|
||||
}
|
||||
message.warning({ content: 'شما به این صفحه دسترسی ندارید', key: 'admin-page-denied' })
|
||||
return next(false)
|
||||
} else {
|
||||
if (['login', '404'].includes(to.name as string)) {
|
||||
return next()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Const } from '@/model/const'
|
||||
import type { PageAccessMap } from '@/utils/adminPageAccess'
|
||||
|
||||
export interface User {
|
||||
active?: boolean
|
||||
@@ -19,6 +20,8 @@ export interface User {
|
||||
settings?: Const[]
|
||||
sms_billing_enabled?: boolean
|
||||
sms_panel_access?: boolean
|
||||
/** null = دسترسی کامل */
|
||||
page_access?: PageAccessMap | null
|
||||
}
|
||||
|
||||
export interface AddId {
|
||||
|
||||
@@ -101,6 +101,7 @@ import OtpInput from 'vue3-otp-input'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { setupRoutes } from '@/router'
|
||||
import { getConfigApp, initStoreModeFromSettings } from '@/utils/config'
|
||||
import { firstAllowedRouteName } from '@/utils/adminPageAccess'
|
||||
|
||||
//define emit
|
||||
const emits = defineEmits(['enter'])
|
||||
@@ -175,7 +176,12 @@ const onUserPassFormFinish = async () => {
|
||||
}*/
|
||||
//router.removeRoute('usersMain')
|
||||
//router.removeRoute('users')
|
||||
await router.push({ name: 'dashboard' })
|
||||
const landing = firstAllowedRouteName()
|
||||
if (!landing) {
|
||||
message.warning({ content: 'شما به این صفحه دسترسی ندارید', key: 'admin-page-denied' })
|
||||
return
|
||||
}
|
||||
await router.push({ name: landing })
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
/* if (e?.response.status === 422) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<MobileSheetHeader :title="sheetTitle" :show-title="isMobile">
|
||||
<template #actions>
|
||||
<MobileIconButton
|
||||
v-if="!row.pargar_ref && !row.customer?.pargar_account_id"
|
||||
v-if="can('register') && !row.pargar_ref && !row.customer?.pargar_account_id"
|
||||
plain
|
||||
icon="vuesax-linear:profile-add"
|
||||
color="#7C3AED"
|
||||
@@ -12,7 +12,7 @@
|
||||
@click="$emit('register-customer', row)"
|
||||
/>
|
||||
<MobileIconButton
|
||||
v-if="canRetry(row)"
|
||||
v-if="can('send') && canRetry(row)"
|
||||
plain
|
||||
icon="vuesax-linear:refresh-circle"
|
||||
color="#CA8A04"
|
||||
@@ -88,6 +88,9 @@ import {
|
||||
} from '@/pages/menuPages/accounting/utils/pargarDisplay'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<MobileSheetHeader :title="sheetTitle" :show-title="isMobile">
|
||||
<template #actions>
|
||||
<MobileIconButton
|
||||
v-if="!row.tahesab_id && !row.customer?.moshtari_code"
|
||||
v-if="can('register') && !row.tahesab_id && !row.customer?.moshtari_code"
|
||||
plain
|
||||
icon="vuesax-linear:profile-add"
|
||||
color="#7C3AED"
|
||||
@@ -12,7 +12,7 @@
|
||||
@click="$emit('register-customer', row)"
|
||||
/>
|
||||
<MobileIconButton
|
||||
v-if="canRetry(row)"
|
||||
v-if="can('send') && canRetry(row)"
|
||||
plain
|
||||
icon="vuesax-linear:refresh-circle"
|
||||
color="#CA8A04"
|
||||
@@ -60,6 +60,9 @@ import {
|
||||
import { canRetryTahesab, tahesabStatusLabel } from '@/pages/menuPages/accounting/utils/tahesabStatus'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<MobileSheetHeader :title="sheetTitle" :show-title="isMobile">
|
||||
<template #actions>
|
||||
<MobileIconButton
|
||||
v-if="!row.zarin_id && !row.customer?.moshtari_code"
|
||||
v-if="can('register') && !row.zarin_id && !row.customer?.moshtari_code"
|
||||
plain
|
||||
icon="vuesax-linear:profile-add"
|
||||
color="#7C3AED"
|
||||
@@ -12,7 +12,7 @@
|
||||
@click="$emit('register-customer', row)"
|
||||
/>
|
||||
<MobileIconButton
|
||||
v-if="canRetry(row)"
|
||||
v-if="can('send') && canRetry(row)"
|
||||
plain
|
||||
icon="vuesax-linear:refresh-circle"
|
||||
color="#CA8A04"
|
||||
@@ -60,6 +60,9 @@ import {
|
||||
import { canRetryZarin, zarinStatusLabel } from '@/pages/menuPages/accounting/utils/zarinStatus'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
<div class="flex justify-center gap-2">
|
||||
|
||||
<!-- دکمه ۱: اطلاعات کلی (همیشه نمایان) -->
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="openInfo(record)">
|
||||
<a-tooltip v-if="can('doc')" placement="bottom" class="cursor-pointer" @click="openInfo(record)">
|
||||
<template #title><span>اطلاعات سند</span></template>
|
||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||
</a-tooltip>
|
||||
|
||||
<!-- دکمه ۲: ثبت مشتری در پرگار (فقط اگر سند هنوز ثبت نشده و مشتری کد ندارد) -->
|
||||
<a-tooltip
|
||||
v-if="canRetryPargar(record) && !record.customer?.pargar_account_id"
|
||||
v-if="can('register') && canRetryPargar(record) && !record.customer?.pargar_account_id"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@click="openRegister(record)"
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<!-- دکمه ۳: ارسال به پرگار (فقط اگر هنوز در پرگار ثبت نشده) -->
|
||||
<a-tooltip
|
||||
v-if="canRetryPargar(record)"
|
||||
v-if="can('send') && canRetryPargar(record)"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@click="openSend(record)"
|
||||
@@ -138,6 +138,9 @@ import {
|
||||
} from '@/pages/menuPages/accounting/utils/pargarDisplay'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import { accountingPargarDocsIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||
|
||||
@@ -79,12 +79,12 @@
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<div class="flex justify-center gap-2">
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="openInfo(record)">
|
||||
<a-tooltip v-if="can('doc')" placement="bottom" class="cursor-pointer" @click="openInfo(record)">
|
||||
<template #title><span>جزئیات</span></template>
|
||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
v-if="!record.tahesab_id && !record.customer?.moshtari_code && record.customer_id"
|
||||
v-if="can('register') && !record.tahesab_id && !record.customer?.moshtari_code && record.customer_id"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@click="openRegister(record)"
|
||||
@@ -93,6 +93,7 @@
|
||||
<Icon icon="vuesax-linear:profile-add" color="#7C3AED" />
|
||||
</a-tooltip>
|
||||
<a-button
|
||||
v-if="can('send')"
|
||||
type="link"
|
||||
size="small"
|
||||
:loading="syncingRowKey === failedRowKey(record)"
|
||||
@@ -111,13 +112,13 @@
|
||||
<BaseTable :key="docsView" v-bind="tblProps" @register="submit" ref="tbl">
|
||||
<template #action="{ record }">
|
||||
<div class="flex justify-center gap-2">
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="openInfo(record)">
|
||||
<a-tooltip v-if="can('doc')" placement="bottom" class="cursor-pointer" @click="openInfo(record)">
|
||||
<template #title><span>اطلاعات سند</span></template>
|
||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip
|
||||
v-if="!record.tahesab_id && !record.customer?.moshtari_code"
|
||||
v-if="can('register') && !record.tahesab_id && !record.customer?.moshtari_code"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@click="openRegister(record)"
|
||||
@@ -127,7 +128,7 @@
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip
|
||||
v-if="!record.tahesab_id"
|
||||
v-if="can('send') && !record.tahesab_id"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@click="openSend(record)"
|
||||
@@ -221,6 +222,9 @@ import {
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import { accountingTahesabDocsIntro } from '@/utils/adminHelpCopy'
|
||||
import { useTahesabDocSyncStore } from '@/store/tahesabDocSyncStore'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const docSyncStore = useTahesabDocSyncStore()
|
||||
|
||||
|
||||
@@ -76,12 +76,12 @@
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<div class="flex justify-center gap-2">
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="openInfo(record)">
|
||||
<a-tooltip v-if="can('doc')" placement="bottom" class="cursor-pointer" @click="openInfo(record)">
|
||||
<template #title><span>جزئیات</span></template>
|
||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
v-if="!record.zarin_id && !record.customer?.moshtari_code && record.customer_id"
|
||||
v-if="can('register') && !record.zarin_id && !record.customer?.moshtari_code && record.customer_id"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@click="openRegister(record)"
|
||||
@@ -90,6 +90,7 @@
|
||||
<Icon icon="vuesax-linear:profile-add" color="#7C3AED" />
|
||||
</a-tooltip>
|
||||
<a-button
|
||||
v-if="can('send')"
|
||||
type="link"
|
||||
size="small"
|
||||
:loading="syncingRowKey === failedRowKey(record)"
|
||||
@@ -108,13 +109,13 @@
|
||||
<BaseTable :key="docsView" v-bind="tblProps" @register="submit" ref="tbl">
|
||||
<template #action="{ record }">
|
||||
<div class="flex justify-center gap-2">
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="openInfo(record)">
|
||||
<a-tooltip v-if="can('doc')" placement="bottom" class="cursor-pointer" @click="openInfo(record)">
|
||||
<template #title><span>اطلاعات سند</span></template>
|
||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip
|
||||
v-if="!record.zarin_id && !record.customer?.moshtari_code"
|
||||
v-if="can('register') && !record.zarin_id && !record.customer?.moshtari_code"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@click="openRegister(record)"
|
||||
@@ -124,7 +125,7 @@
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip
|
||||
v-if="!record.zarin_id"
|
||||
v-if="can('send') && !record.zarin_id"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@click="openSend(record)"
|
||||
@@ -216,6 +217,9 @@ import {
|
||||
} from '@/pages/menuPages/accounting/utils/accountingDocAmounts'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import { accountingZarinDocsIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const props = defineProps<{
|
||||
customerId?: number
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
|
||||
<template #action="{ record }">
|
||||
<div class="flex justify-center gap-2">
|
||||
<a-tooltip placement="bottom">
|
||||
<a-tooltip v-if="can('recipients')" placement="bottom">
|
||||
<template #title>گیرندگان</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:profile-2user"
|
||||
@@ -36,7 +36,7 @@
|
||||
@click="emits('recipients', record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom">
|
||||
<a-tooltip v-if="can('readers')" placement="bottom">
|
||||
<template #title>بازدیدکنندگان</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:people"
|
||||
@@ -45,7 +45,7 @@
|
||||
@click="emits('readers', record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="!isSystemRecord(record)" placement="bottom">
|
||||
<a-tooltip v-if="can('edit') && !isSystemRecord(record)" placement="bottom">
|
||||
<template #title>ویرایش</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:edit"
|
||||
@@ -54,7 +54,7 @@
|
||||
@click="emits('edit', record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom">
|
||||
<a-tooltip v-if="can('toggle')" placement="bottom">
|
||||
<template #title>{{ record.is_active ? 'غیرفعال کردن' : 'فعال کردن' }}</template>
|
||||
<Icon
|
||||
:icon="record.is_active ? 'vuesax-linear:close-circle' : 'vuesax-linear:tick-circle'"
|
||||
@@ -63,7 +63,7 @@
|
||||
@click="emits('toggle', record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom">
|
||||
<a-tooltip v-if="can('delete')" placement="bottom">
|
||||
<template #title>حذف</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:trash"
|
||||
@@ -91,6 +91,9 @@ import {
|
||||
APP_NOTIFICATION_SOURCE_TABS,
|
||||
type AppNotificationSourceFilter,
|
||||
} from '@/model/appNotifications'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
|
||||
|
||||
@@ -18,19 +18,21 @@
|
||||
<div class="truncate font-bold text-slate-800 dark:text-slate-100">{{ item.name }}</div>
|
||||
<div class="text-xs text-gray-500">{{ item.code }}</div>
|
||||
</div>
|
||||
<a-switch :checked="item.is_active" size="small" @change="emits('toggle', item)" />
|
||||
<a-switch v-if="can('toggle')" :checked="item.is_active" size="small" @change="emits('toggle', item)" />
|
||||
</div>
|
||||
<div class="line-clamp-2 text-sm text-slate-600 dark:text-slate-300">{{ item.body }}</div>
|
||||
<div class="flex items-center justify-between text-xs text-gray-500">
|
||||
<span>ارسال {{ Number(item.sent_count ?? 0) }} · ناموفق {{ Number(item.failed_count ?? 0) }}</span>
|
||||
<div class="flex gap-3">
|
||||
<Icon
|
||||
v-if="can('edit')"
|
||||
icon="vuesax-linear:edit"
|
||||
color="#4572E9"
|
||||
class="cursor-pointer"
|
||||
@click="emits('edit', item)"
|
||||
/>
|
||||
<Icon
|
||||
v-if="can('delete')"
|
||||
icon="vuesax-linear:trash"
|
||||
color="#C53030"
|
||||
class="cursor-pointer"
|
||||
@@ -46,11 +48,11 @@
|
||||
|
||||
<BaseTable v-else class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
|
||||
<template #is_active="{ record }">
|
||||
<a-switch :checked="record.is_active" size="small" @change="emits('toggle', record)" />
|
||||
<a-switch v-if="can('toggle')" :checked="record.is_active" size="small" @change="emits('toggle', record)" />
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<div class="flex justify-center gap-2">
|
||||
<a-tooltip placement="bottom">
|
||||
<a-tooltip v-if="can('preview')" placement="bottom">
|
||||
<template #title>اجرای آزمایشی</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:play"
|
||||
@@ -59,7 +61,7 @@
|
||||
@click="emits('runNow', record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom">
|
||||
<a-tooltip v-if="can('log')" placement="bottom">
|
||||
<template #title>لاگ ارسال</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:document-text"
|
||||
@@ -68,7 +70,7 @@
|
||||
@click="emits('sends', record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom">
|
||||
<a-tooltip v-if="can('edit')" placement="bottom">
|
||||
<template #title>ویرایش</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:edit"
|
||||
@@ -77,7 +79,7 @@
|
||||
@click="emits('edit', record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom">
|
||||
<a-tooltip v-if="can('delete')" placement="bottom">
|
||||
<template #title>حذف</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:trash"
|
||||
@@ -104,6 +106,9 @@ import { getAutomationsTable } from '@/api/automations'
|
||||
import type { Automation } from '@/model/automations'
|
||||
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
|
||||
import { automationsPageIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
</div>
|
||||
|
||||
<MobileIconButton
|
||||
v-if="can('toggle')"
|
||||
:icon="item.sts === 2 ? 'vuesax-linear:card-tick' : 'vuesax-linear:card-remove'"
|
||||
:color="item.sts === 2 ? '#16A34A' : '#DC2626'"
|
||||
:size="20"
|
||||
@click="$emit('toggleSts', item)"
|
||||
/>
|
||||
<MobileIconButton
|
||||
v-if="can('delete')"
|
||||
icon="vuesax-linear:trash"
|
||||
color="#DC2626"
|
||||
:size="20"
|
||||
@@ -34,6 +36,9 @@
|
||||
import type { Card } from '@/model/card'
|
||||
import MobileCard from '@/components/mobile/MobileCard.vue'
|
||||
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
type AccountRow = Card & { sts_str?: string; created_at?: string; customer_id?: number }
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<template #action="{ column, record }">
|
||||
<div class="gap-2 flex justify-center items-center">
|
||||
<a-tooltip
|
||||
v-if="can('toggle')"
|
||||
:key="record.sts"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@@ -25,7 +26,7 @@
|
||||
:color="record.sts !== 2 ? 'red' : 'green'"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('delete', record.id)">
|
||||
<a-tooltip v-if="can('delete')" placement="bottom" class="cursor-pointer" @click="emits('delete', record.id)">
|
||||
<template #title>
|
||||
<span>حذف کارت</span>
|
||||
</template>
|
||||
@@ -63,6 +64,9 @@ import type { Card } from '@/model/card'
|
||||
import AccountMobileCardList from './AccountMobileCardList.vue'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { cardsPageIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
|
||||
<template #action="{ record }">
|
||||
<div class="flex justify-center gap-2">
|
||||
<a-tooltip placement="bottom">
|
||||
<a-tooltip v-if="can('edit')" placement="bottom">
|
||||
<template #title>ویرایش</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:edit"
|
||||
@@ -22,7 +22,7 @@
|
||||
@click="emits('edit', record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom">
|
||||
<a-tooltip v-if="can('toggle')" placement="bottom">
|
||||
<template #title>{{ record.is_active ? 'غیرفعال کردن' : 'فعال کردن' }}</template>
|
||||
<Icon
|
||||
:icon="record.is_active ? 'vuesax-linear:eye-slash' : 'vuesax-linear:eye'"
|
||||
@@ -31,7 +31,7 @@
|
||||
@click="emits('toggle', record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom">
|
||||
<a-tooltip v-if="can('delete')" placement="bottom">
|
||||
<template #title>حذف</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:trash"
|
||||
@@ -54,6 +54,9 @@ import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import { cols } from '../hooks/conditionalOrdersColumns'
|
||||
import { getConditionalOrdersTable } from '@/api/conditionalOrders'
|
||||
import { conditionalOrdersPageIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
<div class="flex min-w-0 flex-1 items-center gap-2">
|
||||
<BaseIcon icon="vuesax-linear:user" size="20" color="#6b7280" />
|
||||
<button
|
||||
v-if="can('info')"
|
||||
type="button"
|
||||
class="truncate border-none bg-transparent p-0 text-right text-sm font-700 text-gray-900 cursor-pointer"
|
||||
@click.stop="$emit('info', item)"
|
||||
>
|
||||
{{ item.name || '-' }}
|
||||
</button>
|
||||
<span v-else class="truncate text-sm font-700 text-gray-900">{{ item.name || '-' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="shrink-0 text-sm text-gray-600">{{ item.mobile || '-' }}</div>
|
||||
@@ -19,6 +21,7 @@
|
||||
{{ item.type_str || 'کاربر' }}
|
||||
</span>
|
||||
<MobileIconButton
|
||||
v-if="can('restore')"
|
||||
icon="vuesax-linear:archive-slash"
|
||||
color="#e3bf00"
|
||||
:size="20"
|
||||
@@ -34,6 +37,9 @@
|
||||
import type { Customer } from '@/model/customer'
|
||||
import MobileCard from '@/components/mobile/MobileCard.vue'
|
||||
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const props = defineProps<{
|
||||
item: Customer
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
|
||||
<template #action="{ record }">
|
||||
<div class="flex justify-center gap-2">
|
||||
<a-tooltip :key="record.is_archive" placement="bottom" class="cursor-pointer">
|
||||
<a-tooltip v-if="can('restore')" :key="record.is_archive" placement="bottom" class="cursor-pointer">
|
||||
<template #title>
|
||||
<span>{{ record.is_archive ? 'بیرون بردن از بایگانی' : 'بایگانی کردن' }}</span>
|
||||
</template>
|
||||
@@ -20,7 +20,7 @@
|
||||
</a-popconfirm>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<a-tooltip v-if="can('info')" placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<template #title><span>مشخصات</span></template>
|
||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||
</a-tooltip>
|
||||
@@ -52,6 +52,9 @@ import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
|
||||
import type { TableInput } from '@core/type/baseTable'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { customersArchivePageIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'charge'])
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
</section>
|
||||
|
||||
<BaseButton
|
||||
v-if="can('charge')"
|
||||
class="customer-mobile-sheet__charge-btn"
|
||||
size="middle"
|
||||
type="primary"
|
||||
@@ -39,7 +40,7 @@
|
||||
|
||||
<div class="customer-mobile-sheet__actions">
|
||||
<BaseButton
|
||||
v-if="accountingCodeLabel"
|
||||
v-if="can('accountingCode') && accountingCodeLabel"
|
||||
size="small"
|
||||
type="default"
|
||||
icon="vuesax-linear:card-edit"
|
||||
@@ -47,14 +48,14 @@
|
||||
>
|
||||
کد حسابداری
|
||||
</BaseButton>
|
||||
<BaseButton size="small" type="default" icon="vuesax-linear:stickynote" @click="closeAndEmit('info', row)">
|
||||
<BaseButton v-if="can('info')" size="small" type="default" icon="vuesax-linear:stickynote" @click="closeAndEmit('info', row)">
|
||||
مشخصات
|
||||
</BaseButton>
|
||||
<BaseButton size="small" type="default" icon="vuesax-linear:edit" @click="closeAndEmit('edit', row)">
|
||||
<BaseButton v-if="can('edit')" size="small" type="default" icon="vuesax-linear:edit" @click="closeAndEmit('edit', row)">
|
||||
ویرایش
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="customerLevelProfitEnabled"
|
||||
v-if="can('referral') && customerLevelProfitEnabled"
|
||||
size="small"
|
||||
type="default"
|
||||
icon="vuesax-linear:hierarchy-2"
|
||||
@@ -62,11 +63,11 @@
|
||||
>
|
||||
سود چندسطحی
|
||||
</BaseButton>
|
||||
<BaseButton size="small" type="default" icon="vuesax-linear:archive-tick" @click="closeAndEmit('archive', row)">
|
||||
<BaseButton v-if="can('archive')" size="small" type="default" icon="vuesax-linear:archive-tick" @click="closeAndEmit('archive', row)">
|
||||
بایگانی
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="customerDirectSmsEnabled"
|
||||
v-if="can('sms') && customerDirectSmsEnabled"
|
||||
size="small"
|
||||
type="default"
|
||||
icon="vuesax-linear:sms"
|
||||
@@ -75,7 +76,7 @@
|
||||
پیامک
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="!row.is_legal"
|
||||
v-if="can('approve') && !row.is_legal"
|
||||
size="small"
|
||||
type="default"
|
||||
icon="vuesax-linear:user-search"
|
||||
@@ -84,6 +85,7 @@
|
||||
تایید
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="can('active')"
|
||||
size="small"
|
||||
:type="row.active ? 'default' : 'primary'"
|
||||
:icon="row.active ? 'vuesax-linear:user-remove' : 'vuesax-linear:user-tick'"
|
||||
@@ -92,6 +94,7 @@
|
||||
{{ row.active ? 'غیرفعال' : 'فعال' }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="can('trade')"
|
||||
size="small"
|
||||
:type="row.trade_active === 1 ? 'default' : 'primary'"
|
||||
:icon="row.trade_active === 1 ? 'vuesax-linear:box-remove' : 'vuesax-linear:box-tick'"
|
||||
@@ -135,6 +138,9 @@ import {
|
||||
} from '@/utils/config'
|
||||
import { displayAccountingCode } from '@/utils/accountingCode'
|
||||
import CustomerPresenceBadge from '@/pages/menuPages/customers/base/components/CustomerPresenceBadge.vue'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
|
||||
@@ -10,12 +10,14 @@
|
||||
<div class="min-w-0 flex-1 space-y-1.5">
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
v-if="can('info')"
|
||||
type="button"
|
||||
class="min-w-0 flex-1 truncate border-none bg-transparent p-0 text-right text-sm font-700 text-gray-900"
|
||||
@click.stop="$emit('info', item)"
|
||||
>
|
||||
{{ item.name || '-' }}
|
||||
</button>
|
||||
<span v-else class="min-w-0 flex-1 truncate text-sm font-700 text-gray-900">{{ item.name || '-' }}</span>
|
||||
<span
|
||||
class="inline-block h-2.5 w-2.5 shrink-0 rounded-full"
|
||||
:class="item.active ? 'bg-green-500' : 'bg-red-500'"
|
||||
@@ -41,6 +43,7 @@
|
||||
عملیات
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="can('edit')"
|
||||
type="default"
|
||||
size="small"
|
||||
class="min-w-[4.5rem]"
|
||||
@@ -57,6 +60,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { Customer } from '@/model/customer'
|
||||
import MobileCard from '@/components/mobile/MobileCard.vue'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const props = defineProps<{
|
||||
item: Customer
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
</template>
|
||||
<template #accountingCode="{ record }">
|
||||
<button
|
||||
v-if="can('accountingCode')"
|
||||
type="button"
|
||||
class="inline-flex items-center gap-1.5 border-none bg-transparent p-0 text-sm cursor-pointer"
|
||||
style="color: var(--admin-text)"
|
||||
@@ -29,10 +30,11 @@
|
||||
<span dir="ltr">{{ accountingCodeOf(record) }}</span>
|
||||
<Icon icon="vuesax-linear:edit-2" size="16" color="#4572E9" />
|
||||
</button>
|
||||
<span v-else dir="ltr">{{ accountingCodeOf(record) }}</span>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<div class="gap-2 hidden md:flex justify-center">
|
||||
<a-tooltip :key="record.is_archive" placement="bottom" class="cursor-pointer">
|
||||
<a-tooltip v-if="can('archive')" :key="record.is_archive" placement="bottom" class="cursor-pointer">
|
||||
<template #title>
|
||||
<span>{{ record.is_archive ? 'بیرون بردن از بایگانی' : 'بایگانی کردن' }}</span>
|
||||
</template>
|
||||
@@ -47,6 +49,7 @@
|
||||
</a-popconfirm>
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
v-if="can('active')"
|
||||
:key="record.active"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@@ -60,12 +63,12 @@
|
||||
:color="record.active ? '#54AD06' : '#ad0627'"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('chargeWallet', record)">
|
||||
<a-tooltip v-if="can('charge')" placement="bottom" class="cursor-pointer" @click="emits('chargeWallet', record)">
|
||||
<template #title><span>شارژ کیف پول</span></template>
|
||||
<Icon icon="vuesax-linear:wallet-add" color="#9333ea" />
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
v-if="tahesabEnabled && !isValidAccountingCode(record.moshtari_code)"
|
||||
v-if="can('tahesab') && tahesabEnabled && !isValidAccountingCode(record.moshtari_code)"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@click="openTahesabRegister(record)"
|
||||
@@ -74,7 +77,7 @@
|
||||
<Icon icon="vuesax-linear:profile-add" color="#CA8A04" />
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
v-if="zarinEnabled && !isValidAccountingCode(record.moshtari_code)"
|
||||
v-if="can('zarin') && zarinEnabled && !isValidAccountingCode(record.moshtari_code)"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@click="openZarinRegister(record)"
|
||||
@@ -83,7 +86,7 @@
|
||||
<Icon icon="vuesax-linear:profile-add" color="#0284C7" />
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
v-if="pargarEnabled && !isValidAccountingCode(record.pargar_account_id)"
|
||||
v-if="can('pargar') && pargarEnabled && !isValidAccountingCode(record.pargar_account_id)"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@click="openPargarRegister(record)"
|
||||
@@ -92,7 +95,7 @@
|
||||
<Icon icon="vuesax-linear:profile-add" color="#7C3AED" />
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
v-if="accountingEnabled"
|
||||
v-if="can('accountingCode') && accountingEnabled"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@click="openAccountingCodeEdit(record)"
|
||||
@@ -100,16 +103,16 @@
|
||||
<template #title><span>ویرایش کد حسابداری</span></template>
|
||||
<Icon icon="vuesax-linear:card-edit" color="#0d9488" />
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<a-tooltip v-if="can('info')" placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<template #title><span>مشخصات</span></template>
|
||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
|
||||
<a-tooltip v-if="can('edit')" placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
|
||||
<template #title><span>ویرایش</span></template>
|
||||
<Icon icon="vuesax-linear:edit" color="#4572E9" />
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
v-if="customerLevelProfitEnabled"
|
||||
v-if="can('referral') && customerLevelProfitEnabled"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@click="openLevelProfit(record)"
|
||||
@@ -118,6 +121,7 @@
|
||||
<Icon icon="vuesax-linear:hierarchy-2" color="#ca8a04" />
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
v-if="can('trade')"
|
||||
:key="record.trade_active"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@@ -132,7 +136,7 @@
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
v-if="customerDirectSmsEnabled"
|
||||
v-if="can('sms') && customerDirectSmsEnabled"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@click="openDirectSms(record)"
|
||||
@@ -141,7 +145,7 @@
|
||||
<Icon icon="vuesax-linear:sms" color="#0ea5e9" />
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
v-if="!record.is_legal"
|
||||
v-if="can('approve') && !record.is_legal"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@click="openConfirmUser(record)"
|
||||
@@ -274,6 +278,9 @@ import {
|
||||
} from '@/utils/config'
|
||||
import { displayAccountingCode, isValidAccountingCode } from '@/utils/accountingCode'
|
||||
import { customersPageIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'chargeWallet'])
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<div class="flex gap-3 justify-center items-center">
|
||||
<a-tooltip placement="top">
|
||||
<a-tooltip v-if="can('detail')" placement="top">
|
||||
<template #title>جزئیات</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:eye"
|
||||
@@ -24,7 +24,7 @@
|
||||
@click.stop="openDetails(record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="record.status === 'active'" placement="top">
|
||||
<a-tooltip v-if="can('edit') && record.status === 'active'" placement="top">
|
||||
<template #title>ویرایش</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:edit"
|
||||
@@ -86,6 +86,9 @@ import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import { getCustomersList } from '@/api/customer'
|
||||
import { message } from 'ant-design-vue'
|
||||
import type { Item } from '@/model/Item'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
const modalVisible = ref(false)
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
<BaseTable v-bind="tblProps" @register="registerTable" ref="tbl">
|
||||
<template #action="{ record }">
|
||||
<div v-if="record.status === 'pending'" class="flex gap-2 justify-center">
|
||||
<BaseButton type="primary" size="small" @click.stop="openApprove(record)">
|
||||
<BaseButton v-if="can('approve')" type="primary" size="small" @click.stop="openApprove(record)">
|
||||
تأیید
|
||||
</BaseButton>
|
||||
<BaseButton size="small" danger @click.stop="openReject(record)">رد</BaseButton>
|
||||
<BaseButton v-if="can('reject')" size="small" danger @click.stop="openReject(record)">رد</BaseButton>
|
||||
</div>
|
||||
<span v-else class="text-gray-400 text-xs">—</span>
|
||||
</template>
|
||||
@@ -46,6 +46,9 @@ import {
|
||||
} from '@/api/goldCredit'
|
||||
import GoldCreditModal from '@/pages/menuPages/customers/base/components/goldCreditModal.vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const [registerTable, reload] = useBaseTable()
|
||||
const loading = ref(false)
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
|
||||
<a-tooltip v-if="can('edit')" placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
|
||||
<template #title><span>ویرایش</span></template>
|
||||
<Icon icon="vuesax-linear:edit-2" color="#4572E9" />
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('toggle', record)">
|
||||
<a-tooltip v-if="can('toggle')" placement="bottom" class="cursor-pointer" @click="emits('toggle', record)">
|
||||
<template #title>
|
||||
<span>{{ record.is_active ? 'غیرفعال کردن' : 'فعال کردن' }}</span>
|
||||
</template>
|
||||
@@ -29,6 +29,7 @@
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-popconfirm
|
||||
v-if="can('delete')"
|
||||
title="پکیج حذف شود؟"
|
||||
ok-text="تایید"
|
||||
cancel-text="انصراف"
|
||||
@@ -56,6 +57,9 @@ import { getSpecialPricePackageColumns } from '../hooks/specialPriceColumns'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { ref } from 'vue'
|
||||
import { specialPricePageIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'add'): void
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</template>
|
||||
<template #action="{ column, record }">
|
||||
<div class="gap-2 flex justify-center">
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
|
||||
<a-tooltip v-if="can('edit')" placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
|
||||
<template #title>
|
||||
<span>ویرایش</span>
|
||||
</template>
|
||||
@@ -30,6 +30,9 @@ import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import { cols } from '../hooks/discountColumns'
|
||||
import { getDiscountTable } from '@/api/discount'
|
||||
import { discountPageIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
|
||||
|
||||
@@ -54,6 +54,21 @@
|
||||
>
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
|
||||
<template v-else-if="column.key === 'code'">
|
||||
<div class="flex items-center justify-center gap-1" dir="ltr">
|
||||
<span class="font-mono text-xs">{{ record.code || '-' }}</span>
|
||||
<a-tooltip v-if="record.code" title="کپی کد">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex cursor-pointer items-center border-none bg-transparent p-0"
|
||||
aria-label="کپی کد"
|
||||
@click="copyCode(record.code)"
|
||||
>
|
||||
<Icon icon="vuesax-linear:copy" :size="16" color="#4572E9" />
|
||||
</button>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'sts'">
|
||||
<CustomTag v-if="record.sts === 1" textColor="#ca8a04" bgColor="#fef3c7">فعال</CustomTag>
|
||||
<CustomTag v-else-if="record.sts === 2" textColor="#25855A" bgColor="#E3F4E9">استفاده شده</CustomTag>
|
||||
@@ -110,6 +125,11 @@ import {
|
||||
} from '../utils/exportGiftCardCodes'
|
||||
import { resolveGiftCardCustomer } from '../utils/giftCardCustomer'
|
||||
import { SELECT_DROPDOWN_Z_INDEX } from '@/constants/overlayZIndex'
|
||||
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
|
||||
const { copy, isSupported } = useClipboard()
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
@@ -144,6 +164,27 @@ function close() {
|
||||
visibleProxy.value = false
|
||||
}
|
||||
|
||||
async function copyCode(code?: string) {
|
||||
const value = String(code ?? '').trim()
|
||||
if (!value) return
|
||||
try {
|
||||
if (isSupported.value) await copy(value)
|
||||
else {
|
||||
const el = document.createElement('textarea')
|
||||
el.value = value
|
||||
el.style.position = 'fixed'
|
||||
el.style.opacity = '0'
|
||||
document.body.appendChild(el)
|
||||
el.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(el)
|
||||
}
|
||||
message.success('کد کپی شد')
|
||||
} catch {
|
||||
message.error('کپی انجام نشد')
|
||||
}
|
||||
}
|
||||
|
||||
const giftCardCustomer = computed(() => resolveGiftCardCustomer(props.giftCard))
|
||||
|
||||
const exportMeta = computed<GiftCardCodesExportMeta>(() => ({
|
||||
|
||||
@@ -16,8 +16,13 @@
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:hide-customer="!!customerId"
|
||||
:can-edit="canEdit"
|
||||
:can-delete="canDelete"
|
||||
:can-codes="canCodes"
|
||||
@open="openSheet"
|
||||
@gift-card-codes="$emit('giftCardCodes', $event)"
|
||||
@edit="$emit('edit', $event)"
|
||||
@delete="$emit('delete', $event)"
|
||||
/>
|
||||
</MobileCardList>
|
||||
|
||||
@@ -38,10 +43,15 @@ type GiftCardRow = Giftcard & { customer?: { name?: string; mobile?: string } }
|
||||
|
||||
const props = defineProps<{
|
||||
customerId?: number
|
||||
canEdit?: boolean
|
||||
canDelete?: boolean
|
||||
canCodes?: boolean
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e: 'giftCardCodes', item: GiftCardRow): void
|
||||
(e: 'edit', item: GiftCardRow): void
|
||||
(e: 'delete', id: number): void
|
||||
}>()
|
||||
|
||||
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<GiftCardRow>()
|
||||
|
||||
@@ -18,11 +18,26 @@
|
||||
</div>
|
||||
|
||||
<MobileIconButton
|
||||
v-if="canCodes"
|
||||
icon="vuesax-linear:document-text"
|
||||
color="#1890ff"
|
||||
:size="20"
|
||||
@click="$emit('giftCardCodes', item)"
|
||||
/>
|
||||
<MobileIconButton
|
||||
v-if="canEdit"
|
||||
icon="vuesax-linear:edit"
|
||||
color="#4572E9"
|
||||
:size="20"
|
||||
@click="$emit('edit', item)"
|
||||
/>
|
||||
<MobileIconButton
|
||||
v-if="canDelete"
|
||||
icon="vuesax-linear:trash"
|
||||
color="#EB3535"
|
||||
:size="20"
|
||||
@click="item.id && $emit('delete', item.id)"
|
||||
/>
|
||||
</div>
|
||||
</MobileCard>
|
||||
</template>
|
||||
@@ -38,11 +53,16 @@ type GiftCardRow = Giftcard & { customer?: { name?: string; mobile?: string } }
|
||||
const props = defineProps<{
|
||||
item: GiftCardRow
|
||||
hideCustomer?: boolean
|
||||
canEdit?: boolean
|
||||
canDelete?: boolean
|
||||
canCodes?: boolean
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e: 'open', item: GiftCardRow): void
|
||||
(e: 'giftCardCodes', item: GiftCardRow): void
|
||||
(e: 'edit', item: GiftCardRow): void
|
||||
(e: 'delete', id: number): void
|
||||
}>()
|
||||
|
||||
const unitLabel = computed(() => (props.item.unit === 2 ? metalLabel.goldWalletShort() : 'ریالی'))
|
||||
|
||||
@@ -27,18 +27,30 @@
|
||||
</template>
|
||||
<template #action="{ column, record }">
|
||||
<div class="gap-2 flex justify-center">
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<a-tooltip v-if="can('info')" placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<template #title>
|
||||
<span>مشخصات</span>
|
||||
</template>
|
||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('giftcardcodes', record)">
|
||||
<a-tooltip v-if="can('codes')" placement="bottom" class="cursor-pointer" @click="emits('giftcardcodes', record)">
|
||||
<template #title>
|
||||
<span>کدهای {{ record.unit == 3 ? 'تخفیف' : 'کارت هدیه' }}</span>
|
||||
</template>
|
||||
<Icon icon="vuesax-linear:document-text" color="#1890ff" />
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="can('edit')" placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
|
||||
<template #title>
|
||||
<span>ویرایش</span>
|
||||
</template>
|
||||
<Icon icon="vuesax-linear:edit" color="#4572E9" />
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="can('delete')" placement="bottom" class="cursor-pointer" @click="emits('delete', record.id)">
|
||||
<template #title>
|
||||
<span>حذف</span>
|
||||
</template>
|
||||
<Icon icon="vuesax-linear:trash" color="#EB3535" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</BaseTable>
|
||||
@@ -49,6 +61,11 @@
|
||||
class="min-h-0 flex-1"
|
||||
:customer-id="customerId"
|
||||
@gift-card-codes="emits('giftcardcodes', $event)"
|
||||
:can-edit="can('edit')"
|
||||
:can-delete="can('delete')"
|
||||
:can-codes="can('codes')"
|
||||
@edit="emits('edit', $event)"
|
||||
@delete="emits('delete', $event)"
|
||||
/>
|
||||
</div>
|
||||
</BasicShowContext>
|
||||
@@ -66,6 +83,7 @@ import GiftCardMobileCardList from './GiftCardMobileCardList.vue'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { isStoreApp } from '@/utils/config'
|
||||
import { giftCardsPageIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
interface Prop {
|
||||
customerId?: number
|
||||
@@ -73,6 +91,7 @@ interface Prop {
|
||||
|
||||
const props = defineProps<Prop>()
|
||||
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'giftcardcodes'])
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||
|
||||
@@ -116,10 +116,11 @@ import { confirmDeleteItem } from '@/utils/confirmModal'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import type { Item } from '@/model/Item'
|
||||
import type { Customer } from '@/model/customer'
|
||||
import { createCustomer, deleteCustomer, getCustomersList, updateCustomer } from '@/api/customer'
|
||||
import { createGiftCard, deleteGiftCard, updateGiftCard } from '@/api/giftCard'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { allowsPageAction, notifyPageActionDenied, parsePageAccess } from '@/utils/adminPageAccess'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import type { Giftcard, Giftcardcode, GiftcardInput } from '@/model/giftCard'
|
||||
import { createGiftCard } from '@/api/giftCard'
|
||||
import dayjs from 'dayjs'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
@@ -187,6 +188,10 @@ const selectedGiftCardCodes = ref<Giftcardcode[]>([])
|
||||
const selectedGiftCard = ref<Giftcard | null>(null)
|
||||
|
||||
function info(val: Giftcard) {
|
||||
if (!giftPageAllows('info')) {
|
||||
notifyPageActionDenied('info')
|
||||
return
|
||||
}
|
||||
// نمایش اطلاعات اصلی در BasicInfoModal
|
||||
infoParams.title = 'اطلاعات کارت هدیه'
|
||||
infoParams.name = giftCardCustomerLabel(val)
|
||||
@@ -227,12 +232,6 @@ function info(val: Giftcard) {
|
||||
key: 'مبلغ کل (ریال)',
|
||||
value: usePrice(val.total ?? 0)
|
||||
},
|
||||
{
|
||||
key: val.giftcardcodes?.length > 1 ? 'کدهای یکتا' : 'کد یکتا',
|
||||
value: val.giftcardcodes?.length
|
||||
? val.giftcardcodes.map((c) => c.code).join('، ')
|
||||
: (val.unique_code ?? '-')
|
||||
},
|
||||
{
|
||||
key: 'تعداد استفاده شده',
|
||||
value: val.used_count ?? '-'
|
||||
@@ -258,6 +257,10 @@ function info(val: Giftcard) {
|
||||
}
|
||||
|
||||
function showGiftCardCodes(val: Giftcard) {
|
||||
if (!giftPageAllows('codes')) {
|
||||
notifyPageActionDenied('codes')
|
||||
return
|
||||
}
|
||||
// نمایش کدهای کارت هدیه در مودال جداگانه
|
||||
if (val.giftcardcodes && val.giftcardcodes.length > 0) {
|
||||
selectedGiftCard.value = val
|
||||
@@ -271,7 +274,23 @@ function showGiftCardCodes(val: Giftcard) {
|
||||
//edit
|
||||
let editId: number | undefined = undefined
|
||||
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
|
||||
function giftPageAllows(flag: string) {
|
||||
return allowsPageAction(
|
||||
parsePageAccess(userStore.userInfo?.page_access),
|
||||
userStore.hasFullPageAccess,
|
||||
route.matched.map((item) => item.name),
|
||||
flag
|
||||
)
|
||||
}
|
||||
|
||||
async function edit(record: Giftcard) {
|
||||
if (!giftPageAllows('edit')) {
|
||||
notifyPageActionDenied('edit')
|
||||
return
|
||||
}
|
||||
addGiftCardVisible.value = true
|
||||
editId = record.id
|
||||
form.value = {
|
||||
@@ -357,7 +376,13 @@ const resetData = () => {
|
||||
}
|
||||
|
||||
//delete
|
||||
const deleteItem = (id: number) => confirmDeleteItem('کارت هدیه', id, deleteCustomer, refreshTable)
|
||||
const deleteItem = (id: number) => {
|
||||
if (!giftPageAllows('delete')) {
|
||||
notifyPageActionDenied('delete')
|
||||
return
|
||||
}
|
||||
confirmDeleteItem('کارت هدیه', id, deleteGiftCard, refreshTable)
|
||||
}
|
||||
|
||||
//add project modal
|
||||
const addGiftCardVisible = ref<boolean>(false)
|
||||
@@ -388,8 +413,13 @@ async function ok() {
|
||||
.endOf('day')
|
||||
.format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
await createGiftCard(formData as GiftcardInput)
|
||||
message.success('کارت هدیه با موفقیت ثبت شد')
|
||||
if (editId) {
|
||||
await updateGiftCard(editId, formData as GiftcardInput)
|
||||
message.success('کارت هدیه با موفقیت ویرایش شد')
|
||||
} else {
|
||||
await createGiftCard(formData as GiftcardInput)
|
||||
message.success('کارت هدیه با موفقیت ثبت شد')
|
||||
}
|
||||
addGiftCardVisible.value = false
|
||||
resetData()
|
||||
refreshTable()
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</template>
|
||||
<template #action="{ column, record }">
|
||||
<div class="gap-2 flex justify-center">
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<a-tooltip v-if="can('info')" placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<template #title>
|
||||
<span>مشخصات</span>
|
||||
</template>
|
||||
@@ -50,6 +50,9 @@ import { getOrderTable } from '@/api/order'
|
||||
import OrderMobileCardList from './OrderMobileCardList.vue'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { ordersPageIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<a-tooltip v-if="canAdjust(record)">
|
||||
<a-tooltip v-if="can('amount') && canAdjust(record)">
|
||||
<template #title>ویرایش مقدار</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:edit-2"
|
||||
@@ -22,7 +22,7 @@
|
||||
/>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip v-if="canApprove(record)">
|
||||
<a-tooltip v-if="can('approve') && canApprove(record)">
|
||||
<template #title>{{ physicalWithdrawApproveHelp }}</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:tick-circle"
|
||||
@@ -33,7 +33,7 @@
|
||||
/>
|
||||
</a-tooltip>
|
||||
|
||||
<ADropdown v-if="canChangeStatus(record)" :trigger="['click']">
|
||||
<ADropdown v-if="can('status') && canChangeStatus(record)" :trigger="['click']">
|
||||
<a-tooltip>
|
||||
<template #title>{{ physicalWithdrawStatusHelp }}</template>
|
||||
<Icon icon="vuesax-linear:status-up" color="#CA8A04" class="cursor-pointer" />
|
||||
@@ -53,7 +53,7 @@
|
||||
</template>
|
||||
</ADropdown>
|
||||
|
||||
<a-tooltip v-if="record.sts >= 50 && record.sts <= 70">
|
||||
<a-tooltip v-if="can('factor') && record.sts >= 50 && record.sts <= 70">
|
||||
<template #title>دریافت فاکتور</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:document-download"
|
||||
@@ -92,6 +92,9 @@ import {
|
||||
physicalWithdrawPageIntro,
|
||||
physicalWithdrawStatusHelp
|
||||
} from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const emits = defineEmits(['add'])
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
|
||||
<template #action="{ record }">
|
||||
<div class="flex justify-center gap-2">
|
||||
<a-tooltip placement="bottom">
|
||||
<a-tooltip v-if="can('edit')" placement="bottom">
|
||||
<template #title>ویرایش</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:edit"
|
||||
@@ -20,7 +20,7 @@
|
||||
@click="emits('edit', record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom">
|
||||
<a-tooltip v-if="can('toggle')" placement="bottom">
|
||||
<template #title>{{ record.is_active ? 'غیرفعال کردن' : 'فعال کردن' }}</template>
|
||||
<Icon
|
||||
:icon="record.is_active ? 'vuesax-linear:eye-slash' : 'vuesax-linear:eye'"
|
||||
@@ -29,7 +29,7 @@
|
||||
@click="emits('toggle', record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom">
|
||||
<a-tooltip v-if="can('delete')" placement="bottom">
|
||||
<template #title>حذف</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:trash"
|
||||
@@ -52,6 +52,9 @@ import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import { cols } from '../hooks/priceTriggersColumns'
|
||||
import { getPriceTriggersTable } from '@/api/priceTriggers'
|
||||
import { priceTriggersPageIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<div class="flex justify-center gap-2">
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('detail', record)">
|
||||
<a-tooltip v-if="can('details')" placement="bottom" class="cursor-pointer" @click="emits('detail', record)">
|
||||
<template #title>
|
||||
<span>جزئیات محصولات</span>
|
||||
</template>
|
||||
@@ -63,6 +63,9 @@ import PriceMobileCardList from './PriceMobileCardList.vue'
|
||||
import { pricingPageIntro } from '@/utils/adminHelpCopy'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import type { PriceSyncLog } from '@/model/priceSyncLog'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
defineProps<{ syncing?: boolean }>()
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<span class="truncate text-sm font-700 text-gray-900">{{ item.title || '-' }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<MobileIconButton icon="vuesax-linear:archive-slash" color="#e3bf00" @click="$emit('unarchive', item)" />
|
||||
<MobileIconButton icon="vuesax-linear:stickynote" color="#10913A" @click="$emit('info', item)" />
|
||||
<MobileIconButton v-if="can('restore')" icon="vuesax-linear:archive-slash" color="#e3bf00" @click="$emit('unarchive', item)" />
|
||||
<MobileIconButton v-if="can('info')" icon="vuesax-linear:stickynote" color="#10913A" @click="$emit('info', item)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -32,6 +32,9 @@ import type { Product } from '@/model/product'
|
||||
import MobileCard from '@/components/mobile/MobileCard.vue'
|
||||
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
defineProps<{
|
||||
item: Product
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
|
||||
<template #action="{ record }">
|
||||
<div class="gap-2 flex justify-center">
|
||||
<a-tooltip placement="bottom" class="cursor-pointer">
|
||||
<a-tooltip v-if="can('restore')" placement="bottom" class="cursor-pointer">
|
||||
<template #title>
|
||||
<span>بیرون بردن از بایگانی</span>
|
||||
</template>
|
||||
@@ -26,7 +26,7 @@
|
||||
</a-popconfirm>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<a-tooltip v-if="can('info')" placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<template #title>
|
||||
<span>مشخصات</span>
|
||||
</template>
|
||||
@@ -56,6 +56,9 @@ import { getProductArchiveColumns } from '../hooks/archiveColumns'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import type { DashboardHelp } from '@/pages/dashboard/dashboardSectionCopy'
|
||||
import DashboardLabelWithHelp from '@/pages/dashboard/components/DashboardLabelWithHelp.vue'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const productArchiveHelp: DashboardHelp = {
|
||||
description:
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<span class="min-w-0 truncate">{{ row.title || '-' }}</span>
|
||||
<div class="flex shrink-0 items-center gap-2">
|
||||
<MobileIconButton
|
||||
v-if="can('buy')"
|
||||
plain
|
||||
:key="`buy-${row.id}-${row.open_buy}`"
|
||||
icon="vuesax-linear:money-receive"
|
||||
@@ -15,6 +16,7 @@
|
||||
@click="$emit('toggleBuy', row)"
|
||||
/>
|
||||
<MobileIconButton
|
||||
v-if="can('sell')"
|
||||
plain
|
||||
:key="`sell-${row.id}-${row.open_sell}`"
|
||||
icon="vuesax-linear:money-send"
|
||||
@@ -23,6 +25,7 @@
|
||||
@click="$emit('toggleSell', row)"
|
||||
/>
|
||||
<MobileIconButton
|
||||
v-if="can('list')"
|
||||
plain
|
||||
: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'"
|
||||
@@ -31,6 +34,7 @@
|
||||
@click="$emit('toggleActive', row)"
|
||||
/>
|
||||
<MobileIconButton
|
||||
v-if="can('tv')"
|
||||
plain
|
||||
:key="`tv-${row.id}-${row.show_on_tv}`"
|
||||
icon="vuesax-linear:monitor"
|
||||
@@ -76,6 +80,7 @@
|
||||
</MobileInfoGrid>
|
||||
|
||||
<BaseButton
|
||||
v-if="can('archive')"
|
||||
class="w-full"
|
||||
size="middle"
|
||||
type="default"
|
||||
@@ -100,6 +105,9 @@ import { resolveProductTradeTypeLabel } from '@/utils/productCategories'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{{ item.title || '-' }}
|
||||
</button>
|
||||
</div>
|
||||
<MobileIconButton icon="vuesax-linear:edit" color="#2563eb" :size="20" @click="$emit('edit', item)" />
|
||||
<MobileIconButton v-if="can('edit')" icon="vuesax-linear:edit" color="#2563eb" :size="20" @click="$emit('edit', item)" />
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex items-center justify-between gap-2">
|
||||
@@ -26,6 +26,9 @@ import type { Product } from '@/model/product'
|
||||
import MobileCard from '@/components/mobile/MobileCard.vue'
|
||||
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const props = defineProps<{
|
||||
item: Product
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<div class="gap-2 flex justify-center">
|
||||
<a-tooltip class="cursor-pointer" :key="`buy-${record.id}-${record.open_buy}`">
|
||||
<a-tooltip v-if="can('buy')" class="cursor-pointer" :key="`buy-${record.id}-${record.open_buy}`">
|
||||
<template v-if="!record.open_buy" #title><span>فعال کردن خرید</span></template>
|
||||
<template v-else #title><span>غیر فعال کردن خرید</span></template>
|
||||
<BaseIcon
|
||||
@@ -66,7 +66,7 @@
|
||||
@click="activeBuy(record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip class="cursor-pointer" :key="`sell-${record.id}-${record.open_sell}`">
|
||||
<a-tooltip v-if="can('sell')" class="cursor-pointer" :key="`sell-${record.id}-${record.open_sell}`">
|
||||
<template v-if="!record.open_sell" #title><span>فعال کردن فروش</span></template>
|
||||
<template v-else #title><span>غیر فعال کردن فروش</span></template>
|
||||
<BaseIcon
|
||||
@@ -75,7 +75,7 @@
|
||||
@click="activeSell(record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip class="cursor-pointer" :key="`list-${record.id}-${record.show_in_list ?? record.active}`">
|
||||
<a-tooltip v-if="can('list')" 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>
|
||||
@@ -95,7 +95,7 @@
|
||||
@click="toggleShowInList(record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip class="cursor-pointer" :key="`tv-${record.id}-${record.show_on_tv}`">
|
||||
<a-tooltip v-if="can('tv')" class="cursor-pointer" :key="`tv-${record.id}-${record.show_on_tv}`">
|
||||
<template v-if="!record.show_on_tv" #title>
|
||||
<span>فعال کردن نمایش در تابلو TV</span>
|
||||
</template>
|
||||
@@ -108,11 +108,11 @@
|
||||
@click="toggleShowOnTv(record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
|
||||
<a-tooltip v-if="can('edit')" placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
|
||||
<template #title><span>ویرایش</span></template>
|
||||
<BaseIcon icon="vuesax-linear:edit" color="#4572E9" />
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer">
|
||||
<a-tooltip v-if="can('archive')" placement="bottom" class="cursor-pointer">
|
||||
<template #title><span>بایگانی کردن</span></template>
|
||||
<a-popconfirm
|
||||
title="آیا این محصول بایگانی شود؟ نمایش، خرید و فروش آن غیرفعال میشود."
|
||||
@@ -168,6 +168,9 @@ import type { TableInput } from '@core/type/baseTable'
|
||||
import type { Product } from '@/model/product'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { productsPageIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
:color="record.active ? '#54AD06' : '#ad0627'"
|
||||
/>
|
||||
</a-tooltip>-->
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<a-tooltip v-if="can('info')" placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<template #title>
|
||||
<span>مشخصات</span>
|
||||
</template>
|
||||
@@ -76,6 +76,9 @@ import { getTransactionTable } from '@/api/transaction'
|
||||
import TransactionMobileCardList from './TransactionMobileCardList.vue'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { transactionsPageIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
interface Prop {
|
||||
customerId?: number
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<div class="flex w-full items-center justify-between gap-3">
|
||||
<span class="min-w-0 truncate">{{ sheetTitle }}</span>
|
||||
<MobileIconButton
|
||||
v-if="can('delete')"
|
||||
plain
|
||||
icon="vuesax-linear:user-remove"
|
||||
color="#EB3535"
|
||||
@@ -35,6 +36,9 @@ import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
|
||||
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { toJalaliNoTime } from '@core/utils/useDataTime'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
</div>
|
||||
|
||||
<MobileIconButton
|
||||
v-if="can('edit')"
|
||||
icon="vuesax-linear:edit"
|
||||
color="#4572E9"
|
||||
:size="20"
|
||||
@@ -24,6 +25,9 @@
|
||||
import type { User } from '@/model/user'
|
||||
import MobileCard from '@/components/mobile/MobileCard.vue'
|
||||
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const props = defineProps<{
|
||||
item: User
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
<template>
|
||||
<div :class="scroll ? 'access-scroll' : ''">
|
||||
<a-collapse
|
||||
v-model:activeKey="openKey"
|
||||
accordion
|
||||
:bordered="false"
|
||||
expand-icon-position="start"
|
||||
class="access-collapse"
|
||||
:class="{ 'access-collapse--nested': !scroll }"
|
||||
>
|
||||
<a-collapse-panel v-for="item in list" :key="item.key">
|
||||
<template #header>
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<span class="min-w-0 truncate text-sm font-medium">{{ item.title }}</span>
|
||||
<span class="access-chip shrink-0" :data-state="chipState(item)">{{ summary(item) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<span class="text-xs leading-5 opacity-70">
|
||||
{{ item.route ? 'ورود به این صفحه' : 'برای همهٔ صفحههای این بخش' }}
|
||||
</span>
|
||||
<div class="flex shrink-0 items-center gap-3">
|
||||
<a-checkbox
|
||||
class="access-check"
|
||||
:checked="viewState(item).checked"
|
||||
:indeterminate="viewState(item).indeterminate"
|
||||
@change="toggleView(item, $event)"
|
||||
>
|
||||
مشاهده
|
||||
</a-checkbox>
|
||||
<a-checkbox
|
||||
v-if="!item.route && actionPairs(item).length"
|
||||
class="access-check"
|
||||
:checked="opsState(item).checked"
|
||||
:indeterminate="opsState(item).indeterminate"
|
||||
@change="toggleAllActions(item, $event)"
|
||||
>
|
||||
همه عملیات
|
||||
</a-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="item.route && pageActions(item.route).length"
|
||||
class="grid grid-cols-1 gap-1.5 sm:grid-cols-2"
|
||||
>
|
||||
<a-checkbox
|
||||
v-for="action in pageActions(item.route)"
|
||||
:key="action.key"
|
||||
class="access-op"
|
||||
:checked="actionEnabled(modelValue?.[item.route], action.key)"
|
||||
@change="toggleAction(item.route, action.key, $event)"
|
||||
>
|
||||
<span class="text-xs">{{ action.label }}</span>
|
||||
</a-checkbox>
|
||||
</div>
|
||||
<p v-else-if="item.route" class="m-0 text-xs leading-5 opacity-60">
|
||||
این صفحه عملیات جدولی ندارد.
|
||||
</p>
|
||||
|
||||
<ManagerPageAccessTree
|
||||
v-if="shownChildren(item).length"
|
||||
:nodes="shownChildren(item)"
|
||||
:scroll="false"
|
||||
:model-value="modelValue"
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
/>
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default { name: 'ManagerPageAccessTree' }
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ADMIN_PAGE_TREE,
|
||||
PAGE_TABLE_ACTIONS,
|
||||
actionEnabled,
|
||||
type AccessNode,
|
||||
type PageAccessFlags,
|
||||
type PageAccessMap
|
||||
} from '@/utils/adminPageAccess'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
nodes?: AccessNode[]
|
||||
scroll?: boolean
|
||||
modelValue?: PageAccessMap | null
|
||||
}>(),
|
||||
{
|
||||
scroll: true,
|
||||
modelValue: () => ({})
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: PageAccessMap): void
|
||||
}>()
|
||||
|
||||
const openKey = ref<string>()
|
||||
|
||||
function isShown(node: AccessNode): boolean {
|
||||
if (node.enabled && !node.enabled()) return false
|
||||
if (node.children?.length) return (node.children ?? []).some((child) => isShown(child))
|
||||
return true
|
||||
}
|
||||
|
||||
function shownChildren(node: AccessNode): AccessNode[] {
|
||||
return (node.children ?? []).filter((child) => isShown(child))
|
||||
}
|
||||
|
||||
const list = computed(() => (props.nodes ?? ADMIN_PAGE_TREE).filter((item) => isShown(item)))
|
||||
|
||||
function leavesOf(node: AccessNode): string[] {
|
||||
if (!isShown(node)) return []
|
||||
if (node.route) return [node.route]
|
||||
return shownChildren(node).flatMap(leavesOf)
|
||||
}
|
||||
|
||||
function pageActions(route?: string) {
|
||||
return route ? PAGE_TABLE_ACTIONS[route] ?? [] : []
|
||||
}
|
||||
|
||||
function actionPairs(node: AccessNode): { route: string; key: string }[] {
|
||||
if (!isShown(node)) return []
|
||||
if (node.route) {
|
||||
return pageActions(node.route).map((item) => ({ route: node.route as string, key: item.key }))
|
||||
}
|
||||
return shownChildren(node).flatMap(actionPairs)
|
||||
}
|
||||
|
||||
function blankRow(): PageAccessFlags {
|
||||
return { view: false, edit: false, delete: false, actions: {} }
|
||||
}
|
||||
|
||||
function withActions(route: string, row: PageAccessFlags): PageAccessFlags {
|
||||
const actions: Record<string, boolean> = {}
|
||||
const hasMap = !!row.actions
|
||||
for (const item of pageActions(route)) {
|
||||
if (hasMap) {
|
||||
actions[item.key] = Object.prototype.hasOwnProperty.call(row.actions, item.key)
|
||||
? Boolean(row.actions?.[item.key])
|
||||
: false
|
||||
} else {
|
||||
actions[item.key] = actionEnabled({ ...row, view: true }, item.key)
|
||||
}
|
||||
}
|
||||
return {
|
||||
...row,
|
||||
actions,
|
||||
edit: Boolean(actions.edit),
|
||||
delete: Boolean(actions.delete)
|
||||
}
|
||||
}
|
||||
|
||||
function viewState(node: AccessNode) {
|
||||
const leaves = leavesOf(node)
|
||||
const on = leaves.filter((key) => props.modelValue?.[key]?.view).length
|
||||
return {
|
||||
checked: leaves.length > 0 && on === leaves.length,
|
||||
indeterminate: on > 0 && on < leaves.length,
|
||||
on,
|
||||
total: leaves.length
|
||||
}
|
||||
}
|
||||
|
||||
function opsState(node: AccessNode) {
|
||||
const pairs = actionPairs(node)
|
||||
const on = pairs.filter((pair) => actionEnabled(props.modelValue?.[pair.route], pair.key)).length
|
||||
return {
|
||||
checked: pairs.length > 0 && on === pairs.length,
|
||||
indeterminate: on > 0 && on < pairs.length,
|
||||
on,
|
||||
total: pairs.length
|
||||
}
|
||||
}
|
||||
|
||||
function chipState(node: AccessNode) {
|
||||
const view = viewState(node)
|
||||
if (!view.on) return 'none'
|
||||
const ops = opsState(node)
|
||||
if (view.checked && (!ops.total || ops.checked)) return 'full'
|
||||
return 'part'
|
||||
}
|
||||
|
||||
function summary(node: AccessNode) {
|
||||
const view = viewState(node)
|
||||
const ops = opsState(node)
|
||||
if (!view.on) return 'بدون دسترسی'
|
||||
if (!view.checked) return `${view.on} از ${view.total} صفحه`
|
||||
if (!ops.total || !ops.on) return 'فقط مشاهده'
|
||||
if (ops.checked) return 'دسترسی کامل'
|
||||
return `${ops.on} از ${ops.total} عملیات`
|
||||
}
|
||||
|
||||
function commit(next: PageAccessMap) {
|
||||
emit('update:modelValue', next)
|
||||
}
|
||||
|
||||
function toggleView(node: AccessNode, event: { target?: { checked?: boolean } }) {
|
||||
const checked = Boolean(event?.target?.checked)
|
||||
const next: PageAccessMap = { ...(props.modelValue ?? {}) }
|
||||
for (const route of leavesOf(node)) {
|
||||
if (!checked) {
|
||||
delete next[route]
|
||||
continue
|
||||
}
|
||||
if (!next[route]) {
|
||||
next[route] = {
|
||||
view: true,
|
||||
edit: false,
|
||||
delete: false,
|
||||
actions: Object.fromEntries(pageActions(route).map((item) => [item.key, false]))
|
||||
}
|
||||
continue
|
||||
}
|
||||
next[route] = withActions(route, { ...next[route], view: true })
|
||||
}
|
||||
commit(next)
|
||||
}
|
||||
|
||||
function toggleAction(route: string, actionKey: string, event: { target?: { checked?: boolean } }) {
|
||||
const checked = Boolean(event?.target?.checked)
|
||||
const next: PageAccessMap = { ...(props.modelValue ?? {}) }
|
||||
const row = withActions(route, { ...(next[route] ?? blankRow()), view: true })
|
||||
row.actions = { ...(row.actions ?? {}), [actionKey]: checked }
|
||||
row.view = true
|
||||
row.edit = Boolean(row.actions.edit)
|
||||
row.delete = Boolean(row.actions.delete)
|
||||
next[route] = row
|
||||
commit(next)
|
||||
}
|
||||
|
||||
function toggleAllActions(node: AccessNode, event: { target?: { checked?: boolean } }) {
|
||||
const checked = Boolean(event?.target?.checked)
|
||||
const next: PageAccessMap = { ...(props.modelValue ?? {}) }
|
||||
for (const pair of actionPairs(node)) {
|
||||
const row = withActions(pair.route, {
|
||||
...(next[pair.route] ?? blankRow()),
|
||||
view: next[pair.route]?.view ?? checked
|
||||
})
|
||||
if (checked) row.view = true
|
||||
row.actions = { ...(row.actions ?? {}), [pair.key]: checked }
|
||||
row.edit = Boolean(row.actions.edit)
|
||||
row.delete = Boolean(row.actions.delete)
|
||||
next[pair.route] = row
|
||||
}
|
||||
commit(next)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.access-scroll {
|
||||
max-height: 28rem;
|
||||
overflow: auto;
|
||||
padding-inline-end: 2px;
|
||||
}
|
||||
.access-collapse {
|
||||
background: transparent;
|
||||
}
|
||||
.access-collapse :deep(.ant-collapse-item) {
|
||||
margin-bottom: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--admin-border, rgba(255, 255, 255, 0.08));
|
||||
border-radius: 10px;
|
||||
background: transparent;
|
||||
}
|
||||
.access-collapse :deep(.ant-collapse-item:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.access-collapse :deep(.ant-collapse-header) {
|
||||
align-items: center !important;
|
||||
padding: 8px 10px !important;
|
||||
}
|
||||
.access-collapse :deep(.ant-collapse-content) {
|
||||
background: transparent;
|
||||
border-top: 1px solid var(--admin-border, rgba(255, 255, 255, 0.08));
|
||||
}
|
||||
.access-collapse :deep(.ant-collapse-content-box) {
|
||||
padding: 8px 10px 10px !important;
|
||||
}
|
||||
.access-collapse--nested :deep(.ant-collapse-item) {
|
||||
margin-bottom: 6px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.access-chip {
|
||||
border: 1px solid var(--admin-border, rgba(255, 255, 255, 0.08));
|
||||
border-radius: 999px;
|
||||
padding: 0 8px;
|
||||
font-size: 11px;
|
||||
line-height: 20px;
|
||||
opacity: 0.75;
|
||||
}
|
||||
.access-chip[data-state='part'] {
|
||||
color: #d4af37;
|
||||
opacity: 1;
|
||||
}
|
||||
.access-chip[data-state='full'] {
|
||||
color: #6fbf8b;
|
||||
opacity: 1;
|
||||
}
|
||||
.access-check {
|
||||
margin-inline-start: 0 !important;
|
||||
}
|
||||
.access-op {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin: 0 !important;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--admin-border, rgba(255, 255, 255, 0.08));
|
||||
border-radius: 8px;
|
||||
}
|
||||
.access-check :deep(.ant-checkbox + span),
|
||||
.access-op :deep(.ant-checkbox + span) {
|
||||
padding-inline-start: 6px;
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -22,19 +22,19 @@
|
||||
</template>
|
||||
<template #action="{ column, record }">
|
||||
<div class="gap-2 flex justify-center">
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<a-tooltip v-if="can('info')" placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<template #title>
|
||||
<span>مشخصات</span>
|
||||
</template>
|
||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
|
||||
<a-tooltip v-if="can('edit')" placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
|
||||
<template #title>
|
||||
<span>ویرایش</span>
|
||||
</template>
|
||||
<Icon icon="vuesax-linear:edit" color="#4572E9" />
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('delete', record.id)">
|
||||
<a-tooltip v-if="can('delete')" placement="bottom" class="cursor-pointer" @click="emits('delete', record.id)">
|
||||
<template #title>
|
||||
<span>حذف مدیر</span>
|
||||
</template>
|
||||
@@ -66,6 +66,9 @@ import { cols } from '../hooks/usersColumns'
|
||||
import ManagerMobileCardList from './ManagerMobileCardList.vue'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { usersPageIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<!-- </BasicMainCard>-->
|
||||
<BasicModal
|
||||
v-model:visible="addUserVisible"
|
||||
width="40rem"
|
||||
width="min(52rem, 96vw)"
|
||||
:title="`${editId ? 'ویرایش' : 'افزودن'} کاربر`"
|
||||
@ok="ok"
|
||||
:loading="loading"
|
||||
@@ -48,6 +48,19 @@
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div v-if="canAssignAccess" class="mt-1 flex flex-col gap-2">
|
||||
<div>
|
||||
<div class="text-sm font-medium">دسترسی صفحات</div>
|
||||
<p class="mt-0.5 text-xs leading-5 opacity-70">
|
||||
هر صفحه را باز کنید و مشاهده و عملیات جدول همان صفحه را مشخص کنید. بسته میماند تا وقتی لازم است.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="text-sm">دسترسی کامل به همهٔ صفحات</span>
|
||||
<a-switch v-model:checked="fullAccess" />
|
||||
</div>
|
||||
<ManagerPageAccessTree v-if="!fullAccess" v-model="form.page_access" />
|
||||
</div>
|
||||
</a-form>
|
||||
</BasicModal>
|
||||
<!-- <BasicInfoModal
|
||||
@@ -81,12 +94,18 @@ import { toJalaliNoTime, toTime } from '@core/utils/useDataTime'
|
||||
import { confirmDeleteItem } from '@/utils/confirmModal'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import type { Item } from '@/model/Item'
|
||||
import ManagerPageAccessTree from './components/ManagerPageAccessTree.vue'
|
||||
import { allGrantedMap, hasAnyView, parsePageAccess } from '@/utils/adminPageAccess'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
|
||||
//loading
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
//define store
|
||||
const store = useStaticApiListsStore()
|
||||
const userStore = useUserStore()
|
||||
const canAssignAccess = computed(() => userStore.hasFullPageAccess)
|
||||
const fullAccess = ref(true)
|
||||
|
||||
//define tbl
|
||||
const table = ref(null)
|
||||
@@ -112,6 +131,11 @@ const form = ref<User>({
|
||||
})
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
watch(fullAccess, (on) => {
|
||||
if (on) return
|
||||
if (form.value.page_access == null) form.value.page_access = allGrantedMap()
|
||||
})
|
||||
|
||||
//info
|
||||
let infoParams = reactive<Info>({})
|
||||
|
||||
@@ -141,6 +165,8 @@ async function edit(record: User) {
|
||||
addUserVisible.value = true
|
||||
editId = record.id
|
||||
form.value = cloneDeep(record)
|
||||
form.value.page_access = parsePageAccess(record.page_access)
|
||||
fullAccess.value = form.value.page_access == null
|
||||
}
|
||||
|
||||
//add
|
||||
@@ -164,8 +190,10 @@ const resetData = () => {
|
||||
avatar_id: undefined,
|
||||
sts: undefined,
|
||||
id: undefined,
|
||||
mobile: undefined
|
||||
mobile: undefined,
|
||||
page_access: null
|
||||
}
|
||||
fullAccess.value = true
|
||||
}
|
||||
|
||||
//delete — DELETE /admin/user/:id
|
||||
@@ -226,21 +254,33 @@ async function ok() {
|
||||
formRef.value?.validateFields().then(async () => {
|
||||
// console.log('salam', form.value)
|
||||
// return
|
||||
const payload = cloneDeep(form.value)
|
||||
delete payload.repassword
|
||||
if (canAssignAccess.value) {
|
||||
payload.page_access = fullAccess.value ? null : payload.page_access ?? allGrantedMap()
|
||||
if (!fullAccess.value && !hasAnyView(payload.page_access)) {
|
||||
message.error('حداقل مشاهدهٔ یک صفحه را انتخاب کنید')
|
||||
return
|
||||
}
|
||||
} else {
|
||||
delete payload.page_access
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
if (editId) {
|
||||
if (form.value.password) {
|
||||
if (form.value.password === form.value.repassword) {
|
||||
await updateUser(editId, form.value)
|
||||
await updateUser(editId, payload)
|
||||
} else {
|
||||
message.error('پسورد وارد شده با تکرار پسورد وارد شده یکسان نیست')
|
||||
return
|
||||
}
|
||||
} else {
|
||||
await updateUser(editId, form.value)
|
||||
delete payload.password
|
||||
await updateUser(editId, payload)
|
||||
}
|
||||
} else {
|
||||
await createUser(form.value)
|
||||
await createUser(payload)
|
||||
}
|
||||
addUserVisible.value = false
|
||||
refreshTable()
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<BaseTable v-if="!isMobile" class="min-h-0 min-w-0 flex-1" v-bind="tblProps" @register="submit" ref="tbl">
|
||||
<template #action="{ record }">
|
||||
<div class="flex justify-center gap-2">
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<a-tooltip v-if="can('info')" placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<template #title>
|
||||
<span>مشخصات کیف پول</span>
|
||||
</template>
|
||||
@@ -82,6 +82,9 @@ import { totalMetalWalletTitle } from '@/utils/adminMetalLabels'
|
||||
import type { WalletTableSums } from '@/model/wallet'
|
||||
import { formatMetalDisplayValue, metalDisplayUnit } from '@/utils/metalWeight'
|
||||
import { walletPageIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<template #tracked="{ column, record }">
|
||||
<div v-if="record.tracked_number">{{ record.tracked_number }}</div>
|
||||
<div v-else-if="needsTrackingConfirmation(record)">
|
||||
<BaseButton @click="emits('withdraw', record)">
|
||||
<BaseButton v-if="can('confirm')" @click="emits('withdraw', record)">
|
||||
{{ confirmLabelForRecord(record, props.flowType) }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
@@ -28,14 +28,14 @@
|
||||
<Icon icon="vuesax-linear:message-text" color="#64748b" />
|
||||
</a-badge>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="needsTrackingConfirmation(record)" class="cursor-pointer">
|
||||
<a-tooltip v-if="can('confirm') && needsTrackingConfirmation(record)" class="cursor-pointer">
|
||||
<template #title><span>{{ confirmLabelForRecord(record, props.flowType) }}</span></template>
|
||||
<BaseButton size="small" type="primary" @click="emits('withdraw', record)">
|
||||
{{ confirmLabelForRecord(record, props.flowType) }}
|
||||
</BaseButton>
|
||||
</a-tooltip>
|
||||
<div class="flex gap-2" v-if="record.sts <= 70 && record.sts >= 50">
|
||||
<a-tooltip class="cursor-pointer">
|
||||
<a-tooltip v-if="can('factor')" class="cursor-pointer">
|
||||
<template #title><span>دریافت فاکتور</span></template>
|
||||
<Icon
|
||||
icon="vuesax-linear:document-download"
|
||||
@@ -43,7 +43,7 @@
|
||||
@click="$router.push({ name: 'factor', params: { id: record.id } })"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip class="cursor-pointer">
|
||||
<a-tooltip v-if="can('status')" class="cursor-pointer">
|
||||
<template #title><span>وضعیت</span></template>
|
||||
<ADropdown :trigger="['click']">
|
||||
<Icon icon="vuesax-linear:status-up" color="#CA8A04" @click="test(record)" />
|
||||
@@ -66,7 +66,7 @@
|
||||
</ADropdown>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-tooltip v-if="needsWeightAssignment(record)" class="cursor-pointer">
|
||||
<a-tooltip v-if="can('weight') && needsWeightAssignment(record)" class="cursor-pointer">
|
||||
<template #title><span>تعیین وزن</span></template>
|
||||
<Icon
|
||||
icon="vuesax-linear:3d-cube-scan"
|
||||
@@ -75,7 +75,7 @@
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
v-if="tahesabEnabled && record.tahesab_failed_id"
|
||||
v-if="can('tahesab') && tahesabEnabled && record.tahesab_failed_id"
|
||||
class="cursor-pointer"
|
||||
@click="openTahesabRetry(record)"
|
||||
>
|
||||
@@ -83,7 +83,7 @@
|
||||
<Icon icon="vuesax-linear:refresh-circle" color="#CA8A04" />
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
v-if="zarinEnabled && record.zarin_failed_id"
|
||||
v-if="can('zarin') && zarinEnabled && record.zarin_failed_id"
|
||||
class="cursor-pointer"
|
||||
@click="openZarinRetry(record)"
|
||||
>
|
||||
@@ -242,6 +242,9 @@ onMounted(() => {
|
||||
import { status } from '@/pages/menuPages/withdraw/information/hooks/status'
|
||||
import {toNumber} from "lodash";
|
||||
import { withdrawPageIntro } from '@/utils/adminHelpCopy'
|
||||
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||
|
||||
const { can } = usePageActionAccess()
|
||||
const withdrawId = ref(null)
|
||||
const sts = ref()
|
||||
const statusList = computed(() => {
|
||||
|
||||
@@ -4,12 +4,30 @@ import { getMyInformation } from '@/api/user'
|
||||
import { get } from 'lodash'
|
||||
import { isDeployAdmin } from '@/utils/deployAdmin'
|
||||
import { hasSmsPanelAccess, isSmsBillingEnabled } from '@/utils/smsPanelAccess'
|
||||
import { setAdminAccessState } from '@/utils/adminAccessState'
|
||||
import { parsePageAccess } from '@/utils/adminPageAccess'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
const userInfo = ref<User | undefined>(undefined)
|
||||
|
||||
function syncPageAccess() {
|
||||
const info = userInfo.value
|
||||
const deployAdmin = isDeployAdmin(info?.mobile)
|
||||
const pageAccess = parsePageAccess(info?.page_access)
|
||||
const unrestricted = deployAdmin || pageAccess == null
|
||||
setAdminAccessState({
|
||||
ready: true,
|
||||
unrestricted,
|
||||
pageAccess: unrestricted ? null : pageAccess ?? null,
|
||||
deployAdmin,
|
||||
smsPanel: hasSmsPanelAccess(info) && isSmsBillingEnabled(info)
|
||||
})
|
||||
}
|
||||
|
||||
async function getMyInfo() {
|
||||
try {
|
||||
userInfo.value = await getMyInformation()
|
||||
syncPageAccess()
|
||||
return userInfo.value
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
@@ -37,6 +55,11 @@ export const useUserStore = defineStore('user', () => {
|
||||
|
||||
const isDeployAdminUser = computed(() => isDeployAdmin(userInfo.value?.mobile))
|
||||
|
||||
const hasFullPageAccess = computed(() => {
|
||||
if (isDeployAdminUser.value) return true
|
||||
return parsePageAccess(userInfo.value?.page_access) == null
|
||||
})
|
||||
|
||||
const smsPanelAccess = computed(() => hasSmsPanelAccess(userInfo.value))
|
||||
const smsBillingEnabled = computed(() => isSmsBillingEnabled(userInfo.value))
|
||||
|
||||
@@ -48,6 +71,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
canAccess,
|
||||
isAdmin,
|
||||
isDeployAdminUser,
|
||||
hasFullPageAccess,
|
||||
smsPanelAccess,
|
||||
smsBillingEnabled
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
export type PageAccessFlags = {
|
||||
view: boolean
|
||||
edit?: boolean
|
||||
delete?: boolean
|
||||
/** کلیدهای ستون عملیات همان صفحه */
|
||||
actions?: Record<string, boolean>
|
||||
}
|
||||
|
||||
export type PageAccessMap = Record<string, PageAccessFlags>
|
||||
|
||||
export type AdminAccessState = {
|
||||
/** null یعنی هنوز بارگذاری نشده؛ تا آمدن /admin/me محدود نکن */
|
||||
ready: boolean
|
||||
unrestricted: boolean
|
||||
pageAccess: PageAccessMap | null
|
||||
deployAdmin: boolean
|
||||
smsPanel: boolean
|
||||
routeName: string
|
||||
}
|
||||
|
||||
const state: AdminAccessState = {
|
||||
ready: false,
|
||||
unrestricted: true,
|
||||
pageAccess: null,
|
||||
deployAdmin: false,
|
||||
smsPanel: false,
|
||||
routeName: ''
|
||||
}
|
||||
|
||||
export function readAdminAccessState(): AdminAccessState {
|
||||
return state
|
||||
}
|
||||
|
||||
export function setAdminAccessState(next: Partial<AdminAccessState>) {
|
||||
Object.assign(state, next)
|
||||
}
|
||||
|
||||
export function setCurrentAdminRoute(name: string) {
|
||||
state.routeName = name
|
||||
}
|
||||
@@ -0,0 +1,580 @@
|
||||
import { message } from 'ant-design-vue'
|
||||
import {
|
||||
appNotificationEnabledSetting,
|
||||
automationEnabledSetting,
|
||||
bannerEnabledSetting,
|
||||
conditionalOrderEnabledSetting,
|
||||
customerSpecialPricingEnabledSetting,
|
||||
getConfigApp,
|
||||
giftCardEnabledSetting,
|
||||
goldCreditEnabledSetting,
|
||||
hasLandingSetting,
|
||||
isStoreApp,
|
||||
priceProviderSetting,
|
||||
priceTriggerEnabledSetting,
|
||||
supportChatMenuEnabledSetting,
|
||||
trackingSystemsEnabledSetting
|
||||
} from '@/utils/config'
|
||||
import {
|
||||
readAdminAccessState,
|
||||
type PageAccessFlags,
|
||||
type PageAccessMap
|
||||
} from '@/utils/adminAccessState'
|
||||
|
||||
export type { PageAccessFlags, PageAccessMap }
|
||||
|
||||
export const PAGE_DENIED_MESSAGE = 'شما به این صفحه دسترسی ندارید'
|
||||
export const EDIT_DENIED_MESSAGE = 'شما مجوز ویرایش در این صفحه را ندارید'
|
||||
export const DELETE_DENIED_MESSAGE = 'شما مجوز حذف در این صفحه را ندارید'
|
||||
|
||||
export type AccessNode = {
|
||||
key: string
|
||||
title: string
|
||||
route?: string
|
||||
aliases?: string[]
|
||||
enabled?: () => boolean
|
||||
children?: AccessNode[]
|
||||
}
|
||||
|
||||
const OPEN_ALWAYS = new Set(['login', 'notFound', '404', 'components'])
|
||||
|
||||
export const ADMIN_PAGE_TREE: AccessNode[] = [
|
||||
{
|
||||
key: 'dashboardMain',
|
||||
title: 'میز کار',
|
||||
route: 'dashboardMain',
|
||||
aliases: ['dashboard', 'main']
|
||||
},
|
||||
{
|
||||
key: 'group:customers',
|
||||
title: 'مشتریان',
|
||||
aliases: ['customers', 'customersMain'],
|
||||
children: [
|
||||
{ key: 'customerMain_limitedOrder', title: 'لیست مشتریان', route: 'customerMain_limitedOrder' },
|
||||
{ key: 'customerMain_archive', title: 'بایگانی', route: 'customerMain_archive' },
|
||||
{ key: 'customerMain_online', title: 'کاربران آنلاین', route: 'customerMain_online' },
|
||||
{
|
||||
key: 'customerMain_specialPrice',
|
||||
title: 'قیمت ویژه',
|
||||
route: 'customerMain_specialPrice',
|
||||
enabled: () => customerSpecialPricingEnabledSetting.value
|
||||
},
|
||||
{
|
||||
key: 'customerMain_goldCredit',
|
||||
title: 'اعتبار',
|
||||
route: 'customerMain_goldCredit',
|
||||
enabled: () => goldCreditEnabledSetting.value
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'group:products',
|
||||
title: 'محصولات',
|
||||
aliases: ['products', 'productsMain'],
|
||||
children: [
|
||||
{ key: 'productsMain_limitedOrder', title: 'لیست محصولات', route: 'productsMain_limitedOrder' },
|
||||
{ key: 'productsMain_archive', title: 'بایگانی', route: 'productsMain_archive' },
|
||||
{ key: 'productsMain_categories', title: 'دستهبندیها', route: 'productsMain_categories' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'ordersMain',
|
||||
title: 'سفارشات',
|
||||
route: 'ordersMain',
|
||||
aliases: ['orders']
|
||||
},
|
||||
{
|
||||
key: 'transactionsMain',
|
||||
title: 'گزارشات',
|
||||
route: 'transactionsMain',
|
||||
aliases: ['transactions']
|
||||
},
|
||||
{
|
||||
key: 'group:hesab',
|
||||
title: 'حساب کتاب',
|
||||
aliases: ['hesabKetab', 'hesabKetabMain'],
|
||||
children: [
|
||||
{ key: 'accountsMain', title: 'حسابها', route: 'accountsMain' },
|
||||
{ key: 'walletMain', title: 'کیف پول', route: 'walletMain' },
|
||||
{ key: 'physicalWithdrawMain', title: 'برداشت فیزیکی', route: 'physicalWithdrawMain' },
|
||||
{
|
||||
key: 'withdrawMain',
|
||||
title: 'برداشت و واریز',
|
||||
route: 'withdrawMain',
|
||||
aliases: ['factor']
|
||||
},
|
||||
{ key: 'pricingMain', title: 'تاریخچه قیمت', route: 'pricingMain' },
|
||||
{
|
||||
key: 'priceTriggers',
|
||||
title: 'هشدار قیمت',
|
||||
route: 'priceTriggers',
|
||||
enabled: () => priceTriggerEnabledSetting.value
|
||||
},
|
||||
{
|
||||
key: 'conditionalOrders',
|
||||
title: 'سفارش شرطی',
|
||||
route: 'conditionalOrders',
|
||||
enabled: () => conditionalOrderEnabledSetting.value
|
||||
},
|
||||
{
|
||||
key: 'colleagueOrdersMain',
|
||||
title: 'تراکنش با اپلیکیشن همکار',
|
||||
route: 'colleagueOrdersMain',
|
||||
enabled: () => priceProviderSetting.value === 'montzar'
|
||||
},
|
||||
{ key: 'accountingMain', title: 'حسابداری', route: 'accountingMain' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'supportMain',
|
||||
title: 'چت پشتیبانی',
|
||||
route: 'supportMain',
|
||||
aliases: ['support'],
|
||||
enabled: () => supportChatMenuEnabledSetting.value
|
||||
},
|
||||
{
|
||||
key: 'group:website',
|
||||
title: 'تنظیمات وبسایت',
|
||||
aliases: ['websiteSettings', 'websiteSettingsMain'],
|
||||
enabled: () => hasLandingSetting.value,
|
||||
children: [
|
||||
{ key: 'websiteLandingSite', title: 'تنظیمات کلی سایت', route: 'websiteLandingSite' },
|
||||
{ key: 'websiteLandingHeader', title: 'هدر و ناوبری', route: 'websiteLandingHeader' },
|
||||
{ key: 'websiteLandingHero', title: 'هیرو', route: 'websiteLandingHero' },
|
||||
{ key: 'websiteLandingLivePrice', title: 'لیبلهای قیمت لحظهای', route: 'websiteLandingLivePrice' },
|
||||
{ key: 'websiteLandingCalculator', title: 'عنوان ماشین حساب', route: 'websiteLandingCalculator' },
|
||||
{ key: 'websiteLandingFeature', title: 'امکانات', route: 'websiteLandingFeature' },
|
||||
{ key: 'websiteLandingSteps', title: 'مراحل شروع', route: 'websiteLandingSteps' },
|
||||
{ key: 'websiteLandingTrust', title: 'اعتماد و مجوزها', route: 'websiteLandingTrust' },
|
||||
{ key: 'websiteLandingFaq', title: 'سوالات متداول', route: 'websiteLandingFaq' },
|
||||
{ key: 'websiteLandingBottomCta', title: 'سکشن پایین صفحه', route: 'websiteLandingBottomCta' },
|
||||
{ key: 'websiteLandingFooter', title: 'فوتر', route: 'websiteLandingFooter' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'group:settings',
|
||||
title: 'پیکربندی',
|
||||
aliases: ['settings', 'settingMain'],
|
||||
children: [
|
||||
{ key: 'configuration', title: 'تنظیمات', route: 'configuration' },
|
||||
{
|
||||
key: 'captureSitesMain',
|
||||
title: 'سایتهای ریگیری',
|
||||
route: 'captureSitesMain',
|
||||
enabled: () => trackingSystemsEnabledSetting.value
|
||||
},
|
||||
{
|
||||
key: 'advancedConfiguration',
|
||||
title: 'تنظیمات پیشرفته',
|
||||
route: 'advancedConfiguration',
|
||||
enabled: () => readAdminAccessState().deployAdmin
|
||||
},
|
||||
{
|
||||
key: 'smsPanelMain',
|
||||
title: 'پنل پیامک',
|
||||
route: 'smsPanelMain',
|
||||
enabled: () => readAdminAccessState().smsPanel
|
||||
},
|
||||
{ key: 'tradingHours', title: 'ساعت کاری', route: 'tradingHours' },
|
||||
{
|
||||
key: 'banners',
|
||||
title: 'بنرها',
|
||||
route: 'banners',
|
||||
enabled: () => bannerEnabledSetting.value
|
||||
},
|
||||
{
|
||||
key: 'appNotifications',
|
||||
title: 'اعلانها',
|
||||
route: 'appNotifications',
|
||||
enabled: () => appNotificationEnabledSetting.value
|
||||
},
|
||||
{
|
||||
key: 'automations',
|
||||
title: 'اتوماسیون',
|
||||
route: 'automations',
|
||||
enabled: () => automationEnabledSetting.value
|
||||
},
|
||||
{ key: 'smsMain', title: 'پیامکها', route: 'smsMain' },
|
||||
{
|
||||
key: 'giftCardMain',
|
||||
title: 'کارت هدیه',
|
||||
route: 'giftCardMain',
|
||||
enabled: () => giftCardEnabledSetting.value
|
||||
},
|
||||
{
|
||||
key: 'discounts',
|
||||
title: 'تخفیفات',
|
||||
route: 'discounts',
|
||||
enabled: () => !isStoreApp() && getConfigApp('name') === 'goldsun'
|
||||
},
|
||||
{ key: 'usersMain', title: 'مدیران', route: 'usersMain' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
export type PageTableAction = { key: string; label: string }
|
||||
|
||||
/** عملیاتهای ستون «عملیات» هر صفحه. صفحههای بدون جدول فقط مشاهده دارند. */
|
||||
export const PAGE_TABLE_ACTIONS: Record<string, PageTableAction[]> = {
|
||||
customerMain_limitedOrder: [
|
||||
{ key: 'charge', label: 'شارژ کیف پول' },
|
||||
{ key: 'tahesab', label: 'ثبت در تهحساب' },
|
||||
{ key: 'zarin', label: 'ثبت در زرین' },
|
||||
{ key: 'pargar', label: 'ثبت در پرگار' },
|
||||
{ key: 'accountingCode', label: 'ویرایش کد حسابداری' },
|
||||
{ key: 'info', label: 'مشخصات' },
|
||||
{ key: 'edit', label: 'ویرایش' },
|
||||
{ key: 'referral', label: 'سود چندسطحی معرف' },
|
||||
{ key: 'sms', label: 'ارسال پیامک' },
|
||||
{ key: 'approve', label: 'تایید کاربر' },
|
||||
{ key: 'archive', label: 'بایگانی' },
|
||||
{ key: 'active', label: 'فعال/غیرفعال حساب' },
|
||||
{ key: 'trade', label: 'مجاز سفارش' }
|
||||
],
|
||||
customerMain_archive: [
|
||||
{ key: 'restore', label: 'خروج از بایگانی' },
|
||||
{ key: 'info', label: 'مشخصات' }
|
||||
],
|
||||
customerMain_specialPrice: [
|
||||
{ key: 'edit', label: 'ویرایش' },
|
||||
{ key: 'toggle', label: 'فعال/غیرفعال' },
|
||||
{ key: 'delete', label: 'حذف' }
|
||||
],
|
||||
customerMain_goldCredit: [
|
||||
{ key: 'detail', label: 'جزئیات' },
|
||||
{ key: 'edit', label: 'ویرایش' },
|
||||
{ key: 'approve', label: 'تأیید درخواست' },
|
||||
{ key: 'reject', label: 'رد درخواست' }
|
||||
],
|
||||
productsMain_limitedOrder: [
|
||||
{ key: 'buy', label: 'خرید' },
|
||||
{ key: 'sell', label: 'فروش' },
|
||||
{ key: 'list', label: 'نمایش در لیست' },
|
||||
{ key: 'tv', label: 'نمایش در تابلو' },
|
||||
{ key: 'edit', label: 'ویرایش' },
|
||||
{ key: 'archive', label: 'بایگانی' }
|
||||
],
|
||||
productsMain_archive: [
|
||||
{ key: 'restore', label: 'خروج از بایگانی' },
|
||||
{ key: 'info', label: 'مشخصات' }
|
||||
],
|
||||
ordersMain: [{ key: 'info', label: 'مشخصات' }],
|
||||
transactionsMain: [{ key: 'info', label: 'مشخصات' }],
|
||||
accountsMain: [
|
||||
{ key: 'toggle', label: 'فعال/غیرفعال کارت' },
|
||||
{ key: 'delete', label: 'حذف کارت' }
|
||||
],
|
||||
walletMain: [{ key: 'info', label: 'مشخصات کیف پول' }],
|
||||
physicalWithdrawMain: [
|
||||
{ key: 'amount', label: 'ویرایش مقدار' },
|
||||
{ key: 'approve', label: 'تأیید' },
|
||||
{ key: 'status', label: 'وضعیت' },
|
||||
{ key: 'factor', label: 'دریافت فاکتور' }
|
||||
],
|
||||
withdrawMain: [
|
||||
{ key: 'confirm', label: 'تأیید تراکنش' },
|
||||
{ key: 'factor', label: 'دریافت فاکتور' },
|
||||
{ key: 'status', label: 'وضعیت' },
|
||||
{ key: 'weight', label: 'تعیین وزن' },
|
||||
{ key: 'tahesab', label: 'ارسال به تهحساب' },
|
||||
{ key: 'zarin', label: 'ارسال به زرین' }
|
||||
],
|
||||
pricingMain: [{ key: 'details', label: 'جزئیات محصولات' }],
|
||||
priceTriggers: [
|
||||
{ key: 'edit', label: 'ویرایش' },
|
||||
{ key: 'toggle', label: 'فعال/غیرفعال' },
|
||||
{ key: 'delete', label: 'حذف' }
|
||||
],
|
||||
conditionalOrders: [
|
||||
{ key: 'edit', label: 'ویرایش' },
|
||||
{ key: 'toggle', label: 'فعال/غیرفعال' },
|
||||
{ key: 'delete', label: 'حذف' }
|
||||
],
|
||||
accountingMain: [
|
||||
{ key: 'doc', label: 'اطلاعات سند' },
|
||||
{ key: 'register', label: 'ثبت مشتری' },
|
||||
{ key: 'send', label: 'ارسال سند' }
|
||||
],
|
||||
appNotifications: [
|
||||
{ key: 'recipients', label: 'گیرندگان' },
|
||||
{ key: 'readers', label: 'بازدیدکنندگان' },
|
||||
{ key: 'edit', label: 'ویرایش' },
|
||||
{ key: 'toggle', label: 'فعال/غیرفعال' },
|
||||
{ key: 'delete', label: 'حذف' }
|
||||
],
|
||||
automations: [
|
||||
{ key: 'preview', label: 'اجرای آزمایشی' },
|
||||
{ key: 'log', label: 'لاگ ارسال' },
|
||||
{ key: 'edit', label: 'ویرایش' },
|
||||
{ key: 'toggle', label: 'فعال/غیرفعال' },
|
||||
{ key: 'delete', label: 'حذف' }
|
||||
],
|
||||
giftCardMain: [
|
||||
{ key: 'info', label: 'مشخصات' },
|
||||
{ key: 'codes', label: 'کدها' },
|
||||
{ key: 'edit', label: 'ویرایش' },
|
||||
{ key: 'delete', label: 'حذف' }
|
||||
],
|
||||
discounts: [{ key: 'edit', label: 'ویرایش' }],
|
||||
usersMain: [
|
||||
{ key: 'info', label: 'مشخصات' },
|
||||
{ key: 'edit', label: 'ویرایش' },
|
||||
{ key: 'delete', label: 'حذف مدیر' }
|
||||
]
|
||||
}
|
||||
|
||||
export function leafRouteKeys(): string[] {
|
||||
const keys: string[] = []
|
||||
const walk = (nodes: AccessNode[]) => {
|
||||
for (const node of nodes) {
|
||||
if (node.route) keys.push(node.route)
|
||||
if (node.children?.length) walk(node.children)
|
||||
}
|
||||
}
|
||||
walk(ADMIN_PAGE_TREE)
|
||||
return keys
|
||||
}
|
||||
|
||||
export function allGrantedMap(): PageAccessMap {
|
||||
const map: PageAccessMap = {}
|
||||
for (const key of leafRouteKeys()) {
|
||||
const actions = Object.fromEntries((PAGE_TABLE_ACTIONS[key] ?? []).map((item) => [item.key, true]))
|
||||
map[key] = {
|
||||
view: true,
|
||||
edit: Boolean(actions.edit),
|
||||
delete: Boolean(actions.delete),
|
||||
actions
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
/** مقدار یک عملیات؛ رکوردهای قدیمی بدون actions، ویرایش/حذف را از همان فیلد و بقیه را از مشاهده میگیرند. */
|
||||
export function actionEnabled(row: PageAccessFlags | undefined, actionKey: string): boolean {
|
||||
if (!row?.view) return false
|
||||
if (row.actions && Object.prototype.hasOwnProperty.call(row.actions, actionKey)) {
|
||||
return Boolean(row.actions[actionKey])
|
||||
}
|
||||
if (actionKey === 'edit') return Boolean(row.edit)
|
||||
if (actionKey === 'delete') return Boolean(row.delete)
|
||||
return true
|
||||
}
|
||||
|
||||
export function parsePageAccess(value: unknown): PageAccessMap | null {
|
||||
if (value == null || value === '') return null
|
||||
let parsed = value
|
||||
if (typeof parsed === 'string') {
|
||||
try {
|
||||
parsed = JSON.parse(parsed)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
if (typeof parsed !== 'object' || Array.isArray(parsed)) return null
|
||||
return parsed as PageAccessMap
|
||||
}
|
||||
|
||||
export function hasAnyView(map?: PageAccessMap | null): boolean {
|
||||
if (!map) return false
|
||||
return Object.values(map).some((row) => row?.view)
|
||||
}
|
||||
|
||||
function isNodeEnabled(node: AccessNode): boolean {
|
||||
if (node.enabled && !node.enabled()) return false
|
||||
return true
|
||||
}
|
||||
|
||||
function findNode(name: string, nodes: AccessNode[] = ADMIN_PAGE_TREE): AccessNode | null {
|
||||
for (const node of nodes) {
|
||||
if (node.key === name || node.route === name || node.aliases?.includes(name)) return node
|
||||
if (node.children?.length) {
|
||||
const found = findNode(name, node.children)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function flagOn(route: string, flag: 'view' | 'edit' | 'delete'): boolean {
|
||||
const row = readAdminAccessState().pageAccess?.[route]
|
||||
if (flag === 'view') return Boolean(row?.view)
|
||||
return actionEnabled(row, flag)
|
||||
}
|
||||
|
||||
function nodeOpens(node: AccessNode): boolean {
|
||||
if (!isNodeEnabled(node)) return false
|
||||
if (node.route) return flagOn(node.route, 'view')
|
||||
return (node.children ?? []).some(nodeOpens)
|
||||
}
|
||||
|
||||
export function canOpenRoute(routeName: unknown): boolean {
|
||||
const name = String(routeName ?? '')
|
||||
if (!name || OPEN_ALWAYS.has(name)) return true
|
||||
const access = readAdminAccessState()
|
||||
if (!access.ready || access.unrestricted) return true
|
||||
const node = findNode(name)
|
||||
if (!node) return false
|
||||
return nodeOpens(node)
|
||||
}
|
||||
|
||||
export function firstAllowedRouteName(): string | null {
|
||||
const access = readAdminAccessState()
|
||||
if (!access.ready || access.unrestricted) return 'dashboardMain'
|
||||
return firstOpen(ADMIN_PAGE_TREE)
|
||||
}
|
||||
|
||||
export function firstAllowedInBranch(routeName: string): string | null {
|
||||
const node = findNode(routeName)
|
||||
if (!node) return null
|
||||
return firstOpen([node])
|
||||
}
|
||||
|
||||
function firstOpen(nodes: AccessNode[]): string | null {
|
||||
for (const node of nodes) {
|
||||
if (!isNodeEnabled(node)) continue
|
||||
if (node.route && flagOn(node.route, 'view')) return node.route
|
||||
if (node.children?.length) {
|
||||
const found = firstOpen(node.children)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** دسترسی ویرایش/حذف همان صفحه. routeName میتواند نام فعلی یا زنجیرهٔ matched باشد. */
|
||||
export function allowsPageFlag(
|
||||
pageAccess: PageAccessMap | null,
|
||||
fullAccess: boolean,
|
||||
routeName: unknown,
|
||||
flag: keyof PageAccessFlags
|
||||
): boolean {
|
||||
if (fullAccess) return true
|
||||
if (!pageAccess) return false
|
||||
const names = Array.isArray(routeName) ? routeName : [routeName]
|
||||
for (let i = names.length - 1; i >= 0; i--) {
|
||||
const node = findNode(String(names[i] ?? ''))
|
||||
if (!node?.route) continue
|
||||
const row = pageAccess[node.route]
|
||||
if (flag === 'view') return Boolean(row?.view)
|
||||
return actionEnabled(row, flag)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function allowsPageAction(
|
||||
pageAccess: PageAccessMap | null,
|
||||
fullAccess: boolean,
|
||||
routeName: unknown,
|
||||
actionKey: string
|
||||
): boolean {
|
||||
if (fullAccess) return true
|
||||
if (!pageAccess) return false
|
||||
const names = Array.isArray(routeName) ? routeName : [routeName]
|
||||
for (let i = names.length - 1; i >= 0; i--) {
|
||||
const node = findNode(String(names[i] ?? ''))
|
||||
if (!node?.route) continue
|
||||
return actionEnabled(pageAccess[node.route], actionKey)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function notifyPageActionDenied(flag: string) {
|
||||
const content =
|
||||
flag === 'delete'
|
||||
? DELETE_DENIED_MESSAGE
|
||||
: flag === 'edit'
|
||||
? EDIT_DENIED_MESSAGE
|
||||
: 'شما به این عملیات دسترسی ندارید'
|
||||
message.warning({
|
||||
content,
|
||||
key: 'admin-page-denied'
|
||||
})
|
||||
}
|
||||
|
||||
export function notifyPageDenied() {
|
||||
message.warning({ content: PAGE_DENIED_MESSAGE, key: 'admin-page-denied' })
|
||||
}
|
||||
|
||||
export function confirmMenuAccess(routeName: unknown): boolean {
|
||||
if (canOpenRoute(routeName)) return true
|
||||
notifyPageDenied()
|
||||
return false
|
||||
}
|
||||
|
||||
function adminRelativePath(url: string): string | null {
|
||||
const clean = url.split('?')[0].replace(/^https?:\/\/[^/]+/i, '')
|
||||
const idx = clean.indexOf('/admin/')
|
||||
if (idx === -1) return null
|
||||
return clean.slice(idx + '/admin/'.length).replace(/^\/+|\/+$/g, '').toLowerCase()
|
||||
}
|
||||
|
||||
const READ_POST_EXACT = new Set([
|
||||
'getbalance',
|
||||
'prices',
|
||||
'prices/chart',
|
||||
'gateway',
|
||||
'finance',
|
||||
'check_card',
|
||||
'dashboard',
|
||||
'dashboard/finance',
|
||||
'dashboard/customer/all_sell',
|
||||
'dashboard/customer/single/all_sell',
|
||||
'dashboard/customer/flow',
|
||||
'dashboard/customer/order-peak-hours',
|
||||
'dashboard/profit-conversions',
|
||||
'dashboard/montzar-reconcile'
|
||||
])
|
||||
|
||||
function isReadPost(path: string): boolean {
|
||||
if (path.includes('/table') || path.endsWith('table')) return true
|
||||
if (path.includes('preview')) return true
|
||||
if (path.includes('get_customer_list') || path.includes('products-by-categories')) return true
|
||||
if (path.includes('mande-by-codes')) return true
|
||||
if (READ_POST_EXACT.has(path)) return true
|
||||
if (/^customers\/\d+\/(referrals|funds|piggy-funds)$/.test(path)) return true
|
||||
if (/^customers\/\d+\/assets\/\d+\/(piggy-funds|transactions)$/.test(path)) return true
|
||||
if (/^customers\/\d+\/piggy-funds\/\d+\/transactions$/.test(path)) return true
|
||||
if (/^customers\/\d+\/gold_credit\/installments$/.test(path)) return true
|
||||
if (/^chat\/(focus|unfocus|ensure-webhook)$/.test(path)) return true
|
||||
return false
|
||||
}
|
||||
|
||||
function currentRouteAllows(flag: keyof PageAccessFlags): boolean {
|
||||
const access = readAdminAccessState()
|
||||
if (!access.ready || access.unrestricted) return true
|
||||
const name = access.routeName
|
||||
if (!name || OPEN_ALWAYS.has(name)) return true
|
||||
const node = findNode(name)
|
||||
const route = node?.route
|
||||
if (!route) return false
|
||||
if (node?.enabled && !node.enabled()) return false
|
||||
return flagOn(route, flag)
|
||||
}
|
||||
|
||||
/** اگر عملیات مجاز نیست، متن پیام را برمیگرداند. */
|
||||
export function mutationBlockMessage(method: string, url?: string): string | null {
|
||||
const access = readAdminAccessState()
|
||||
if (!access.ready || access.unrestricted) return null
|
||||
const path = adminRelativePath(String(url ?? ''))
|
||||
if (path == null) return null
|
||||
if (path === 'me' || path === 'me/update') return null
|
||||
|
||||
const verb = method.toUpperCase()
|
||||
if (verb === 'GET' || verb === 'HEAD' || verb === 'OPTIONS') {
|
||||
if (/\/active(\/|$)/.test(path)) {
|
||||
return currentRouteAllows('edit') ? null : EDIT_DENIED_MESSAGE
|
||||
}
|
||||
return null
|
||||
}
|
||||
if (verb === 'DELETE') {
|
||||
return currentRouteAllows('delete') ? null : DELETE_DENIED_MESSAGE
|
||||
}
|
||||
if (verb === 'PUT' || verb === 'PATCH') {
|
||||
return currentRouteAllows('edit') ? null : EDIT_DENIED_MESSAGE
|
||||
}
|
||||
if (verb === 'POST') {
|
||||
if (isReadPost(path)) return null
|
||||
return currentRouteAllows('edit') ? null : EDIT_DENIED_MESSAGE
|
||||
}
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user