[UI] Mobile sticky action bar on shift/job details (native-app feel)
CI/CD / CI · dotnet build (push) Successful in 46s
CI/CD / Deploy · hamkadr (push) Successful in 1m6s

On mobile the action panel (CTA + map) stacked at the very bottom, so users had to scroll the whole page to act. Add a fixed bottom action bar (<=860px) with the primary «اعلام تمایل» button + a quick save, always reachable like a native app; when contact is revealed it becomes a «تماس با مرکز» tel: button. The in-aside primary CTA is hidden on mobile (.aside-apply) to avoid duplication, and pages get bottom padding (.has-action-bar) so the bar never covers content. Desktop layout unchanged (bar hidden, sidebar CTA shown).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-04 19:12:34 +03:30
parent 86809190e7
commit 2170ba250c
3 changed files with 82 additions and 11 deletions
+27 -1
View File
@@ -30,7 +30,7 @@
</div> </div>
</div> </div>
<div class="container section"> <div class="container section has-action-bar">
<div class="detail-grid"> <div class="detail-grid">
<div> <div>
@if (Model.ShowContact) @if (Model.ShowContact)
@@ -77,10 +77,12 @@
<div class="card card-pad"> <div class="card card-pad">
<div class="pay" style="font-size:19px; margin-bottom:6px; color:var(--primary-dark); font-weight:800;">@salary</div> <div class="pay" style="font-size:19px; margin-bottom:6px; color:var(--primary-dark); font-weight:800;">@salary</div>
<p class="muted" style="font-size:13px; margin-top:0;">@empLabel</p> <p class="muted" style="font-size:13px; margin-top:0;">@empLabel</p>
<div class="aside-apply">
<form method="post"> <form method="post">
<button type="submit" asp-page-handler="Interest" asp-route-id="@j.Id" <button type="submit" asp-page-handler="Interest" asp-route-id="@j.Id"
class="btn btn-accent btn-block btn-lg">اعلام تمایل و مشاهده راه ارتباطی</button> class="btn btn-accent btn-block btn-lg">اعلام تمایل و مشاهده راه ارتباطی</button>
</form> </form>
</div>
<div style="display:flex; gap:8px; margin-top:8px;"> <div style="display:flex; gap:8px; margin-top:8px;">
<form method="post" style="flex:1;"> <form method="post" style="flex:1;">
<button type="submit" asp-page-handler="Save" asp-route-id="@j.Id" class="btn btn-outline btn-block">♡ ذخیره</button> <button type="submit" asp-page-handler="Save" asp-route-id="@j.Id" class="btn btn-outline btn-block">♡ ذخیره</button>
@@ -147,6 +149,30 @@
</div> </div>
</div> </div>
@* Sticky bottom action bar — mobile only. *@
<div class="mobile-action-bar">
@if (Model.ShowContact)
{
@if (!string.IsNullOrEmpty(f.Phone))
{
<a class="btn btn-accent btn-lg cta-main" href="tel:@f.Phone">📞 تماس با مرکز</a>
}
else
{
<span class="cta-main center muted" style="align-self:center;">اطلاعات تماس در بالای صفحه</span>
}
}
else
{
<form method="post" class="cta-main">
<button type="submit" asp-page-handler="Interest" asp-route-id="@j.Id" class="btn btn-accent btn-block btn-lg">اعلام تمایل و مشاهده تماس</button>
</form>
<form method="post">
<button type="submit" asp-page-handler="Save" asp-route-id="@j.Id" class="btn btn-outline btn-lg" aria-label="ذخیره" title="ذخیره">♡</button>
</form>
}
</div>
@if (!string.IsNullOrEmpty(Model.MapKey) && Model.Job?.Facility?.Lat is not null) @if (!string.IsNullOrEmpty(Model.MapKey) && Model.Job?.Facility?.Lat is not null)
{ {
<partial name="_NeshanMap" model="Model.MapKey" /> <partial name="_NeshanMap" model="Model.MapKey" />
@@ -28,7 +28,7 @@
</div> </div>
</div> </div>
<div class="container section"> <div class="container section has-action-bar">
<div class="detail-grid"> <div class="detail-grid">
<div> <div>
@if (Model.ShowContact) @if (Model.ShowContact)
@@ -93,11 +93,13 @@
{ {
<div class="alert alert-success" style="margin-bottom:12px;">✓ این فرصت ذخیره شد و در پیشنهادهای شما لحاظ می‌شود.</div> <div class="alert alert-success" style="margin-bottom:12px;">✓ این فرصت ذخیره شد و در پیشنهادهای شما لحاظ می‌شود.</div>
} }
<div class="aside-apply">
<form method="post"> <form method="post">
<button type="submit" asp-page-handler="Interest" asp-route-id="@s.Id" <button type="submit" asp-page-handler="Interest" asp-route-id="@s.Id"
class="btn btn-accent btn-block btn-lg">اعلام تمایل و مشاهده راه ارتباطی</button> class="btn btn-accent btn-block btn-lg">اعلام تمایل و مشاهده راه ارتباطی</button>
</form> </form>
<p class="muted center" style="font-size:12px; margin:8px 0;">با اعلام تمایل، اطلاعات تماس مرکز نمایش داده می‌شود.</p> <p class="muted center" style="font-size:12px; margin:8px 0;">با اعلام تمایل، اطلاعات تماس مرکز نمایش داده می‌شود.</p>
</div>
<div style="display:flex; gap:8px;"> <div style="display:flex; gap:8px;">
<form method="post" style="flex:1;"> <form method="post" style="flex:1;">
<button type="submit" asp-page-handler="Save" asp-route-id="@s.Id" <button type="submit" asp-page-handler="Save" asp-route-id="@s.Id"
@@ -167,6 +169,30 @@
</div> </div>
</div> </div>
@* Sticky bottom action bar — mobile only. Always-reachable primary action (native-app feel). *@
<div class="mobile-action-bar">
@if (Model.ShowContact)
{
@if (!string.IsNullOrEmpty(f.Phone))
{
<a class="btn btn-accent btn-lg cta-main" href="tel:@f.Phone">📞 تماس با مرکز</a>
}
else
{
<span class="cta-main center muted" style="align-self:center;">اطلاعات تماس در بالای صفحه</span>
}
}
else
{
<form method="post" class="cta-main">
<button type="submit" asp-page-handler="Interest" asp-route-id="@s.Id" class="btn btn-accent btn-block btn-lg">اعلام تمایل و مشاهده تماس</button>
</form>
<form method="post">
<button type="submit" asp-page-handler="Save" asp-route-id="@s.Id" class="btn btn-outline btn-lg" aria-label="ذخیره" title="ذخیره">♡</button>
</form>
}
</div>
@if (!string.IsNullOrEmpty(Model.MapKey) && Model.Shift?.Facility?.Lat is not null) @if (!string.IsNullOrEmpty(Model.MapKey) && Model.Shift?.Facility?.Lat is not null)
{ {
<partial name="_NeshanMap" model="Model.MapKey" /> <partial name="_NeshanMap" model="Model.MapKey" />
+19
View File
@@ -256,6 +256,25 @@ label { font-size: 13px; }
.tour-count { font-size: 12px; color: var(--muted); } .tour-count { font-size: 12px; color: var(--muted); }
.tour-btns { display: flex; gap: 6px; } .tour-btns { display: flex; gap: 6px; }
/* ---------- Mobile sticky action bar (detail pages feel like a native app) ---------- */
.mobile-action-bar { display: none; }
@media (max-width: 860px) {
.mobile-action-bar {
display: flex; gap: 8px; align-items: stretch;
position: fixed; inset-inline: 0; bottom: 0; z-index: 95;
padding: 10px 14px; padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
background: var(--surface); border-top: 1px solid var(--line);
box-shadow: 0 -8px 24px rgba(0,0,0,.10);
}
.mobile-action-bar form { margin: 0; }
.mobile-action-bar .cta-main { flex: 1; }
.mobile-action-bar .btn { margin: 0; }
/* breathing room so the fixed bar never covers page content */
.has-action-bar { padding-bottom: 92px; }
/* the in-aside primary CTA is redundant on mobile — the sticky bar replaces it */
.aside-apply { display: none; }
}
/* ---------- Admin settings: sidebar tabs ---------- */ /* ---------- Admin settings: sidebar tabs ---------- */
.settings-layout { display: grid; grid-template-columns: 220px 1fr; gap: 18px; align-items: start; } .settings-layout { display: grid; grid-template-columns: 220px 1fr; gap: 18px; align-items: start; }
.settings-tabs { .settings-tabs {