Files
CRM_Montzar/apps/web/src/components/SmsHeaderWallet.vue
T

241 lines
6.0 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue';
import type { SmsWalletSummaryDto } from '@mento/shared';
import { api } from '@/lib/api';
import { toFaDigits } from '@/lib/jalali';
import { useAuthStore } from '@/stores/auth';
const auth = useAuthStore();
const summary = ref<SmsWalletSummaryDto | null>(null);
const loading = ref(false);
const chargeOpen = ref(false);
const warnOpen = ref(false);
const NEGATIVE_KEY = 'mento_sms_negative_warning_shown';
const visible = computed(() => Boolean(summary.value?.billingEnabled));
const balanceLabel = computed(() => {
const n = summary.value?.balanceRials ?? 0;
return toFaDigits(new Intl.NumberFormat('fa-IR').format(n));
});
const toneClass = computed(() => {
const level = summary.value?.balanceLevel;
if (level === 'critical') return 'critical';
if (level === 'warning') return 'warning';
if ((summary.value?.balanceRials ?? 0) < 0) return 'negative';
return 'ok';
});
async function load() {
if (!auth.isAuthenticated || !auth.user?.organizationId) {
summary.value = null;
return;
}
loading.value = true;
try {
const { data } = await api.get<SmsWalletSummaryDto>('/sms-wallet/summary');
summary.value = data;
syncWarn(data);
} catch {
summary.value = null;
} finally {
loading.value = false;
}
}
function syncWarn(data: SmsWalletSummaryDto) {
if (!data.billingEnabled || data.balanceRials >= 0) {
warnOpen.value = false;
try {
sessionStorage.removeItem(NEGATIVE_KEY);
} catch {
/* ignore */
}
return;
}
try {
if (sessionStorage.getItem(NEGATIVE_KEY) === '1') return;
sessionStorage.setItem(NEGATIVE_KEY, '1');
warnOpen.value = true;
} catch {
warnOpen.value = true;
}
}
watch(
() => auth.user?.organizationId,
() => {
void load();
},
);
onMounted(() => {
void load();
});
defineExpose({ refresh: load });
</script>
<template>
<div v-if="visible" class="sms-wallet">
<div class="chip" :class="toneClass" :title="loading ? '…' : undefined">
<span>اعتبار پیامک: {{ balanceLabel }} ریال</span>
<button type="button" class="charge" @click="chargeOpen = true">شارژ</button>
</div>
<div v-if="chargeOpen" class="layer">
<div class="backdrop" @click="chargeOpen = false" />
<div class="modal" role="dialog" aria-label="شارژ پیامک">
<header>
<strong>شارژ اعتبار پیامک</strong>
<button type="button" class="x" @click="chargeOpen = false">×</button>
</header>
<p class="muted">
برای شارژ با پشتیبانی هماهنگ کنید یا فیش واریز را ارسال کنید.
</p>
<template v-if="summary?.chargeInfo">
<dl>
<div v-if="summary.chargeInfo.card">
<dt>کارت</dt>
<dd dir="ltr">{{ summary.chargeInfo.card }}</dd>
</div>
<div v-if="summary.chargeInfo.iban">
<dt>شبا</dt>
<dd dir="ltr">{{ summary.chargeInfo.iban }}</dd>
</div>
<div v-if="summary.chargeInfo.accountHolder">
<dt>صاحب حساب</dt>
<dd>{{ summary.chargeInfo.accountHolder }}</dd>
</div>
</dl>
<p class="instruction">{{ summary.chargeInfo.instruction }}</p>
</template>
<button type="button" class="ok" @click="chargeOpen = false">متوجه شدم</button>
</div>
</div>
<div v-if="warnOpen" class="layer">
<div class="backdrop" @click="warnOpen = false" />
<div class="modal warn" role="dialog" aria-label="هشدار موجودی منفی">
<header>
<strong>موجودی پیامک منفی است</strong>
<button type="button" class="x" @click="warnOpen = false">×</button>
</header>
<p>
اعتبار پیامک محیط شما منفی شده
({{ balanceLabel }} ریال). ارسال تا رسیدن به آستانه قطع یا مسدودسازی دستی ادامه دارد؛ لطفاً
شارژ کنید.
</p>
<button type="button" class="ok" @click="warnOpen = false">متوجه شدم</button>
</div>
</div>
</div>
</template>
<style scoped>
.chip {
display: flex;
align-items: center;
gap: 0.45rem;
white-space: nowrap;
border-radius: 12px;
border: 1px solid #a7f3d0;
background: rgba(236, 253, 245, 0.7);
padding: 0.35rem 0.65rem;
font-size: 0.82rem;
font-weight: 600;
color: #065f46;
}
.chip.warning {
border-color: #fde68a;
background: rgba(255, 251, 235, 0.85);
color: #92400e;
}
.chip.critical,
.chip.negative {
border-color: #fecaca;
background: rgba(254, 242, 242, 0.9);
color: #991b1b;
}
.charge {
border: 0;
background: #059669;
color: #fff;
border-radius: 8px;
padding: 0.2rem 0.55rem;
font: inherit;
font-size: 0.78rem;
cursor: pointer;
}
.chip.critical .charge,
.chip.negative .charge,
.chip.warning .charge {
background: #b45309;
}
.layer {
position: fixed;
inset: 0;
z-index: 80;
display: grid;
place-items: center;
}
.backdrop {
position: absolute;
inset: 0;
background: rgba(15, 23, 42, 0.35);
}
.modal {
position: relative;
width: min(420px, calc(100vw - 2rem));
background: #fff;
border-radius: 16px;
padding: 1rem 1.1rem;
display: grid;
gap: 0.75rem;
box-shadow: 0 20px 50px rgba(15, 23, 42, 0.18);
}
.modal header {
display: flex;
justify-content: space-between;
align-items: center;
}
.x {
border: 0;
background: transparent;
font-size: 1.3rem;
cursor: pointer;
line-height: 1;
}
.muted,
.instruction {
margin: 0;
color: var(--muted);
font-size: 0.9rem;
line-height: 1.6;
}
dl {
margin: 0;
display: grid;
gap: 0.45rem;
}
dl > div {
display: flex;
justify-content: space-between;
gap: 0.75rem;
font-size: 0.9rem;
}
dt {
color: var(--muted);
}
.ok {
border: 0;
background: #0f766e;
color: #fff;
border-radius: 10px;
padding: 0.55rem 0.9rem;
font: inherit;
cursor: pointer;
}
</style>