Files
boursecu_admin/src/pages/dashboard/components/DashboardQuickActionsBar.vue
T

489 lines
14 KiB
Vue

<template>
<template v-if="isMobile">
<button
type="button"
class="dashboard-operations-trigger"
:class="{ 'is-open': menuOpen }"
@click="menuOpen = true"
>
<BaseIcon icon="vuesax-linear:setting-2" :size="18" />
<span>عملیات</span>
<BaseIcon
icon="vuesax-linear:arrow-down"
:size="14"
class="dashboard-operations-trigger__chevron"
/>
</button>
<BasicActionSheet
v-model:visible="menuOpen"
title="عملیات سریع"
compact
drawer-height="auto"
:show-button="false"
>
<div class="dash-quick-sheet">
<header class="dash-quick-sheet__header">
<h3 class="dash-quick-sheet__title">عملیات سریع</h3>
<p class="dash-quick-sheet__subtitle">دسترسی سریع به ابزارهای میز کار</p>
</header>
<button
type="button"
class="dash-quick-sheet__featured"
@click="runAction('wallet')"
>
<span class="dash-quick-sheet__featured-icon">
<BaseIcon icon="vuesax-linear:empty-wallet" :size="22" />
</span>
<span class="dash-quick-sheet__featured-text">
<span class="dash-quick-sheet__featured-label">شارژ / دشارژ کیف پول</span>
<span class="dash-quick-sheet__featured-hint">{{ metalLabel.manageGoldCash() }}</span>
</span>
<BaseIcon icon="vuesax-linear:arrow-left-2" :size="16" class="dash-quick-sheet__featured-arrow" />
</button>
<section v-for="group in mobileMenuGroups" :key="group.title" class="dash-quick-sheet__section">
<p class="dash-quick-sheet__section-title">{{ group.title }}</p>
<div class="dash-quick-sheet__list">
<button
v-for="item in group.items"
:key="item.key"
type="button"
class="dash-quick-sheet__item"
:class="[
item.tone ? `dash-quick-sheet__item--${item.tone}` : '',
{ 'dash-quick-sheet__item--nav': item.nav },
]"
:disabled="item.disabled"
@click="runAction(item.key)"
>
<span class="dash-quick-sheet__badge" :class="`dash-quick-sheet__badge--${item.tone ?? 'slate'}`">
<BaseIcon :icon="item.icon" :size="18" />
</span>
<span class="dash-quick-sheet__item-body">
<span class="dash-quick-sheet__item-label">{{ item.label }}</span>
<span v-if="item.hint" class="dash-quick-sheet__item-hint">{{ item.hint }}</span>
</span>
<span v-if="item.badge" class="dash-quick-sheet__count">{{ item.badge }}</span>
<BaseIcon
v-if="item.nav"
icon="vuesax-linear:arrow-left-2"
:size="15"
class="dash-quick-sheet__item-arrow"
/>
</button>
</div>
</section>
</div>
</BasicActionSheet>
</template>
<div v-else class="dashboard-quick-actions-bar">
<DashboardQuickActionBtn
v-for="item in actionItems"
:key="item.key"
:icon="item.icon"
:label="item.shortLabel"
:variant="item.variant"
:loading="item.loading"
:disabled="item.disabled"
:to="item.to"
@click="item.onClick?.()"
>
<template v-if="item.key === 'restore'">
{{ item.shortLabel }}
<span v-if="productSnapshotCount" class="mr-0.5 opacity-80">({{ productSnapshotCount }})</span>
</template>
</DashboardQuickActionBtn>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import type { RouteLocationRaw } from 'vue-router'
import BasicActionSheet from '@/components/BasicActionSheet.vue'
import DashboardQuickActionBtn from '@/pages/dashboard/components/DashboardQuickActionBtn.vue'
import { isMobile } from '@core/utils/is'
import { metalLabel } from '@/utils/adminMetalLabels'
type ActionVariant = 'default' | 'accent' | 'danger' | 'primary'
type ItemTone = 'slate' | 'emerald' | 'rose' | 'amber' | 'sky' | 'violet'
type ActionItem = {
key: string
label: string
shortLabel: string
icon: string
variant?: ActionVariant
danger?: boolean
disabled?: boolean
loading?: boolean
to?: RouteLocationRaw
onClick?: () => void
}
type MobileMenuItem = {
key: string
label: string
hint?: string
badge?: string
icon: string
tone?: ItemTone
disabled?: boolean
nav?: boolean
}
type MobileMenuGroup = {
title: string
items: MobileMenuItem[]
}
const props = defineProps<{
quickActionLoading: boolean
hasProductSnapshot: boolean
productSnapshotCount: number
}>()
const emit = defineEmits<{
wallet: []
deactivateAll: []
restoreProducts: []
closeAllBuy: []
closeAllSell: []
syncPrices: []
syncShow: []
}>()
const router = useRouter()
const menuOpen = ref(false)
const actionItems = computed<ActionItem[]>(() => [
{
key: 'wallet',
label: 'شارژ / دشارژ کیف پول',
shortLabel: 'کیف پول',
icon: 'vuesax-linear:empty-wallet',
variant: 'accent',
onClick: () => emit('wallet'),
},
{
key: 'deactivate-all',
label: 'بستن همه محصولات',
shortLabel: 'بستن همه محصولات',
icon: 'vuesax-linear:close-circle',
variant: 'danger',
danger: true,
loading: props.quickActionLoading,
onClick: () => emit('deactivateAll'),
},
{
key: 'restore',
label: 'بازگردانی محصولات قبلی',
shortLabel: 'بازگردانی محصولات',
icon: 'vuesax-linear:refresh-circle',
disabled: !props.hasProductSnapshot,
loading: props.quickActionLoading,
onClick: () => emit('restoreProducts'),
},
{
key: 'close-buy',
label: 'بستن خرید همه محصولات',
shortLabel: 'بستن خرید',
icon: 'vuesax-linear:money-receive',
loading: props.quickActionLoading,
onClick: () => emit('closeAllBuy'),
},
{
key: 'close-sell',
label: 'بستن فروش همه محصولات',
shortLabel: 'بستن فروش',
icon: 'vuesax-linear:money-send',
loading: props.quickActionLoading,
onClick: () => emit('closeAllSell'),
},
{
key: 'sync-prices',
label: 'همگام‌سازی قیمت محصولات',
shortLabel: 'همگام‌سازی قیمت',
icon: 'vuesax-linear:coin',
loading: props.quickActionLoading,
onClick: () => emit('syncPrices'),
},
{
key: 'sync-show',
label: 'بروزرسانی نمایش محصولات',
shortLabel: 'بروز نمایش',
icon: 'vuesax-linear:eye',
loading: props.quickActionLoading,
onClick: () => emit('syncShow'),
},
{
key: 'products',
label: 'مدیریت محصولات',
shortLabel: 'محصولات',
icon: 'vuesax-linear:box',
to: { name: 'productsMain_limitedOrder' },
},
{
key: 'orders',
label: 'لیست سفارشات',
shortLabel: 'سفارشات',
icon: 'vuesax-linear:receipt-2',
variant: 'primary',
to: { name: 'ordersMain' },
},
])
const mobileMenuGroups = computed<MobileMenuGroup[]>(() => [
{
title: 'محصولات',
items: [
{
key: 'deactivate-all',
label: 'بستن همه محصولات',
icon: 'vuesax-linear:close-circle',
tone: 'rose',
disabled: props.quickActionLoading,
},
{
key: 'restore',
label: 'بازگردانی محصولات',
hint: props.hasProductSnapshot ? 'بازگشت به وضعیت قبلی' : 'ابتدا محصولات را ببندید',
icon: 'vuesax-linear:refresh-circle',
tone: 'sky',
badge: props.productSnapshotCount ? String(props.productSnapshotCount) : undefined,
disabled: !props.hasProductSnapshot || props.quickActionLoading,
},
{
key: 'close-buy',
label: 'بستن خرید همه',
icon: 'vuesax-linear:money-receive',
tone: 'slate',
disabled: props.quickActionLoading,
},
{
key: 'close-sell',
label: 'بستن فروش همه',
icon: 'vuesax-linear:money-send',
tone: 'slate',
disabled: props.quickActionLoading,
},
{
key: 'sync-prices',
label: 'همگام‌سازی قیمت',
icon: 'vuesax-linear:coin',
tone: 'amber',
disabled: props.quickActionLoading,
},
{
key: 'sync-show',
label: 'بروزرسانی نمایش',
icon: 'vuesax-linear:eye',
tone: 'violet',
disabled: props.quickActionLoading,
},
],
},
{
title: 'صفحات',
items: [
{
key: 'products',
label: 'مدیریت محصولات',
icon: 'vuesax-linear:box',
tone: 'slate',
nav: true,
},
{
key: 'orders',
label: 'لیست سفارشات',
icon: 'vuesax-linear:receipt-2',
tone: 'amber',
nav: true,
},
],
},
])
const mobileActions: Record<string, () => void> = {
wallet: () => emit('wallet'),
'deactivate-all': () => emit('deactivateAll'),
restore: () => emit('restoreProducts'),
'close-buy': () => emit('closeAllBuy'),
'close-sell': () => emit('closeAllSell'),
'sync-prices': () => emit('syncPrices'),
'sync-show': () => emit('syncShow'),
products: () => void router.push({ name: 'productsMain_limitedOrder' }),
orders: () => void router.push({ name: 'ordersMain' }),
}
function runAction(key: string) {
menuOpen.value = false
mobileActions[key]?.()
}
</script>
<style scoped>
.dashboard-operations-trigger {
@apply inline-flex h-9 w-full items-center justify-center gap-2 rounded-lg border border-white/30 bg-white px-3.5 text-sm font-semibold text-slate-800 shadow-md transition-all;
appearance: none;
}
.dashboard-operations-trigger:hover {
@apply bg-slate-50;
}
.dashboard-operations-trigger.is-open {
@apply border-amber-300 bg-amber-50 text-slate-900;
}
.dashboard-operations-trigger__chevron {
@apply opacity-60 transition-transform duration-200;
}
.dashboard-operations-trigger.is-open .dashboard-operations-trigger__chevron {
@apply rotate-180 opacity-80;
}
.dashboard-quick-actions-bar {
display: flex;
max-width: 100%;
flex-wrap: wrap;
align-items: center;
justify-content: flex-start;
gap: 0.375rem;
}
@media (min-width: 768px) and (max-width: 1279px) {
.dashboard-quick-actions-bar {
width: 100%;
justify-content: flex-start;
}
}
.dash-quick-sheet {
@apply pb-2;
}
.dash-quick-sheet__header {
@apply mb-4 px-1 text-center;
}
.dash-quick-sheet__title {
@apply text-base font-bold text-slate-800 dark:text-slate-100;
}
.dash-quick-sheet__subtitle {
@apply mt-0.5 text-xs text-slate-500;
}
.dash-quick-sheet__featured {
@apply mb-5 flex w-full items-center gap-3 rounded-2xl bg-gradient-to-l from-emerald-600 to-teal-600 p-3.5 text-right text-white shadow-lg shadow-emerald-900/15 transition-transform active:scale-[0.98];
appearance: none;
border: 0;
}
.dash-quick-sheet__featured-icon {
@apply flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-white/20 backdrop-blur-sm;
}
.dash-quick-sheet__featured-text {
@apply flex min-w-0 flex-1 flex-col gap-0.5;
}
.dash-quick-sheet__featured-label {
@apply text-sm font-bold leading-tight;
}
.dash-quick-sheet__featured-hint {
@apply text-[11px] leading-tight text-white/75;
}
.dash-quick-sheet__featured-arrow {
@apply shrink-0 opacity-70;
}
.dash-quick-sheet__section + .dash-quick-sheet__section {
@apply mt-4;
}
.dash-quick-sheet__section-title {
@apply mb-2 px-1 text-[11px] font-semibold tracking-wide text-slate-400;
}
.dash-quick-sheet__list {
@apply overflow-hidden rounded-2xl border border-slate-100 bg-slate-50/80 dark:border-slate-700/60 dark:bg-slate-800/40;
}
.dash-quick-sheet__item {
@apply flex w-full items-center gap-3 border-0 bg-transparent px-3 py-3 text-right transition-colors;
appearance: none;
}
.dash-quick-sheet__item + .dash-quick-sheet__item {
@apply border-t border-slate-100 dark:border-slate-700/60;
}
.dash-quick-sheet__item:active:not(:disabled) {
@apply bg-white dark:bg-slate-700/50;
}
.dash-quick-sheet__item:disabled {
@apply cursor-not-allowed opacity-45;
}
.dash-quick-sheet__badge {
@apply flex h-9 w-9 shrink-0 items-center justify-center rounded-xl;
}
.dash-quick-sheet__badge--slate {
@apply bg-slate-200/80 text-slate-600 dark:bg-slate-700 dark:text-slate-300;
}
.dash-quick-sheet__badge--emerald {
@apply bg-emerald-100 text-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-300;
}
.dash-quick-sheet__badge--rose {
@apply bg-rose-100 text-rose-600 dark:bg-rose-900/40 dark:text-rose-300;
}
.dash-quick-sheet__badge--amber {
@apply bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300;
}
.dash-quick-sheet__badge--sky {
@apply bg-sky-100 text-sky-600 dark:bg-sky-900/40 dark:text-sky-300;
}
.dash-quick-sheet__badge--violet {
@apply bg-violet-100 text-violet-600 dark:bg-violet-900/40 dark:text-violet-300;
}
.dash-quick-sheet__item-body {
@apply flex min-w-0 flex-1 flex-col gap-0.5;
}
.dash-quick-sheet__item-label {
@apply text-[13px] font-semibold leading-tight text-slate-800 dark:text-slate-100;
}
.dash-quick-sheet__item-hint {
@apply text-[11px] leading-tight text-slate-400;
}
.dash-quick-sheet__item--rose .dash-quick-sheet__item-label {
@apply text-rose-700 dark:text-rose-300;
}
.dash-quick-sheet__count {
@apply rounded-full bg-slate-200/90 px-2 py-0.5 text-[10px] font-bold text-slate-600 dark:bg-slate-600 dark:text-slate-200;
}
.dash-quick-sheet__item-arrow {
@apply shrink-0 text-slate-300 dark:text-slate-500;
}
</style>