Fix contacts page overlapping and squeeze on mobile layouts.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -244,16 +244,18 @@ onMounted(load);
|
||||
<template>
|
||||
<section class="page" @click="closePopups">
|
||||
<header class="page-head">
|
||||
<div>
|
||||
<div class="head-copy">
|
||||
<h1>مخاطبین</h1>
|
||||
<p class="lead">مدیریت و مشاهده اشخاص و شرکتها</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="btn btn-ghost" type="button" :disabled="busy" @click.stop="exportExcel">
|
||||
خروجی اکسل
|
||||
<button class="btn btn-ghost excel-btn" type="button" :disabled="busy" @click.stop="exportExcel">
|
||||
<span class="btn-full">خروجی اکسل</span>
|
||||
<span class="btn-short">خروجی</span>
|
||||
</button>
|
||||
<button class="btn btn-ghost" type="button" :disabled="busy" @click.stop="pickImportFile">
|
||||
ورود از اکسل
|
||||
<button class="btn btn-ghost excel-btn" type="button" :disabled="busy" @click.stop="pickImportFile">
|
||||
<span class="btn-full">ورود از اکسل</span>
|
||||
<span class="btn-short">ورود</span>
|
||||
</button>
|
||||
<input
|
||||
ref="fileInput"
|
||||
@@ -286,16 +288,6 @@ onMounted(load);
|
||||
</nav>
|
||||
|
||||
<div class="toolbar">
|
||||
<div class="pop">
|
||||
<button class="tool" type="button" @click.stop="sortOpen = !sortOpen; filterOpen = false">
|
||||
<UiIcon name="trend" :size="16" />
|
||||
مرتبسازی
|
||||
</button>
|
||||
<ul v-if="sortOpen" class="menu">
|
||||
<li><button type="button" :class="{ on: sort === 'name' }" @click.stop="setSort('name')">نام</button></li>
|
||||
<li><button type="button" :class="{ on: sort === 'newest' }" @click.stop="setSort('newest')">جدیدترین</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
<form class="search" @submit.prevent="onSearch">
|
||||
<UiIcon name="search" :size="16" />
|
||||
<input
|
||||
@@ -304,21 +296,33 @@ onMounted(load);
|
||||
:placeholder="tab === 'people' ? 'جستجو در اشخاص…' : 'جستجو در شرکتها…'"
|
||||
/>
|
||||
</form>
|
||||
<div v-if="tab === 'people'" class="pop">
|
||||
<button class="tool" type="button" @click.stop="filterOpen = !filterOpen; sortOpen = false">
|
||||
<UiIcon name="filter" :size="16" />
|
||||
فیلتر
|
||||
</button>
|
||||
<ul v-if="filterOpen" class="menu">
|
||||
<li>
|
||||
<button type="button" :class="{ on: moodFilter === 'all' }" @click.stop="setMood('all')">همه</button>
|
||||
</li>
|
||||
<li v-for="mood in PERSON_MOODS" :key="mood">
|
||||
<button type="button" :class="{ on: moodFilter === mood }" @click.stop="setMood(mood)">
|
||||
{{ moodLabel(mood) }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="toolbar-tools">
|
||||
<div class="pop">
|
||||
<button class="tool" type="button" @click.stop="sortOpen = !sortOpen; filterOpen = false">
|
||||
<UiIcon name="trend" :size="16" />
|
||||
<span class="tool-label">مرتبسازی</span>
|
||||
</button>
|
||||
<ul v-if="sortOpen" class="menu">
|
||||
<li><button type="button" :class="{ on: sort === 'name' }" @click.stop="setSort('name')">نام</button></li>
|
||||
<li><button type="button" :class="{ on: sort === 'newest' }" @click.stop="setSort('newest')">جدیدترین</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="tab === 'people'" class="pop">
|
||||
<button class="tool" type="button" @click.stop="filterOpen = !filterOpen; sortOpen = false">
|
||||
<UiIcon name="filter" :size="16" />
|
||||
<span class="tool-label">فیلتر</span>
|
||||
</button>
|
||||
<ul v-if="filterOpen" class="menu">
|
||||
<li>
|
||||
<button type="button" :class="{ on: moodFilter === 'all' }" @click.stop="setMood('all')">همه</button>
|
||||
</li>
|
||||
<li v-for="mood in PERSON_MOODS" :key="mood">
|
||||
<button type="button" :class="{ on: moodFilter === mood }" @click.stop="setMood(mood)">
|
||||
{{ moodLabel(mood) }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -329,13 +333,12 @@ onMounted(load);
|
||||
<template v-else-if="tab === 'people'">
|
||||
<div v-if="people?.items.length" class="cards">
|
||||
<article v-for="p in people.items" :key="`m-${p.id}`" class="m-card">
|
||||
<CustomerMoodFace :mood="moodOf(p)" :size="52" />
|
||||
<CustomerMoodFace :mood="moodOf(p)" :size="48" />
|
||||
<div class="m-body">
|
||||
<div class="m-top">
|
||||
<RouterLink class="name" :to="{ name: 'person-detail', params: { id: p.id } }">
|
||||
{{ p.fullName }}
|
||||
</RouterLink>
|
||||
<i class="dot" :class="moodOf(p)" />
|
||||
<button class="more" type="button" @click.stop="menuId = menuId === p.id ? null : p.id">
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
</button>
|
||||
@@ -343,16 +346,16 @@ onMounted(load);
|
||||
<p class="role">
|
||||
{{ [p.position, p.company?.name].filter(Boolean).join(' • ') || 'بدون سمت' }}
|
||||
</p>
|
||||
<p v-if="p.mobile || p.phone" class="line">
|
||||
<p v-if="p.mobile || p.phone" class="line" dir="ltr">
|
||||
<UiIcon name="phone" :size="13" />
|
||||
{{ p.mobile || p.phone }}
|
||||
<span>{{ p.mobile || p.phone }}</span>
|
||||
</p>
|
||||
<p v-if="p.email" class="line">
|
||||
<p v-if="p.email" class="line" dir="ltr">
|
||||
<UiIcon name="mail" :size="13" />
|
||||
{{ p.email }}
|
||||
<span>{{ p.email }}</span>
|
||||
</p>
|
||||
<span class="badge" :class="moodOf(p)">{{ moodLabel(moodOf(p)) }}</span>
|
||||
</div>
|
||||
<span class="badge" :class="moodOf(p)">{{ moodLabel(moodOf(p)) }}</span>
|
||||
<ul v-if="menuId === p.id" class="menu row-menu">
|
||||
<li>
|
||||
<RouterLink :to="{ name: 'person-detail', params: { id: p.id } }">پرونده</RouterLink>
|
||||
@@ -441,11 +444,19 @@ onMounted(load);
|
||||
<article v-for="c in companies.items" :key="`mc-${c.id}`" class="m-card">
|
||||
<span class="co-ico"><UiIcon name="building" :size="22" /></span>
|
||||
<div class="m-body">
|
||||
<RouterLink class="name" :to="{ name: 'company-detail', params: { id: c.id } }">
|
||||
{{ c.name }}
|
||||
</RouterLink>
|
||||
<p v-if="c.phone" class="line"><UiIcon name="phone" :size="13" /> {{ c.phone }}</p>
|
||||
<p v-if="c.email" class="line"><UiIcon name="mail" :size="13" /> {{ c.email }}</p>
|
||||
<div class="m-top">
|
||||
<RouterLink class="name" :to="{ name: 'company-detail', params: { id: c.id } }">
|
||||
{{ c.name }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
<p v-if="c.phone" class="line" dir="ltr">
|
||||
<UiIcon name="phone" :size="13" />
|
||||
<span>{{ c.phone }}</span>
|
||||
</p>
|
||||
<p v-if="c.email" class="line" dir="ltr">
|
||||
<UiIcon name="mail" :size="13" />
|
||||
<span>{{ c.email }}</span>
|
||||
</p>
|
||||
<p class="role">افراد: {{ toFaDigits(c.peopleCount) }}</p>
|
||||
</div>
|
||||
</article>
|
||||
@@ -499,19 +510,34 @@ onMounted(load);
|
||||
.page {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow-x: clip;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.page > * {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.page-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
gap: 0.75rem 1rem;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.head-copy {
|
||||
min-width: 0;
|
||||
flex: 1 1 12rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 1.7rem;
|
||||
font-size: clamp(1.3rem, 4vw, 1.7rem);
|
||||
}
|
||||
|
||||
.lead,
|
||||
@@ -519,6 +545,8 @@ h1 {
|
||||
.role,
|
||||
.line {
|
||||
color: #64748b;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.lead {
|
||||
@@ -527,9 +555,16 @@ h1 {
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
gap: 0.45rem;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.btn-short {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
@@ -540,25 +575,35 @@ h1 {
|
||||
color: #fff;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 8px 18px rgba(13, 148, 136, 0.28);
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 1.2rem;
|
||||
gap: 0.35rem 1.2rem;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.tabs::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tabs button {
|
||||
border: 0;
|
||||
background: none;
|
||||
padding: 0.55rem 0;
|
||||
padding: 0.55rem 0.1rem;
|
||||
font: inherit;
|
||||
color: #94a3b8;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tabs .on {
|
||||
@@ -577,10 +622,18 @@ h1 {
|
||||
display: flex;
|
||||
gap: 0.55rem;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.toolbar-tools {
|
||||
display: flex;
|
||||
gap: 0.45rem;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.search {
|
||||
flex: 1;
|
||||
flex: 1 1 12rem;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -632,7 +685,8 @@ h1 {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 14px 36px rgba(15, 23, 42, 0.14);
|
||||
min-width: 12rem;
|
||||
min-width: min(12rem, calc(100vw - 2rem));
|
||||
max-width: calc(100vw - 1.5rem);
|
||||
}
|
||||
|
||||
.menu button,
|
||||
@@ -671,11 +725,13 @@ h1 {
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 8px 22px rgba(15, 23, 42, 0.04);
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
min-width: 640px;
|
||||
}
|
||||
|
||||
th,
|
||||
@@ -705,6 +761,8 @@ th {
|
||||
.name {
|
||||
color: #0f766e;
|
||||
font-weight: 800;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.mail {
|
||||
@@ -724,6 +782,10 @@ th {
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex-shrink: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
@@ -774,40 +836,68 @@ strong.newcomer {
|
||||
.m-card {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
gap: 0.7rem;
|
||||
align-items: start;
|
||||
background: #fff;
|
||||
border-radius: 18px;
|
||||
padding: 0.85rem;
|
||||
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.04);
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.m-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.m-top .name {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.m-body {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
gap: 0.28rem;
|
||||
}
|
||||
|
||||
.role,
|
||||
.line {
|
||||
margin: 0.2rem 0 0;
|
||||
margin: 0;
|
||||
font-size: 0.8rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
gap: 0.3rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.line :deep(svg),
|
||||
.role :deep(svg) {
|
||||
flex-shrink: 0;
|
||||
margin-top: 0.15rem;
|
||||
}
|
||||
|
||||
.line span {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.badge {
|
||||
align-self: start;
|
||||
justify-self: start;
|
||||
border-radius: 999px;
|
||||
padding: 0.28rem 0.6rem;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 800;
|
||||
margin-top: 0.25rem;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -832,18 +922,20 @@ strong.newcomer {
|
||||
}
|
||||
|
||||
.co-ico {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: #ecfdf5;
|
||||
color: #0f766e;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.row-menu {
|
||||
inset-inline-start: auto;
|
||||
inset-inline-end: 0;
|
||||
top: 2.4rem;
|
||||
}
|
||||
|
||||
.empty {
|
||||
@@ -868,6 +960,7 @@ strong.newcomer {
|
||||
display: flex;
|
||||
gap: 0.3rem;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pages button {
|
||||
@@ -889,32 +982,96 @@ strong.newcomer {
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
h1 {
|
||||
font-size: 1.35rem;
|
||||
.page {
|
||||
gap: 0.85rem;
|
||||
}
|
||||
|
||||
.page-head {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.actions .btn-ghost,
|
||||
.table-wrap,
|
||||
.pager {
|
||||
.actions .excel-btn .btn-full {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.actions .excel-btn .btn-short {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.actions .excel-btn {
|
||||
padding-inline: 0.7rem;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.search {
|
||||
flex: 0 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.toolbar-tools {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.toolbar-tools .pop {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.toolbar-tools .tool {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.table-wrap {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.pager {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
text-align: center;
|
||||
gap: 0.65rem;
|
||||
}
|
||||
|
||||
.pages {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.actions .excel-btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tool-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toolbar-tools .tool {
|
||||
width: auto;
|
||||
flex: 1 1 auto;
|
||||
min-width: 44px;
|
||||
justify-content: center;
|
||||
padding-inline: 0.65rem;
|
||||
}
|
||||
|
||||
.m-card {
|
||||
padding: 0.75rem;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user