diff --git a/apps/web/src/components/TeamsSettingsPanel.vue b/apps/web/src/components/TeamsSettingsPanel.vue index e4f93aa..1772262 100644 --- a/apps/web/src/components/TeamsSettingsPanel.vue +++ b/apps/web/src/components/TeamsSettingsPanel.vue @@ -778,5 +778,35 @@ select { .prior-picker { 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; + } } diff --git a/apps/web/src/components/ui/UiPagination.vue b/apps/web/src/components/ui/UiPagination.vue index 16218c0..7189e9f 100644 --- a/apps/web/src/components/ui/UiPagination.vue +++ b/apps/web/src/components/ui/UiPagination.vue @@ -184,8 +184,43 @@ function onSizeChange(e: Event) { @media (max-width: 900px) { .ui-pagination { flex-direction: column; - align-items: center; - gap: var(--sp-3); + align-items: stretch; + 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; } } diff --git a/apps/web/src/views/TeamSettingsView.vue b/apps/web/src/views/TeamSettingsView.vue index 440cf6e..88a402d 100644 --- a/apps/web/src/views/TeamSettingsView.vue +++ b/apps/web/src/views/TeamSettingsView.vue @@ -9,7 +9,7 @@ import axios from 'axios'; import { api } from '@/lib/api'; import { useAuthStore } from '@/stores/auth'; import UiIcon from '@/components/UiIcon.vue'; -import { UiDropdownMenu, UiModal } from '@/components/ui'; +import { UiDropdownMenu, UiModal, UiPagination } from '@/components/ui'; import { DATA_ACCESS_SECTIONS, ROLE_PERMISSION_ITEMS, @@ -32,13 +32,14 @@ import { formatJalaliNumeric, toFaDigits, } from '@/lib/jalali'; +import { PAGE_SIZE_OPTIONS, usePageSize } from '@/lib/pageSize'; import TeamsSettingsPanel from '@/components/TeamsSettingsPanel.vue'; import DataAccessPolicyPanel from '@/components/DataAccessPolicyPanel.vue'; type TabKey = 'users' | 'roles' | 'access' | 'teams'; 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 ROLE_COLOR_OPTIONS: Array<{ value: RoleColor; label: string }> = [ { 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 start = (page.value - 1) * PAGE_SIZE; - return filteredUsers.value.slice(start, start + PAGE_SIZE); + const start = (page.value - 1) * pageSize.value; + return filteredUsers.value.slice(start, start + pageSize.value); }); -const pageLabel = computed(() => { - const total = filteredUsers.value.length; - if (!total) return '۰ کاربر'; - const from = (page.value - 1) * PAGE_SIZE + 1; - const to = Math.min(page.value * PAGE_SIZE, total); - return `${toFaDigits(from)} تا ${toFaDigits(to)} از ${toFaDigits(total)} کاربر`; -}); +function setPage(next: number) { + page.value = next; +} + +function onPageSize(size: number) { + pageSize.value = size; + page.value = 1; +} const visibleMembers = computed(() => { const role = selectedRole.value; @@ -930,10 +934,19 @@ onUnmounted(() => {

{{ error }}

@@ -1228,25 +1241,25 @@ onUnmounted(() => { - - + + - - - - + + + + - - + - - - - +
نام و نام خانوادگیموبایلنام و نام خانوادگیموبایل نقشپورسانت %وضعیتآخرین ورودساعت خروجپورسانت %وضعیتساعت خروج
+
- {{ user.fullName }} + {{ user.fullName }}
{{ user.mobile || '—' }}{{ user.mobile || '—' }} { {{ user.orgRoleName || '—' }} + + {{ user.isActive === false ? 'غیرفعال' : 'فعال' }} @@ -1284,8 +1297,8 @@ onUnmounted(() => { ورود با کد {{ formatLastLogin(user.lastLoginAt) }} + — + + +
- - - - {{ user.isActive === false ? 'غیرفعال' : 'فعال' }} - - - +
+

{{ user.mobile || '—' }}

+ + + + {{ user.isActive === false ? 'غیرفعال' : 'فعال' }} + +

کاربری یافت نشد.

@@ -1439,22 +1466,16 @@ onUnmounted(() => {

نقشی با این جستجو پیدا نشد.

-
- {{ pageLabel }} -
- - - -
-
+ @@ -1733,8 +1754,12 @@ h1 { .tabs { display: flex; justify-content: flex-start; - gap: 1.75rem; + gap: 1.25rem; border-bottom: 1px solid #e2e8f0; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + scrollbar-width: thin; + overscroll-behavior-x: contain; } .tabs button { @@ -1748,6 +1773,8 @@ h1 { font: inherit; font-weight: 700; cursor: pointer; + flex: 0 0 auto; + white-space: nowrap; } .tabs button.on { @@ -1755,11 +1782,15 @@ h1 { border-bottom-color: #14b8a6; } +.tab-short { + display: none; +} + .stage { display: grid; - grid-template-columns: minmax(0, 1fr) minmax(280px, 340px); + grid-template-columns: minmax(0, 1fr) 220px; grid-template-areas: 'list panel'; - gap: 1rem; + gap: 0.85rem; align-items: start; } @@ -1925,25 +1956,30 @@ h1 { } .table-card { - overflow: hidden; + overflow-x: auto; + -webkit-overflow-scrolling: touch; } table { - width: 100%; + width: max-content; + min-width: 100%; border-collapse: collapse; + table-layout: auto; } th, td { - padding: 0.85rem 1rem; + padding: 0.55rem 0.55rem; text-align: right; - font-size: 0.9rem; + font-size: 0.88rem; + vertical-align: middle; } thead th { color: #94a3b8; font-weight: 600; border-bottom: 1px solid #f1f5f9; + white-space: nowrap; } tbody tr { @@ -1955,27 +1991,105 @@ tbody tr + tr td { } .ops { - width: 44px; - padding-inline: 0.4rem; + width: 2.5rem; + 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 { + 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; 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, .members img { - width: 40px; - height: 40px; + flex: 0 0 36px; + width: 36px; + height: 36px; + min-width: 36px; + min-height: 36px; + aspect-ratio: 1 / 1; border-radius: 999px; object-fit: cover; + object-position: center; background: #e2e8f0; } +.who-name, .who strong { 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 { @@ -1983,9 +2097,9 @@ tbody tr + tr td { align-items: center; justify-content: center; border-radius: 6px; - padding: 0.2rem 0.5rem; + padding: 0.15rem 0.45rem; line-height: 1.3; - font-size: 0.78rem; + font-size: 0.76rem; font-weight: 600; text-align: center; background: #ccfbf1; @@ -1993,12 +2107,17 @@ tbody tr + tr td { } .table-card .col-role { - width: 6.5rem; - max-width: 6.5rem; 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 { width: max-content; max-width: 5.75rem; @@ -2007,6 +2126,25 @@ tbody tr + tr td { 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'] { background: #f3e8ff; color: #7c3aed; @@ -2085,8 +2223,11 @@ tbody tr + tr td { top: 0.75rem; display: flex; flex-direction: column; - min-height: 34rem; - padding: 1rem 1.05rem 1.1rem; + width: 220px; + max-width: 220px; + min-height: 0; + padding: 0.85rem 0.75rem 0.95rem; + box-sizing: border-box; } .panel-head, @@ -2104,17 +2245,18 @@ tbody tr + tr td { .panel-head h2, .role-hero h3 { margin: 0; - font-size: 0.95rem; + font-size: 0.88rem; color: #0d9488; } .role-hero { align-items: flex-start; - margin: 1rem 0 1.15rem; + margin: 0.75rem 0 0.9rem; } .role-hero h3 { - font-size: 1.05rem; + font-size: 0.95rem; + line-height: 1.4; } .role-hero p, @@ -2122,17 +2264,17 @@ tbody tr + tr td { .role-card p { margin: 0.25rem 0 0; color: #94a3b8; - font-size: 0.82rem; - line-height: 1.7; + font-size: 0.78rem; + line-height: 1.55; } .panel-block + .panel-block { - margin-top: 1.15rem; + margin-top: 1rem; } .panel-block h4 { - margin: 0 0 0.7rem; - font-size: 0.92rem; + margin: 0 0 0.55rem; + font-size: 0.85rem; } .perms, @@ -2141,15 +2283,15 @@ tbody tr + tr td { margin: 0; padding: 0; display: grid; - gap: 0.55rem; + gap: 0.45rem; } .perms li, .members li { display: flex; align-items: center; - gap: 0.55rem; - font-size: 0.88rem; + gap: 0.4rem; + font-size: 0.8rem; } .perm-check { @@ -2212,7 +2354,8 @@ tbody tr + tr td { .btn.add { white-space: nowrap; - order: 2; + order: 4; + flex: 0 0 auto; } .btn.outline { @@ -2238,36 +2381,8 @@ tbody tr + tr td { color: #334155; } -.pager { - display: flex; - 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; +.main-col :deep(.ui-pagination) { + margin-top: 0.75rem; } .mobile-list { @@ -2276,26 +2391,93 @@ tbody tr + tr td { } .user-card { + display: grid; + gap: 0.55rem; + padding: 0.9rem 1rem; + min-width: 0; +} + +.user-card .who { display: flex; - justify-content: space-between; align-items: center; - gap: 0.75rem; - padding: 0.85rem 1rem; + gap: 0.45rem; + 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 { - margin-top: 0.35rem; + flex: 0 1 auto; + max-width: 7.5rem; + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -.mobile-line { - margin: 0.2rem 0 0; - font-size: 0.78rem; +.user-card .card-ops { + flex: 0 0 auto; + margin-inline-start: -0.15rem; +} + +.user-card .card-ops .ghost-ico { + width: 28px; + height: 28px; } .card-meta { + padding-top: 0.55rem; + margin-top: 0.15rem; + border-top: 1px solid #f1f5f9; +} + +.card-meta-row { display: grid; - justify-items: end; - gap: 0.45rem; + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); + 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 { @@ -2574,14 +2756,36 @@ textarea { transform: rotate(180deg); } -@media (max-width: 1100px) { +@media (max-width: 1200px) { .stage { - grid-template-columns: minmax(0, 1fr) 280px; + grid-template-columns: minmax(0, 1fr) 220px; grid-template-areas: 'list panel'; } + + .who { + max-width: 12rem; + } + + .who-name, + .who strong { + max-width: 9.5rem; + } } @media (max-width: 900px) { + .tab-full { + display: none; + } + + .tab-short { + display: inline; + } + + .tabs { + gap: 1rem; + padding-bottom: 1px; + } + .stage { grid-template-columns: 1fr; grid-template-areas: 'list'; @@ -2603,37 +2807,54 @@ textarea { .stats { grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.4rem; } .stats article { flex-direction: row; align-items: center; - gap: 0.35rem; - padding: 0.35rem 0.55rem; + gap: 0.3rem; + 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 { flex-wrap: wrap; + gap: 0.55rem; } .search { flex: 1 1 100%; - order: 4; + order: 1; } .role-filter { - flex: 1 1 auto; + flex: 1 1 0; order: 2; min-width: 0; + width: auto; } .toolbar-cols { - order: 3; + display: none; } .btn.add { flex: 0 0 auto; - order: 1; + order: 3; + padding-inline: 0.75rem; } .org-grid, @@ -2642,6 +2863,15 @@ textarea { 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 { display: none; } @@ -2734,10 +2964,13 @@ textarea { bottom: var(--mobile-tabbar-h, 64px); top: auto; z-index: 71; + width: auto; + max-width: none; min-height: 0; max-height: min(78dvh, 640px); + overflow: auto; border-radius: 22px 22px 0 0; - padding-bottom: 0.85rem; + padding: 0.85rem 1rem 0.85rem; animation: sheet-up 0.22s ease; } @@ -2757,8 +2990,23 @@ textarea { 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; } }