diff --git a/src/JobsMedical.Web/Pages/Jobs/Index.cshtml b/src/JobsMedical.Web/Pages/Jobs/Index.cshtml
index 5d6ec4f..c669581 100644
--- a/src/JobsMedical.Web/Pages/Jobs/Index.cshtml
+++ b/src/JobsMedical.Web/Pages/Jobs/Index.cshtml
@@ -9,7 +9,7 @@
@Model.PageHeading
- @JalaliDate.ToPersianDigits(Model.Results.Count.ToString()) موقعیت شغلی پیدا شد
+ @JalaliDate.ToPersianDigits(Model.TotalCount.ToString()) موقعیت شغلی پیدا شد
@if (Model.NearMeActive)
{
— مرتبشده بر اساس نزدیکترین به شما 📍
@@ -117,6 +117,7 @@
}
+
}
diff --git a/src/JobsMedical.Web/Pages/Jobs/Index.cshtml.cs b/src/JobsMedical.Web/Pages/Jobs/Index.cshtml.cs
index af1289a..69547c8 100644
--- a/src/JobsMedical.Web/Pages/Jobs/Index.cshtml.cs
+++ b/src/JobsMedical.Web/Pages/Jobs/Index.cshtml.cs
@@ -24,6 +24,12 @@ public class IndexModel : PageModel
[BindProperty(SupportsGet = true)] public string? RoleSlug { get; set; }
[BindProperty(SupportsGet = true)] public string? CitySlug { get; set; }
+ [BindProperty(SupportsGet = true)] public int Page { get; set; } = 1;
+ private const int PageSize = 24;
+ public int TotalCount { get; private set; }
+ public int TotalPages { get; private set; }
+ public int CurrentPage { get; private set; }
+
public bool NearMeActive => Lat is not null && Lng is not null;
public List Results { get; private set; } = new();
@@ -78,19 +84,24 @@ public class IndexModel : PageModel
if (GenderFilter is Gender g && g != Gender.Any)
q = q.Where(j => j.GenderRequirement == Gender.Any || j.GenderRequirement == g);
- var results = await q.ToListAsync();
+ TotalCount = await q.CountAsync();
+ TotalPages = Math.Max(1, (int)Math.Ceiling(TotalCount / (double)PageSize));
+ CurrentPage = Math.Clamp(Page, 1, TotalPages);
+ var skip = (CurrentPage - 1) * PageSize;
if (NearMeActive)
{
- foreach (var j in results)
+ // Distance sort needs all rows in memory; paginate after sorting.
+ var all = await q.ToListAsync();
+ foreach (var j in all)
if (j.Facility.Lat is double flat && j.Facility.Lng is double flng)
j.DistanceKm = Geo.DistanceKm(Lat!.Value, Lng!.Value, flat, flng);
- Results = results.OrderBy(j => j.DistanceKm ?? double.MaxValue)
- .ThenByDescending(j => j.CreatedAt).ToList();
+ Results = all.OrderBy(j => j.DistanceKm ?? double.MaxValue)
+ .ThenByDescending(j => j.CreatedAt).Skip(skip).Take(PageSize).ToList();
}
else
{
- Results = results.OrderByDescending(j => j.CreatedAt).ToList();
+ Results = await q.OrderByDescending(j => j.CreatedAt).Skip(skip).Take(PageSize).ToListAsync();
}
SetSeo(Roles.FirstOrDefault(r => r.Id == RoleId)?.Name, Cities.FirstOrDefault(c => c.Id == CityId)?.Name);
diff --git a/src/JobsMedical.Web/Pages/Shared/_Pager.cshtml b/src/JobsMedical.Web/Pages/Shared/_Pager.cshtml
new file mode 100644
index 0000000..6eaf8b5
--- /dev/null
+++ b/src/JobsMedical.Web/Pages/Shared/_Pager.cshtml
@@ -0,0 +1,51 @@
+@model (int Current, int Total)
+@{
+ var (cur, total) = Model;
+}
+@if (total > 1)
+{
+ @* Build a page URL that preserves every current filter in the query string. *@
+ Func pageUrl = p =>
+ {
+ var parts = Context.Request.Query
+ .Where(kv => !string.Equals(kv.Key, "Page", StringComparison.OrdinalIgnoreCase))
+ .Select(kv => Uri.EscapeDataString(kv.Key) + "=" + Uri.EscapeDataString(kv.Value.ToString()))
+ .ToList();
+ parts.Add("Page=" + p);
+ return Context.Request.Path + "?" + string.Join("&", parts);
+ };
+ var from = Math.Max(1, cur - 2);
+ var to = Math.Min(total, cur + 2);
+
+
+}
diff --git a/src/JobsMedical.Web/Pages/Shifts/Index.cshtml b/src/JobsMedical.Web/Pages/Shifts/Index.cshtml
index 503e500..56ef4ae 100644
--- a/src/JobsMedical.Web/Pages/Shifts/Index.cshtml
+++ b/src/JobsMedical.Web/Pages/Shifts/Index.cshtml
@@ -9,7 +9,7 @@
@Model.PageHeading
- @JalaliDate.ToPersianDigits(Model.Results.Count.ToString()) شیفت باز پیدا شد
+ @JalaliDate.ToPersianDigits(Model.TotalCount.ToString()) شیفت باز پیدا شد
@if (Model.NearMeActive)
{
— مرتبشده بر اساس نزدیکترین به شما 📍
@@ -145,6 +145,7 @@
}
+
}
diff --git a/src/JobsMedical.Web/Pages/Shifts/Index.cshtml.cs b/src/JobsMedical.Web/Pages/Shifts/Index.cshtml.cs
index faa9fb9..3b5d1be 100644
--- a/src/JobsMedical.Web/Pages/Shifts/Index.cshtml.cs
+++ b/src/JobsMedical.Web/Pages/Shifts/Index.cshtml.cs
@@ -29,6 +29,12 @@ public class IndexModel : PageModel
[BindProperty(SupportsGet = true)] public string? RoleSlug { get; set; }
[BindProperty(SupportsGet = true)] public string? CitySlug { get; set; }
+ [BindProperty(SupportsGet = true)] public int Page { get; set; } = 1;
+ private const int PageSize = 24;
+ public int TotalCount { get; private set; }
+ public int TotalPages { get; private set; }
+ public int CurrentPage { get; private set; }
+
public bool NearMeActive => Lat is not null && Lng is not null;
public List Results { get; private set; } = new();
@@ -91,24 +97,27 @@ public class IndexModel : PageModel
if (GenderFilter is Gender g && g != Gender.Any)
q = q.Where(s => s.GenderRequirement == Gender.Any || s.GenderRequirement == g);
- var results = await q.ToListAsync();
+ TotalCount = await q.CountAsync();
+ TotalPages = Math.Max(1, (int)Math.Ceiling(TotalCount / (double)PageSize));
+ CurrentPage = Math.Clamp(Page, 1, TotalPages);
+ var skip = (CurrentPage - 1) * PageSize;
if (NearMeActive)
{
- // Compute distance to each facility, then nearest-first (shifts without coords last).
- foreach (var s in results)
- {
+ // Distance sort needs all rows in memory; paginate after sorting (shifts without coords last).
+ var all = await q.ToListAsync();
+ foreach (var s in all)
if (s.Facility.Lat is double flat && s.Facility.Lng is double flng)
s.DistanceKm = Geo.DistanceKm(Lat!.Value, Lng!.Value, flat, flng);
- }
- Results = results
+ Results = all
.OrderBy(s => s.DistanceKm ?? double.MaxValue)
.ThenBy(s => s.Date).ThenBy(s => s.StartTime)
- .ToList();
+ .Skip(skip).Take(PageSize).ToList();
}
else
{
- Results = results.OrderBy(s => s.Date).ThenBy(s => s.StartTime).ToList();
+ Results = await q.OrderBy(s => s.Date).ThenBy(s => s.StartTime)
+ .Skip(skip).Take(PageSize).ToListAsync();
}
SetSeo(Roles.FirstOrDefault(r => r.Id == RoleId)?.Name, Cities.FirstOrDefault(c => c.Id == CityId)?.Name);
diff --git a/src/JobsMedical.Web/Pages/Talent/Index.cshtml b/src/JobsMedical.Web/Pages/Talent/Index.cshtml
index 1633a1e..6e20f45 100644
--- a/src/JobsMedical.Web/Pages/Talent/Index.cshtml
+++ b/src/JobsMedical.Web/Pages/Talent/Index.cshtml
@@ -9,7 +9,7 @@
@Model.PageHeading
- @JalaliDate.ToPersianDigits(Model.Results.Count.ToString()) نیروی کادر درمان آمادهی همکاری —
+ @JalaliDate.ToPersianDigits(Model.TotalCount.ToString()) نیروی کادر درمان آمادهی همکاری —
مراکز درمانی میتوانند مستقیم تماس بگیرند.
@@ -83,6 +83,7 @@
}
+
}
diff --git a/src/JobsMedical.Web/Pages/Talent/Index.cshtml.cs b/src/JobsMedical.Web/Pages/Talent/Index.cshtml.cs
index c12a8c2..d451d77 100644
--- a/src/JobsMedical.Web/Pages/Talent/Index.cshtml.cs
+++ b/src/JobsMedical.Web/Pages/Talent/Index.cshtml.cs
@@ -17,6 +17,11 @@ public class IndexModel : PageModel
[BindProperty(SupportsGet = true)] public int? RoleId { get; set; }
[BindProperty(SupportsGet = true)] public Gender? GenderFilter { get; set; }
[BindProperty(SupportsGet = true)] public string? Q { get; set; } // deep search
+ [BindProperty(SupportsGet = true)] public int Page { get; set; } = 1;
+ private const int PageSize = 24;
+ public int TotalCount { get; private set; }
+ public int TotalPages { get; private set; }
+ public int CurrentPage { get; private set; }
public List Results { get; private set; } = new();
public List Cities { get; private set; } = new();
@@ -60,7 +65,11 @@ public class IndexModel : PageModel
EF.Functions.ILike(t.City.Name, like));
}
- Results = await q.OrderByDescending(t => t.CreatedAt).ToListAsync();
+ TotalCount = await q.CountAsync();
+ TotalPages = Math.Max(1, (int)Math.Ceiling(TotalCount / (double)PageSize));
+ CurrentPage = Math.Clamp(Page, 1, TotalPages);
+ Results = await q.OrderByDescending(t => t.CreatedAt)
+ .Skip((CurrentPage - 1) * PageSize).Take(PageSize).ToListAsync();
var role = Roles.FirstOrDefault(r => r.Id == RoleId)?.Name;
var city = Cities.FirstOrDefault(c => c.Id == CityId)?.Name;
diff --git a/src/JobsMedical.Web/wwwroot/css/site.css b/src/JobsMedical.Web/wwwroot/css/site.css
index 479e086..f096a6a 100644
--- a/src/JobsMedical.Web/wwwroot/css/site.css
+++ b/src/JobsMedical.Web/wwwroot/css/site.css
@@ -80,6 +80,17 @@ a { color: inherit; text-decoration: none; }
.nav-more-menu a { color: var(--text); font-weight: 600; font-size: 14px; padding: 9px 12px; border-radius: 8px; white-space: nowrap; }
.nav-more-menu a:hover, .nav-more-menu a.active { background: var(--primary-soft); color: var(--primary-dark); }
+/* Pagination */
+.pager { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 6px; margin: 26px 0 4px; }
+.pager-btn, .pager-num {
+ display: inline-flex; align-items: center; justify-content: center; min-width: 38px; height: 38px;
+ padding: 0 12px; border: 1px solid var(--line); border-radius: 9px; background: #fff;
+ color: var(--text); font-weight: 600; font-size: 14px; text-decoration: none; transition: all .15s;
+}
+.pager-btn:hover, .pager-num:hover { border-color: var(--primary); color: var(--primary-dark); }
+.pager-num.active { background: var(--primary); border-color: var(--primary); color: #fff; cursor: default; }
+.pager-gap { padding: 0 2px; color: var(--muted); }
+
.cta-post { white-space: nowrap; box-shadow: 0 2px 8px rgba(240,132,62,.35); }
.header-actions { display: flex; align-items: center; gap: 12px; margin-inline-start: auto; }
.nav-action { font-weight: 600; font-size: 15px; color: var(--muted); white-space: nowrap; transition: color .15s; }