Improve Leads mobile UX and nest leads under Customers nav.
Restore the 5-tab mobile bar, add responsive lead cards with long-press select, and fix stats/filters layout.
This commit is contained in:
@@ -1,17 +1,29 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, onUnmounted } from 'vue';
|
import { onMounted, onUnmounted } from 'vue';
|
||||||
import UiIcon from '@/components/UiIcon.vue';
|
import UiIcon from '@/components/UiIcon.vue';
|
||||||
import type { NavHubDef } from '@/lib/nav-hubs';
|
import type { NavHubDef, NavHubKey } from '@/lib/nav-hubs';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
hub: NavHubDef;
|
hub: NavHubDef;
|
||||||
|
/** اگر از هاب والد آمده (مثلاً مشتریان → لیدها) */
|
||||||
|
canBack?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
close: [];
|
close: [];
|
||||||
|
back: [];
|
||||||
go: [to: string];
|
go: [to: string];
|
||||||
|
openHub: [key: NavHubKey];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
function onCard(card: NavHubDef['cards'][number]) {
|
||||||
|
if (card.hub) {
|
||||||
|
emit('openHub', card.hub);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit('go', card.to);
|
||||||
|
}
|
||||||
|
|
||||||
function onKey(event: KeyboardEvent) {
|
function onKey(event: KeyboardEvent) {
|
||||||
if (event.key === 'Escape') emit('close');
|
if (event.key === 'Escape') emit('close');
|
||||||
}
|
}
|
||||||
@@ -29,7 +41,18 @@ onUnmounted(() => {
|
|||||||
<section class="hub" role="dialog" aria-modal="true" :aria-label="hub.title">
|
<section class="hub" role="dialog" aria-modal="true" :aria-label="hub.title">
|
||||||
<header class="head">
|
<header class="head">
|
||||||
<div class="copy">
|
<div class="copy">
|
||||||
<h1>{{ hub.title }}</h1>
|
<div class="title-row">
|
||||||
|
<button
|
||||||
|
v-if="canBack"
|
||||||
|
type="button"
|
||||||
|
class="back-btn"
|
||||||
|
aria-label="بازگشت"
|
||||||
|
@click="emit('back')"
|
||||||
|
>
|
||||||
|
<UiIcon name="chevron" :size="16" class="back-chev" />
|
||||||
|
</button>
|
||||||
|
<h1>{{ hub.title }}</h1>
|
||||||
|
</div>
|
||||||
<p>{{ hub.desc }}</p>
|
<p>{{ hub.desc }}</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="head-ico" :class="hub.tint" aria-hidden="true">
|
<span class="head-ico" :class="hub.tint" aria-hidden="true">
|
||||||
@@ -40,10 +63,10 @@ onUnmounted(() => {
|
|||||||
<div class="grid">
|
<div class="grid">
|
||||||
<button
|
<button
|
||||||
v-for="card in hub.cards"
|
v-for="card in hub.cards"
|
||||||
:key="card.to + card.title"
|
:key="(card.hub ?? card.to) + card.title"
|
||||||
class="card"
|
class="card"
|
||||||
type="button"
|
type="button"
|
||||||
@click="emit('go', card.to)"
|
@click="onCard(card)"
|
||||||
>
|
>
|
||||||
<span class="card-top">
|
<span class="card-top">
|
||||||
<span class="card-title">
|
<span class="card-title">
|
||||||
@@ -105,6 +128,36 @@ onUnmounted(() => {
|
|||||||
color: #0f172a;
|
color: #0f172a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
width: 1.75rem;
|
||||||
|
height: 1.75rem;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #64748b;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn:hover {
|
||||||
|
background: #e2e8f0;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-chev {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
.copy p {
|
.copy p {
|
||||||
margin: 0.15rem 0 0;
|
margin: 0.15rem 0 0;
|
||||||
color: #94a3b8;
|
color: #94a3b8;
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ const SIDEBAR_KEY = 'mento_sidebar_collapsed';
|
|||||||
const collapsed = ref(false);
|
const collapsed = ref(false);
|
||||||
|
|
||||||
const openHubKey = ref<NavHubKey | null>(null);
|
const openHubKey = ref<NavHubKey | null>(null);
|
||||||
|
const hubStack = ref<NavHubKey[]>([]);
|
||||||
|
const isMobileNav = ref(false);
|
||||||
const globalSearch = ref('');
|
const globalSearch = ref('');
|
||||||
const activityBadge = ref(0);
|
const activityBadge = ref(0);
|
||||||
|
|
||||||
@@ -44,10 +46,12 @@ function pathIn(prefixes: string[]) {
|
|||||||
const dashboardActive = computed(() => route.path === '/');
|
const dashboardActive = computed(() => route.path === '/');
|
||||||
const loyaltyActive = computed(() => pathIn(['/loyalty']));
|
const loyaltyActive = computed(() => pathIn(['/loyalty']));
|
||||||
const reportsActive = computed(() => pathIn(['/reports']));
|
const reportsActive = computed(() => pathIn(['/reports']));
|
||||||
const customersActive = computed(() => pathIn(['/contacts']));
|
|
||||||
const leadsActive = computed(() =>
|
const leadsActive = computed(() =>
|
||||||
pathIn(['/leads', '/settings/lead-stages', '/settings/lead-forms']),
|
pathIn(['/leads', '/settings/lead-stages', '/settings/lead-forms']),
|
||||||
);
|
);
|
||||||
|
const customersActive = computed(
|
||||||
|
() => pathIn(['/contacts']) || (isMobileNav.value && leadsActive.value),
|
||||||
|
);
|
||||||
const salesActive = computed(() =>
|
const salesActive = computed(() =>
|
||||||
pathIn([
|
pathIn([
|
||||||
'/pipeline',
|
'/pipeline',
|
||||||
@@ -111,23 +115,6 @@ function moduleAllowed(module?: string) {
|
|||||||
return enabledModules.value.includes(module as never);
|
return enabledModules.value.includes(module as never);
|
||||||
}
|
}
|
||||||
|
|
||||||
const openHub = computed<NavHubDef | null>(() => {
|
|
||||||
if (!openHubKey.value) return null;
|
|
||||||
const hub = NAV_HUBS.find((item) => item.key === openHubKey.value);
|
|
||||||
if (!hub) return null;
|
|
||||||
return {
|
|
||||||
...hub,
|
|
||||||
cards: hub.cards.filter(
|
|
||||||
(card) =>
|
|
||||||
(!card.admin || canSettings.value) && moduleAllowed(card.module),
|
|
||||||
),
|
|
||||||
shortcuts: hub.shortcuts.filter(
|
|
||||||
(item) =>
|
|
||||||
(!item.admin || canSettings.value) && moduleAllowed(item.module),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const hubVisible = computed(() => {
|
const hubVisible = computed(() => {
|
||||||
const map: Record<string, boolean> = {};
|
const map: Record<string, boolean> = {};
|
||||||
for (const hub of NAV_HUBS) {
|
for (const hub of NAV_HUBS) {
|
||||||
@@ -139,6 +126,41 @@ const hubVisible = computed(() => {
|
|||||||
return map;
|
return map;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const openHub = computed<NavHubDef | null>(() => {
|
||||||
|
if (!openHubKey.value) return null;
|
||||||
|
const hub = NAV_HUBS.find((item) => item.key === openHubKey.value);
|
||||||
|
if (!hub) return null;
|
||||||
|
const cards = hub.cards.filter(
|
||||||
|
(card) =>
|
||||||
|
(!card.admin || canSettings.value) && moduleAllowed(card.module),
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
hub.key === 'customers' &&
|
||||||
|
isMobileNav.value &&
|
||||||
|
hubVisible.value.leads
|
||||||
|
) {
|
||||||
|
cards.push({
|
||||||
|
to: '/leads',
|
||||||
|
hub: 'leads',
|
||||||
|
title: 'لیدها',
|
||||||
|
desc: 'مخاطبین بالقوه قبل از تبدیل به مشتری',
|
||||||
|
icon: 'user-plus',
|
||||||
|
tint: 'sky',
|
||||||
|
module: 'leads',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...hub,
|
||||||
|
cards,
|
||||||
|
shortcuts: hub.shortcuts.filter(
|
||||||
|
(item) =>
|
||||||
|
(!item.admin || canSettings.value) && moduleAllowed(item.module),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const hubCanBack = computed(() => hubStack.value.length > 0);
|
||||||
|
|
||||||
function givenName(fullName: string): string {
|
function givenName(fullName: string): string {
|
||||||
const parts = fullName.trim().split(/\s+/).filter(Boolean);
|
const parts = fullName.trim().split(/\s+/).filter(Boolean);
|
||||||
if (parts.length <= 1) return fullName.trim();
|
if (parts.length <= 1) return fullName.trim();
|
||||||
@@ -177,12 +199,31 @@ watch([profileFullName, collapsed], () => {
|
|||||||
|
|
||||||
function closeHub() {
|
function closeHub() {
|
||||||
openHubKey.value = null;
|
openHubKey.value = null;
|
||||||
|
hubStack.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function onHubClick(key: NavHubKey) {
|
function onHubClick(key: NavHubKey) {
|
||||||
|
hubStack.value = [];
|
||||||
openHubKey.value = openHubKey.value === key ? null : key;
|
openHubKey.value = openHubKey.value === key ? null : key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openNestedHub(key: NavHubKey) {
|
||||||
|
if (openHubKey.value) {
|
||||||
|
hubStack.value = [...hubStack.value, openHubKey.value];
|
||||||
|
}
|
||||||
|
openHubKey.value = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
function backHub() {
|
||||||
|
const prev = hubStack.value[hubStack.value.length - 1];
|
||||||
|
if (!prev) {
|
||||||
|
closeHub();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hubStack.value = hubStack.value.slice(0, -1);
|
||||||
|
openHubKey.value = prev;
|
||||||
|
}
|
||||||
|
|
||||||
function openTabHub(key: NavHubKey) {
|
function openTabHub(key: NavHubKey) {
|
||||||
moreOpen.value = false;
|
moreOpen.value = false;
|
||||||
notifOpen.value = false;
|
notifOpen.value = false;
|
||||||
@@ -233,7 +274,11 @@ function closeNotif() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onMobileMqChange(event: MediaQueryListEvent) {
|
function onMobileMqChange(event: MediaQueryListEvent) {
|
||||||
if (!event.matches) closeMore();
|
isMobileNav.value = event.matches;
|
||||||
|
if (!event.matches) {
|
||||||
|
closeMore();
|
||||||
|
hubStack.value = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onLogout() {
|
function onLogout() {
|
||||||
@@ -358,6 +403,7 @@ onMounted(() => {
|
|||||||
if (meta) profileNameObserver.observe(meta);
|
if (meta) profileNameObserver.observe(meta);
|
||||||
}
|
}
|
||||||
mobileMq = window.matchMedia('(max-width: 900px)');
|
mobileMq = window.matchMedia('(max-width: 900px)');
|
||||||
|
isMobileNav.value = mobileMq.matches;
|
||||||
mobileMq.addEventListener('change', onMobileMqChange);
|
mobileMq.addEventListener('change', onMobileMqChange);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -717,7 +763,14 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<div v-if="openHub" class="hub-layer">
|
<div v-if="openHub" class="hub-layer">
|
||||||
<button class="hub-dim" type="button" aria-label="بستن منو" @click="closeHub" />
|
<button class="hub-dim" type="button" aria-label="بستن منو" @click="closeHub" />
|
||||||
<NavHubPanel :hub="openHub" @close="closeHub" @go="goHub" />
|
<NavHubPanel
|
||||||
|
:hub="openHub"
|
||||||
|
:can-back="hubCanBack"
|
||||||
|
@close="closeHub"
|
||||||
|
@back="backHub"
|
||||||
|
@go="goHub"
|
||||||
|
@open-hub="openNestedHub"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="mobile-tabbar mobile-only" aria-label="منوی اصلی">
|
<nav class="mobile-tabbar mobile-only" aria-label="منوی اصلی">
|
||||||
@@ -733,23 +786,21 @@ onUnmounted(() => {
|
|||||||
<button
|
<button
|
||||||
class="tab"
|
class="tab"
|
||||||
type="button"
|
type="button"
|
||||||
:class="{ on: !moreOpen && (customersActive || openHubKey === 'customers') }"
|
:class="{
|
||||||
:aria-expanded="openHubKey === 'customers'"
|
on:
|
||||||
|
!moreOpen &&
|
||||||
|
(customersActive ||
|
||||||
|
openHubKey === 'customers' ||
|
||||||
|
(isMobileNav && openHubKey === 'leads')),
|
||||||
|
}"
|
||||||
|
:aria-expanded="
|
||||||
|
openHubKey === 'customers' || (isMobileNav && openHubKey === 'leads')
|
||||||
|
"
|
||||||
@click="openTabHub('customers')"
|
@click="openTabHub('customers')"
|
||||||
>
|
>
|
||||||
<UiIcon name="users" :size="20" />
|
<UiIcon name="users" :size="20" />
|
||||||
<span>مشتریان</span>
|
<span>مشتریان</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
class="tab"
|
|
||||||
type="button"
|
|
||||||
:class="{ on: !moreOpen && (leadsActive || openHubKey === 'leads') }"
|
|
||||||
:aria-expanded="openHubKey === 'leads'"
|
|
||||||
@click="openTabHub('leads')"
|
|
||||||
>
|
|
||||||
<UiIcon name="user-plus" :size="20" />
|
|
||||||
<span>لیدها</span>
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
class="tab"
|
class="tab"
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ export type NavHubCard = {
|
|||||||
tint: NavHubTint;
|
tint: NavHubTint;
|
||||||
admin?: boolean;
|
admin?: boolean;
|
||||||
module?: OrgModuleKey;
|
module?: OrgModuleKey;
|
||||||
|
/** اگر ست باشد، بهجای مسیر، هاب تو در تو باز میشود (موبایل: مشتریان → لیدها) */
|
||||||
|
hub?: NavHubKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NavHubShortcut = {
|
export type NavHubShortcut = {
|
||||||
|
|||||||
@@ -500,6 +500,64 @@ function toggleAll(checked: boolean) {
|
|||||||
selected.value = new Set(data.value?.items.map((l) => l.id) ?? []);
|
selected.value = new Set(data.value?.items.map((l) => l.id) ?? []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LONG_PRESS_MS = 450;
|
||||||
|
const LONG_PRESS_MOVE_PX = 12;
|
||||||
|
let cardPressTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
let cardPressId: string | null = null;
|
||||||
|
let cardPressTriggered = false;
|
||||||
|
let cardPressX = 0;
|
||||||
|
let cardPressY = 0;
|
||||||
|
|
||||||
|
function clearCardPress() {
|
||||||
|
if (cardPressTimer) clearTimeout(cardPressTimer);
|
||||||
|
cardPressTimer = null;
|
||||||
|
cardPressId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCardPointerDown(leadId: string, event: PointerEvent) {
|
||||||
|
if (event.pointerType === 'mouse' && event.button !== 0) return;
|
||||||
|
clearCardPress();
|
||||||
|
cardPressTriggered = false;
|
||||||
|
cardPressId = leadId;
|
||||||
|
cardPressX = event.clientX;
|
||||||
|
cardPressY = event.clientY;
|
||||||
|
cardPressTimer = setTimeout(() => {
|
||||||
|
cardPressTriggered = true;
|
||||||
|
cardPressTimer = null;
|
||||||
|
toggleOne(leadId, !selected.value.has(leadId));
|
||||||
|
try {
|
||||||
|
navigator.vibrate?.(18);
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}, LONG_PRESS_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCardPointerMove(event: PointerEvent) {
|
||||||
|
if (!cardPressTimer || !cardPressId) return;
|
||||||
|
const dx = event.clientX - cardPressX;
|
||||||
|
const dy = event.clientY - cardPressY;
|
||||||
|
if (dx * dx + dy * dy > LONG_PRESS_MOVE_PX * LONG_PRESS_MOVE_PX) {
|
||||||
|
clearCardPress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCardPointerEnd() {
|
||||||
|
clearCardPress();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCardClick(lead: LeadDto) {
|
||||||
|
if (cardPressTriggered) {
|
||||||
|
cardPressTriggered = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selected.value.has(lead.id)) {
|
||||||
|
toggleOne(lead.id, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
goDetail(lead.id);
|
||||||
|
}
|
||||||
|
|
||||||
function mergeFacets(items: LeadDto[]) {
|
function mergeFacets(items: LeadDto[]) {
|
||||||
const cities = new Set(cityOptions.value);
|
const cities = new Set(cityOptions.value);
|
||||||
const sources = new Set(sourceOptions.value);
|
const sources = new Set(sourceOptions.value);
|
||||||
@@ -1079,30 +1137,32 @@ onBeforeUnmount(() => {
|
|||||||
<UiIcon name="search" :size="16" />
|
<UiIcon name="search" :size="16" />
|
||||||
<input v-model="q" type="search" placeholder="جستجو..." />
|
<input v-model="q" type="search" placeholder="جستجو..." />
|
||||||
</form>
|
</form>
|
||||||
<select
|
<div class="filter-selects">
|
||||||
class="filter-select"
|
<select
|
||||||
:value="cityFilter"
|
class="filter-select"
|
||||||
@change="setCityFilter(($event.target as HTMLSelectElement).value)"
|
:value="cityFilter"
|
||||||
>
|
@change="setCityFilter(($event.target as HTMLSelectElement).value)"
|
||||||
<option value="">همه مناطق</option>
|
>
|
||||||
<option v-for="c in cityOptions" :key="c" :value="c">{{ c }}</option>
|
<option value="">همه مناطق</option>
|
||||||
</select>
|
<option v-for="c in cityOptions" :key="c" :value="c">{{ c }}</option>
|
||||||
<select
|
</select>
|
||||||
class="filter-select"
|
<select
|
||||||
:value="sourceFilter"
|
class="filter-select"
|
||||||
@change="setSourceFilter(($event.target as HTMLSelectElement).value)"
|
:value="sourceFilter"
|
||||||
>
|
@change="setSourceFilter(($event.target as HTMLSelectElement).value)"
|
||||||
<option value="">همه منابع</option>
|
>
|
||||||
<option v-for="s in sourceOptions" :key="s" :value="s">{{ s }}</option>
|
<option value="">همه منابع</option>
|
||||||
</select>
|
<option v-for="s in sourceOptions" :key="s" :value="s">{{ s }}</option>
|
||||||
<select
|
</select>
|
||||||
class="filter-select"
|
<select
|
||||||
:value="stageId"
|
class="filter-select"
|
||||||
@change="setStageFilter(($event.target as HTMLSelectElement).value)"
|
:value="stageId"
|
||||||
>
|
@change="setStageFilter(($event.target as HTMLSelectElement).value)"
|
||||||
<option value="">همه وضعیت</option>
|
>
|
||||||
<option v-for="s in stages" :key="s.id" :value="s.id">{{ s.name }}</option>
|
<option value="">همه وضعیت</option>
|
||||||
</select>
|
<option v-for="s in stages" :key="s.id" :value="s.id">{{ s.name }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="filters-end">
|
<div class="filters-end">
|
||||||
<div class="pop">
|
<div class="pop">
|
||||||
@@ -1189,10 +1249,135 @@ onBeforeUnmount(() => {
|
|||||||
<p v-else-if="loading" class="muted">در حال بارگذاری…</p>
|
<p v-else-if="loading" class="muted">در حال بارگذاری…</p>
|
||||||
|
|
||||||
<template v-else-if="data?.items.length">
|
<template v-else-if="data?.items.length">
|
||||||
|
<div class="cards">
|
||||||
|
<article
|
||||||
|
v-for="(lead, idx) in data.items"
|
||||||
|
:key="`m-${lead.id}`"
|
||||||
|
class="m-card"
|
||||||
|
:class="{ selected: selected.has(lead.id) }"
|
||||||
|
:data-tint="stageTintForLead(lead)"
|
||||||
|
@pointerdown="onCardPointerDown(lead.id, $event)"
|
||||||
|
@pointermove="onCardPointerMove"
|
||||||
|
@pointerup="onCardPointerEnd"
|
||||||
|
@pointercancel="onCardPointerEnd"
|
||||||
|
@contextmenu.prevent
|
||||||
|
@click="onCardClick(lead)"
|
||||||
|
>
|
||||||
|
<div class="m-head">
|
||||||
|
<span class="m-badge" :data-tint="stageTintForLead(lead)">
|
||||||
|
{{ toFaDigits(rangeFrom + idx) }}
|
||||||
|
</span>
|
||||||
|
<UiDropdownMenu
|
||||||
|
:open="logoMenuId === `m:${lead.id}`"
|
||||||
|
align="start"
|
||||||
|
@update:open="(v) => (logoMenuId = v ? `m:${lead.id}` : null)"
|
||||||
|
>
|
||||||
|
<template #trigger="{ toggle }">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="row-ico"
|
||||||
|
:class="{ busy: logoBusyId === lead.id }"
|
||||||
|
:data-tint="stageTintForLead(lead)"
|
||||||
|
:title="lead.hasLogo ? 'تغییر لوگو' : 'افزودن لوگو'"
|
||||||
|
@click.stop="toggle"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="logoUrls[lead.id]"
|
||||||
|
:src="logoUrls[lead.id]"
|
||||||
|
:alt="lead.title"
|
||||||
|
class="row-logo"
|
||||||
|
/>
|
||||||
|
<UiIcon v-else :name="rowIcon(lead)" :size="16" />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<button type="button" @click="openLogoPicker(lead.id)">
|
||||||
|
{{ lead.hasLogo ? 'تغییر لوگو' : 'افزودن لوگو' }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="lead.hasLogo"
|
||||||
|
type="button"
|
||||||
|
class="danger-item"
|
||||||
|
@click="removeLeadLogo(lead.id)"
|
||||||
|
>
|
||||||
|
حذف لوگو
|
||||||
|
</button>
|
||||||
|
</UiDropdownMenu>
|
||||||
|
<div class="m-title">
|
||||||
|
<strong class="name">{{ lead.title }}</strong>
|
||||||
|
<span v-if="lead.contactName" class="sub">{{ lead.contactName }}</span>
|
||||||
|
</div>
|
||||||
|
<UiDropdownMenu
|
||||||
|
:open="rowMenuId === `m:${lead.id}`"
|
||||||
|
align="end"
|
||||||
|
@update:open="(v) => (rowMenuId = v ? `m:${lead.id}` : null)"
|
||||||
|
>
|
||||||
|
<template #trigger="{ toggle }">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="act-btn"
|
||||||
|
title="بیشتر"
|
||||||
|
@click.stop="toggle"
|
||||||
|
>
|
||||||
|
<UiIcon name="dots-v" :size="16" />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<button type="button" @click="goDetail(lead.id)">مشاهده</button>
|
||||||
|
<button type="button" @click="openEdit(lead)">ویرایش</button>
|
||||||
|
<a :href="`tel:${lead.mobile}`">تماس</a>
|
||||||
|
<button type="button" class="danger-item" @click="deleteLead(lead)">
|
||||||
|
حذف
|
||||||
|
</button>
|
||||||
|
</UiDropdownMenu>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="m-meta-row">
|
||||||
|
<button
|
||||||
|
v-if="lead.mobile"
|
||||||
|
type="button"
|
||||||
|
class="m-line m-phone"
|
||||||
|
dir="ltr"
|
||||||
|
@click.stop="callLead(lead)"
|
||||||
|
>
|
||||||
|
<UiIcon name="phone" :size="13" />
|
||||||
|
<span>{{ formatPhone(lead.mobile) }}</span>
|
||||||
|
</button>
|
||||||
|
<p v-if="lead.city || lead.region" class="m-line m-place">
|
||||||
|
<UiIcon name="building" :size="13" />
|
||||||
|
<span>{{ [lead.city, lead.region].filter(Boolean).join(' · ') }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="m-foot">
|
||||||
|
<span v-if="lead.assigneeName" class="assignee">
|
||||||
|
<span class="avatar">{{ initials(lead.assigneeName) }}</span>
|
||||||
|
{{ lead.assigneeName }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="muted-dash">بدون مسئول</span>
|
||||||
|
<span class="m-activity">{{ lastActivity(lead).when }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="m-pills">
|
||||||
|
<span class="stage-pill" :data-tint="stageTintForLead(lead)">
|
||||||
|
{{ lead.stageName }}
|
||||||
|
</span>
|
||||||
|
<span v-if="lead.source" class="source-pill">{{ lead.source }}</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table class="data-table">
|
<table class="data-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th class="check-col">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="allSelected"
|
||||||
|
@click.stop
|
||||||
|
@change="toggleAll(($event.target as HTMLInputElement).checked)"
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
<th class="num-col">#</th>
|
||||||
<th class="icon-col"></th>
|
<th class="icon-col"></th>
|
||||||
<th>نام / شرکت</th>
|
<th>نام / شرکت</th>
|
||||||
<th>شماره تماس</th>
|
<th>شماره تماس</th>
|
||||||
@@ -1202,23 +1387,23 @@ onBeforeUnmount(() => {
|
|||||||
<th>مسئول</th>
|
<th>مسئول</th>
|
||||||
<th>آخرین فعالیت</th>
|
<th>آخرین فعالیت</th>
|
||||||
<th class="actions-col">اقدامات</th>
|
<th class="actions-col">اقدامات</th>
|
||||||
<th class="check-col">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
:checked="allSelected"
|
|
||||||
@click.stop
|
|
||||||
@change="toggleAll(($event.target as HTMLInputElement).checked)"
|
|
||||||
/>
|
|
||||||
</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
v-for="lead in data.items"
|
v-for="(lead, idx) in data.items"
|
||||||
:key="lead.id"
|
:key="lead.id"
|
||||||
class="row-click"
|
class="row-click"
|
||||||
@click="goDetail(lead.id)"
|
@click="goDetail(lead.id)"
|
||||||
>
|
>
|
||||||
|
<td class="check-col" @click.stop>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="selected.has(lead.id)"
|
||||||
|
@change="toggleOne(lead.id, ($event.target as HTMLInputElement).checked)"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td class="num-col">{{ toFaDigits(rangeFrom + idx) }}</td>
|
||||||
<td class="icon-col" @click.stop>
|
<td class="icon-col" @click.stop>
|
||||||
<UiDropdownMenu
|
<UiDropdownMenu
|
||||||
:open="logoMenuId === lead.id"
|
:open="logoMenuId === lead.id"
|
||||||
@@ -1287,9 +1472,6 @@ onBeforeUnmount(() => {
|
|||||||
<strong>{{ lastActivity(lead).label }}</strong>
|
<strong>{{ lastActivity(lead).label }}</strong>
|
||||||
<span class="sub">{{ lastActivity(lead).when }}</span>
|
<span class="sub">{{ lastActivity(lead).when }}</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="activity-ico">
|
|
||||||
<UiIcon :name="lastActivity(lead).icon" :size="14" />
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="actions-col" @click.stop>
|
<td class="actions-col" @click.stop>
|
||||||
@@ -1334,13 +1516,6 @@ onBeforeUnmount(() => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="check-col" @click.stop>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
:checked="selected.has(lead.id)"
|
|
||||||
@change="toggleOne(lead.id, ($event.target as HTMLInputElement).checked)"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -1764,22 +1939,85 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.stats {
|
.stats {
|
||||||
flex-wrap: wrap;
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 0.65rem;
|
||||||
|
overflow: visible;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* جدا کردن جفت تا هر کارت یک سلول گرید شود */
|
||||||
|
.stat-pair {
|
||||||
|
display: contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-total,
|
.stat-total,
|
||||||
.stat-pair,
|
.stat-cell {
|
||||||
.stat-cell.alone {
|
width: 100%;
|
||||||
flex: 1 1 calc(50% - 0.4rem);
|
min-width: 0;
|
||||||
min-width: 9rem;
|
height: 5.5rem;
|
||||||
|
min-height: 5.5rem;
|
||||||
|
max-height: 5.5rem;
|
||||||
|
flex: none;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 520px) {
|
.stat-total {
|
||||||
.stat-total,
|
padding: 0.65rem 0.75rem;
|
||||||
.stat-pair,
|
gap: 0.55rem;
|
||||||
.stat-cell.alone {
|
}
|
||||||
flex: 1 1 100%;
|
|
||||||
|
.stat-total-ico {
|
||||||
|
width: 2.15rem;
|
||||||
|
height: 2.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-total .stat-label {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-total .stat-num {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-cell {
|
||||||
|
padding: 1rem 0.45rem 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-cell .stat-label {
|
||||||
|
white-space: normal;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-cell .stat-num {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-pair .stat-cell {
|
||||||
|
border: 1px solid #e5e7eb !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-pair .stat-cell.wide {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-pair .stat-cell + .stat-cell {
|
||||||
|
border-inline-start: 1px solid #e5e7eb !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-pair .stat-cell:hover:not(:disabled),
|
||||||
|
.stat-pair .stat-cell.on {
|
||||||
|
border-color: color-mix(in srgb, var(--accent) 35%, #e5e7eb) !important;
|
||||||
|
box-shadow: 0 0 0 2px var(--accent-soft) !important;
|
||||||
|
background: color-mix(in srgb, var(--accent-soft) 70%, #fff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1803,6 +2041,10 @@ onBeforeUnmount(() => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-selects {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
.filters-label {
|
.filters-label {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1854,6 +2096,70 @@ onBeforeUnmount(() => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.filters {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-start {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-label {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
align-self: center;
|
||||||
|
padding-inline-end: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
grid-column: 2;
|
||||||
|
grid-row: 1;
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search input {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-selects {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 0.5rem;
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
grid-row: 2;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-select {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
padding-inline: 0.45rem;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-end {
|
||||||
|
width: 100%;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-end .sort-btn {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.sort-btn {
|
.sort-btn {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1967,6 +2273,213 @@ onBeforeUnmount(() => {
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cards {
|
||||||
|
display: none;
|
||||||
|
gap: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-card {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
gap: 0.55rem;
|
||||||
|
padding: 0.85rem;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
|
||||||
|
touch-action: pan-y;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-card:active {
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-card.selected {
|
||||||
|
background: #ecfdf5;
|
||||||
|
border-color: #6ee7b7;
|
||||||
|
box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-card.selected:active {
|
||||||
|
background: #d1fae5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-badge {
|
||||||
|
width: 1.55rem;
|
||||||
|
height: 1.55rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 800;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
background: #e2e8f0;
|
||||||
|
color: #334155;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-badge[data-tint='mint'],
|
||||||
|
.m-badge[data-tint='teal'] {
|
||||||
|
background: #d1fae5;
|
||||||
|
color: #047857;
|
||||||
|
}
|
||||||
|
.m-badge[data-tint='amber'] {
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #b45309;
|
||||||
|
}
|
||||||
|
.m-badge[data-tint='peach'] {
|
||||||
|
background: #ffedd5;
|
||||||
|
color: #c2410c;
|
||||||
|
}
|
||||||
|
.m-badge[data-tint='sky'] {
|
||||||
|
background: #e0f2fe;
|
||||||
|
color: #0369a1;
|
||||||
|
}
|
||||||
|
.m-badge[data-tint='violet'] {
|
||||||
|
background: #ede9fe;
|
||||||
|
color: #6d28d9;
|
||||||
|
}
|
||||||
|
.m-badge[data-tint='rose'] {
|
||||||
|
background: #fee2e2;
|
||||||
|
color: #b91c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.45rem;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-title {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 0.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-title .name {
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-title .sub {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-meta-row {
|
||||||
|
display: flex;
|
||||||
|
direction: ltr;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-line {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #475569;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-meta-row .m-place {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
max-width: 55%;
|
||||||
|
direction: rtl;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-meta-row .m-phone {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
max-width: 55%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.m-line {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
font: inherit;
|
||||||
|
text-align: start;
|
||||||
|
cursor: pointer;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-phone {
|
||||||
|
color: #059669;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-phone:active {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-line :deep(svg) {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-phone :deep(svg) {
|
||||||
|
color: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-line span {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-pills {
|
||||||
|
display: flex;
|
||||||
|
direction: ltr;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-pills .stage-pill {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
max-width: 55%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-pills .source-pill {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
max-width: 55%;
|
||||||
|
margin-left: auto;
|
||||||
|
direction: rtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-foot {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.45rem;
|
||||||
|
padding-top: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-activity {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #64748b;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.data-table {
|
.data-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
@@ -2001,6 +2514,14 @@ onBeforeUnmount(() => {
|
|||||||
width: 2.75rem;
|
width: 2.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.num-col {
|
||||||
|
width: 2.5rem;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--muted);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
.check-col {
|
.check-col {
|
||||||
width: 2.5rem;
|
width: 2.5rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -2161,8 +2682,6 @@ onBeforeUnmount(() => {
|
|||||||
.activity {
|
.activity {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.55rem;
|
|
||||||
justify-content: space-between;
|
|
||||||
min-width: 8rem;
|
min-width: 8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2171,18 +2690,6 @@ onBeforeUnmount(() => {
|
|||||||
font-size: 0.84rem;
|
font-size: 0.84rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.activity-ico {
|
|
||||||
width: 1.7rem;
|
|
||||||
height: 1.7rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: #f1f5f9;
|
|
||||||
color: #64748b;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-actions {
|
.row-actions {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -2216,6 +2723,16 @@ onBeforeUnmount(() => {
|
|||||||
background: #d1fae5;
|
background: #d1fae5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.table-wrap {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cards {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.ui-dd-panel) .danger-item,
|
:deep(.ui-dd-panel) .danger-item,
|
||||||
.danger-item {
|
.danger-item {
|
||||||
color: #b91c1c !important;
|
color: #b91c1c !important;
|
||||||
@@ -2463,10 +2980,6 @@ onBeforeUnmount(() => {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search input {
|
|
||||||
width: 6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.actions) .btn-full {
|
:deep(.actions) .btn-full {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user