Initial commit — Hamkadr (همکادر) healthcare-staffing marketplace

ASP.NET Core 10 Razor Pages + PostgreSQL/EF Core. RTL Persian, Jalali dates, self-hosted Vazirmatn, teal/coral brand.

Features:
- Shift listings: browse/filter (city, district, role, type, pay), weekly Jalali calendar, detail + interest handoff, near-me distance sort
- Hiring (استخدام) listings with employment type + salary range
- Pattern-engine recommendations + anonymous interest tracking (visitor cookie)
- Heuristic Persian listing-parser + admin queue (raw channel post → shift/job)
- Phone-OTP cookie auth + visitor-history linking + profile

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-03 01:43:55 +03:30
commit 2fb86a435e
150 changed files with 90993 additions and 0 deletions
@@ -0,0 +1,72 @@
@page
@model JobsMedical.Web.Pages.Calendar.IndexModel
@{
ViewData["Title"] = "تقویم هفتگی شیفت‌ها";
var weekEnd = Model.WeekStart.AddDays(6);
}
<div class="page-head">
<div class="container">
<h1>تقویم هفتگی شیفت‌ها</h1>
<form method="get" style="margin-top:12px; max-width:360px;">
<input type="hidden" name="WeekOffset" value="@Model.WeekOffset" />
<select name="FacilityId" onchange="this.form.submit()">
<option value="">همه مراکز درمانی</option>
@foreach (var f in Model.Facilities)
{
<option value="@f.Id" selected="@(Model.FacilityId == f.Id)">@f.Name</option>
}
</select>
</form>
</div>
</div>
<div class="container section">
<div class="cal-nav">
<a class="btn btn-outline" asp-page="/Calendar/Index"
asp-route-FacilityId="@Model.FacilityId" asp-route-WeekOffset="@(Model.WeekOffset - 1)">→ هفته قبل</a>
<strong>
@JalaliDate.DayOfMonth(Model.WeekStart) @JalaliDate.MonthName(Model.WeekStart)
تا
@JalaliDate.DayOfMonth(weekEnd) @JalaliDate.MonthName(weekEnd)
</strong>
<a class="btn btn-outline" asp-page="/Calendar/Index"
asp-route-FacilityId="@Model.FacilityId" asp-route-WeekOffset="@(Model.WeekOffset + 1)">هفته بعد ←</a>
</div>
<table class="cal">
<thead>
<tr>
@foreach (var (date, _) in Model.Days)
{
<th>@JalaliDate.WeekDayName(date)</th>
}
</tr>
</thead>
<tbody>
<tr>
@foreach (var (date, dayShifts) in Model.Days)
{
var isToday = date == Model.Today;
<td class="@(isToday ? "today" : "") @(dayShifts.Count == 0 ? "empty" : "")">
<div class="day-num">@JalaliDate.DayOfMonth(date)</div>
@foreach (var s in dayShifts)
{
var cls = s.ShiftType switch
{
ShiftType.Day => "day",
ShiftType.Evening => "evening",
ShiftType.Night => "night",
_ => "oncall",
};
<a class="cal-chip @cls" asp-page="/Shifts/Details" asp-route-id="@s.Id"
title="@s.Facility?.Name">
@JalaliDate.Time(s.StartTime) @s.Facility?.Name
</a>
}
</td>
}
</tr>
</tbody>
</table>
</div>
@@ -0,0 +1,46 @@
using JobsMedical.Web.Data;
using JobsMedical.Web.Models;
using JobsMedical.Web.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
namespace JobsMedical.Web.Pages.Calendar;
public class IndexModel : PageModel
{
private readonly AppDbContext _db;
public IndexModel(AppDbContext db) => _db = db;
[BindProperty(SupportsGet = true)] public int? FacilityId { get; set; }
[BindProperty(SupportsGet = true)] public int WeekOffset { get; set; } // 0 = current week
public List<Facility> Facilities { get; private set; } = new();
public DateOnly WeekStart { get; private set; }
public DateOnly Today { get; private set; }
/// <summary>7 days (Saturday→Friday), each with its open shifts.</summary>
public List<(DateOnly Date, List<Shift> Shifts)> Days { get; private set; } = new();
public async Task OnGetAsync()
{
Today = DateOnly.FromDateTime(DateTime.UtcNow);
Facilities = await _db.Facilities.OrderBy(f => f.Name).ToListAsync();
WeekStart = JalaliDate.StartOfPersianWeek(Today).AddDays(WeekOffset * 7);
var weekEnd = WeekStart.AddDays(6);
var q = _db.Shifts
.Include(s => s.Facility)
.Where(s => s.Status == ShiftStatus.Open && s.Date >= WeekStart && s.Date <= weekEnd);
if (FacilityId is not null) q = q.Where(s => s.FacilityId == FacilityId);
var shifts = await q.OrderBy(s => s.StartTime).ToListAsync();
Days = Enumerable.Range(0, 7)
.Select(i => WeekStart.AddDays(i))
.Select(d => (d, shifts.Where(s => s.Date == d).ToList()))
.ToList();
}
}