Improve team settings responsive layout, narrower role panel, and mobile pagination.

EOF

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-12 15:31:58 +03:30
co-authored by Cursor
parent 57e0fdd600
commit f4f2311ae4
3 changed files with 485 additions and 172 deletions
@@ -778,5 +778,35 @@ select {
.prior-picker { .prior-picker {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.card-head {
flex-direction: column;
align-items: stretch;
}
.card-head :deep(.field),
.card-head :deep(input) {
max-width: none;
min-width: 0;
width: 100%;
}
.team header {
flex-direction: column;
align-items: stretch;
}
.team .actions {
width: 100%;
}
.vis-row {
align-items: start;
}
.vis-badge {
display: inline-block;
margin: 0.25rem 0 0;
}
} }
</style> </style>
+37 -2
View File
@@ -184,8 +184,43 @@ function onSizeChange(e: Event) {
@media (max-width: 900px) { @media (max-width: 900px) {
.ui-pagination { .ui-pagination {
flex-direction: column; flex-direction: column;
align-items: center; align-items: stretch;
gap: var(--sp-3); gap: 0.65rem;
}
.pagination-info {
width: 100%;
flex-wrap: nowrap;
justify-content: space-between;
gap: 0.35rem;
font-size: 0.78rem;
}
.page-count {
flex: 0 1 auto;
white-space: nowrap;
}
.size-picker {
margin-inline-start: 0;
flex: 0 0 auto;
gap: 0.25rem;
white-space: nowrap;
}
.size-picker span:last-child {
display: none;
}
.size-picker select {
height: 28px;
min-width: 3.25rem;
padding: 0 0.3rem;
}
.pagination-controls {
width: 100%;
justify-content: center;
} }
} }
</style> </style>
+418 -170
View File
@@ -9,7 +9,7 @@ import axios from 'axios';
import { api } from '@/lib/api'; import { api } from '@/lib/api';
import { useAuthStore } from '@/stores/auth'; import { useAuthStore } from '@/stores/auth';
import UiIcon from '@/components/UiIcon.vue'; import UiIcon from '@/components/UiIcon.vue';
import { UiDropdownMenu, UiModal } from '@/components/ui'; import { UiDropdownMenu, UiModal, UiPagination } from '@/components/ui';
import { import {
DATA_ACCESS_SECTIONS, DATA_ACCESS_SECTIONS,
ROLE_PERMISSION_ITEMS, ROLE_PERMISSION_ITEMS,
@@ -32,13 +32,14 @@ import {
formatJalaliNumeric, formatJalaliNumeric,
toFaDigits, toFaDigits,
} from '@/lib/jalali'; } from '@/lib/jalali';
import { PAGE_SIZE_OPTIONS, usePageSize } from '@/lib/pageSize';
import TeamsSettingsPanel from '@/components/TeamsSettingsPanel.vue'; import TeamsSettingsPanel from '@/components/TeamsSettingsPanel.vue';
import DataAccessPolicyPanel from '@/components/DataAccessPolicyPanel.vue'; import DataAccessPolicyPanel from '@/components/DataAccessPolicyPanel.vue';
type TabKey = 'users' | 'roles' | 'access' | 'teams'; type TabKey = 'users' | 'roles' | 'access' | 'teams';
type RoleColor = 'teal' | 'violet' | 'rose' | 'sky' | 'amber' | 'slate' | 'pink'; type RoleColor = 'teal' | 'violet' | 'rose' | 'sky' | 'amber' | 'slate' | 'pink';
const PAGE_SIZE = 10; const pageSize = usePageSize('team.users', 10);
const DEFAULT_AVATAR = '/brand/user-avatar.png'; const DEFAULT_AVATAR = '/brand/user-avatar.png';
const ROLE_COLOR_OPTIONS: Array<{ value: RoleColor; label: string }> = [ const ROLE_COLOR_OPTIONS: Array<{ value: RoleColor; label: string }> = [
{ value: 'teal', label: 'سبز' }, { value: 'teal', label: 'سبز' },
@@ -228,20 +229,23 @@ const filteredRoles = computed(() => {
); );
}); });
const pageCount = computed(() => Math.max(1, Math.ceil(filteredUsers.value.length / PAGE_SIZE))); const pageCount = computed(() =>
Math.max(1, Math.ceil(filteredUsers.value.length / pageSize.value)),
);
const pagedUsers = computed(() => { const pagedUsers = computed(() => {
const start = (page.value - 1) * PAGE_SIZE; const start = (page.value - 1) * pageSize.value;
return filteredUsers.value.slice(start, start + PAGE_SIZE); return filteredUsers.value.slice(start, start + pageSize.value);
}); });
const pageLabel = computed(() => { function setPage(next: number) {
const total = filteredUsers.value.length; page.value = next;
if (!total) return '۰ کاربر'; }
const from = (page.value - 1) * PAGE_SIZE + 1;
const to = Math.min(page.value * PAGE_SIZE, total); function onPageSize(size: number) {
return `${toFaDigits(from)} تا ${toFaDigits(to)} از ${toFaDigits(total)} کاربر`; pageSize.value = size;
}); page.value = 1;
}
const visibleMembers = computed(() => { const visibleMembers = computed(() => {
const role = selectedRole.value; const role = selectedRole.value;
@@ -930,10 +934,19 @@ onUnmounted(() => {
</header> </header>
<nav class="tabs" aria-label="بخش‌های مدیریت کاربران"> <nav class="tabs" aria-label="بخش‌های مدیریت کاربران">
<button type="button" :class="{ on: tab === 'users' }" @click="setTab('users')">لیست کاربران</button> <button type="button" :class="{ on: tab === 'users' }" @click="setTab('users')">
<span class="tab-full">لیست کاربران</span>
<span class="tab-short">کاربران</span>
</button>
<button type="button" :class="{ on: tab === 'roles' }" @click="setTab('roles')">نقش‌ها</button> <button type="button" :class="{ on: tab === 'roles' }" @click="setTab('roles')">نقش‌ها</button>
<button type="button" :class="{ on: tab === 'teams' }" @click="setTab('teams')">تیم‌ها و سطح دسترسی به پرونده‌ها</button> <button type="button" :class="{ on: tab === 'teams' }" @click="setTab('teams')">
<button type="button" :class="{ on: tab === 'access' }" @click="setTab('access')">تنظیمات دسترسی</button> <span class="tab-full">تیم‌ها و سطح دسترسی به پرونده‌ها</span>
<span class="tab-short">تیم‌ها و دسترسی</span>
</button>
<button type="button" :class="{ on: tab === 'access' }" @click="setTab('access')">
<span class="tab-full">تنظیمات دسترسی</span>
<span class="tab-short">دسترسی</span>
</button>
</nav> </nav>
<p v-if="error" class="error">{{ error }}</p> <p v-if="error" class="error">{{ error }}</p>
@@ -1228,25 +1241,25 @@ onUnmounted(() => {
<table v-table-cols="'team.users'"> <table v-table-cols="'team.users'">
<thead> <thead>
<tr> <tr>
<th>نام و نام خانوادگی</th> <th class="col-who">نام و نام خانوادگی</th>
<th>موبایل</th> <th class="col-mobile">موبایل</th>
<th class="col-role">نقش</th> <th class="col-role">نقش</th>
<th>پورسانت %</th> <th class="col-comm">پورسانت %</th>
<th>وضعیت</th> <th class="col-status">وضعیت</th>
<th>آخرین ورود</th> <th class="col-login">آخرین ورود</th>
<th v-if="canSetLogoutTime">ساعت خروج</th> <th v-if="canSetLogoutTime" class="col-logout">ساعت خروج</th>
<th class="ops" /> <th class="ops" />
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="user in pagedUsers" :key="user.id" @click="onUserActivate(user)"> <tr v-for="user in pagedUsers" :key="user.id" @click="onUserActivate(user)">
<td> <td class="col-who">
<div class="who"> <div class="who">
<img :src="avatarOf(user.id)" :alt="user.fullName" /> <img :src="avatarOf(user.id)" :alt="user.fullName" />
<strong>{{ user.fullName }}</strong> <strong class="who-name" :title="user.fullName">{{ user.fullName }}</strong>
</div> </div>
</td> </td>
<td class="muted" dir="ltr">{{ user.mobile || '—' }}</td> <td class="col-mobile muted" dir="ltr">{{ user.mobile || '—' }}</td>
<td class="col-role"> <td class="col-role">
<span <span
class="pill" class="pill"
@@ -1256,7 +1269,7 @@ onUnmounted(() => {
{{ user.orgRoleName || '—' }} {{ user.orgRoleName || '—' }}
</span> </span>
</td> </td>
<td @click.stop> <td class="col-comm" @click.stop>
<label class="comm-field"> <label class="comm-field">
<input <input
type="number" type="number"
@@ -1271,7 +1284,7 @@ onUnmounted(() => {
/> />
</label> </label>
</td> </td>
<td> <td class="col-status">
<span class="status" :class="{ off: user.isActive === false }"> <span class="status" :class="{ off: user.isActive === false }">
<i /> <i />
{{ user.isActive === false ? 'غیرفعال' : 'فعال' }} {{ user.isActive === false ? 'غیرفعال' : 'فعال' }}
@@ -1284,8 +1297,8 @@ onUnmounted(() => {
ورود با کد ورود با کد
</span> </span>
</td> </td>
<td class="muted">{{ formatLastLogin(user.lastLoginAt) }}</td> <td class="col-login muted">{{ formatLastLogin(user.lastLoginAt) }}</td>
<td v-if="canSetLogoutTime" @click.stop> <td v-if="canSetLogoutTime" class="col-logout" @click.stop>
<span v-if="user.role === 'owner'" class="muted">—</span> <span v-if="user.role === 'owner'" class="muted">—</span>
<span v-else class="logout-cell"> <span v-else class="logout-cell">
<button <button
@@ -1360,55 +1373,69 @@ onUnmounted(() => {
<article v-for="user in pagedUsers" :key="user.id" class="user-card" @click="onUserActivate(user)"> <article v-for="user in pagedUsers" :key="user.id" class="user-card" @click="onUserActivate(user)">
<div class="who"> <div class="who">
<img :src="avatarOf(user.id)" :alt="user.fullName" /> <img :src="avatarOf(user.id)" :alt="user.fullName" />
<div> <strong class="who-name" :title="user.fullName">{{ user.fullName }}</strong>
<strong>{{ user.fullName }}</strong> <span
<span class="pill"
class="pill" :data-color="user.orgRoleColor || 'teal'"
:data-color="user.orgRoleColor || 'teal'" :title="user.orgRoleName || undefined"
:title="user.orgRoleName || undefined" >
{{ user.orgRoleName || '—' }}
</span>
<div class="card-ops" @click.stop>
<UiDropdownMenu
:open="menuUserId === `m:${user.id}`"
align="end"
@update:open="(v) => (menuUserId = v ? `m:${user.id}` : null)"
> >
{{ user.orgRoleName || '—' }} <template #trigger="{ toggle }">
</span> <button type="button" class="ghost-ico" aria-label="عملیات" @click.stop="toggle">
<p v-if="user.mobile" class="muted mobile-line" dir="ltr">{{ user.mobile }}</p> <UiIcon name="dots-v" :size="16" />
</button>
</template>
<button
v-if="canInvite"
type="button"
@click="openEditUser(user)"
>
ویرایش مشخصات
</button>
<button
v-if="canInvite && user.role !== 'owner'"
type="button"
:class="{ on: user.loginOtpOnly }"
:title="LOGIN_OTP_ONLY_TIP"
@click="toggleLoginOtpOnly(user)"
>
{{ user.loginOtpOnly ? 'لغو ورود با کد' : 'ورود با کد' }}
</button>
<button type="button" @click="toggleStatus(user)">
{{ user.isActive === false ? 'فعال کردن' : 'غیرفعال کردن' }}
</button>
</UiDropdownMenu>
</div> </div>
</div> </div>
<div class="card-meta" @click.stop> <div class="card-meta" @click.stop>
<label class="comm-field"> <div class="card-meta-row">
<span>پورسانت %</span> <p class="muted mobile-line" dir="ltr">{{ user.mobile || '—' }}</p>
<input <label class="comm-field">
type="number" <span>پورسانت %</span>
min="0" <input
max="100" type="number"
step="0.1" min="0"
dir="ltr" max="100"
:value="user.commissionPercent ?? 0" step="0.1"
:disabled="!canInvite" dir="ltr"
:aria-label="`پورسانت ${user.fullName}`" :value="user.commissionPercent ?? 0"
@change="changeCommission(user, ($event.target as HTMLInputElement).value)" :disabled="!canInvite"
/> :aria-label="`پورسانت ${user.fullName}`"
</label> @change="changeCommission(user, ($event.target as HTMLInputElement).value)"
<span class="status" :class="{ off: user.isActive === false }"> />
<i /> </label>
{{ user.isActive === false ? 'غیرفعال' : 'فعال' }} <span class="status" :class="{ off: user.isActive === false }">
</span> <i />
<button {{ user.isActive === false ? 'غیرفعال' : 'فعال' }}
v-if="canInvite" </span>
type="button" </div>
class="btn ghost"
@click="openEditUser(user)"
>
ویرایش
</button>
<button
v-if="canInvite && user.role !== 'owner'"
type="button"
class="btn ghost"
:class="{ 'otp-on': user.loginOtpOnly }"
:title="LOGIN_OTP_ONLY_TIP"
@click="toggleLoginOtpOnly(user)"
>
{{ user.loginOtpOnly ? 'لغو ورود با کد' : 'ورود با کد' }}
</button>
</div> </div>
</article> </article>
<p v-if="!loading && !pagedUsers.length" class="empty">کاربری یافت نشد.</p> <p v-if="!loading && !pagedUsers.length" class="empty">کاربری یافت نشد.</p>
@@ -1439,22 +1466,16 @@ onUnmounted(() => {
<p v-if="!loading && !filteredRoles.length" class="empty">نقشی با این جستجو پیدا نشد.</p> <p v-if="!loading && !filteredRoles.length" class="empty">نقشی با این جستجو پیدا نشد.</p>
</div> </div>
<footer v-if="tab === 'users' && filteredUsers.length" class="pager"> <UiPagination
<span>{{ pageLabel }}</span> v-if="tab === 'users' && filteredUsers.length"
<div class="pages"> :page="page"
<button type="button" :disabled="page <= 1" @click="page -= 1">‹</button> :total-pages="pageCount"
<button :total-items="filteredUsers.length"
v-for="n in pageCount" :page-size="pageSize"
:key="n" :page-size-options="PAGE_SIZE_OPTIONS"
type="button" @update:page="setPage"
:class="{ on: n === page }" @update:page-size="onPageSize"
@click="page = n" />
>
{{ toFaDigits(n) }}
</button>
<button type="button" :disabled="page >= pageCount" @click="page += 1">›</button>
</div>
</footer>
</div> </div>
</div> </div>
@@ -1733,8 +1754,12 @@ h1 {
.tabs { .tabs {
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
gap: 1.75rem; gap: 1.25rem;
border-bottom: 1px solid #e2e8f0; border-bottom: 1px solid #e2e8f0;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: thin;
overscroll-behavior-x: contain;
} }
.tabs button { .tabs button {
@@ -1748,6 +1773,8 @@ h1 {
font: inherit; font: inherit;
font-weight: 700; font-weight: 700;
cursor: pointer; cursor: pointer;
flex: 0 0 auto;
white-space: nowrap;
} }
.tabs button.on { .tabs button.on {
@@ -1755,11 +1782,15 @@ h1 {
border-bottom-color: #14b8a6; border-bottom-color: #14b8a6;
} }
.tab-short {
display: none;
}
.stage { .stage {
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr) minmax(280px, 340px); grid-template-columns: minmax(0, 1fr) 220px;
grid-template-areas: 'list panel'; grid-template-areas: 'list panel';
gap: 1rem; gap: 0.85rem;
align-items: start; align-items: start;
} }
@@ -1925,25 +1956,30 @@ h1 {
} }
.table-card { .table-card {
overflow: hidden; overflow-x: auto;
-webkit-overflow-scrolling: touch;
} }
table { table {
width: 100%; width: max-content;
min-width: 100%;
border-collapse: collapse; border-collapse: collapse;
table-layout: auto;
} }
th, th,
td { td {
padding: 0.85rem 1rem; padding: 0.55rem 0.55rem;
text-align: right; text-align: right;
font-size: 0.9rem; font-size: 0.88rem;
vertical-align: middle;
} }
thead th { thead th {
color: #94a3b8; color: #94a3b8;
font-weight: 600; font-weight: 600;
border-bottom: 1px solid #f1f5f9; border-bottom: 1px solid #f1f5f9;
white-space: nowrap;
} }
tbody tr { tbody tr {
@@ -1955,27 +1991,105 @@ tbody tr + tr td {
} }
.ops { .ops {
width: 44px; width: 2.5rem;
padding-inline: 0.4rem; padding-inline: 0.25rem;
}
.col-who {
width: 1%;
white-space: nowrap;
padding-inline-start: 0.65rem;
}
.col-mobile {
width: 1%;
white-space: nowrap;
font-variant-numeric: tabular-nums;
letter-spacing: 0.01em;
}
.col-role {
width: 1%;
}
.col-comm {
width: 1%;
white-space: nowrap;
}
.col-status {
width: 1%;
white-space: nowrap;
}
.col-login {
width: 1%;
white-space: nowrap;
}
.col-logout {
width: 1%;
white-space: nowrap;
} }
.who { .who {
display: inline-flex;
align-items: center;
gap: 0.5rem;
max-width: 14rem;
min-width: 0;
}
.who-text {
display: grid;
gap: 0.3rem;
min-width: 0;
flex: 1;
}
.who-title {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.7rem; justify-content: space-between;
gap: 0.5rem;
min-width: 0;
}
.who-title .who-name {
flex: 1 1 auto;
min-width: 0;
}
.who-title .pill {
flex: 0 0 auto;
margin: 0;
} }
.who img, .who img,
.members img { .members img {
width: 40px; flex: 0 0 36px;
height: 40px; width: 36px;
height: 36px;
min-width: 36px;
min-height: 36px;
aspect-ratio: 1 / 1;
border-radius: 999px; border-radius: 999px;
object-fit: cover; object-fit: cover;
object-position: center;
background: #e2e8f0; background: #e2e8f0;
} }
.who-name,
.who strong { .who strong {
display: block; display: block;
min-width: 0;
max-width: 11rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1.35;
font-weight: 700;
color: #0f172a;
} }
.pill { .pill {
@@ -1983,9 +2097,9 @@ tbody tr + tr td {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 6px; border-radius: 6px;
padding: 0.2rem 0.5rem; padding: 0.15rem 0.45rem;
line-height: 1.3; line-height: 1.3;
font-size: 0.78rem; font-size: 0.76rem;
font-weight: 600; font-weight: 600;
text-align: center; text-align: center;
background: #ccfbf1; background: #ccfbf1;
@@ -1993,12 +2107,17 @@ tbody tr + tr td {
} }
.table-card .col-role { .table-card .col-role {
width: 6.5rem;
max-width: 6.5rem;
vertical-align: middle; vertical-align: middle;
} }
.table-card .pill, .table-card .pill {
width: max-content;
max-width: 6.25rem;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
}
.user-card .pill { .user-card .pill {
width: max-content; width: max-content;
max-width: 5.75rem; max-width: 5.75rem;
@@ -2007,6 +2126,25 @@ tbody tr + tr td {
word-break: break-word; word-break: break-word;
} }
.table-card .comm-field {
margin: 0;
}
.table-card .comm-field input {
width: 3.6rem;
min-height: 2rem;
padding: 0 0.35rem;
}
.table-card .col-status .status,
.table-card .col-status .otp-badge {
white-space: nowrap;
}
.table-card .otp-badge {
margin-inline-start: 0.25rem;
}
.pill[data-color='violet'] { .pill[data-color='violet'] {
background: #f3e8ff; background: #f3e8ff;
color: #7c3aed; color: #7c3aed;
@@ -2085,8 +2223,11 @@ tbody tr + tr td {
top: 0.75rem; top: 0.75rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 34rem; width: 220px;
padding: 1rem 1.05rem 1.1rem; max-width: 220px;
min-height: 0;
padding: 0.85rem 0.75rem 0.95rem;
box-sizing: border-box;
} }
.panel-head, .panel-head,
@@ -2104,17 +2245,18 @@ tbody tr + tr td {
.panel-head h2, .panel-head h2,
.role-hero h3 { .role-hero h3 {
margin: 0; margin: 0;
font-size: 0.95rem; font-size: 0.88rem;
color: #0d9488; color: #0d9488;
} }
.role-hero { .role-hero {
align-items: flex-start; align-items: flex-start;
margin: 1rem 0 1.15rem; margin: 0.75rem 0 0.9rem;
} }
.role-hero h3 { .role-hero h3 {
font-size: 1.05rem; font-size: 0.95rem;
line-height: 1.4;
} }
.role-hero p, .role-hero p,
@@ -2122,17 +2264,17 @@ tbody tr + tr td {
.role-card p { .role-card p {
margin: 0.25rem 0 0; margin: 0.25rem 0 0;
color: #94a3b8; color: #94a3b8;
font-size: 0.82rem; font-size: 0.78rem;
line-height: 1.7; line-height: 1.55;
} }
.panel-block + .panel-block { .panel-block + .panel-block {
margin-top: 1.15rem; margin-top: 1rem;
} }
.panel-block h4 { .panel-block h4 {
margin: 0 0 0.7rem; margin: 0 0 0.55rem;
font-size: 0.92rem; font-size: 0.85rem;
} }
.perms, .perms,
@@ -2141,15 +2283,15 @@ tbody tr + tr td {
margin: 0; margin: 0;
padding: 0; padding: 0;
display: grid; display: grid;
gap: 0.55rem; gap: 0.45rem;
} }
.perms li, .perms li,
.members li { .members li {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.55rem; gap: 0.4rem;
font-size: 0.88rem; font-size: 0.8rem;
} }
.perm-check { .perm-check {
@@ -2212,7 +2354,8 @@ tbody tr + tr td {
.btn.add { .btn.add {
white-space: nowrap; white-space: nowrap;
order: 2; order: 4;
flex: 0 0 auto;
} }
.btn.outline { .btn.outline {
@@ -2238,36 +2381,8 @@ tbody tr + tr td {
color: #334155; color: #334155;
} }
.pager { .main-col :deep(.ui-pagination) {
display: flex; margin-top: 0.75rem;
justify-content: space-between;
align-items: center;
gap: 0.75rem;
padding: 0.85rem 0.2rem 0;
color: #94a3b8;
font-size: 0.85rem;
}
.pages {
display: flex;
gap: 0.35rem;
}
.pages button {
width: 32px;
height: 32px;
border: 0;
border-radius: 10px;
background: transparent;
color: #64748b;
font: inherit;
cursor: pointer;
}
.pages button.on {
background: #ccfbf1;
color: #0f766e;
font-weight: 800;
} }
.mobile-list { .mobile-list {
@@ -2276,26 +2391,93 @@ tbody tr + tr td {
} }
.user-card { .user-card {
display: grid;
gap: 0.55rem;
padding: 0.9rem 1rem;
min-width: 0;
}
.user-card .who {
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
gap: 0.75rem; gap: 0.45rem;
padding: 0.85rem 1rem; max-width: none;
width: 100%;
min-width: 0;
}
.user-card .who-name {
flex: 1 1 auto;
min-width: 0;
max-width: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.25;
} }
.user-card .pill { .user-card .pill {
margin-top: 0.35rem; flex: 0 1 auto;
max-width: 7.5rem;
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.mobile-line { .user-card .card-ops {
margin: 0.2rem 0 0; flex: 0 0 auto;
font-size: 0.78rem; margin-inline-start: -0.15rem;
}
.user-card .card-ops .ghost-ico {
width: 28px;
height: 28px;
} }
.card-meta { .card-meta {
padding-top: 0.55rem;
margin-top: 0.15rem;
border-top: 1px solid #f1f5f9;
}
.card-meta-row {
display: grid; display: grid;
justify-items: end; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
gap: 0.45rem; align-items: center;
gap: 0.5rem;
min-width: 0;
min-height: 2rem;
}
.card-meta-row .mobile-line {
margin: 0;
justify-self: start;
font-size: 0.78rem;
line-height: 1.3;
white-space: nowrap;
}
.card-meta-row .comm-field {
display: inline-flex;
align-items: center;
justify-self: center;
gap: 0.35rem;
margin: 0;
font-size: 0.75rem;
white-space: nowrap;
}
.card-meta-row .comm-field input {
width: 3.4rem;
min-height: 2rem;
}
.card-meta-row .status {
justify-self: end;
flex: 0 0 auto;
line-height: 1;
white-space: nowrap;
} }
.comm-field { .comm-field {
@@ -2574,14 +2756,36 @@ textarea {
transform: rotate(180deg); transform: rotate(180deg);
} }
@media (max-width: 1100px) { @media (max-width: 1200px) {
.stage { .stage {
grid-template-columns: minmax(0, 1fr) 280px; grid-template-columns: minmax(0, 1fr) 220px;
grid-template-areas: 'list panel'; grid-template-areas: 'list panel';
} }
.who {
max-width: 12rem;
}
.who-name,
.who strong {
max-width: 9.5rem;
}
} }
@media (max-width: 900px) { @media (max-width: 900px) {
.tab-full {
display: none;
}
.tab-short {
display: inline;
}
.tabs {
gap: 1rem;
padding-bottom: 1px;
}
.stage { .stage {
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-template-areas: 'list'; grid-template-areas: 'list';
@@ -2603,37 +2807,54 @@ textarea {
.stats { .stats {
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.4rem;
} }
.stats article { .stats article {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
gap: 0.35rem; gap: 0.3rem;
padding: 0.35rem 0.55rem; padding: 0.4rem 0.5rem;
}
.stats p {
flex-wrap: wrap;
font-size: 0.72rem;
}
.stats strong {
font-size: 0.88rem;
}
.stat-ico {
display: none;
} }
.toolbar { .toolbar {
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.55rem;
} }
.search { .search {
flex: 1 1 100%; flex: 1 1 100%;
order: 4; order: 1;
} }
.role-filter { .role-filter {
flex: 1 1 auto; flex: 1 1 0;
order: 2; order: 2;
min-width: 0; min-width: 0;
width: auto;
} }
.toolbar-cols { .toolbar-cols {
order: 3; display: none;
} }
.btn.add { .btn.add {
flex: 0 0 auto; flex: 0 0 auto;
order: 1; order: 3;
padding-inline: 0.75rem;
} }
.org-grid, .org-grid,
@@ -2642,6 +2863,15 @@ textarea {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.roles-tree .role-card {
margin-inline-start: calc(var(--role-depth, 0) * 0.65rem);
padding: 0.85rem;
}
.role-card-top {
gap: 0.35rem;
}
.matrix-scroll { .matrix-scroll {
display: none; display: none;
} }
@@ -2734,10 +2964,13 @@ textarea {
bottom: var(--mobile-tabbar-h, 64px); bottom: var(--mobile-tabbar-h, 64px);
top: auto; top: auto;
z-index: 71; z-index: 71;
width: auto;
max-width: none;
min-height: 0; min-height: 0;
max-height: min(78dvh, 640px); max-height: min(78dvh, 640px);
overflow: auto;
border-radius: 22px 22px 0 0; border-radius: 22px 22px 0 0;
padding-bottom: 0.85rem; padding: 0.85rem 1rem 0.85rem;
animation: sheet-up 0.22s ease; animation: sheet-up 0.22s ease;
} }
@@ -2757,8 +2990,23 @@ textarea {
padding-top: 0.75rem; padding-top: 0.75rem;
} }
.pager { }
flex-direction: column;
@media (max-width: 480px) {
.page {
gap: 0.75rem;
}
.lead {
font-size: 0.85rem;
}
.role-chips {
grid-template-columns: 1fr;
}
.user-card .pill {
max-width: 6.25rem;
} }
} }