[Applications] Applicant pipeline: employer accept/reject + status to applicant
CI/CD / CI · dotnet build (push) Successful in 43s
CI/CD / Deploy · hamkadr (push) Successful in 43s

InterestEvent gains a Status (ApplicationStatus: Interested→Accepted/Rejected; migration, default Interested). Employer/Listings shows each applicant's status with پذیرفتن/رد buttons (ownership-checked handlers update the status and notify the applicant via bell/SSE/push linking to the listing). The کارجو panel (/Me) now shows a status badge (در انتظار بررسی / پذیرفته شد / رد شد) on each applied shift/job. Reusable _ApplicantRow partial for the employer list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-04 21:27:53 +03:30
parent 60c1997642
commit 167d263560
10 changed files with 1373 additions and 7 deletions
+22 -2
View File
@@ -8,6 +8,12 @@
UserRole.FacilityAdmin => "کارفرما",
_ => "کارجو (کادر درمان)",
};
(string cls, string txt) AppBadge(JobsMedical.Web.Models.ApplicationStatus s) => s switch
{
JobsMedical.Web.Models.ApplicationStatus.Accepted => ("badge-verified", "✓ پذیرفته شد"),
JobsMedical.Web.Models.ApplicationStatus.Rejected => ("badge-gender", "رد شد"),
_ => ("badge-type", "در انتظار بررسی"),
};
}
<div class="page-head">
@@ -73,8 +79,22 @@
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" /> }
@foreach (var s in Model.AppliedShifts)
{
var b = AppBadge(Model.ShiftAppStatus.GetValueOrDefault(s.Id, JobsMedical.Web.Models.ApplicationStatus.Interested));
<div>
<span class="badge @b.cls" style="margin-bottom:6px; display:inline-block;">@b.txt</span>
<partial name="_ShiftCard" model="s" />
</div>
}
@foreach (var j in Model.AppliedJobs)
{
var b = AppBadge(Model.JobAppStatus.GetValueOrDefault(j.Id, JobsMedical.Web.Models.ApplicationStatus.Interested));
<div>
<span class="badge @b.cls" style="margin-bottom:6px; display:inline-block;">@b.txt</span>
<partial name="_JobCard" model="j" />
</div>
}
</div>
}
</div>
@@ -29,6 +29,8 @@ public class IndexModel : PageModel
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 Dictionary<int, ApplicationStatus> ShiftAppStatus { get; private set; } = new();
public Dictionary<int, ApplicationStatus> JobAppStatus { get; private set; } = new();
public async Task OnGetAsync()
{
@@ -52,6 +54,14 @@ public class IndexModel : PageModel
AppliedJobs = await _db.JobOpenings
.Include(j => j.Facility).ThenInclude(f => f.City).Include(j => j.Role)
.Where(j => appliedJobIds.Contains(j.Id)).ToListAsync();
// Latest application status per applied listing (employer accept/reject shows here).
ShiftAppStatus = events.Where(e => e.EventType == InterestEventType.Apply && e.ShiftId != null)
.GroupBy(e => e.ShiftId!.Value)
.ToDictionary(g => g.Key, g => g.OrderByDescending(e => e.CreatedAt).First().Status);
JobAppStatus = events.Where(e => e.EventType == InterestEventType.Apply && e.JobOpeningId != null)
.GroupBy(e => e.JobOpeningId!.Value)
.ToDictionary(g => g.Key, g => g.OrderByDescending(e => e.CreatedAt).First().Status);
}
private Task<List<Shift>> ShiftsByIds(List<int> ids) => _db.Shifts