mirror of
https://git.meshkee.com/Meshkee/dashboards.git
synced 2026-08-11 22:30:58 +04:30
Tighten Users filters and admin-only header badges.
Match sticky headers to sidebar glass, drop the Users role column, and show compact owner/admin badges only where needed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
6c52ce1247
commit
722933dda9
@@ -77,11 +77,12 @@
|
||||
|
||||
.roleBadge {
|
||||
display: inline-block;
|
||||
margin-top: 4px;
|
||||
padding: 2px 8px;
|
||||
margin-top: 2px;
|
||||
padding: 0 5px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
color: var(--primary);
|
||||
background: rgba(var(--primary-rgb) / 0.1);
|
||||
border: 1px solid rgba(var(--primary-rgb) / 0.18);
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 50;
|
||||
background: color-mix(in srgb, #ffffff 42%, transparent);
|
||||
backdrop-filter: saturate(180%) blur(var(--blur-glass, 28px));
|
||||
-webkit-backdrop-filter: saturate(180%) blur(var(--blur-glass, 28px));
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
/* Keep sticky header on its own compositor layer so backdrop blur samples content beneath */
|
||||
isolation: isolate;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
@@ -112,9 +112,19 @@
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.role {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
.roleBadge {
|
||||
display: inline-block;
|
||||
margin-top: 2px;
|
||||
padding: 1px 8px;
|
||||
border-radius: 999px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
color: var(--primary);
|
||||
background: rgba(var(--primary-rgb) / 0.1);
|
||||
border: 1px solid rgba(var(--primary-rgb) / 0.18);
|
||||
font-family: var(--font-ui);
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.chevron {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Menu, Bell, MessageSquare, ChevronDown, User, Settings, KeyRound, LogOu
|
||||
import { LanguageSelect, PasswordResetModal, useLocale } from '@meshkee/dashboard-ui'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
import { useT } from '../i18n/useT'
|
||||
import { getActiveBusinessId } from '../lib/businessContext'
|
||||
import { changePassword } from '../services/authService'
|
||||
import styles from './Header.module.css'
|
||||
|
||||
@@ -40,13 +41,16 @@ export function Header() {
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const displayName = displayUserName(user, locale, t('app.userFallback'))
|
||||
const roleLabel =
|
||||
user?.roleLabel ??
|
||||
(user?.isSuperAdmin || user?.roles.includes('super_admin')
|
||||
const isSuperAdmin = Boolean(user?.isSuperAdmin || user?.roles.includes('super_admin'))
|
||||
const activeBusinessId = getActiveBusinessId()
|
||||
const membership =
|
||||
user?.businesses.find((b) => String(b.id) === String(activeBusinessId)) ??
|
||||
user?.businesses[0]
|
||||
const adminBadgeLabel = isSuperAdmin
|
||||
? t('role.superAdmin')
|
||||
: user?.businesses[0]?.isOwner
|
||||
? t('role.owner')
|
||||
: user?.businesses[0]?.teamRole ?? t('role.staff'))
|
||||
: membership?.teamRole === 'admin'
|
||||
? t('role.admin')
|
||||
: null
|
||||
|
||||
useEffect(() => {
|
||||
if (!menuOpen) return
|
||||
@@ -112,7 +116,9 @@ export function Header() {
|
||||
>
|
||||
<div className={styles.profileInfo}>
|
||||
<span className={styles.name}>{displayName}</span>
|
||||
<span className={styles.role}>{roleLabel}</span>
|
||||
{adminBadgeLabel ? (
|
||||
<span className={styles.roleBadge}>{adminBadgeLabel}</span>
|
||||
) : null}
|
||||
</div>
|
||||
<ChevronDown
|
||||
size={16}
|
||||
|
||||
@@ -41,6 +41,18 @@
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.filterActionsCol1 {
|
||||
grid-column: span 1;
|
||||
justify-content: flex-start;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.filterSpacerCol4 {
|
||||
grid-column: span 4;
|
||||
min-width: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -56,6 +68,10 @@
|
||||
min-width: 88px;
|
||||
}
|
||||
|
||||
.fieldCol1 {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.fieldCol3 {
|
||||
grid-column: span 3;
|
||||
}
|
||||
@@ -64,6 +80,10 @@
|
||||
grid-column: span 4;
|
||||
}
|
||||
|
||||
.fieldCol8 {
|
||||
grid-column: span 8;
|
||||
}
|
||||
|
||||
.field label {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
@@ -187,9 +207,20 @@
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.filterActionsCol1 {
|
||||
grid-column: span 12;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.filterSpacerCol4 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fieldCol1,
|
||||
.fieldCol2,
|
||||
.fieldCol3,
|
||||
.fieldCol4 {
|
||||
.fieldCol4,
|
||||
.fieldCol8 {
|
||||
grid-column: span 6;
|
||||
}
|
||||
}
|
||||
@@ -199,9 +230,11 @@
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.fieldCol1,
|
||||
.fieldCol2,
|
||||
.fieldCol3,
|
||||
.fieldCol4 {
|
||||
.fieldCol4,
|
||||
.fieldCol8 {
|
||||
grid-column: span 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ const en = {
|
||||
|
||||
'role.superAdmin': 'Super Admin',
|
||||
'role.owner': 'Business Owner',
|
||||
'role.admin': 'Admin',
|
||||
'role.staff': 'Staff',
|
||||
|
||||
'nav.home': 'Home',
|
||||
@@ -1383,6 +1384,7 @@ const fa: Record<MessageKey, string> = {
|
||||
|
||||
'role.superAdmin': 'سوپرادمین',
|
||||
'role.owner': 'صاحب کسبوکار',
|
||||
'role.admin': 'ادمین',
|
||||
'role.staff': 'کارمند',
|
||||
|
||||
'nav.home': 'خانه',
|
||||
|
||||
@@ -41,27 +41,23 @@
|
||||
}
|
||||
|
||||
.colName {
|
||||
width: 18%;
|
||||
}
|
||||
|
||||
.colRole {
|
||||
width: 16%;
|
||||
width: 24%;
|
||||
}
|
||||
|
||||
.colCell {
|
||||
width: 13%;
|
||||
width: 16%;
|
||||
}
|
||||
|
||||
.colOrders {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.colTransactions {
|
||||
width: 12%;
|
||||
}
|
||||
|
||||
.colTransactions {
|
||||
width: 14%;
|
||||
}
|
||||
|
||||
.colDate {
|
||||
width: 11%;
|
||||
width: 12%;
|
||||
}
|
||||
|
||||
.colActions {
|
||||
|
||||
@@ -33,7 +33,7 @@ import accessStyles from '../components/ChangeUserAccessModal.module.css'
|
||||
import styles from './CustomersPage.module.css'
|
||||
|
||||
const PAGE_SIZE = 24
|
||||
const COLUMN_COUNT = 7
|
||||
const COLUMN_COUNT = 6
|
||||
|
||||
function formatDate(value: string, locale: 'en' | 'fa') {
|
||||
const d = new Date(value)
|
||||
@@ -54,13 +54,11 @@ function formatTransactionTotal(total: number | null | undefined) {
|
||||
return formatIrtPrice(total)
|
||||
}
|
||||
|
||||
function accessBadgeKey(user: BusinessCustomerListItem): BusinessMessageKey {
|
||||
/** Badge under name — owner / admin only. */
|
||||
function adminBadgeKey(user: BusinessCustomerListItem): BusinessMessageKey | null {
|
||||
if (user.isBusinessOwner) return 'customers.access.badge.owner'
|
||||
if (user.teamRole === 'admin') return 'customers.access.badge.admin'
|
||||
if (user.teamRole === 'editor') return 'customers.access.badge.editor'
|
||||
if (user.teamRole === 'viewer') return 'customers.access.badge.viewer'
|
||||
if (user.businessMemberId) return 'customers.access.badge.manager'
|
||||
return 'customers.access.customer'
|
||||
return null
|
||||
}
|
||||
|
||||
function teamRoleMessageKey(teamRole: string): BusinessMessageKey {
|
||||
@@ -322,8 +320,26 @@ export function CustomersPage() {
|
||||
|
||||
<div className={filterStyles.filtersPanel}>
|
||||
<form className={filterStyles.filtersGrid} onSubmit={(e) => { e.preventDefault(); applyFilters() }}>
|
||||
<div className={filterStyles.filtersInputs}>
|
||||
<div className={`${filterStyles.field} ${filterStyles.fieldCol3}`}>
|
||||
<input
|
||||
id="filter-customer-name"
|
||||
value={draftName}
|
||||
onChange={(e) => setDraftName(e.target.value)}
|
||||
placeholder={t('customers.filter.name')}
|
||||
aria-label={t('customers.filter.name')}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${filterStyles.field} ${filterStyles.fieldCol2}`}>
|
||||
<input
|
||||
id="filter-customer-cell"
|
||||
value={draftCell}
|
||||
onChange={(e) => setDraftCell(e.target.value)}
|
||||
placeholder={t('customers.filter.cell')}
|
||||
aria-label={t('customers.filter.cell')}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
<div className={`${filterStyles.field} ${filterStyles.fieldCol2}`}>
|
||||
<select
|
||||
id="filter-customer-access"
|
||||
value={draftAccess}
|
||||
@@ -336,27 +352,8 @@ export function CustomersPage() {
|
||||
<option value="managers">{t('customers.filter.access.managers')}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className={`${filterStyles.field} ${filterStyles.fieldCol3}`}>
|
||||
<input
|
||||
id="filter-customer-name"
|
||||
value={draftName}
|
||||
onChange={(e) => setDraftName(e.target.value)}
|
||||
placeholder={t('customers.filter.name')}
|
||||
aria-label={t('customers.filter.name')}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${filterStyles.field} ${styles.filterCellNarrow}`}>
|
||||
<input
|
||||
id="filter-customer-cell"
|
||||
value={draftCell}
|
||||
onChange={(e) => setDraftCell(e.target.value)}
|
||||
placeholder={t('customers.filter.cell')}
|
||||
aria-label={t('customers.filter.cell')}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={filterStyles.filterActions}>
|
||||
<div className={filterStyles.filterSpacerCol4} aria-hidden="true" />
|
||||
<div className={`${filterStyles.filterActions} ${filterStyles.filterActionsCol1}`}>
|
||||
<button
|
||||
type="submit"
|
||||
className={`${filterStyles.iconActionBtn} ${filterStyles.iconActionBtnPrimary}`}
|
||||
@@ -408,7 +405,6 @@ export function CustomersPage() {
|
||||
>
|
||||
<colgroup>
|
||||
<col className={styles.colName} />
|
||||
<col className={styles.colRole} />
|
||||
<col className={styles.colCell} />
|
||||
<col className={styles.colOrders} />
|
||||
<col className={styles.colTransactions} />
|
||||
@@ -418,7 +414,6 @@ export function CustomersPage() {
|
||||
<thead>
|
||||
<tr>
|
||||
<th className={styles.th}>{t('customers.col.name')}</th>
|
||||
<th className={styles.th}>{t('customers.col.role')}</th>
|
||||
<th className={styles.th}>{t('customers.col.cell')}</th>
|
||||
<th className={styles.th}>{t('customers.col.orders')}</th>
|
||||
<th className={styles.th}>{t('customers.col.transactions')}</th>
|
||||
@@ -447,7 +442,7 @@ export function CustomersPage() {
|
||||
data?.items?.map((customer) => {
|
||||
const name = displayName(customer)
|
||||
const nameLocale = textLocaleAttrs(name)
|
||||
const badgeKey = accessBadgeKey(customer)
|
||||
const badgeKey = adminBadgeKey(customer)
|
||||
const accessLocked = accessChangeDisabledReason(customer)
|
||||
return (
|
||||
<tr
|
||||
@@ -468,13 +463,13 @@ export function CustomersPage() {
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
{badgeKey ? (
|
||||
<span className={accessStyles.roleBadge}>{t(badgeKey)}</span>
|
||||
) : null}
|
||||
{!customer.isEnabled && (
|
||||
<div className={styles.statusDisabled}>{t('customers.disabled')}</div>
|
||||
)}
|
||||
</td>
|
||||
<td className={styles.td}>
|
||||
<span className={accessStyles.roleBadge}>{t(badgeKey)}</span>
|
||||
</td>
|
||||
<td className={styles.td}>{formatCellForDisplay(customer.cellNumber)}</td>
|
||||
<td className={styles.td}>
|
||||
{customer.orderCount == null || customer.orderCount === 0 ? (
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 50;
|
||||
background: color-mix(in srgb, #ffffff 42%, transparent);
|
||||
backdrop-filter: saturate(180%) blur(var(--blur-glass, 28px));
|
||||
-webkit-backdrop-filter: saturate(180%) blur(var(--blur-glass, 28px));
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
isolation: isolate;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
@@ -182,6 +183,21 @@
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.roleBadge {
|
||||
display: inline-block;
|
||||
margin-top: 2px;
|
||||
padding: 1px 8px;
|
||||
border-radius: 999px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
color: var(--primary);
|
||||
background: rgba(var(--primary-rgb) / 0.1);
|
||||
border: 1px solid rgba(var(--primary-rgb) / 0.18);
|
||||
font-family: var(--font-ui);
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.chevron {
|
||||
color: var(--text-muted);
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
@@ -60,9 +60,17 @@ export function Header({ hideMenu = false, showBrand = false }: HeaderProps) {
|
||||
: (businessNameEn && businessNameEn !== brandTitle ? businessNameEn : '')
|
||||
|
||||
const activeBusinessId = getActiveBusinessId()
|
||||
const isSuperAdmin = Boolean(user?.isSuperAdmin || user?.roles.includes('super_admin'))
|
||||
const membership = user?.businesses.find(
|
||||
(business) => String(business.id) === String(activeBusinessId),
|
||||
)
|
||||
const adminBadgeLabel = isSuperAdmin
|
||||
? t('app.role.superAdmin')
|
||||
: membership?.teamRole === 'admin'
|
||||
? t('app.role.admin')
|
||||
: null
|
||||
const canOpenBusinessDashboard = Boolean(
|
||||
user?.isSuperAdmin ||
|
||||
user?.roles.includes('super_admin') ||
|
||||
isSuperAdmin ||
|
||||
user?.businesses.some(
|
||||
(business) =>
|
||||
String(business.id) === String(activeBusinessId) &&
|
||||
@@ -162,7 +170,9 @@ export function Header({ hideMenu = false, showBrand = false }: HeaderProps) {
|
||||
>
|
||||
<div className={styles.profileInfo}>
|
||||
<span className={styles.name}>{displayName}</span>
|
||||
<span className={styles.role}>{t('app.role.customer')}</span>
|
||||
{adminBadgeLabel ? (
|
||||
<span className={styles.roleBadge}>{adminBadgeLabel}</span>
|
||||
) : null}
|
||||
</div>
|
||||
<ChevronDown
|
||||
size={16}
|
||||
|
||||
@@ -3,6 +3,8 @@ import type { DashboardLocale } from '@meshkee/dashboard-core'
|
||||
const en = {
|
||||
'app.dashboardName': 'Customer Dashboard',
|
||||
'app.role.customer': 'Website user',
|
||||
'app.role.superAdmin': 'Super Admin',
|
||||
'app.role.admin': 'Admin',
|
||||
'app.storeFallback': 'Store',
|
||||
'app.poweredBy': 'powered by Meshkee.app',
|
||||
|
||||
@@ -389,6 +391,8 @@ type MessageKey = keyof typeof en
|
||||
const fa: Record<MessageKey, string> = {
|
||||
'app.dashboardName': 'پنل مشتری',
|
||||
'app.role.customer': 'کاربر وبسایت',
|
||||
'app.role.superAdmin': 'سوپرادمین',
|
||||
'app.role.admin': 'ادمین',
|
||||
'app.storeFallback': 'فروشگاه',
|
||||
'app.poweredBy': 'قدرتگرفته از Meshkee.app',
|
||||
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 50;
|
||||
background: color-mix(in srgb, #ffffff 42%, transparent);
|
||||
backdrop-filter: saturate(180%) blur(var(--blur-glass, 28px));
|
||||
-webkit-backdrop-filter: saturate(180%) blur(var(--blur-glass, 28px));
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
isolation: isolate;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user