Redesign header menu: separate account dropdown from dashboard nav
CI/CD / CI · dotnet build (push) Successful in 1m7s
CI/CD / Deploy · hamkadr (push) Successful in 1m57s

The profile dropdown was doing three jobs at once (account actions, the
job-seeker panel menu, and the admin panel menu) and a stray inline @if
for the notification badge leaked into the markup as literal text.

- Profile dropdown is now account-only: identity card (avatar + name +
  phone), one role-aware dashboard entry, edit profile, logout. This
  removes the leaked @if and de-clutters the menu.
- Dashboard menu is centralized in _PanelNav and auto-rendered by the
  layout on every logged-in panel page (/Admin, /Me, /Employer,
  /Preferences) instead of being duplicated in the dropdown and pages.
- Drop the now-duplicate manual <partial name="_PanelNav" /> from
  Overview, Ingested, Me/Index, Employer/Index.
- CSS: identity-card (.pd-id) styles + mobile tweaks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-08 07:33:22 +03:30
parent e6a796ab27
commit bdcca5e548
6 changed files with 46 additions and 29 deletions
@@ -8,8 +8,6 @@
$"<a class=\"ing-pill {(Model.Status == key || (Model.Status is null && key == "all") ? "active" : "")}\" href=\"?status={key}\">{label} ({P(count)})</a>";
}
<partial name="_PanelNav" />
<div class="page-head">
<div class="container">
<h1>نتایج جمع‌آوری</h1>
@@ -5,8 +5,6 @@
string P(int n) => JalaliDate.ToPersianDigits(n.ToString());
}
<partial name="_PanelNav" />
<div class="page-head">
<div class="container">
<h1>داشبورد مدیریت</h1>
@@ -10,8 +10,6 @@
};
}
<partial name="_PanelNav" />
<div class="page-head">
<div class="container">
<h1>پنل مرکز درمانی</h1>
@@ -16,8 +16,6 @@
};
}
<partial name="_PanelNav" />
<div class="page-head">
<div class="container">
<h1>پنل کارجو</h1>
+40 -21
View File
@@ -6,20 +6,26 @@
var title = ViewData["Title"] as string;
int unreadCount = 0;
int meId = 0;
string? meName = null;
string? meFullName = null;
string? mePhone = null;
bool meHasAvatar = false;
if (User.Identity?.IsAuthenticated == true && int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out meId))
{
unreadCount = await Notifications.UnreadCountAsync(meId);
var info = await Db.Users.Where(u => u.Id == meId)
.Select(u => new { u.FullName, u.Phone, HasAvatar = u.Avatar != null }).FirstOrDefaultAsync();
meName = string.IsNullOrWhiteSpace(info?.FullName) ? info?.Phone : info!.FullName;
meFullName = string.IsNullOrWhiteSpace(info?.FullName) ? null : info!.FullName!.Trim();
mePhone = info?.Phone;
meHasAvatar = info?.HasAvatar ?? false;
}
// Person glyph when there's no real name yet (avoid showing a phone digit like "0").
var meInitial = (!string.IsNullOrWhiteSpace(meName) && !char.IsDigit(meName!.Trim()[0]))
? meName!.Trim().Substring(0, 1) : "👤";
var meLabel = (!string.IsNullOrWhiteSpace(meName) && !char.IsDigit(meName!.Trim()[0])) ? meName! : "حساب من";
// Avatar glyph/label: prefer a real name; never show a bare phone digit like "0".
var meInitial = meFullName is not null ? meFullName.Substring(0, 1) : "👤";
var meLabel = meFullName ?? "حساب من";
// Single, role-aware dashboard entry — the full menu lives in the panel sub-nav (_PanelNav).
var dashUrl = "/Me/Index"; var dashLabel = "داشبورد من"; var dashIcon = "🗂️";
if (User.IsInRole("Admin")) { dashUrl = "/Admin/Overview"; dashLabel = "پنل مدیریت"; dashIcon = "🛠️"; }
else if (User.IsInRole("FacilityAdmin")) { dashUrl = "/Employer/Index"; dashLabel = "پنل کارفرما"; dashIcon = "🏥"; }
// --- SEO context ---
var baseUrl = $"{Context.Request.Scheme}://{Context.Request.Host}";
@@ -33,6 +39,11 @@
string[] noindexPrefixes = { "/Admin", "/Me", "/Employer", "/Account", "/Preferences" };
var noIndex = (ViewData["NoIndex"] as bool? ?? false)
|| noindexPrefixes.Any(p => path.StartsWith(p, StringComparison.OrdinalIgnoreCase));
// Show the centralized dashboard sub-nav on any logged-in panel page.
string[] panelPrefixes = { "/Admin", "/Me", "/Employer", "/Preferences" };
var showPanelNav = User.Identity?.IsAuthenticated == true
&& panelPrefixes.Any(p => path.StartsWith(p, StringComparison.OrdinalIgnoreCase));
}
<!DOCTYPE html>
<html lang="fa" dir="rtl">
@@ -126,22 +137,26 @@
<span class="avatar-caret">▾</span>
</label>
<nav class="profile-dropdown">
<div class="pd-head">@meName</div>
<div class="pd-id">
@if (meHasAvatar)
{
<img class="avatar-img" src="/avatar/@meId" alt="" />
}
else
{
<span class="avatar-fallback">@meInitial</span>
}
<div class="pd-id-text">
<strong>@(meFullName ?? "کاربر همکادر")</strong>
@if (mePhone is not null)
{
<span class="muted" dir="ltr">@mePhone</span>
}
</div>
</div>
<div class="pd-sep"></div>
<a href="@dashUrl" data-tour="panel">@dashIcon @dashLabel</a>
<a asp-page="/Me/Profile">👤 ویرایش پروفایل</a>
<a asp-page="/Me/Index" data-tour="panel">🗂️ پنل کارجو</a>
<a asp-page="/Me/Alerts">🔎 هشدارهای شغلی</a>
<a asp-page="/Preferences/Index">⭐ علاقه‌مندی‌ها</a>
<a asp-page="/Me/Notifications">🔔 اعلان‌ها@if (unreadCount > 0) {<span class="bell-badge" style="position:static; margin-inline-start:6px;">@JalaliDate.ToPersianDigits(unreadCount > 99 ? "99+" : unreadCount.ToString())</span>}</a>
@if (User.IsInRole("FacilityAdmin"))
{
<a asp-page="/Employer/Index">🏥 پنل کارفرما</a>
}
@if (User.IsInRole("Admin"))
{
<div class="pd-sep"></div>
<a asp-page="/Admin/Overview">🛠️ پنل مدیریت</a>
<a asp-page="/Admin/Settings">⚙️ تنظیمات</a>
}
<div class="pd-sep"></div>
<form method="post" asp-page="/Account/Logout">
<button type="submit" class="pd-logout">🚪 خروج</button>
@@ -159,6 +174,10 @@
</header>
<main role="main">
@if (showPanelNav)
{
<partial name="_PanelNav" />
}
@RenderBody()
</main>
+6
View File
@@ -132,6 +132,11 @@ a { color: inherit; text-decoration: none; }
.profile-dropdown a:hover, .pd-logout:hover { background: var(--primary-soft); color: var(--primary-dark); }
.profile-dropdown form { margin: 0; }
.pd-head { padding: 8px 12px; font-weight: 800; color: var(--muted); font-size: 13px; }
.pd-id { display: flex; align-items: center; gap: 10px; padding: 8px 10px 12px; }
.pd-id .avatar-img, .pd-id .avatar-fallback { width: 42px; height: 42px; font-size: 17px; }
.pd-id-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.45; }
.pd-id-text strong { font-size: 14px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pd-id-text .muted { font-size: 12px; }
.pd-sep { height: 1px; background: var(--line); margin: 4px 0; }
.pd-logout { color: var(--danger); }
@@ -483,6 +488,7 @@ label { font-size: 13px; }
.profile-dropdown { position: static; display: block; box-shadow: none; border: none; padding: 0; min-width: 0; }
.profile-dropdown a, .pd-logout { padding: 12px 6px; font-size: 15px; }
.pd-head { display: none; }
.pd-id { padding: 10px 6px; border-bottom: 1px solid var(--line); margin-bottom: 4px; }
.pd-sep { display: none; }
.cal { border-spacing: 4px; }