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) =>
|
export const createGiftCard = async (obj: GiftcardInput) =>
|
||||||
await useAxios.post<Giftcard>(`/admin/giftcard/generate`, obj)
|
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 { defineProps } from 'vue'
|
||||||
import type { RouteRecordRaw } from 'vue-router'
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { confirmMenuAccess } from '@/utils/adminPageAccess'
|
||||||
|
|
||||||
interface Prop {
|
interface Prop {
|
||||||
menu: RouteRecordRaw[]
|
menu: RouteRecordRaw[]
|
||||||
@@ -30,7 +31,7 @@ interface Prop {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
function handleClickMenu(item: RouteRecordRaw) {
|
function handleClickMenu(item: RouteRecordRaw) {
|
||||||
//debugger
|
if (!confirmMenuAccess(item.name)) return
|
||||||
router.push({ name: item.name })
|
router.push({ name: item.name })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<AMenu
|
<AMenu
|
||||||
v-model:selectedKeys="selectedKeys"
|
:selectedKeys="selectedKeys"
|
||||||
:style="{ width: props.width }"
|
:style="{ width: props.width }"
|
||||||
:mode="props.mode"
|
:mode="props.mode"
|
||||||
:theme="resolvedTheme"
|
:theme="resolvedTheme"
|
||||||
@@ -49,10 +49,13 @@ const props = withDefaults(defineProps<Prop>(), {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const resolvedTheme = computed(() => props.theme ?? menuTheme.value)
|
const resolvedTheme = computed(() => props.theme ?? menuTheme.value)
|
||||||
onMounted(() => {
|
watch(
|
||||||
selectedKeys.value = [router.currentRoute.value.name as string]
|
() => router.currentRoute.value.name,
|
||||||
//emits('onChange', '1')
|
(name) => {
|
||||||
})
|
selectedKeys.value = name ? [String(name)] : []
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<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">
|
<script setup lang="ts">
|
||||||
import { menuIconSize } from '@root/config'
|
import { menuIconSize } from '@root/config'
|
||||||
import type { RouteRecordRaw } from 'vue-router'
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
|
import { confirmMenuAccess } from '@/utils/adminPageAccess'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -25,6 +26,7 @@ interface Props {
|
|||||||
const props = withDefaults(defineProps<Props>(), {})
|
const props = withDefaults(defineProps<Props>(), {})
|
||||||
|
|
||||||
async function handleClickMenuItem(item: RouteRecordRaw) {
|
async function handleClickMenuItem(item: RouteRecordRaw) {
|
||||||
|
if (!confirmMenuAccess(item.name)) return
|
||||||
router.push({ name: item.name })
|
router.push({ name: item.name })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<AMenu
|
<AMenu
|
||||||
:mode="layoutStore.sideMenu.mode"
|
:mode="layoutStore.sideMenu.mode"
|
||||||
:theme="menuTheme"
|
:theme="menuTheme"
|
||||||
v-model:selectedKeys="selectedKeys"
|
:selectedKeys="selectedKeys"
|
||||||
>
|
>
|
||||||
<template v-for="item in menu">
|
<template v-for="item in menu">
|
||||||
<BasicSubMenu
|
<BasicSubMenu
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import type { ComputedRef } from 'vue'
|
import type { ComputedRef } from 'vue'
|
||||||
import { sortBy } from 'lodash'
|
import { sortBy } from 'lodash'
|
||||||
import { usePrefix } from '@core/composable/usePrefix'
|
import { usePrefix } from '@core/composable/usePrefix'
|
||||||
@@ -45,29 +45,28 @@ const selectedKeys = ref<(RouteRecordName | undefined)[]>([])
|
|||||||
const routerStore = useRouterStore()
|
const routerStore = useRouterStore()
|
||||||
const { menuTheme } = useAdminTheme()
|
const { menuTheme } = useAdminTheme()
|
||||||
|
|
||||||
const path: (RouteRecordName | undefined)[] = []
|
function syncSelectedKeys() {
|
||||||
|
const path: (RouteRecordName | undefined)[] = []
|
||||||
function walkInRoutes(routes: RouteRecordRaw[]) {
|
function walkInRoutes(routes: RouteRecordRaw[]): boolean {
|
||||||
for (const route of routes) {
|
for (const route of routes) {
|
||||||
if (route.name == currentRoute.name) {
|
if (route.name == currentRoute.name) {
|
||||||
path.push(route.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 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)
|
watch(() => currentRoute.name, syncSelectedKeys, { immediate: true })
|
||||||
selectedKeys.value = path
|
|
||||||
|
|
||||||
function isSupportMenuRoute(item: RouteRecordRaw): boolean {
|
function isSupportMenuRoute(item: RouteRecordRaw): boolean {
|
||||||
return item.name === 'support' || item.meta?.permissionsKey === 'support'
|
return item.name === 'support' || item.meta?.permissionsKey === 'support'
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import {
|
|||||||
showServerUnavailableOverlay,
|
showServerUnavailableOverlay,
|
||||||
} from '@/utils/serverUnavailable'
|
} from '@/utils/serverUnavailable'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
|
import { mutationBlockMessage } from '@/utils/adminPageAccess'
|
||||||
|
import { readAdminAccessState } from '@/utils/adminAccessState'
|
||||||
//import { isToken } from '@/utils/is'
|
//import { isToken } from '@/utils/is'
|
||||||
//import router from '@/router'
|
//import router from '@/router'
|
||||||
//import { getToken, removeToken } from '@/utils/useUtility'
|
//import { getToken, removeToken } from '@/utils/useUtility'
|
||||||
@@ -58,6 +60,18 @@ _axios.interceptors.request.use(
|
|||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
config.headers['Authorization'] = 'Bearer ' + token
|
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
|
return config
|
||||||
},
|
},
|
||||||
@@ -77,6 +91,15 @@ _axios.interceptors.response.use(
|
|||||||
return response.data
|
return response.data
|
||||||
},
|
},
|
||||||
async function (error) {
|
async function (error) {
|
||||||
|
const deniedText = String(error?.message || '')
|
||||||
|
if (
|
||||||
|
error?.pageAccessDenied ||
|
||||||
|
deniedText === 'شما به این صفحه دسترسی ندارید' ||
|
||||||
|
deniedText === 'شما مجوز ویرایش در این صفحه را ندارید' ||
|
||||||
|
deniedText === 'شما مجوز حذف در این صفحه را ندارید'
|
||||||
|
) {
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
//console.log('this is axios error', error)
|
//console.log('this is axios error', error)
|
||||||
/* if (error.response.data && error.response.data.code) {
|
/* if (error.response.data && error.response.data.code) {
|
||||||
@@ -109,7 +132,12 @@ _axios.interceptors.response.use(
|
|||||||
await router.push({ name: 'login' })
|
await router.push({ name: 'login' })
|
||||||
} else if (error.response && error.response.status == 403) {
|
} else if (error.response && error.response.status == 403) {
|
||||||
if (!error.config?.meta?.silent) {
|
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) {
|
} else if (error.response && error.response.status == 422) {
|
||||||
if (!error.config?.meta?.silent) {
|
if (!error.config?.meta?.silent) {
|
||||||
|
|||||||
+30
-2
@@ -14,6 +14,14 @@ import { setupRoutes } from '@/router'
|
|||||||
import { useUserStore } from '@/store/userStore'
|
import { useUserStore } from '@/store/userStore'
|
||||||
import { setupPinia } from '@core/plugins/pinia'
|
import { setupPinia } from '@core/plugins/pinia'
|
||||||
import { initStoreModeFromSettings } from '@/utils/config'
|
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 init = async () => {
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
@@ -57,9 +65,29 @@ const init = async () => {
|
|||||||
const token = getToken()
|
const token = getToken()
|
||||||
if (token) {
|
if (token) {
|
||||||
if (to.name == 'login') {
|
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 {
|
} else {
|
||||||
if (['login', '404'].includes(to.name as string)) {
|
if (['login', '404'].includes(to.name as string)) {
|
||||||
return next()
|
return next()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Const } from '@/model/const'
|
import type { Const } from '@/model/const'
|
||||||
|
import type { PageAccessMap } from '@/utils/adminPageAccess'
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
active?: boolean
|
active?: boolean
|
||||||
@@ -19,6 +20,8 @@ export interface User {
|
|||||||
settings?: Const[]
|
settings?: Const[]
|
||||||
sms_billing_enabled?: boolean
|
sms_billing_enabled?: boolean
|
||||||
sms_panel_access?: boolean
|
sms_panel_access?: boolean
|
||||||
|
/** null = دسترسی کامل */
|
||||||
|
page_access?: PageAccessMap | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AddId {
|
export interface AddId {
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ import OtpInput from 'vue3-otp-input'
|
|||||||
import { useUserStore } from '@/store/userStore'
|
import { useUserStore } from '@/store/userStore'
|
||||||
import { setupRoutes } from '@/router'
|
import { setupRoutes } from '@/router'
|
||||||
import { getConfigApp, initStoreModeFromSettings } from '@/utils/config'
|
import { getConfigApp, initStoreModeFromSettings } from '@/utils/config'
|
||||||
|
import { firstAllowedRouteName } from '@/utils/adminPageAccess'
|
||||||
|
|
||||||
//define emit
|
//define emit
|
||||||
const emits = defineEmits(['enter'])
|
const emits = defineEmits(['enter'])
|
||||||
@@ -175,7 +176,12 @@ const onUserPassFormFinish = async () => {
|
|||||||
}*/
|
}*/
|
||||||
//router.removeRoute('usersMain')
|
//router.removeRoute('usersMain')
|
||||||
//router.removeRoute('users')
|
//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) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
/* if (e?.response.status === 422) {
|
/* if (e?.response.status === 422) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<MobileSheetHeader :title="sheetTitle" :show-title="isMobile">
|
<MobileSheetHeader :title="sheetTitle" :show-title="isMobile">
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<MobileIconButton
|
<MobileIconButton
|
||||||
v-if="!row.pargar_ref && !row.customer?.pargar_account_id"
|
v-if="can('register') && !row.pargar_ref && !row.customer?.pargar_account_id"
|
||||||
plain
|
plain
|
||||||
icon="vuesax-linear:profile-add"
|
icon="vuesax-linear:profile-add"
|
||||||
color="#7C3AED"
|
color="#7C3AED"
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
@click="$emit('register-customer', row)"
|
@click="$emit('register-customer', row)"
|
||||||
/>
|
/>
|
||||||
<MobileIconButton
|
<MobileIconButton
|
||||||
v-if="canRetry(row)"
|
v-if="can('send') && canRetry(row)"
|
||||||
plain
|
plain
|
||||||
icon="vuesax-linear:refresh-circle"
|
icon="vuesax-linear:refresh-circle"
|
||||||
color="#CA8A04"
|
color="#CA8A04"
|
||||||
@@ -88,6 +88,9 @@ import {
|
|||||||
} from '@/pages/menuPages/accounting/utils/pargarDisplay'
|
} from '@/pages/menuPages/accounting/utils/pargarDisplay'
|
||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import { toJalali } from '@core/utils/useDataTime'
|
import { toJalali } from '@core/utils/useDataTime'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean
|
visible: boolean
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<MobileSheetHeader :title="sheetTitle" :show-title="isMobile">
|
<MobileSheetHeader :title="sheetTitle" :show-title="isMobile">
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<MobileIconButton
|
<MobileIconButton
|
||||||
v-if="!row.tahesab_id && !row.customer?.moshtari_code"
|
v-if="can('register') && !row.tahesab_id && !row.customer?.moshtari_code"
|
||||||
plain
|
plain
|
||||||
icon="vuesax-linear:profile-add"
|
icon="vuesax-linear:profile-add"
|
||||||
color="#7C3AED"
|
color="#7C3AED"
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
@click="$emit('register-customer', row)"
|
@click="$emit('register-customer', row)"
|
||||||
/>
|
/>
|
||||||
<MobileIconButton
|
<MobileIconButton
|
||||||
v-if="canRetry(row)"
|
v-if="can('send') && canRetry(row)"
|
||||||
plain
|
plain
|
||||||
icon="vuesax-linear:refresh-circle"
|
icon="vuesax-linear:refresh-circle"
|
||||||
color="#CA8A04"
|
color="#CA8A04"
|
||||||
@@ -60,6 +60,9 @@ import {
|
|||||||
import { canRetryTahesab, tahesabStatusLabel } from '@/pages/menuPages/accounting/utils/tahesabStatus'
|
import { canRetryTahesab, tahesabStatusLabel } from '@/pages/menuPages/accounting/utils/tahesabStatus'
|
||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import { toJalali } from '@core/utils/useDataTime'
|
import { toJalali } from '@core/utils/useDataTime'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean
|
visible: boolean
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<MobileSheetHeader :title="sheetTitle" :show-title="isMobile">
|
<MobileSheetHeader :title="sheetTitle" :show-title="isMobile">
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<MobileIconButton
|
<MobileIconButton
|
||||||
v-if="!row.zarin_id && !row.customer?.moshtari_code"
|
v-if="can('register') && !row.zarin_id && !row.customer?.moshtari_code"
|
||||||
plain
|
plain
|
||||||
icon="vuesax-linear:profile-add"
|
icon="vuesax-linear:profile-add"
|
||||||
color="#7C3AED"
|
color="#7C3AED"
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
@click="$emit('register-customer', row)"
|
@click="$emit('register-customer', row)"
|
||||||
/>
|
/>
|
||||||
<MobileIconButton
|
<MobileIconButton
|
||||||
v-if="canRetry(row)"
|
v-if="can('send') && canRetry(row)"
|
||||||
plain
|
plain
|
||||||
icon="vuesax-linear:refresh-circle"
|
icon="vuesax-linear:refresh-circle"
|
||||||
color="#CA8A04"
|
color="#CA8A04"
|
||||||
@@ -60,6 +60,9 @@ import {
|
|||||||
import { canRetryZarin, zarinStatusLabel } from '@/pages/menuPages/accounting/utils/zarinStatus'
|
import { canRetryZarin, zarinStatusLabel } from '@/pages/menuPages/accounting/utils/zarinStatus'
|
||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import { toJalali } from '@core/utils/useDataTime'
|
import { toJalali } from '@core/utils/useDataTime'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean
|
visible: boolean
|
||||||
|
|||||||
@@ -7,14 +7,14 @@
|
|||||||
<div class="flex justify-center gap-2">
|
<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>
|
<template #title><span>اطلاعات سند</span></template>
|
||||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|
||||||
<!-- دکمه ۲: ثبت مشتری در پرگار (فقط اگر سند هنوز ثبت نشده و مشتری کد ندارد) -->
|
<!-- دکمه ۲: ثبت مشتری در پرگار (فقط اگر سند هنوز ثبت نشده و مشتری کد ندارد) -->
|
||||||
<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"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="openRegister(record)"
|
@click="openRegister(record)"
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
<!-- دکمه ۳: ارسال به پرگار (فقط اگر هنوز در پرگار ثبت نشده) -->
|
<!-- دکمه ۳: ارسال به پرگار (فقط اگر هنوز در پرگار ثبت نشده) -->
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
v-if="canRetryPargar(record)"
|
v-if="can('send') && canRetryPargar(record)"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="openSend(record)"
|
@click="openSend(record)"
|
||||||
@@ -138,6 +138,9 @@ import {
|
|||||||
} from '@/pages/menuPages/accounting/utils/pargarDisplay'
|
} from '@/pages/menuPages/accounting/utils/pargarDisplay'
|
||||||
import { toJalali } from '@core/utils/useDataTime'
|
import { toJalali } from '@core/utils/useDataTime'
|
||||||
import { accountingPargarDocsIntro } from '@/utils/adminHelpCopy'
|
import { accountingPargarDocsIntro } from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const [submit, reload] = useBaseTable()
|
const [submit, reload] = useBaseTable()
|
||||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||||
|
|||||||
@@ -79,12 +79,12 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'action'">
|
<template v-else-if="column.key === 'action'">
|
||||||
<div class="flex justify-center gap-2">
|
<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>
|
<template #title><span>جزئیات</span></template>
|
||||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<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"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="openRegister(record)"
|
@click="openRegister(record)"
|
||||||
@@ -93,6 +93,7 @@
|
|||||||
<Icon icon="vuesax-linear:profile-add" color="#7C3AED" />
|
<Icon icon="vuesax-linear:profile-add" color="#7C3AED" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-button
|
<a-button
|
||||||
|
v-if="can('send')"
|
||||||
type="link"
|
type="link"
|
||||||
size="small"
|
size="small"
|
||||||
:loading="syncingRowKey === failedRowKey(record)"
|
:loading="syncingRowKey === failedRowKey(record)"
|
||||||
@@ -111,13 +112,13 @@
|
|||||||
<BaseTable :key="docsView" v-bind="tblProps" @register="submit" ref="tbl">
|
<BaseTable :key="docsView" v-bind="tblProps" @register="submit" ref="tbl">
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<div class="flex justify-center gap-2">
|
<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>
|
<template #title><span>اطلاعات سند</span></template>
|
||||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|
||||||
<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"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="openRegister(record)"
|
@click="openRegister(record)"
|
||||||
@@ -127,7 +128,7 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
v-if="!record.tahesab_id"
|
v-if="can('send') && !record.tahesab_id"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="openSend(record)"
|
@click="openSend(record)"
|
||||||
@@ -221,6 +222,9 @@ import {
|
|||||||
import { toJalali } from '@core/utils/useDataTime'
|
import { toJalali } from '@core/utils/useDataTime'
|
||||||
import { accountingTahesabDocsIntro } from '@/utils/adminHelpCopy'
|
import { accountingTahesabDocsIntro } from '@/utils/adminHelpCopy'
|
||||||
import { useTahesabDocSyncStore } from '@/store/tahesabDocSyncStore'
|
import { useTahesabDocSyncStore } from '@/store/tahesabDocSyncStore'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const docSyncStore = useTahesabDocSyncStore()
|
const docSyncStore = useTahesabDocSyncStore()
|
||||||
|
|
||||||
|
|||||||
@@ -76,12 +76,12 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'action'">
|
<template v-else-if="column.key === 'action'">
|
||||||
<div class="flex justify-center gap-2">
|
<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>
|
<template #title><span>جزئیات</span></template>
|
||||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<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"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="openRegister(record)"
|
@click="openRegister(record)"
|
||||||
@@ -90,6 +90,7 @@
|
|||||||
<Icon icon="vuesax-linear:profile-add" color="#7C3AED" />
|
<Icon icon="vuesax-linear:profile-add" color="#7C3AED" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-button
|
<a-button
|
||||||
|
v-if="can('send')"
|
||||||
type="link"
|
type="link"
|
||||||
size="small"
|
size="small"
|
||||||
:loading="syncingRowKey === failedRowKey(record)"
|
:loading="syncingRowKey === failedRowKey(record)"
|
||||||
@@ -108,13 +109,13 @@
|
|||||||
<BaseTable :key="docsView" v-bind="tblProps" @register="submit" ref="tbl">
|
<BaseTable :key="docsView" v-bind="tblProps" @register="submit" ref="tbl">
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<div class="flex justify-center gap-2">
|
<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>
|
<template #title><span>اطلاعات سند</span></template>
|
||||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|
||||||
<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"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="openRegister(record)"
|
@click="openRegister(record)"
|
||||||
@@ -124,7 +125,7 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
v-if="!record.zarin_id"
|
v-if="can('send') && !record.zarin_id"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="openSend(record)"
|
@click="openSend(record)"
|
||||||
@@ -216,6 +217,9 @@ import {
|
|||||||
} from '@/pages/menuPages/accounting/utils/accountingDocAmounts'
|
} from '@/pages/menuPages/accounting/utils/accountingDocAmounts'
|
||||||
import { toJalali } from '@core/utils/useDataTime'
|
import { toJalali } from '@core/utils/useDataTime'
|
||||||
import { accountingZarinDocsIntro } from '@/utils/adminHelpCopy'
|
import { accountingZarinDocsIntro } from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
customerId?: number
|
customerId?: number
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
|
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<div class="flex justify-center gap-2">
|
<div class="flex justify-center gap-2">
|
||||||
<a-tooltip placement="bottom">
|
<a-tooltip v-if="can('recipients')" placement="bottom">
|
||||||
<template #title>گیرندگان</template>
|
<template #title>گیرندگان</template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:profile-2user"
|
icon="vuesax-linear:profile-2user"
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
@click="emits('recipients', record)"
|
@click="emits('recipients', record)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip placement="bottom">
|
<a-tooltip v-if="can('readers')" placement="bottom">
|
||||||
<template #title>بازدیدکنندگان</template>
|
<template #title>بازدیدکنندگان</template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:people"
|
icon="vuesax-linear:people"
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
@click="emits('readers', record)"
|
@click="emits('readers', record)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip v-if="!isSystemRecord(record)" placement="bottom">
|
<a-tooltip v-if="can('edit') && !isSystemRecord(record)" placement="bottom">
|
||||||
<template #title>ویرایش</template>
|
<template #title>ویرایش</template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:edit"
|
icon="vuesax-linear:edit"
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
@click="emits('edit', record)"
|
@click="emits('edit', record)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip placement="bottom">
|
<a-tooltip v-if="can('toggle')" placement="bottom">
|
||||||
<template #title>{{ record.is_active ? 'غیرفعال کردن' : 'فعال کردن' }}</template>
|
<template #title>{{ record.is_active ? 'غیرفعال کردن' : 'فعال کردن' }}</template>
|
||||||
<Icon
|
<Icon
|
||||||
:icon="record.is_active ? 'vuesax-linear:close-circle' : 'vuesax-linear:tick-circle'"
|
:icon="record.is_active ? 'vuesax-linear:close-circle' : 'vuesax-linear:tick-circle'"
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
@click="emits('toggle', record)"
|
@click="emits('toggle', record)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip placement="bottom">
|
<a-tooltip v-if="can('delete')" placement="bottom">
|
||||||
<template #title>حذف</template>
|
<template #title>حذف</template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:trash"
|
icon="vuesax-linear:trash"
|
||||||
@@ -91,6 +91,9 @@ import {
|
|||||||
APP_NOTIFICATION_SOURCE_TABS,
|
APP_NOTIFICATION_SOURCE_TABS,
|
||||||
type AppNotificationSourceFilter,
|
type AppNotificationSourceFilter,
|
||||||
} from '@/model/appNotifications'
|
} from '@/model/appNotifications'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const [submit, reload] = useBaseTable()
|
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="truncate font-bold text-slate-800 dark:text-slate-100">{{ item.name }}</div>
|
||||||
<div class="text-xs text-gray-500">{{ item.code }}</div>
|
<div class="text-xs text-gray-500">{{ item.code }}</div>
|
||||||
</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>
|
||||||
<div class="line-clamp-2 text-sm text-slate-600 dark:text-slate-300">{{ item.body }}</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">
|
<div class="flex items-center justify-between text-xs text-gray-500">
|
||||||
<span>ارسال {{ Number(item.sent_count ?? 0) }} · ناموفق {{ Number(item.failed_count ?? 0) }}</span>
|
<span>ارسال {{ Number(item.sent_count ?? 0) }} · ناموفق {{ Number(item.failed_count ?? 0) }}</span>
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<Icon
|
<Icon
|
||||||
|
v-if="can('edit')"
|
||||||
icon="vuesax-linear:edit"
|
icon="vuesax-linear:edit"
|
||||||
color="#4572E9"
|
color="#4572E9"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="emits('edit', item)"
|
@click="emits('edit', item)"
|
||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
|
v-if="can('delete')"
|
||||||
icon="vuesax-linear:trash"
|
icon="vuesax-linear:trash"
|
||||||
color="#C53030"
|
color="#C53030"
|
||||||
class="cursor-pointer"
|
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">
|
<BaseTable v-else class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
|
||||||
<template #is_active="{ record }">
|
<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>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<div class="flex justify-center gap-2">
|
<div class="flex justify-center gap-2">
|
||||||
<a-tooltip placement="bottom">
|
<a-tooltip v-if="can('preview')" placement="bottom">
|
||||||
<template #title>اجرای آزمایشی</template>
|
<template #title>اجرای آزمایشی</template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:play"
|
icon="vuesax-linear:play"
|
||||||
@@ -59,7 +61,7 @@
|
|||||||
@click="emits('runNow', record)"
|
@click="emits('runNow', record)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip placement="bottom">
|
<a-tooltip v-if="can('log')" placement="bottom">
|
||||||
<template #title>لاگ ارسال</template>
|
<template #title>لاگ ارسال</template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:document-text"
|
icon="vuesax-linear:document-text"
|
||||||
@@ -68,7 +70,7 @@
|
|||||||
@click="emits('sends', record)"
|
@click="emits('sends', record)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip placement="bottom">
|
<a-tooltip v-if="can('edit')" placement="bottom">
|
||||||
<template #title>ویرایش</template>
|
<template #title>ویرایش</template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:edit"
|
icon="vuesax-linear:edit"
|
||||||
@@ -77,7 +79,7 @@
|
|||||||
@click="emits('edit', record)"
|
@click="emits('edit', record)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip placement="bottom">
|
<a-tooltip v-if="can('delete')" placement="bottom">
|
||||||
<template #title>حذف</template>
|
<template #title>حذف</template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:trash"
|
icon="vuesax-linear:trash"
|
||||||
@@ -104,6 +106,9 @@ import { getAutomationsTable } from '@/api/automations'
|
|||||||
import type { Automation } from '@/model/automations'
|
import type { Automation } from '@/model/automations'
|
||||||
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
|
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
|
||||||
import { automationsPageIntro } from '@/utils/adminHelpCopy'
|
import { automationsPageIntro } from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const [submit, reload] = useBaseTable()
|
const [submit, reload] = useBaseTable()
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MobileIconButton
|
<MobileIconButton
|
||||||
|
v-if="can('toggle')"
|
||||||
:icon="item.sts === 2 ? 'vuesax-linear:card-tick' : 'vuesax-linear:card-remove'"
|
:icon="item.sts === 2 ? 'vuesax-linear:card-tick' : 'vuesax-linear:card-remove'"
|
||||||
:color="item.sts === 2 ? '#16A34A' : '#DC2626'"
|
:color="item.sts === 2 ? '#16A34A' : '#DC2626'"
|
||||||
:size="20"
|
:size="20"
|
||||||
@click="$emit('toggleSts', item)"
|
@click="$emit('toggleSts', item)"
|
||||||
/>
|
/>
|
||||||
<MobileIconButton
|
<MobileIconButton
|
||||||
|
v-if="can('delete')"
|
||||||
icon="vuesax-linear:trash"
|
icon="vuesax-linear:trash"
|
||||||
color="#DC2626"
|
color="#DC2626"
|
||||||
:size="20"
|
:size="20"
|
||||||
@@ -34,6 +36,9 @@
|
|||||||
import type { Card } from '@/model/card'
|
import type { Card } from '@/model/card'
|
||||||
import MobileCard from '@/components/mobile/MobileCard.vue'
|
import MobileCard from '@/components/mobile/MobileCard.vue'
|
||||||
import MobileIconButton from '@/components/mobile/MobileIconButton.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 }
|
type AccountRow = Card & { sts_str?: string; created_at?: string; customer_id?: number }
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
<template #action="{ column, record }">
|
<template #action="{ column, record }">
|
||||||
<div class="gap-2 flex justify-center items-center">
|
<div class="gap-2 flex justify-center items-center">
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
|
v-if="can('toggle')"
|
||||||
:key="record.sts"
|
:key="record.sts"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@@ -25,7 +26,7 @@
|
|||||||
:color="record.sts !== 2 ? 'red' : 'green'"
|
:color="record.sts !== 2 ? 'red' : 'green'"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</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>
|
<template #title>
|
||||||
<span>حذف کارت</span>
|
<span>حذف کارت</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -63,6 +64,9 @@ import type { Card } from '@/model/card'
|
|||||||
import AccountMobileCardList from './AccountMobileCardList.vue'
|
import AccountMobileCardList from './AccountMobileCardList.vue'
|
||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import { cardsPageIntro } from '@/utils/adminHelpCopy'
|
import { cardsPageIntro } from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const [submit, reload] = useBaseTable()
|
const [submit, reload] = useBaseTable()
|
||||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
|
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<div class="flex justify-center gap-2">
|
<div class="flex justify-center gap-2">
|
||||||
<a-tooltip placement="bottom">
|
<a-tooltip v-if="can('edit')" placement="bottom">
|
||||||
<template #title>ویرایش</template>
|
<template #title>ویرایش</template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:edit"
|
icon="vuesax-linear:edit"
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
@click="emits('edit', record)"
|
@click="emits('edit', record)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip placement="bottom">
|
<a-tooltip v-if="can('toggle')" placement="bottom">
|
||||||
<template #title>{{ record.is_active ? 'غیرفعال کردن' : 'فعال کردن' }}</template>
|
<template #title>{{ record.is_active ? 'غیرفعال کردن' : 'فعال کردن' }}</template>
|
||||||
<Icon
|
<Icon
|
||||||
:icon="record.is_active ? 'vuesax-linear:eye-slash' : 'vuesax-linear:eye'"
|
:icon="record.is_active ? 'vuesax-linear:eye-slash' : 'vuesax-linear:eye'"
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
@click="emits('toggle', record)"
|
@click="emits('toggle', record)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip placement="bottom">
|
<a-tooltip v-if="can('delete')" placement="bottom">
|
||||||
<template #title>حذف</template>
|
<template #title>حذف</template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:trash"
|
icon="vuesax-linear:trash"
|
||||||
@@ -54,6 +54,9 @@ import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
|||||||
import { cols } from '../hooks/conditionalOrdersColumns'
|
import { cols } from '../hooks/conditionalOrdersColumns'
|
||||||
import { getConditionalOrdersTable } from '@/api/conditionalOrders'
|
import { getConditionalOrdersTable } from '@/api/conditionalOrders'
|
||||||
import { conditionalOrdersPageIntro } from '@/utils/adminHelpCopy'
|
import { conditionalOrdersPageIntro } from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const [submit, reload] = useBaseTable()
|
const [submit, reload] = useBaseTable()
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,14 @@
|
|||||||
<div class="flex min-w-0 flex-1 items-center gap-2">
|
<div class="flex min-w-0 flex-1 items-center gap-2">
|
||||||
<BaseIcon icon="vuesax-linear:user" size="20" color="#6b7280" />
|
<BaseIcon icon="vuesax-linear:user" size="20" color="#6b7280" />
|
||||||
<button
|
<button
|
||||||
|
v-if="can('info')"
|
||||||
type="button"
|
type="button"
|
||||||
class="truncate border-none bg-transparent p-0 text-right text-sm font-700 text-gray-900 cursor-pointer"
|
class="truncate border-none bg-transparent p-0 text-right text-sm font-700 text-gray-900 cursor-pointer"
|
||||||
@click.stop="$emit('info', item)"
|
@click.stop="$emit('info', item)"
|
||||||
>
|
>
|
||||||
{{ item.name || '-' }}
|
{{ item.name || '-' }}
|
||||||
</button>
|
</button>
|
||||||
|
<span v-else class="truncate text-sm font-700 text-gray-900">{{ item.name || '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="shrink-0 text-sm text-gray-600">{{ item.mobile || '-' }}</div>
|
<div class="shrink-0 text-sm text-gray-600">{{ item.mobile || '-' }}</div>
|
||||||
@@ -19,6 +21,7 @@
|
|||||||
{{ item.type_str || 'کاربر' }}
|
{{ item.type_str || 'کاربر' }}
|
||||||
</span>
|
</span>
|
||||||
<MobileIconButton
|
<MobileIconButton
|
||||||
|
v-if="can('restore')"
|
||||||
icon="vuesax-linear:archive-slash"
|
icon="vuesax-linear:archive-slash"
|
||||||
color="#e3bf00"
|
color="#e3bf00"
|
||||||
:size="20"
|
:size="20"
|
||||||
@@ -34,6 +37,9 @@
|
|||||||
import type { Customer } from '@/model/customer'
|
import type { Customer } from '@/model/customer'
|
||||||
import MobileCard from '@/components/mobile/MobileCard.vue'
|
import MobileCard from '@/components/mobile/MobileCard.vue'
|
||||||
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
|
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
item: Customer
|
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">
|
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<div class="flex justify-center gap-2">
|
<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>
|
<template #title>
|
||||||
<span>{{ record.is_archive ? 'بیرون بردن از بایگانی' : 'بایگانی کردن' }}</span>
|
<span>{{ record.is_archive ? 'بیرون بردن از بایگانی' : 'بایگانی کردن' }}</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</a-tooltip>
|
</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>
|
<template #title><span>مشخصات</span></template>
|
||||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -52,6 +52,9 @@ import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
|
|||||||
import type { TableInput } from '@core/type/baseTable'
|
import type { TableInput } from '@core/type/baseTable'
|
||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import { customersArchivePageIntro } from '@/utils/adminHelpCopy'
|
import { customersArchivePageIntro } from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const [submit, reload] = useBaseTable()
|
const [submit, reload] = useBaseTable()
|
||||||
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'charge'])
|
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'charge'])
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
|
v-if="can('charge')"
|
||||||
class="customer-mobile-sheet__charge-btn"
|
class="customer-mobile-sheet__charge-btn"
|
||||||
size="middle"
|
size="middle"
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -39,7 +40,7 @@
|
|||||||
|
|
||||||
<div class="customer-mobile-sheet__actions">
|
<div class="customer-mobile-sheet__actions">
|
||||||
<BaseButton
|
<BaseButton
|
||||||
v-if="accountingCodeLabel"
|
v-if="can('accountingCode') && accountingCodeLabel"
|
||||||
size="small"
|
size="small"
|
||||||
type="default"
|
type="default"
|
||||||
icon="vuesax-linear:card-edit"
|
icon="vuesax-linear:card-edit"
|
||||||
@@ -47,14 +48,14 @@
|
|||||||
>
|
>
|
||||||
کد حسابداری
|
کد حسابداری
|
||||||
</BaseButton>
|
</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>
|
||||||
<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>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
v-if="customerLevelProfitEnabled"
|
v-if="can('referral') && customerLevelProfitEnabled"
|
||||||
size="small"
|
size="small"
|
||||||
type="default"
|
type="default"
|
||||||
icon="vuesax-linear:hierarchy-2"
|
icon="vuesax-linear:hierarchy-2"
|
||||||
@@ -62,11 +63,11 @@
|
|||||||
>
|
>
|
||||||
سود چندسطحی
|
سود چندسطحی
|
||||||
</BaseButton>
|
</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>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
v-if="customerDirectSmsEnabled"
|
v-if="can('sms') && customerDirectSmsEnabled"
|
||||||
size="small"
|
size="small"
|
||||||
type="default"
|
type="default"
|
||||||
icon="vuesax-linear:sms"
|
icon="vuesax-linear:sms"
|
||||||
@@ -75,7 +76,7 @@
|
|||||||
پیامک
|
پیامک
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
v-if="!row.is_legal"
|
v-if="can('approve') && !row.is_legal"
|
||||||
size="small"
|
size="small"
|
||||||
type="default"
|
type="default"
|
||||||
icon="vuesax-linear:user-search"
|
icon="vuesax-linear:user-search"
|
||||||
@@ -84,6 +85,7 @@
|
|||||||
تایید
|
تایید
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
|
v-if="can('active')"
|
||||||
size="small"
|
size="small"
|
||||||
:type="row.active ? 'default' : 'primary'"
|
:type="row.active ? 'default' : 'primary'"
|
||||||
:icon="row.active ? 'vuesax-linear:user-remove' : 'vuesax-linear:user-tick'"
|
:icon="row.active ? 'vuesax-linear:user-remove' : 'vuesax-linear:user-tick'"
|
||||||
@@ -92,6 +94,7 @@
|
|||||||
{{ row.active ? 'غیرفعال' : 'فعال' }}
|
{{ row.active ? 'غیرفعال' : 'فعال' }}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
|
v-if="can('trade')"
|
||||||
size="small"
|
size="small"
|
||||||
:type="row.trade_active === 1 ? 'default' : 'primary'"
|
:type="row.trade_active === 1 ? 'default' : 'primary'"
|
||||||
:icon="row.trade_active === 1 ? 'vuesax-linear:box-remove' : 'vuesax-linear:box-tick'"
|
:icon="row.trade_active === 1 ? 'vuesax-linear:box-remove' : 'vuesax-linear:box-tick'"
|
||||||
@@ -135,6 +138,9 @@ import {
|
|||||||
} from '@/utils/config'
|
} from '@/utils/config'
|
||||||
import { displayAccountingCode } from '@/utils/accountingCode'
|
import { displayAccountingCode } from '@/utils/accountingCode'
|
||||||
import CustomerPresenceBadge from '@/pages/menuPages/customers/base/components/CustomerPresenceBadge.vue'
|
import CustomerPresenceBadge from '@/pages/menuPages/customers/base/components/CustomerPresenceBadge.vue'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean
|
visible: boolean
|
||||||
|
|||||||
@@ -10,12 +10,14 @@
|
|||||||
<div class="min-w-0 flex-1 space-y-1.5">
|
<div class="min-w-0 flex-1 space-y-1.5">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
|
v-if="can('info')"
|
||||||
type="button"
|
type="button"
|
||||||
class="min-w-0 flex-1 truncate border-none bg-transparent p-0 text-right text-sm font-700 text-gray-900"
|
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)"
|
@click.stop="$emit('info', item)"
|
||||||
>
|
>
|
||||||
{{ item.name || '-' }}
|
{{ item.name || '-' }}
|
||||||
</button>
|
</button>
|
||||||
|
<span v-else class="min-w-0 flex-1 truncate text-sm font-700 text-gray-900">{{ item.name || '-' }}</span>
|
||||||
<span
|
<span
|
||||||
class="inline-block h-2.5 w-2.5 shrink-0 rounded-full"
|
class="inline-block h-2.5 w-2.5 shrink-0 rounded-full"
|
||||||
:class="item.active ? 'bg-green-500' : 'bg-red-500'"
|
:class="item.active ? 'bg-green-500' : 'bg-red-500'"
|
||||||
@@ -41,6 +43,7 @@
|
|||||||
عملیات
|
عملیات
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
|
v-if="can('edit')"
|
||||||
type="default"
|
type="default"
|
||||||
size="small"
|
size="small"
|
||||||
class="min-w-[4.5rem]"
|
class="min-w-[4.5rem]"
|
||||||
@@ -57,6 +60,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Customer } from '@/model/customer'
|
import type { Customer } from '@/model/customer'
|
||||||
import MobileCard from '@/components/mobile/MobileCard.vue'
|
import MobileCard from '@/components/mobile/MobileCard.vue'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
item: Customer
|
item: Customer
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #accountingCode="{ record }">
|
<template #accountingCode="{ record }">
|
||||||
<button
|
<button
|
||||||
|
v-if="can('accountingCode')"
|
||||||
type="button"
|
type="button"
|
||||||
class="inline-flex items-center gap-1.5 border-none bg-transparent p-0 text-sm cursor-pointer"
|
class="inline-flex items-center gap-1.5 border-none bg-transparent p-0 text-sm cursor-pointer"
|
||||||
style="color: var(--admin-text)"
|
style="color: var(--admin-text)"
|
||||||
@@ -29,10 +30,11 @@
|
|||||||
<span dir="ltr">{{ accountingCodeOf(record) }}</span>
|
<span dir="ltr">{{ accountingCodeOf(record) }}</span>
|
||||||
<Icon icon="vuesax-linear:edit-2" size="16" color="#4572E9" />
|
<Icon icon="vuesax-linear:edit-2" size="16" color="#4572E9" />
|
||||||
</button>
|
</button>
|
||||||
|
<span v-else dir="ltr">{{ accountingCodeOf(record) }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<div class="gap-2 hidden md:flex justify-center">
|
<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>
|
<template #title>
|
||||||
<span>{{ record.is_archive ? 'بیرون بردن از بایگانی' : 'بایگانی کردن' }}</span>
|
<span>{{ record.is_archive ? 'بیرون بردن از بایگانی' : 'بایگانی کردن' }}</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -47,6 +49,7 @@
|
|||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
|
v-if="can('active')"
|
||||||
:key="record.active"
|
:key="record.active"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@@ -60,12 +63,12 @@
|
|||||||
:color="record.active ? '#54AD06' : '#ad0627'"
|
:color="record.active ? '#54AD06' : '#ad0627'"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</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>
|
<template #title><span>شارژ کیف پول</span></template>
|
||||||
<Icon icon="vuesax-linear:wallet-add" color="#9333ea" />
|
<Icon icon="vuesax-linear:wallet-add" color="#9333ea" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
v-if="tahesabEnabled && !isValidAccountingCode(record.moshtari_code)"
|
v-if="can('tahesab') && tahesabEnabled && !isValidAccountingCode(record.moshtari_code)"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="openTahesabRegister(record)"
|
@click="openTahesabRegister(record)"
|
||||||
@@ -74,7 +77,7 @@
|
|||||||
<Icon icon="vuesax-linear:profile-add" color="#CA8A04" />
|
<Icon icon="vuesax-linear:profile-add" color="#CA8A04" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
v-if="zarinEnabled && !isValidAccountingCode(record.moshtari_code)"
|
v-if="can('zarin') && zarinEnabled && !isValidAccountingCode(record.moshtari_code)"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="openZarinRegister(record)"
|
@click="openZarinRegister(record)"
|
||||||
@@ -83,7 +86,7 @@
|
|||||||
<Icon icon="vuesax-linear:profile-add" color="#0284C7" />
|
<Icon icon="vuesax-linear:profile-add" color="#0284C7" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
v-if="pargarEnabled && !isValidAccountingCode(record.pargar_account_id)"
|
v-if="can('pargar') && pargarEnabled && !isValidAccountingCode(record.pargar_account_id)"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="openPargarRegister(record)"
|
@click="openPargarRegister(record)"
|
||||||
@@ -92,7 +95,7 @@
|
|||||||
<Icon icon="vuesax-linear:profile-add" color="#7C3AED" />
|
<Icon icon="vuesax-linear:profile-add" color="#7C3AED" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
v-if="accountingEnabled"
|
v-if="can('accountingCode') && accountingEnabled"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="openAccountingCodeEdit(record)"
|
@click="openAccountingCodeEdit(record)"
|
||||||
@@ -100,16 +103,16 @@
|
|||||||
<template #title><span>ویرایش کد حسابداری</span></template>
|
<template #title><span>ویرایش کد حسابداری</span></template>
|
||||||
<Icon icon="vuesax-linear:card-edit" color="#0d9488" />
|
<Icon icon="vuesax-linear:card-edit" color="#0d9488" />
|
||||||
</a-tooltip>
|
</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>
|
<template #title><span>مشخصات</span></template>
|
||||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||||
</a-tooltip>
|
</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>
|
<template #title><span>ویرایش</span></template>
|
||||||
<Icon icon="vuesax-linear:edit" color="#4572E9" />
|
<Icon icon="vuesax-linear:edit" color="#4572E9" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
v-if="customerLevelProfitEnabled"
|
v-if="can('referral') && customerLevelProfitEnabled"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="openLevelProfit(record)"
|
@click="openLevelProfit(record)"
|
||||||
@@ -118,6 +121,7 @@
|
|||||||
<Icon icon="vuesax-linear:hierarchy-2" color="#ca8a04" />
|
<Icon icon="vuesax-linear:hierarchy-2" color="#ca8a04" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
|
v-if="can('trade')"
|
||||||
:key="record.trade_active"
|
:key="record.trade_active"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@@ -132,7 +136,7 @@
|
|||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
v-if="customerDirectSmsEnabled"
|
v-if="can('sms') && customerDirectSmsEnabled"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="openDirectSms(record)"
|
@click="openDirectSms(record)"
|
||||||
@@ -141,7 +145,7 @@
|
|||||||
<Icon icon="vuesax-linear:sms" color="#0ea5e9" />
|
<Icon icon="vuesax-linear:sms" color="#0ea5e9" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
v-if="!record.is_legal"
|
v-if="can('approve') && !record.is_legal"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="openConfirmUser(record)"
|
@click="openConfirmUser(record)"
|
||||||
@@ -274,6 +278,9 @@ import {
|
|||||||
} from '@/utils/config'
|
} from '@/utils/config'
|
||||||
import { displayAccountingCode, isValidAccountingCode } from '@/utils/accountingCode'
|
import { displayAccountingCode, isValidAccountingCode } from '@/utils/accountingCode'
|
||||||
import { customersPageIntro } from '@/utils/adminHelpCopy'
|
import { customersPageIntro } from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const [submit, reload] = useBaseTable()
|
const [submit, reload] = useBaseTable()
|
||||||
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'chargeWallet'])
|
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'chargeWallet'])
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<div class="flex gap-3 justify-center items-center">
|
<div class="flex gap-3 justify-center items-center">
|
||||||
<a-tooltip placement="top">
|
<a-tooltip v-if="can('detail')" placement="top">
|
||||||
<template #title>جزئیات</template>
|
<template #title>جزئیات</template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:eye"
|
icon="vuesax-linear:eye"
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
@click.stop="openDetails(record)"
|
@click.stop="openDetails(record)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</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>
|
<template #title>ویرایش</template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:edit"
|
icon="vuesax-linear:edit"
|
||||||
@@ -86,6 +86,9 @@ import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
|||||||
import { getCustomersList } from '@/api/customer'
|
import { getCustomersList } from '@/api/customer'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import type { Item } from '@/model/Item'
|
import type { Item } from '@/model/Item'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const [submit, reload] = useBaseTable()
|
const [submit, reload] = useBaseTable()
|
||||||
const modalVisible = ref(false)
|
const modalVisible = ref(false)
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
<BaseTable v-bind="tblProps" @register="registerTable" ref="tbl">
|
<BaseTable v-bind="tblProps" @register="registerTable" ref="tbl">
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<div v-if="record.status === 'pending'" class="flex gap-2 justify-center">
|
<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>
|
||||||
<BaseButton size="small" danger @click.stop="openReject(record)">رد</BaseButton>
|
<BaseButton v-if="can('reject')" size="small" danger @click.stop="openReject(record)">رد</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
<span v-else class="text-gray-400 text-xs">—</span>
|
<span v-else class="text-gray-400 text-xs">—</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -46,6 +46,9 @@ import {
|
|||||||
} from '@/api/goldCredit'
|
} from '@/api/goldCredit'
|
||||||
import GoldCreditModal from '@/pages/menuPages/customers/base/components/goldCreditModal.vue'
|
import GoldCreditModal from '@/pages/menuPages/customers/base/components/goldCreditModal.vue'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const [registerTable, reload] = useBaseTable()
|
const [registerTable, reload] = useBaseTable()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|||||||
@@ -15,11 +15,11 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<div class="flex items-center justify-center gap-2">
|
<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>
|
<template #title><span>ویرایش</span></template>
|
||||||
<Icon icon="vuesax-linear:edit-2" color="#4572E9" />
|
<Icon icon="vuesax-linear:edit-2" color="#4572E9" />
|
||||||
</a-tooltip>
|
</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>
|
<template #title>
|
||||||
<span>{{ record.is_active ? 'غیرفعال کردن' : 'فعال کردن' }}</span>
|
<span>{{ record.is_active ? 'غیرفعال کردن' : 'فعال کردن' }}</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-popconfirm
|
<a-popconfirm
|
||||||
|
v-if="can('delete')"
|
||||||
title="پکیج حذف شود؟"
|
title="پکیج حذف شود؟"
|
||||||
ok-text="تایید"
|
ok-text="تایید"
|
||||||
cancel-text="انصراف"
|
cancel-text="انصراف"
|
||||||
@@ -56,6 +57,9 @@ import { getSpecialPricePackageColumns } from '../hooks/specialPriceColumns'
|
|||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { specialPricePageIntro } from '@/utils/adminHelpCopy'
|
import { specialPricePageIntro } from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
(e: 'add'): void
|
(e: 'add'): void
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #action="{ column, record }">
|
<template #action="{ column, record }">
|
||||||
<div class="gap-2 flex justify-center">
|
<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>
|
<template #title>
|
||||||
<span>ویرایش</span>
|
<span>ویرایش</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -30,6 +30,9 @@ import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
|||||||
import { cols } from '../hooks/discountColumns'
|
import { cols } from '../hooks/discountColumns'
|
||||||
import { getDiscountTable } from '@/api/discount'
|
import { getDiscountTable } from '@/api/discount'
|
||||||
import { discountPageIntro } from '@/utils/adminHelpCopy'
|
import { discountPageIntro } from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const [submit, reload] = useBaseTable()
|
const [submit, reload] = useBaseTable()
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,21 @@
|
|||||||
>
|
>
|
||||||
<template #bodyCell="{ column, record, index }">
|
<template #bodyCell="{ column, record, index }">
|
||||||
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
|
<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'">
|
<template v-else-if="column.key === 'sts'">
|
||||||
<CustomTag v-if="record.sts === 1" textColor="#ca8a04" bgColor="#fef3c7">فعال</CustomTag>
|
<CustomTag v-if="record.sts === 1" textColor="#ca8a04" bgColor="#fef3c7">فعال</CustomTag>
|
||||||
<CustomTag v-else-if="record.sts === 2" textColor="#25855A" bgColor="#E3F4E9">استفاده شده</CustomTag>
|
<CustomTag v-else-if="record.sts === 2" textColor="#25855A" bgColor="#E3F4E9">استفاده شده</CustomTag>
|
||||||
@@ -110,6 +125,11 @@ import {
|
|||||||
} from '../utils/exportGiftCardCodes'
|
} from '../utils/exportGiftCardCodes'
|
||||||
import { resolveGiftCardCustomer } from '../utils/giftCardCustomer'
|
import { resolveGiftCardCustomer } from '../utils/giftCardCustomer'
|
||||||
import { SELECT_DROPDOWN_Z_INDEX } from '@/constants/overlayZIndex'
|
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<{
|
const props = defineProps<{
|
||||||
visible: boolean
|
visible: boolean
|
||||||
@@ -144,6 +164,27 @@ function close() {
|
|||||||
visibleProxy.value = false
|
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 giftCardCustomer = computed(() => resolveGiftCardCustomer(props.giftCard))
|
||||||
|
|
||||||
const exportMeta = computed<GiftCardCodesExportMeta>(() => ({
|
const exportMeta = computed<GiftCardCodesExportMeta>(() => ({
|
||||||
|
|||||||
@@ -16,8 +16,13 @@
|
|||||||
:key="item.id"
|
:key="item.id"
|
||||||
:item="item"
|
:item="item"
|
||||||
:hide-customer="!!customerId"
|
:hide-customer="!!customerId"
|
||||||
|
:can-edit="canEdit"
|
||||||
|
:can-delete="canDelete"
|
||||||
|
:can-codes="canCodes"
|
||||||
@open="openSheet"
|
@open="openSheet"
|
||||||
@gift-card-codes="$emit('giftCardCodes', $event)"
|
@gift-card-codes="$emit('giftCardCodes', $event)"
|
||||||
|
@edit="$emit('edit', $event)"
|
||||||
|
@delete="$emit('delete', $event)"
|
||||||
/>
|
/>
|
||||||
</MobileCardList>
|
</MobileCardList>
|
||||||
|
|
||||||
@@ -38,10 +43,15 @@ type GiftCardRow = Giftcard & { customer?: { name?: string; mobile?: string } }
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
customerId?: number
|
customerId?: number
|
||||||
|
canEdit?: boolean
|
||||||
|
canDelete?: boolean
|
||||||
|
canCodes?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
(e: 'giftCardCodes', item: GiftCardRow): void
|
(e: 'giftCardCodes', item: GiftCardRow): void
|
||||||
|
(e: 'edit', item: GiftCardRow): void
|
||||||
|
(e: 'delete', id: number): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<GiftCardRow>()
|
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<GiftCardRow>()
|
||||||
|
|||||||
@@ -18,11 +18,26 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MobileIconButton
|
<MobileIconButton
|
||||||
|
v-if="canCodes"
|
||||||
icon="vuesax-linear:document-text"
|
icon="vuesax-linear:document-text"
|
||||||
color="#1890ff"
|
color="#1890ff"
|
||||||
:size="20"
|
:size="20"
|
||||||
@click="$emit('giftCardCodes', item)"
|
@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>
|
</div>
|
||||||
</MobileCard>
|
</MobileCard>
|
||||||
</template>
|
</template>
|
||||||
@@ -38,11 +53,16 @@ type GiftCardRow = Giftcard & { customer?: { name?: string; mobile?: string } }
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
item: GiftCardRow
|
item: GiftCardRow
|
||||||
hideCustomer?: boolean
|
hideCustomer?: boolean
|
||||||
|
canEdit?: boolean
|
||||||
|
canDelete?: boolean
|
||||||
|
canCodes?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
(e: 'open', item: GiftCardRow): void
|
(e: 'open', item: GiftCardRow): void
|
||||||
(e: 'giftCardCodes', 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() : 'ریالی'))
|
const unitLabel = computed(() => (props.item.unit === 2 ? metalLabel.goldWalletShort() : 'ریالی'))
|
||||||
|
|||||||
@@ -27,18 +27,30 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #action="{ column, record }">
|
<template #action="{ column, record }">
|
||||||
<div class="gap-2 flex justify-center">
|
<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>
|
<template #title>
|
||||||
<span>مشخصات</span>
|
<span>مشخصات</span>
|
||||||
</template>
|
</template>
|
||||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||||
</a-tooltip>
|
</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>
|
<template #title>
|
||||||
<span>کدهای {{ record.unit == 3 ? 'تخفیف' : 'کارت هدیه' }}</span>
|
<span>کدهای {{ record.unit == 3 ? 'تخفیف' : 'کارت هدیه' }}</span>
|
||||||
</template>
|
</template>
|
||||||
<Icon icon="vuesax-linear:document-text" color="#1890ff" />
|
<Icon icon="vuesax-linear:document-text" color="#1890ff" />
|
||||||
</a-tooltip>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</BaseTable>
|
</BaseTable>
|
||||||
@@ -49,6 +61,11 @@
|
|||||||
class="min-h-0 flex-1"
|
class="min-h-0 flex-1"
|
||||||
:customer-id="customerId"
|
:customer-id="customerId"
|
||||||
@gift-card-codes="emits('giftcardcodes', $event)"
|
@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>
|
</div>
|
||||||
</BasicShowContext>
|
</BasicShowContext>
|
||||||
@@ -66,6 +83,7 @@ import GiftCardMobileCardList from './GiftCardMobileCardList.vue'
|
|||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import { isStoreApp } from '@/utils/config'
|
import { isStoreApp } from '@/utils/config'
|
||||||
import { giftCardsPageIntro } from '@/utils/adminHelpCopy'
|
import { giftCardsPageIntro } from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
interface Prop {
|
interface Prop {
|
||||||
customerId?: number
|
customerId?: number
|
||||||
@@ -73,6 +91,7 @@ interface Prop {
|
|||||||
|
|
||||||
const props = defineProps<Prop>()
|
const props = defineProps<Prop>()
|
||||||
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'giftcardcodes'])
|
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'giftcardcodes'])
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const [submit, reload] = useBaseTable()
|
const [submit, reload] = useBaseTable()
|
||||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||||
|
|||||||
@@ -116,10 +116,11 @@ import { confirmDeleteItem } from '@/utils/confirmModal'
|
|||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
import type { Item } from '@/model/Item'
|
import type { Item } from '@/model/Item'
|
||||||
import type { Customer } from '@/model/customer'
|
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 { usePrice } from '@core/utils/usePrice'
|
||||||
import type { Giftcard, Giftcardcode, GiftcardInput } from '@/model/giftCard'
|
import type { Giftcard, Giftcardcode, GiftcardInput } from '@/model/giftCard'
|
||||||
import { createGiftCard } from '@/api/giftCard'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||||
@@ -187,6 +188,10 @@ const selectedGiftCardCodes = ref<Giftcardcode[]>([])
|
|||||||
const selectedGiftCard = ref<Giftcard | null>(null)
|
const selectedGiftCard = ref<Giftcard | null>(null)
|
||||||
|
|
||||||
function info(val: Giftcard) {
|
function info(val: Giftcard) {
|
||||||
|
if (!giftPageAllows('info')) {
|
||||||
|
notifyPageActionDenied('info')
|
||||||
|
return
|
||||||
|
}
|
||||||
// نمایش اطلاعات اصلی در BasicInfoModal
|
// نمایش اطلاعات اصلی در BasicInfoModal
|
||||||
infoParams.title = 'اطلاعات کارت هدیه'
|
infoParams.title = 'اطلاعات کارت هدیه'
|
||||||
infoParams.name = giftCardCustomerLabel(val)
|
infoParams.name = giftCardCustomerLabel(val)
|
||||||
@@ -227,12 +232,6 @@ function info(val: Giftcard) {
|
|||||||
key: 'مبلغ کل (ریال)',
|
key: 'مبلغ کل (ریال)',
|
||||||
value: usePrice(val.total ?? 0)
|
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: 'تعداد استفاده شده',
|
key: 'تعداد استفاده شده',
|
||||||
value: val.used_count ?? '-'
|
value: val.used_count ?? '-'
|
||||||
@@ -258,6 +257,10 @@ function info(val: Giftcard) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showGiftCardCodes(val: Giftcard) {
|
function showGiftCardCodes(val: Giftcard) {
|
||||||
|
if (!giftPageAllows('codes')) {
|
||||||
|
notifyPageActionDenied('codes')
|
||||||
|
return
|
||||||
|
}
|
||||||
// نمایش کدهای کارت هدیه در مودال جداگانه
|
// نمایش کدهای کارت هدیه در مودال جداگانه
|
||||||
if (val.giftcardcodes && val.giftcardcodes.length > 0) {
|
if (val.giftcardcodes && val.giftcardcodes.length > 0) {
|
||||||
selectedGiftCard.value = val
|
selectedGiftCard.value = val
|
||||||
@@ -271,7 +274,23 @@ function showGiftCardCodes(val: Giftcard) {
|
|||||||
//edit
|
//edit
|
||||||
let editId: number | undefined = undefined
|
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) {
|
async function edit(record: Giftcard) {
|
||||||
|
if (!giftPageAllows('edit')) {
|
||||||
|
notifyPageActionDenied('edit')
|
||||||
|
return
|
||||||
|
}
|
||||||
addGiftCardVisible.value = true
|
addGiftCardVisible.value = true
|
||||||
editId = record.id
|
editId = record.id
|
||||||
form.value = {
|
form.value = {
|
||||||
@@ -357,7 +376,13 @@ const resetData = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//delete
|
//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
|
//add project modal
|
||||||
const addGiftCardVisible = ref<boolean>(false)
|
const addGiftCardVisible = ref<boolean>(false)
|
||||||
@@ -388,8 +413,13 @@ async function ok() {
|
|||||||
.endOf('day')
|
.endOf('day')
|
||||||
.format('YYYY-MM-DD HH:mm:ss')
|
.format('YYYY-MM-DD HH:mm:ss')
|
||||||
}
|
}
|
||||||
await createGiftCard(formData as GiftcardInput)
|
if (editId) {
|
||||||
message.success('کارت هدیه با موفقیت ثبت شد')
|
await updateGiftCard(editId, formData as GiftcardInput)
|
||||||
|
message.success('کارت هدیه با موفقیت ویرایش شد')
|
||||||
|
} else {
|
||||||
|
await createGiftCard(formData as GiftcardInput)
|
||||||
|
message.success('کارت هدیه با موفقیت ثبت شد')
|
||||||
|
}
|
||||||
addGiftCardVisible.value = false
|
addGiftCardVisible.value = false
|
||||||
resetData()
|
resetData()
|
||||||
refreshTable()
|
refreshTable()
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #action="{ column, record }">
|
<template #action="{ column, record }">
|
||||||
<div class="gap-2 flex justify-center">
|
<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>
|
<template #title>
|
||||||
<span>مشخصات</span>
|
<span>مشخصات</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -50,6 +50,9 @@ import { getOrderTable } from '@/api/order'
|
|||||||
import OrderMobileCardList from './OrderMobileCardList.vue'
|
import OrderMobileCardList from './OrderMobileCardList.vue'
|
||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import { ordersPageIntro } from '@/utils/adminHelpCopy'
|
import { ordersPageIntro } from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const [submit, reload] = useBaseTable()
|
const [submit, reload] = useBaseTable()
|
||||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<div class="flex items-center justify-center gap-2">
|
<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>
|
<template #title>ویرایش مقدار</template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:edit-2"
|
icon="vuesax-linear:edit-2"
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|
||||||
<a-tooltip v-if="canApprove(record)">
|
<a-tooltip v-if="can('approve') && canApprove(record)">
|
||||||
<template #title>{{ physicalWithdrawApproveHelp }}</template>
|
<template #title>{{ physicalWithdrawApproveHelp }}</template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:tick-circle"
|
icon="vuesax-linear:tick-circle"
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|
||||||
<ADropdown v-if="canChangeStatus(record)" :trigger="['click']">
|
<ADropdown v-if="can('status') && canChangeStatus(record)" :trigger="['click']">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template #title>{{ physicalWithdrawStatusHelp }}</template>
|
<template #title>{{ physicalWithdrawStatusHelp }}</template>
|
||||||
<Icon icon="vuesax-linear:status-up" color="#CA8A04" class="cursor-pointer" />
|
<Icon icon="vuesax-linear:status-up" color="#CA8A04" class="cursor-pointer" />
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</ADropdown>
|
</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>
|
<template #title>دریافت فاکتور</template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:document-download"
|
icon="vuesax-linear:document-download"
|
||||||
@@ -92,6 +92,9 @@ import {
|
|||||||
physicalWithdrawPageIntro,
|
physicalWithdrawPageIntro,
|
||||||
physicalWithdrawStatusHelp
|
physicalWithdrawStatusHelp
|
||||||
} from '@/utils/adminHelpCopy'
|
} from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const emits = defineEmits(['add'])
|
const emits = defineEmits(['add'])
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
|
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<div class="flex justify-center gap-2">
|
<div class="flex justify-center gap-2">
|
||||||
<a-tooltip placement="bottom">
|
<a-tooltip v-if="can('edit')" placement="bottom">
|
||||||
<template #title>ویرایش</template>
|
<template #title>ویرایش</template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:edit"
|
icon="vuesax-linear:edit"
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
@click="emits('edit', record)"
|
@click="emits('edit', record)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip placement="bottom">
|
<a-tooltip v-if="can('toggle')" placement="bottom">
|
||||||
<template #title>{{ record.is_active ? 'غیرفعال کردن' : 'فعال کردن' }}</template>
|
<template #title>{{ record.is_active ? 'غیرفعال کردن' : 'فعال کردن' }}</template>
|
||||||
<Icon
|
<Icon
|
||||||
:icon="record.is_active ? 'vuesax-linear:eye-slash' : 'vuesax-linear:eye'"
|
:icon="record.is_active ? 'vuesax-linear:eye-slash' : 'vuesax-linear:eye'"
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
@click="emits('toggle', record)"
|
@click="emits('toggle', record)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip placement="bottom">
|
<a-tooltip v-if="can('delete')" placement="bottom">
|
||||||
<template #title>حذف</template>
|
<template #title>حذف</template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:trash"
|
icon="vuesax-linear:trash"
|
||||||
@@ -52,6 +52,9 @@ import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
|||||||
import { cols } from '../hooks/priceTriggersColumns'
|
import { cols } from '../hooks/priceTriggersColumns'
|
||||||
import { getPriceTriggersTable } from '@/api/priceTriggers'
|
import { getPriceTriggersTable } from '@/api/priceTriggers'
|
||||||
import { priceTriggersPageIntro } from '@/utils/adminHelpCopy'
|
import { priceTriggersPageIntro } from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const [submit, reload] = useBaseTable()
|
const [submit, reload] = useBaseTable()
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<div class="flex justify-center gap-2">
|
<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>
|
<template #title>
|
||||||
<span>جزئیات محصولات</span>
|
<span>جزئیات محصولات</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -63,6 +63,9 @@ import PriceMobileCardList from './PriceMobileCardList.vue'
|
|||||||
import { pricingPageIntro } from '@/utils/adminHelpCopy'
|
import { pricingPageIntro } from '@/utils/adminHelpCopy'
|
||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import type { PriceSyncLog } from '@/model/priceSyncLog'
|
import type { PriceSyncLog } from '@/model/priceSyncLog'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
defineProps<{ syncing?: boolean }>()
|
defineProps<{ syncing?: boolean }>()
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<span class="truncate text-sm font-700 text-gray-900">{{ item.title || '-' }}</span>
|
<span class="truncate text-sm font-700 text-gray-900">{{ item.title || '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<MobileIconButton icon="vuesax-linear:archive-slash" color="#e3bf00" @click="$emit('unarchive', item)" />
|
<MobileIconButton v-if="can('restore')" 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('info')" icon="vuesax-linear:stickynote" color="#10913A" @click="$emit('info', item)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -32,6 +32,9 @@ import type { Product } from '@/model/product'
|
|||||||
import MobileCard from '@/components/mobile/MobileCard.vue'
|
import MobileCard from '@/components/mobile/MobileCard.vue'
|
||||||
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
|
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
|
||||||
import { usePrice } from '@core/utils/usePrice'
|
import { usePrice } from '@core/utils/usePrice'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
item: Product
|
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">
|
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<div class="gap-2 flex justify-center">
|
<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>
|
<template #title>
|
||||||
<span>بیرون بردن از بایگانی</span>
|
<span>بیرون بردن از بایگانی</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</a-tooltip>
|
</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>
|
<template #title>
|
||||||
<span>مشخصات</span>
|
<span>مشخصات</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -56,6 +56,9 @@ import { getProductArchiveColumns } from '../hooks/archiveColumns'
|
|||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import type { DashboardHelp } from '@/pages/dashboard/dashboardSectionCopy'
|
import type { DashboardHelp } from '@/pages/dashboard/dashboardSectionCopy'
|
||||||
import DashboardLabelWithHelp from '@/pages/dashboard/components/DashboardLabelWithHelp.vue'
|
import DashboardLabelWithHelp from '@/pages/dashboard/components/DashboardLabelWithHelp.vue'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const productArchiveHelp: DashboardHelp = {
|
const productArchiveHelp: DashboardHelp = {
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<span class="min-w-0 truncate">{{ row.title || '-' }}</span>
|
<span class="min-w-0 truncate">{{ row.title || '-' }}</span>
|
||||||
<div class="flex shrink-0 items-center gap-2">
|
<div class="flex shrink-0 items-center gap-2">
|
||||||
<MobileIconButton
|
<MobileIconButton
|
||||||
|
v-if="can('buy')"
|
||||||
plain
|
plain
|
||||||
:key="`buy-${row.id}-${row.open_buy}`"
|
:key="`buy-${row.id}-${row.open_buy}`"
|
||||||
icon="vuesax-linear:money-receive"
|
icon="vuesax-linear:money-receive"
|
||||||
@@ -15,6 +16,7 @@
|
|||||||
@click="$emit('toggleBuy', row)"
|
@click="$emit('toggleBuy', row)"
|
||||||
/>
|
/>
|
||||||
<MobileIconButton
|
<MobileIconButton
|
||||||
|
v-if="can('sell')"
|
||||||
plain
|
plain
|
||||||
:key="`sell-${row.id}-${row.open_sell}`"
|
:key="`sell-${row.id}-${row.open_sell}`"
|
||||||
icon="vuesax-linear:money-send"
|
icon="vuesax-linear:money-send"
|
||||||
@@ -23,6 +25,7 @@
|
|||||||
@click="$emit('toggleSell', row)"
|
@click="$emit('toggleSell', row)"
|
||||||
/>
|
/>
|
||||||
<MobileIconButton
|
<MobileIconButton
|
||||||
|
v-if="can('list')"
|
||||||
plain
|
plain
|
||||||
:key="`list-${row.id}-${row.show_in_list ?? row.active}`"
|
: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'"
|
:icon="(row.show_in_list ?? row.active) ? 'vuesax-linear:tick-circle' : 'vuesax-linear:close-circle'"
|
||||||
@@ -31,6 +34,7 @@
|
|||||||
@click="$emit('toggleActive', row)"
|
@click="$emit('toggleActive', row)"
|
||||||
/>
|
/>
|
||||||
<MobileIconButton
|
<MobileIconButton
|
||||||
|
v-if="can('tv')"
|
||||||
plain
|
plain
|
||||||
:key="`tv-${row.id}-${row.show_on_tv}`"
|
:key="`tv-${row.id}-${row.show_on_tv}`"
|
||||||
icon="vuesax-linear:monitor"
|
icon="vuesax-linear:monitor"
|
||||||
@@ -76,6 +80,7 @@
|
|||||||
</MobileInfoGrid>
|
</MobileInfoGrid>
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
|
v-if="can('archive')"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
size="middle"
|
size="middle"
|
||||||
type="default"
|
type="default"
|
||||||
@@ -100,6 +105,9 @@ import { resolveProductTradeTypeLabel } from '@/utils/productCategories'
|
|||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import { toJalali } from '@core/utils/useDataTime'
|
import { toJalali } from '@core/utils/useDataTime'
|
||||||
import { usePrice } from '@core/utils/usePrice'
|
import { usePrice } from '@core/utils/usePrice'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean
|
visible: boolean
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
{{ item.title || '-' }}
|
{{ item.title || '-' }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="mt-3 flex items-center justify-between gap-2">
|
<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 MobileCard from '@/components/mobile/MobileCard.vue'
|
||||||
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
|
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
|
||||||
import { usePrice } from '@core/utils/usePrice'
|
import { usePrice } from '@core/utils/usePrice'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
item: Product
|
item: Product
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<div class="gap-2 flex justify-center">
|
<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-if="!record.open_buy" #title><span>فعال کردن خرید</span></template>
|
||||||
<template v-else #title><span>غیر فعال کردن خرید</span></template>
|
<template v-else #title><span>غیر فعال کردن خرید</span></template>
|
||||||
<BaseIcon
|
<BaseIcon
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
@click="activeBuy(record)"
|
@click="activeBuy(record)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</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-if="!record.open_sell" #title><span>فعال کردن فروش</span></template>
|
||||||
<template v-else #title><span>غیر فعال کردن فروش</span></template>
|
<template v-else #title><span>غیر فعال کردن فروش</span></template>
|
||||||
<BaseIcon
|
<BaseIcon
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
@click="activeSell(record)"
|
@click="activeSell(record)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</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>
|
<template v-if="!(record.show_in_list ?? record.active)" #title>
|
||||||
<span>فعال کردن نمایش در لیست محصولات</span>
|
<span>فعال کردن نمایش در لیست محصولات</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
@click="toggleShowInList(record)"
|
@click="toggleShowInList(record)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</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>
|
<template v-if="!record.show_on_tv" #title>
|
||||||
<span>فعال کردن نمایش در تابلو TV</span>
|
<span>فعال کردن نمایش در تابلو TV</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -108,11 +108,11 @@
|
|||||||
@click="toggleShowOnTv(record)"
|
@click="toggleShowOnTv(record)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</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>
|
<template #title><span>ویرایش</span></template>
|
||||||
<BaseIcon icon="vuesax-linear:edit" color="#4572E9" />
|
<BaseIcon icon="vuesax-linear:edit" color="#4572E9" />
|
||||||
</a-tooltip>
|
</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>
|
<template #title><span>بایگانی کردن</span></template>
|
||||||
<a-popconfirm
|
<a-popconfirm
|
||||||
title="آیا این محصول بایگانی شود؟ نمایش، خرید و فروش آن غیرفعال میشود."
|
title="آیا این محصول بایگانی شود؟ نمایش، خرید و فروش آن غیرفعال میشود."
|
||||||
@@ -168,6 +168,9 @@ import type { TableInput } from '@core/type/baseTable'
|
|||||||
import type { Product } from '@/model/product'
|
import type { Product } from '@/model/product'
|
||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import { productsPageIntro } from '@/utils/adminHelpCopy'
|
import { productsPageIntro } from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
:color="record.active ? '#54AD06' : '#ad0627'"
|
:color="record.active ? '#54AD06' : '#ad0627'"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>-->
|
</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>
|
<template #title>
|
||||||
<span>مشخصات</span>
|
<span>مشخصات</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -76,6 +76,9 @@ import { getTransactionTable } from '@/api/transaction'
|
|||||||
import TransactionMobileCardList from './TransactionMobileCardList.vue'
|
import TransactionMobileCardList from './TransactionMobileCardList.vue'
|
||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import { transactionsPageIntro } from '@/utils/adminHelpCopy'
|
import { transactionsPageIntro } from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
interface Prop {
|
interface Prop {
|
||||||
customerId?: number
|
customerId?: number
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<div class="flex w-full items-center justify-between gap-3">
|
<div class="flex w-full items-center justify-between gap-3">
|
||||||
<span class="min-w-0 truncate">{{ sheetTitle }}</span>
|
<span class="min-w-0 truncate">{{ sheetTitle }}</span>
|
||||||
<MobileIconButton
|
<MobileIconButton
|
||||||
|
v-if="can('delete')"
|
||||||
plain
|
plain
|
||||||
icon="vuesax-linear:user-remove"
|
icon="vuesax-linear:user-remove"
|
||||||
color="#EB3535"
|
color="#EB3535"
|
||||||
@@ -35,6 +36,9 @@ import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
|
|||||||
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
|
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
|
||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import { toJalaliNoTime } from '@core/utils/useDataTime'
|
import { toJalaliNoTime } from '@core/utils/useDataTime'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean
|
visible: boolean
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MobileIconButton
|
<MobileIconButton
|
||||||
|
v-if="can('edit')"
|
||||||
icon="vuesax-linear:edit"
|
icon="vuesax-linear:edit"
|
||||||
color="#4572E9"
|
color="#4572E9"
|
||||||
:size="20"
|
:size="20"
|
||||||
@@ -24,6 +25,9 @@
|
|||||||
import type { User } from '@/model/user'
|
import type { User } from '@/model/user'
|
||||||
import MobileCard from '@/components/mobile/MobileCard.vue'
|
import MobileCard from '@/components/mobile/MobileCard.vue'
|
||||||
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
|
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
item: User
|
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>
|
||||||
<template #action="{ column, record }">
|
<template #action="{ column, record }">
|
||||||
<div class="gap-2 flex justify-center">
|
<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>
|
<template #title>
|
||||||
<span>مشخصات</span>
|
<span>مشخصات</span>
|
||||||
</template>
|
</template>
|
||||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||||
</a-tooltip>
|
</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>
|
<template #title>
|
||||||
<span>ویرایش</span>
|
<span>ویرایش</span>
|
||||||
</template>
|
</template>
|
||||||
<Icon icon="vuesax-linear:edit" color="#4572E9" />
|
<Icon icon="vuesax-linear:edit" color="#4572E9" />
|
||||||
</a-tooltip>
|
</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>
|
<template #title>
|
||||||
<span>حذف مدیر</span>
|
<span>حذف مدیر</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -66,6 +66,9 @@ import { cols } from '../hooks/usersColumns'
|
|||||||
import ManagerMobileCardList from './ManagerMobileCardList.vue'
|
import ManagerMobileCardList from './ManagerMobileCardList.vue'
|
||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import { usersPageIntro } from '@/utils/adminHelpCopy'
|
import { usersPageIntro } from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const [submit, reload] = useBaseTable()
|
const [submit, reload] = useBaseTable()
|
||||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<!-- </BasicMainCard>-->
|
<!-- </BasicMainCard>-->
|
||||||
<BasicModal
|
<BasicModal
|
||||||
v-model:visible="addUserVisible"
|
v-model:visible="addUserVisible"
|
||||||
width="40rem"
|
width="min(52rem, 96vw)"
|
||||||
:title="`${editId ? 'ویرایش' : 'افزودن'} کاربر`"
|
:title="`${editId ? 'ویرایش' : 'افزودن'} کاربر`"
|
||||||
@ok="ok"
|
@ok="ok"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -48,6 +48,19 @@
|
|||||||
</a-switch>
|
</a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</div>
|
</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>
|
</a-form>
|
||||||
</BasicModal>
|
</BasicModal>
|
||||||
<!-- <BasicInfoModal
|
<!-- <BasicInfoModal
|
||||||
@@ -81,12 +94,18 @@ import { toJalaliNoTime, toTime } from '@core/utils/useDataTime'
|
|||||||
import { confirmDeleteItem } from '@/utils/confirmModal'
|
import { confirmDeleteItem } from '@/utils/confirmModal'
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
import type { Item } from '@/model/Item'
|
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
|
//loading
|
||||||
const loading = ref<boolean>(false)
|
const loading = ref<boolean>(false)
|
||||||
|
|
||||||
//define store
|
//define store
|
||||||
const store = useStaticApiListsStore()
|
const store = useStaticApiListsStore()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const canAssignAccess = computed(() => userStore.hasFullPageAccess)
|
||||||
|
const fullAccess = ref(true)
|
||||||
|
|
||||||
//define tbl
|
//define tbl
|
||||||
const table = ref(null)
|
const table = ref(null)
|
||||||
@@ -112,6 +131,11 @@ const form = ref<User>({
|
|||||||
})
|
})
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
watch(fullAccess, (on) => {
|
||||||
|
if (on) return
|
||||||
|
if (form.value.page_access == null) form.value.page_access = allGrantedMap()
|
||||||
|
})
|
||||||
|
|
||||||
//info
|
//info
|
||||||
let infoParams = reactive<Info>({})
|
let infoParams = reactive<Info>({})
|
||||||
|
|
||||||
@@ -141,6 +165,8 @@ async function edit(record: User) {
|
|||||||
addUserVisible.value = true
|
addUserVisible.value = true
|
||||||
editId = record.id
|
editId = record.id
|
||||||
form.value = cloneDeep(record)
|
form.value = cloneDeep(record)
|
||||||
|
form.value.page_access = parsePageAccess(record.page_access)
|
||||||
|
fullAccess.value = form.value.page_access == null
|
||||||
}
|
}
|
||||||
|
|
||||||
//add
|
//add
|
||||||
@@ -164,8 +190,10 @@ const resetData = () => {
|
|||||||
avatar_id: undefined,
|
avatar_id: undefined,
|
||||||
sts: undefined,
|
sts: undefined,
|
||||||
id: undefined,
|
id: undefined,
|
||||||
mobile: undefined
|
mobile: undefined,
|
||||||
|
page_access: null
|
||||||
}
|
}
|
||||||
|
fullAccess.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
//delete — DELETE /admin/user/:id
|
//delete — DELETE /admin/user/:id
|
||||||
@@ -226,21 +254,33 @@ async function ok() {
|
|||||||
formRef.value?.validateFields().then(async () => {
|
formRef.value?.validateFields().then(async () => {
|
||||||
// console.log('salam', form.value)
|
// console.log('salam', form.value)
|
||||||
// return
|
// 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
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
if (editId) {
|
if (editId) {
|
||||||
if (form.value.password) {
|
if (form.value.password) {
|
||||||
if (form.value.password === form.value.repassword) {
|
if (form.value.password === form.value.repassword) {
|
||||||
await updateUser(editId, form.value)
|
await updateUser(editId, payload)
|
||||||
} else {
|
} else {
|
||||||
message.error('پسورد وارد شده با تکرار پسورد وارد شده یکسان نیست')
|
message.error('پسورد وارد شده با تکرار پسورد وارد شده یکسان نیست')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await updateUser(editId, form.value)
|
delete payload.password
|
||||||
|
await updateUser(editId, payload)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await createUser(form.value)
|
await createUser(payload)
|
||||||
}
|
}
|
||||||
addUserVisible.value = false
|
addUserVisible.value = false
|
||||||
refreshTable()
|
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">
|
<BaseTable v-if="!isMobile" class="min-h-0 min-w-0 flex-1" v-bind="tblProps" @register="submit" ref="tbl">
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<div class="flex justify-center gap-2">
|
<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>
|
<template #title>
|
||||||
<span>مشخصات کیف پول</span>
|
<span>مشخصات کیف پول</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -82,6 +82,9 @@ import { totalMetalWalletTitle } from '@/utils/adminMetalLabels'
|
|||||||
import type { WalletTableSums } from '@/model/wallet'
|
import type { WalletTableSums } from '@/model/wallet'
|
||||||
import { formatMetalDisplayValue, metalDisplayUnit } from '@/utils/metalWeight'
|
import { formatMetalDisplayValue, metalDisplayUnit } from '@/utils/metalWeight'
|
||||||
import { walletPageIntro } from '@/utils/adminHelpCopy'
|
import { walletPageIntro } from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
|
|
||||||
const [submit, reload] = useBaseTable()
|
const [submit, reload] = useBaseTable()
|
||||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<template #tracked="{ column, record }">
|
<template #tracked="{ column, record }">
|
||||||
<div v-if="record.tracked_number">{{ record.tracked_number }}</div>
|
<div v-if="record.tracked_number">{{ record.tracked_number }}</div>
|
||||||
<div v-else-if="needsTrackingConfirmation(record)">
|
<div v-else-if="needsTrackingConfirmation(record)">
|
||||||
<BaseButton @click="emits('withdraw', record)">
|
<BaseButton v-if="can('confirm')" @click="emits('withdraw', record)">
|
||||||
{{ confirmLabelForRecord(record, props.flowType) }}
|
{{ confirmLabelForRecord(record, props.flowType) }}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -28,14 +28,14 @@
|
|||||||
<Icon icon="vuesax-linear:message-text" color="#64748b" />
|
<Icon icon="vuesax-linear:message-text" color="#64748b" />
|
||||||
</a-badge>
|
</a-badge>
|
||||||
</a-tooltip>
|
</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>
|
<template #title><span>{{ confirmLabelForRecord(record, props.flowType) }}</span></template>
|
||||||
<BaseButton size="small" type="primary" @click="emits('withdraw', record)">
|
<BaseButton size="small" type="primary" @click="emits('withdraw', record)">
|
||||||
{{ confirmLabelForRecord(record, props.flowType) }}
|
{{ confirmLabelForRecord(record, props.flowType) }}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<div class="flex gap-2" v-if="record.sts <= 70 && record.sts >= 50">
|
<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>
|
<template #title><span>دریافت فاکتور</span></template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:document-download"
|
icon="vuesax-linear:document-download"
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
@click="$router.push({ name: 'factor', params: { id: record.id } })"
|
@click="$router.push({ name: 'factor', params: { id: record.id } })"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip class="cursor-pointer">
|
<a-tooltip v-if="can('status')" class="cursor-pointer">
|
||||||
<template #title><span>وضعیت</span></template>
|
<template #title><span>وضعیت</span></template>
|
||||||
<ADropdown :trigger="['click']">
|
<ADropdown :trigger="['click']">
|
||||||
<Icon icon="vuesax-linear:status-up" color="#CA8A04" @click="test(record)" />
|
<Icon icon="vuesax-linear:status-up" color="#CA8A04" @click="test(record)" />
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
</ADropdown>
|
</ADropdown>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</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>
|
<template #title><span>تعیین وزن</span></template>
|
||||||
<Icon
|
<Icon
|
||||||
icon="vuesax-linear:3d-cube-scan"
|
icon="vuesax-linear:3d-cube-scan"
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
v-if="tahesabEnabled && record.tahesab_failed_id"
|
v-if="can('tahesab') && tahesabEnabled && record.tahesab_failed_id"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="openTahesabRetry(record)"
|
@click="openTahesabRetry(record)"
|
||||||
>
|
>
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
<Icon icon="vuesax-linear:refresh-circle" color="#CA8A04" />
|
<Icon icon="vuesax-linear:refresh-circle" color="#CA8A04" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
v-if="zarinEnabled && record.zarin_failed_id"
|
v-if="can('zarin') && zarinEnabled && record.zarin_failed_id"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="openZarinRetry(record)"
|
@click="openZarinRetry(record)"
|
||||||
>
|
>
|
||||||
@@ -242,6 +242,9 @@ onMounted(() => {
|
|||||||
import { status } from '@/pages/menuPages/withdraw/information/hooks/status'
|
import { status } from '@/pages/menuPages/withdraw/information/hooks/status'
|
||||||
import {toNumber} from "lodash";
|
import {toNumber} from "lodash";
|
||||||
import { withdrawPageIntro } from '@/utils/adminHelpCopy'
|
import { withdrawPageIntro } from '@/utils/adminHelpCopy'
|
||||||
|
import { usePageActionAccess } from '@/composables/usePageActionAccess'
|
||||||
|
|
||||||
|
const { can } = usePageActionAccess()
|
||||||
const withdrawId = ref(null)
|
const withdrawId = ref(null)
|
||||||
const sts = ref()
|
const sts = ref()
|
||||||
const statusList = computed(() => {
|
const statusList = computed(() => {
|
||||||
|
|||||||
@@ -4,12 +4,30 @@ import { getMyInformation } from '@/api/user'
|
|||||||
import { get } from 'lodash'
|
import { get } from 'lodash'
|
||||||
import { isDeployAdmin } from '@/utils/deployAdmin'
|
import { isDeployAdmin } from '@/utils/deployAdmin'
|
||||||
import { hasSmsPanelAccess, isSmsBillingEnabled } from '@/utils/smsPanelAccess'
|
import { hasSmsPanelAccess, isSmsBillingEnabled } from '@/utils/smsPanelAccess'
|
||||||
|
import { setAdminAccessState } from '@/utils/adminAccessState'
|
||||||
|
import { parsePageAccess } from '@/utils/adminPageAccess'
|
||||||
|
|
||||||
export const useUserStore = defineStore('user', () => {
|
export const useUserStore = defineStore('user', () => {
|
||||||
const userInfo = ref<User | undefined>(undefined)
|
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() {
|
async function getMyInfo() {
|
||||||
try {
|
try {
|
||||||
userInfo.value = await getMyInformation()
|
userInfo.value = await getMyInformation()
|
||||||
|
syncPageAccess()
|
||||||
return userInfo.value
|
return userInfo.value
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
@@ -37,6 +55,11 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
|
|
||||||
const isDeployAdminUser = computed(() => isDeployAdmin(userInfo.value?.mobile))
|
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 smsPanelAccess = computed(() => hasSmsPanelAccess(userInfo.value))
|
||||||
const smsBillingEnabled = computed(() => isSmsBillingEnabled(userInfo.value))
|
const smsBillingEnabled = computed(() => isSmsBillingEnabled(userInfo.value))
|
||||||
|
|
||||||
@@ -48,6 +71,7 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
canAccess,
|
canAccess,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
isDeployAdminUser,
|
isDeployAdminUser,
|
||||||
|
hasFullPageAccess,
|
||||||
smsPanelAccess,
|
smsPanelAccess,
|
||||||
smsBillingEnabled
|
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