Add gender requirement (آقا/خانم/فرقی نمی‌کند) + employee (کارجو) panel
CI/CD / CI · dotnet build (push) Successful in 6m23s
CI/CD / Deploy · hamkadr (push) Failing after 6m30s

- Gender enum + GenderRequirement on Shift/JobOpening + Gender on UserPreferences (migration)
- Employer PostShift/PostJob + admin Review have a gender select; parser detects آقا/خانم/مرد/زن
- Gender badge on cards + detail; gender filter on Shifts/Jobs; gender in preferences
- Recommendations exclude listings whose gender requirement conflicts with the person's gender
- Two panels: new /Me employee (کارجو) panel (recommendations + saved + applied + prefs) alongside /Employer; nav routes by role; /Account/Profile → /Me

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-04 00:19:32 +03:30
parent 8f5d926d42
commit 6cfdd16c42
35 changed files with 1177 additions and 123 deletions
@@ -1,77 +1,3 @@
@page
@model JobsMedical.Web.Pages.Account.ProfileModel
@{
ViewData["Title"] = "پروفایل من";
string RoleLabel(UserRole r) => r switch
{
UserRole.Admin => "مدیر",
UserRole.FacilityAdmin => "مدیر مرکز درمانی",
_ => "کادر درمان",
};
}
<div class="page-head">
<div class="container">
<h1>پروفایل من</h1>
<p class="muted">
📱 <span dir="ltr">@JalaliDate.ToPersianDigits(Model.CurrentUser?.Phone ?? "")</span>
— @RoleLabel(Model.CurrentUser?.Role ?? UserRole.Doctor)
</p>
</div>
</div>
<div class="container section">
<div class="rec-banner">
<div>
<h2 style="margin:0 0 4px;">علاقه‌مندی‌هایت را کامل کن</h2>
<span style="opacity:.9; font-size:14px;">تا پیشنهادهای دقیق‌تری بگیری</span>
</div>
<a class="btn btn-outline" asp-page="/Preferences/Index">تنظیم علاقه‌مندی‌ها</a>
</div>
@if (User.IsInRole("FacilityAdmin") || User.IsInRole("Admin"))
{
<p><a asp-page="/Employer/Index">→ ورود به پنل کارفرما</a></p>
}
else
{
<p class="muted">مرکز درمانی هستی و می‌خواهی شیفت یا استخدام منتشر کنی؟
<a asp-page="/Employer/RegisterFacility">مرکز خود را ثبت کن</a></p>
}
<h2 style="font-size:20px;">شیفت‌های ذخیره‌شده</h2>
@if (Model.SavedShifts.Count == 0)
{
<div class="card empty-state">هنوز شیفتی ذخیره نکرده‌ای.</div>
}
else
{
<div class="grid grid-3">
@foreach (var s in Model.SavedShifts) { <partial name="_ShiftCard" model="s" /> }
</div>
}
<h2 style="font-size:20px; margin-top:32px;">شیفت‌هایی که اعلام تمایل کردی</h2>
@if (Model.AppliedShifts.Count == 0)
{
<div class="card empty-state">هنوز برای شیفتی اعلام تمایل نکرده‌ای.</div>
}
else
{
<div class="grid grid-3">
@foreach (var s in Model.AppliedShifts) { <partial name="_ShiftCard" model="s" /> }
</div>
}
<h2 style="font-size:20px; margin-top:32px;">موقعیت‌های استخدامی که اعلام تمایل کردی</h2>
@if (Model.AppliedJobs.Count == 0)
{
<div class="card empty-state">هنوز برای موقعیتی اعلام تمایل نکرده‌ای.</div>
}
else
{
<div class="grid grid-3">
@foreach (var j in Model.AppliedJobs) { <partial name="_JobCard" model="j" /> }
</div>
}
</div>
@* Redirects to /Me (employee panel). *@
@@ -1,53 +1,12 @@
using System.Security.Claims;
using JobsMedical.Web.Data;
using JobsMedical.Web.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
namespace JobsMedical.Web.Pages.Account;
// Profile was merged into the employee panel (/Me). Keep the old URL working.
[Authorize]
public class ProfileModel : PageModel
{
private readonly AppDbContext _db;
public ProfileModel(AppDbContext db) => _db = db;
public User? CurrentUser { get; private set; }
public List<Shift> SavedShifts { get; private set; } = new();
public List<JobOpening> AppliedJobs { get; private set; } = new();
public List<Shift> AppliedShifts { get; private set; } = new();
public async Task OnGetAsync()
{
var userId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
CurrentUser = await _db.Users.FindAsync(userId);
// All visitor ids this account has been linked to (across devices).
var visitorIds = await _db.Visitors.Where(v => v.UserId == userId).Select(v => v.Id).ToListAsync();
var events = await _db.InterestEvents
.Where(e => visitorIds.Contains(e.VisitorId))
.OrderByDescending(e => e.CreatedAt)
.ToListAsync();
var savedShiftIds = events.Where(e => e.EventType == InterestEventType.Save && e.ShiftId != null)
.Select(e => e.ShiftId!.Value).Distinct().ToList();
var appliedShiftIds = events.Where(e => e.EventType == InterestEventType.Apply && e.ShiftId != null)
.Select(e => e.ShiftId!.Value).Distinct().ToList();
var appliedJobIds = events.Where(e => e.EventType == InterestEventType.Apply && e.JobOpeningId != null)
.Select(e => e.JobOpeningId!.Value).Distinct().ToList();
SavedShifts = await ShiftsByIds(savedShiftIds);
AppliedShifts = await ShiftsByIds(appliedShiftIds);
AppliedJobs = await _db.JobOpenings
.Include(j => j.Facility).ThenInclude(f => f.City).Include(j => j.Role)
.Where(j => appliedJobIds.Contains(j.Id)).ToListAsync();
}
private Task<List<Shift>> ShiftsByIds(List<int> ids) => _db.Shifts
.Include(s => s.Facility).ThenInclude(f => f.City)
.Include(s => s.Facility).ThenInclude(f => f.District)
.Include(s => s.Role)
.Where(s => ids.Contains(s.Id)).ToListAsync();
public IActionResult OnGet() => RedirectToPage("/Me/Index");
}
@@ -63,6 +63,15 @@
</select>
</div>
<div class="filter-group">
<label>جنسیت مورد نیاز</label>
<select name="GenderRequirement">
<option value="0" selected="@(Model.GenderRequirement == JobsMedical.Web.Models.Gender.Any)">فرقی نمی‌کند</option>
<option value="1" selected="@(Model.GenderRequirement == JobsMedical.Web.Models.Gender.Male)">آقا</option>
<option value="2" selected="@(Model.GenderRequirement == JobsMedical.Web.Models.Gender.Female)">خانم</option>
</select>
</div>
<div id="shiftFields">
<div class="filter-group">
<label>تاریخ شیفت (میلادی)</label>
@@ -38,6 +38,7 @@ public class ReviewModel : PageModel
[BindProperty] public long? PayAmount { get; set; }
[BindProperty] public int? SharePercent { get; set; }
[BindProperty] public bool Negotiable { get; set; }
[BindProperty] public Gender GenderRequirement { get; set; }
// Job fields
[BindProperty] public string? Title { get; set; }
[BindProperty] public EmploymentType EmploymentType { get; set; }
@@ -62,6 +63,7 @@ public class ReviewModel : PageModel
ShiftDate = DateOnly.FromDateTime(DateTime.UtcNow).AddDays(1);
Negotiable = Parsed.PayNegotiable;
SharePercent = Parsed.SharePercent;
GenderRequirement = Parsed.Gender;
if (Parsed.PayAmount is not null) { PayAmount = Parsed.PayAmount; SalaryMin = Parsed.PayAmount; }
Description = Raw.RawText;
Title = Parsed.RoleName is not null ? $"استخدام {Parsed.RoleName}" : "موقعیت استخدامی";
@@ -90,6 +92,7 @@ public class ReviewModel : PageModel
: (PayAmount is null && SharePercent is not null ? PayType.Percentage : PayType.PerShift),
PayAmount = Negotiable ? null : PayAmount,
SharePercent = Negotiable ? null : SharePercent,
GenderRequirement = GenderRequirement,
Status = ShiftStatus.Open,
Source = ShiftSource.Aggregated,
SourceUrl = Raw.SourceUrl,
@@ -109,6 +112,7 @@ public class ReviewModel : PageModel
EmploymentType = EmploymentType,
SalaryMin = Negotiable ? null : SalaryMin,
SalaryMax = Negotiable ? null : SalaryMax,
GenderRequirement = GenderRequirement,
Description = Description,
Status = ShiftStatus.Open,
Source = ShiftSource.Aggregated,
@@ -43,6 +43,14 @@
}
</select>
</div>
<div class="filter-group">
<label>جنسیت مورد نیاز</label>
<select name="GenderRequirement">
<option value="0">فرقی نمی‌کند</option>
<option value="1">آقا</option>
<option value="2">خانم</option>
</select>
</div>
<div class="filter-group">
<label>نوع همکاری</label>
<select name="EmploymentType">
@@ -25,6 +25,7 @@ public class PostJobModel : PageModel
[BindProperty] public long? SalaryMin { get; set; }
[BindProperty] public long? SalaryMax { get; set; }
[BindProperty] public bool Negotiable { get; set; }
[BindProperty] public Gender GenderRequirement { get; set; }
[BindProperty] public string? Description { get; set; }
[BindProperty] public string? Requirements { get; set; }
@@ -48,6 +49,7 @@ public class PostJobModel : PageModel
EmploymentType = EmploymentType,
SalaryMin = Negotiable ? null : SalaryMin,
SalaryMax = Negotiable ? null : SalaryMax,
GenderRequirement = GenderRequirement,
Description = Description,
Requirements = Requirements,
Status = ShiftStatus.Open,
@@ -39,6 +39,14 @@
}
</select>
</div>
<div class="filter-group">
<label>جنسیت مورد نیاز</label>
<select name="GenderRequirement">
<option value="0">فرقی نمی‌کند</option>
<option value="1">آقا</option>
<option value="2">خانم</option>
</select>
</div>
<div class="filter-group">
<label>تاریخ (میلادی)</label>
<input type="date" name="Date" value="@Model.Date.ToString("yyyy-MM-dd")" dir="ltr" />
@@ -27,6 +27,7 @@ public class PostShiftModel : PageModel
[BindProperty] public long? PayAmount { get; set; }
[BindProperty] public int? SharePercent { get; set; } // سهم درآمد (٪)
[BindProperty] public bool Negotiable { get; set; }
[BindProperty] public Gender GenderRequirement { get; set; }
[BindProperty] public string? Description { get; set; }
public async Task OnGetAsync()
@@ -60,6 +61,7 @@ public class PostShiftModel : PageModel
: (PayAmount is null && SharePercent is not null ? PayType.Percentage : PayType.PerShift),
PayAmount = Negotiable ? null : PayAmount,
SharePercent = Negotiable ? null : SharePercent,
GenderRequirement = GenderRequirement,
Status = ShiftStatus.Open,
Source = ShiftSource.Direct, // posted directly by the facility
});
@@ -50,6 +50,10 @@
<h3 style="margin-top:0;">مشخصات موقعیت</h3>
<div class="info-row"><span class="k">نوع همکاری</span><span class="v">@empLabel</span></div>
<div class="info-row"><span class="k">نقش</span><span class="v">@j.Role?.Name</span></div>
@if (j.GenderRequirement != Gender.Any)
{
<div class="info-row"><span class="k">جنسیت</span><span class="v">@JalaliDate.GenderLabel(j.GenderRequirement)</span></div>
}
<div class="info-row"><span class="k">حقوق ماهانه</span><span class="v" style="color:var(--primary-dark)">@salary</span></div>
</div>
@@ -65,6 +65,14 @@
}
</select>
</div>
<div class="filter-group">
<label>جنسیت</label>
<select name="GenderFilter" onchange="this.form.submit()">
<option value="">فرقی نمی‌کند</option>
<option value="1" selected="@(Model.GenderFilter == JobsMedical.Web.Models.Gender.Male)">آقا</option>
<option value="2" selected="@(Model.GenderFilter == JobsMedical.Web.Models.Gender.Female)">خانم</option>
</select>
</div>
<div class="filter-group">
<label>نوع همکاری</label>
<select name="EmploymentType" onchange="this.form.submit()">
@@ -16,6 +16,7 @@ public class IndexModel : PageModel
[BindProperty(SupportsGet = true)] public int? DistrictId { get; set; }
[BindProperty(SupportsGet = true)] public int? RoleId { get; set; }
[BindProperty(SupportsGet = true)] public EmploymentType? EmploymentType { get; set; }
[BindProperty(SupportsGet = true)] public Gender? GenderFilter { get; set; }
[BindProperty(SupportsGet = true)] public double? Lat { get; set; }
[BindProperty(SupportsGet = true)] public double? Lng { get; set; }
@@ -44,6 +45,8 @@ public class IndexModel : PageModel
if (DistrictId is not null) q = q.Where(j => j.Facility.DistrictId == DistrictId);
if (RoleId is not null) q = q.Where(j => j.RoleId == RoleId);
if (EmploymentType is not null) q = q.Where(j => j.EmploymentType == EmploymentType);
if (GenderFilter is Gender g && g != Gender.Any)
q = q.Where(j => j.GenderRequirement == Gender.Any || j.GenderRequirement == g);
var results = await q.ToListAsync();
+77
View File
@@ -0,0 +1,77 @@
@page
@model JobsMedical.Web.Pages.Me.IndexModel
@{
ViewData["Title"] = "پنل کارجو";
string RoleLabel(UserRole r) => r switch
{
UserRole.Admin => "مدیر",
UserRole.FacilityAdmin => "کارفرما",
_ => "کارجو (کادر درمان)",
};
}
<div class="page-head">
<div class="container">
<h1>پنل کارجو</h1>
<p class="muted">
📱 <span dir="ltr">@JalaliDate.ToPersianDigits(Model.CurrentUser?.Phone ?? "")</span>
— @RoleLabel(Model.CurrentUser?.Role ?? UserRole.Doctor)
</p>
</div>
</div>
<div class="container section">
<div class="rec-banner">
<div>
<h2 style="margin:0 0 4px;">✨ پیشنهادهای ویژه شما</h2>
<span style="opacity:.9; font-size:14px;">
@if (Model.Prefs?.HasAny == true)
{
<text>بر اساس نقش، شهر، نوع شیفت و جنسیت شما</text>
}
else
{
<text>علاقه‌مندی‌هایت را تنظیم کن تا دقیق‌تر شوند</text>
}
</span>
</div>
<a class="btn btn-outline" asp-page="/Preferences/Index">تنظیم علاقه‌مندی‌ها</a>
</div>
@if (Model.Recommendations.Count > 0)
{
<div class="grid grid-3">
@foreach (var rec in Model.Recommendations)
{
<partial name="_RecommendationCard" model="rec" />
}
</div>
}
else
{
<div class="card empty-state">فعلاً پیشنهادی نیست. <a asp-page="/Shifts/Index">جستجوی شیفت‌ها</a></div>
}
<h2 style="font-size:20px; margin-top:32px;">شیفت‌های ذخیره‌شده</h2>
@if (Model.SavedShifts.Count == 0)
{
<div class="card empty-state">هنوز شیفتی ذخیره نکرده‌ای.</div>
}
else
{
<div class="grid grid-3">@foreach (var s in Model.SavedShifts) { <partial name="_ShiftCard" model="s" /> }</div>
}
<h2 style="font-size:20px; margin-top:32px;">اعلام تمایل‌های شما</h2>
@if (Model.AppliedShifts.Count == 0 && Model.AppliedJobs.Count == 0)
{
<div class="card empty-state">هنوز برای فرصتی اعلام تمایل نکرده‌ای.</div>
}
else
{
<div class="grid grid-3">
@foreach (var s in Model.AppliedShifts) { <partial name="_ShiftCard" model="s" /> }
@foreach (var j in Model.AppliedJobs) { <partial name="_JobCard" model="j" /> }
</div>
}
</div>
@@ -0,0 +1,62 @@
using System.Security.Claims;
using JobsMedical.Web.Data;
using JobsMedical.Web.Models;
using JobsMedical.Web.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
namespace JobsMedical.Web.Pages.Me;
/// <summary>The employee / job-seeker (کارجو) panel — the staff-side counterpart to /Employer.</summary>
[Authorize]
public class IndexModel : PageModel
{
private readonly AppDbContext _db;
private readonly RecommendationService _recs;
private readonly InterestService _interest;
public IndexModel(AppDbContext db, RecommendationService recs, InterestService interest)
{
_db = db;
_recs = recs;
_interest = interest;
}
public User? CurrentUser { get; private set; }
public UserPreferences? Prefs { get; private set; }
public List<Recommendation> Recommendations { get; private set; } = new();
public List<Shift> SavedShifts { get; private set; } = new();
public List<Shift> AppliedShifts { get; private set; } = new();
public List<JobOpening> AppliedJobs { get; private set; } = new();
public async Task OnGetAsync()
{
var userId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
CurrentUser = await _db.Users.FindAsync(userId);
Prefs = await _interest.GetPreferencesAsync();
Recommendations = await _recs.GetForVisitorAsync(6);
var visitorIds = await _db.Visitors.Where(v => v.UserId == userId).Select(v => v.Id).ToListAsync();
var events = await _db.InterestEvents.Where(e => visitorIds.Contains(e.VisitorId)).ToListAsync();
var savedShiftIds = events.Where(e => e.EventType == InterestEventType.Save && e.ShiftId != null)
.Select(e => e.ShiftId!.Value).Distinct().ToList();
var appliedShiftIds = events.Where(e => e.EventType == InterestEventType.Apply && e.ShiftId != null)
.Select(e => e.ShiftId!.Value).Distinct().ToList();
var appliedJobIds = events.Where(e => e.EventType == InterestEventType.Apply && e.JobOpeningId != null)
.Select(e => e.JobOpeningId!.Value).Distinct().ToList();
SavedShifts = await ShiftsByIds(savedShiftIds);
AppliedShifts = await ShiftsByIds(appliedShiftIds);
AppliedJobs = await _db.JobOpenings
.Include(j => j.Facility).ThenInclude(f => f.City).Include(j => j.Role)
.Where(j => appliedJobIds.Contains(j.Id)).ToListAsync();
}
private Task<List<Shift>> ShiftsByIds(List<int> ids) => _db.Shifts
.Include(s => s.Facility).ThenInclude(f => f.City)
.Include(s => s.Facility).ThenInclude(f => f.District)
.Include(s => s.Role)
.Where(s => ids.Contains(s.Id)).ToListAsync();
}
@@ -35,6 +35,15 @@
</select>
</div>
<div class="filter-group">
<label>جنسیت شما</label>
<select name="Gender">
<option value="0" selected="@(Model.Gender == JobsMedical.Web.Models.Gender.Any)">نمی‌خواهم بگویم</option>
<option value="1" selected="@(Model.Gender == JobsMedical.Web.Models.Gender.Male)">آقا</option>
<option value="2" selected="@(Model.Gender == JobsMedical.Web.Models.Gender.Female)">خانم</option>
</select>
<p class="muted" style="font-size:12px; margin:4px 0 0;">برای حذف آگهی‌هایی که با جنسیت شما هم‌خوان نیستند.</p>
</div>
<div class="filter-group">
<label>نوع شیفت ترجیحی</label>
<select name="PreferredShiftType">
@@ -25,6 +25,7 @@ public class IndexModel : PageModel
[BindProperty] public int? CityId { get; set; }
[BindProperty] public ShiftType? PreferredShiftType { get; set; }
[BindProperty] public long? MinPay { get; set; }
[BindProperty] public Gender Gender { get; set; }
public bool Saved { get; private set; }
@@ -38,12 +39,13 @@ public class IndexModel : PageModel
CityId = prefs.CityId;
PreferredShiftType = prefs.PreferredShiftType;
MinPay = prefs.MinPay;
Gender = prefs.Gender;
}
}
public async Task<IActionResult> OnPostAsync()
{
await _interest.SavePreferencesAsync(RoleId, CityId, PreferredShiftType, MinPay);
await _interest.SavePreferencesAsync(RoleId, CityId, PreferredShiftType, MinPay, Gender);
// Back to home so the personalized feed is the immediate payoff.
TempData["prefsSaved"] = true;
return RedirectToPage("/Index");
@@ -22,6 +22,10 @@
{
<span class="badge badge-type">@Model.Role.Name</span>
}
@if (Model.GenderRequirement != Gender.Any)
{
<span class="badge badge-gender">@JalaliDate.GenderLabel(Model.GenderRequirement)</span>
}
<span>🏥 @Model.Facility?.Name</span>
</div>
<div class="row">📍 @Model.Facility?.City?.Name@(Model.Facility?.District is not null ? "، " + Model.Facility.District.Name : "")</div>
@@ -39,7 +39,7 @@
{
<a asp-page="/Employer/Index" style="margin-inline-end:14px; font-weight:600;">پنل کارفرما</a>
}
<a asp-page="/Account/Profile" style="margin-inline-end:10px; font-weight:600;">پروفایل</a>
<a asp-page="/Me/Index" style="margin-inline-end:10px; font-weight:600;">پنل کارجو</a>
<form method="post" asp-page="/Account/Logout" style="display:inline;">
<button type="submit" class="btn btn-outline" style="padding:7px 14px;">خروج</button>
</form>
@@ -19,6 +19,10 @@
{
<span class="badge badge-type">@s.Role.Name</span>
}
@if (s.GenderRequirement != Gender.Any)
{
<span class="badge badge-gender">@JalaliDate.GenderLabel(s.GenderRequirement)</span>
}
<span>📍 @s.Facility?.City?.Name</span>
</div>
<div class="row">📅 @JalaliDate.WeekDayName(s.Date)، @JalaliDate.ToLongDate(s.Date) — 🕐 @JalaliDate.Time(s.StartTime)</div>
@@ -18,6 +18,10 @@
{
<span class="badge badge-type">@Model.Role.Name</span>
}
@if (Model.GenderRequirement != Gender.Any)
{
<span class="badge badge-gender">@JalaliDate.GenderLabel(Model.GenderRequirement)</span>
}
<span>📍 @Model.Facility?.City?.Name@(Model.Facility?.District is not null ? "، " + Model.Facility.District.Name : "")</span>
@if (Model.Facility?.IsVerified == true)
{
@@ -49,6 +49,10 @@
<div class="info-row"><span class="k">ساعت</span><span class="v">@JalaliDate.Time(s.StartTime) تا @JalaliDate.Time(s.EndTime)</span></div>
<div class="info-row"><span class="k">مدت</span><span class="v">@JalaliDate.ToPersianDigits(s.DurationHours.ToString("0.#")) ساعت</span></div>
<div class="info-row"><span class="k">نقش مورد نیاز</span><span class="v">@(s.Role?.Name ?? s.SpecialtyRequired)</span></div>
@if (s.GenderRequirement != Gender.Any)
{
<div class="info-row"><span class="k">جنسیت</span><span class="v">@JalaliDate.GenderLabel(s.GenderRequirement)</span></div>
}
<div class="info-row"><span class="k">پرداخت</span><span class="v" style="color:var(--primary-dark)">@JalaliDate.PayLabel(s.PayType, s.PayAmount, s.SharePercent)</span></div>
<div style="padding-top:12px;">
<span class="k" style="font-size:13px; color:var(--muted);">بازه ساعت کاری در شبانه‌روز</span>
@@ -78,6 +78,14 @@
}
</select>
</div>
<div class="filter-group">
<label>جنسیت</label>
<select name="GenderFilter" onchange="this.form.submit()">
<option value="">فرقی نمی‌کند</option>
<option value="1" selected="@(Model.GenderFilter == JobsMedical.Web.Models.Gender.Male)">آقا</option>
<option value="2" selected="@(Model.GenderFilter == JobsMedical.Web.Models.Gender.Female)">خانم</option>
</select>
</div>
<div class="filter-group">
<label>نوع شیفت</label>
<select name="ShiftType" onchange="this.form.submit()">
@@ -19,6 +19,7 @@ public class IndexModel : PageModel
[BindProperty(SupportsGet = true)] public ShiftType? ShiftType { get; set; }
[BindProperty(SupportsGet = true)] public bool PaidOnly { get; set; }
[BindProperty(SupportsGet = true)] public bool ShareOnly { get; set; } // فقط شیفت‌های سهم درآمد
[BindProperty(SupportsGet = true)] public Gender? GenderFilter { get; set; }
// "Near me": the browser sends the visitor's coordinates and we sort by distance.
[BindProperty(SupportsGet = true)] public double? Lat { get; set; }
@@ -58,6 +59,9 @@ public class IndexModel : PageModel
if (ShiftType is not null) q = q.Where(s => s.ShiftType == ShiftType);
if (PaidOnly) q = q.Where(s => s.PayAmount != null);
if (ShareOnly) q = q.Where(s => s.SharePercent != null);
// A given gender sees listings open to them (their gender or "فرقی نمی‌کند").
if (GenderFilter is Gender g && g != Gender.Any)
q = q.Where(s => s.GenderRequirement == Gender.Any || s.GenderRequirement == g);
var results = await q.ToListAsync();