feat: routines module; super admin subscription badge, 7-day trial and lifetime plan

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-06 19:54:38 +03:30
co-authored by Cursor
parent 3de2d0fa67
commit b3784818fc
58 changed files with 13018 additions and 18 deletions
+41 -1
View File
@@ -38,6 +38,7 @@ const hubStack = ref<NavHubKey[]>([]);
const isMobileNav = ref(false);
const globalSearch = ref('');
const activityBadge = ref(0);
const routineBadge = ref(0);
function pathIn(prefixes: string[]) {
return prefixes.some(
@@ -67,6 +68,7 @@ const salesActive = computed(() =>
);
const activitiesActive = computed(() => pathIn(['/activities']));
const notesActive = computed(() => route.path === '/notes' || route.path.startsWith('/notes/'));
const routinesActive = computed(() => pathIn(['/routines']));
const conversationsActive = computed(() => pathIn(['/conversations']));
const supportActive = computed(() => pathIn(['/tickets']));
const automationsActive = computed(
@@ -100,6 +102,7 @@ const settingsActive = computed(() =>
);
const moreSectionActive = computed(
() =>
routinesActive.value ||
notesActive.value ||
conversationsActive.value ||
supportActive.value ||
@@ -285,6 +288,16 @@ async function loadActivityBadge() {
}
}
async function loadRoutineBadge() {
if (!moduleAllowed('routines')) return;
try {
const { data } = await api.get<{ remaining: number; overdue: number }>('/routine-tasks/badge');
routineBadge.value = data.remaining + data.overdue;
} catch {
routineBadge.value = 0;
}
}
async function loadChatBadge() {
if (!moduleAllowed('conversations')) return;
try {
@@ -374,9 +387,11 @@ onMounted(() => {
}
void refreshNotifs();
void loadActivityBadge();
void loadRoutineBadge();
void loadChatBadge();
pollTimer = setInterval(() => {
void refreshNotifs();
void loadRoutineBadge();
void refreshUnreadTotal().catch(() => undefined);
}, 45000);
mobileMq = window.matchMedia('(max-width: 900px)');
@@ -510,6 +525,19 @@ onUnmounted(() => {
</button>
</div>
<RouterLink
v-if="moduleAllowed('routines')"
to="/routines"
class="nav-item"
title="کارهای روتین"
:class="{ 'router-link-active': routinesActive }"
@click="closeHub(); closeMenu()"
>
<UiIcon name="refresh" :size="collapsed ? 20 : 18" />
<span>کارهای روتین</span>
<span v-if="routineBadge" class="nav-badge">{{ toFaDigits(routineBadge) }}</span>
</RouterLink>
<RouterLink
v-if="moduleAllowed('conversations')"
to="/conversations"
@@ -725,7 +753,8 @@ onUnmounted(() => {
:title="`انقضا: ${subscription.expiresAt}`"
>
<UiIcon name="clock" :size="15" />
<span>
<span v-if="subscription.planCode === 'lifetime'">اشتراک دائمی</span>
<span v-else>
{{ toFaDigits(subscription.daysRemaining) }} روز باقی‌مانده
</span>
</div>
@@ -858,6 +887,17 @@ onUnmounted(() => {
<button class="round-btn sm" type="button" aria-label="بستن" @click="closeMore">✕</button>
</header>
<nav class="more-body">
<RouterLink
v-if="moduleAllowed('routines')"
to="/routines"
class="nav-item"
:class="{ 'router-link-active': routinesActive }"
@click="closeHub(); closeMore()"
>
<UiIcon name="refresh" :size="18" />
<span>کارهای روتین</span>
<span v-if="routineBadge" class="nav-badge">{{ toFaDigits(routineBadge) }}</span>
</RouterLink>
<RouterLink to="/notes" class="nav-item" @click="closeHub(); closeMore()">
<UiIcon name="document" :size="18" />
<span>یادداشت‌ها</span>