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,59 @@
@page
@model JobsMedical.Web.Pages.Admin.IndexModel
@{
ViewData["Title"] = "مدیریت — صف آگهی‌ها";
}
<div class="page-head">
<div class="container">
<h1>پنل مدیریت — صف آگهی‌های خام</h1>
<p class="muted">
آگهی‌های جمع‌آوری‌شده از کانال‌ها را اینجا بررسی، ساختارمند و منتشر کن.
(@JalaliDate.ToPersianDigits(Model.Queue.Count.ToString()) در انتظار بررسی)
</p>
</div>
</div>
<div class="container section">
<div class="layout-2">
<aside class="card card-pad filter-card">
<h3>افزودن آگهی خام</h3>
<form method="post">
<div class="filter-group">
<label>منبع (کانال/سایت)</label>
<input type="text" name="SourceChannel" placeholder="مثلاً کانال شیفت تهران" />
</div>
<div class="filter-group">
<label>متن آگهی</label>
<textarea name="RawText" rows="6" placeholder="متن کپی‌شده از تلگرام/بله/دیوار را اینجا بچسبان..."></textarea>
</div>
<button type="submit" asp-page-handler="Add" class="btn btn-primary btn-block">افزودن به صف</button>
</form>
<p class="muted" style="font-size:12px; margin-bottom:0;">
منتشرشده: @JalaliDate.ToPersianDigits(Model.PublishedShifts.ToString()) شیفت،
@JalaliDate.ToPersianDigits(Model.PublishedJobs.ToString()) استخدام
</p>
</aside>
<div>
@if (Model.Queue.Count == 0)
{
<div class="card empty-state">صف خالی است. آگهی جدیدی برای بررسی وجود ندارد.</div>
}
else
{
foreach (var r in Model.Queue)
{
<div class="card card-pad" style="margin-bottom:14px;">
<div class="row" style="display:flex; justify-content:space-between;">
<strong>@r.SourceChannel</strong>
<span class="muted" style="font-size:12px;">@JalaliDate.ToLongDate(DateOnly.FromDateTime(r.FetchedAt))</span>
</div>
<p style="margin:10px 0; white-space:pre-wrap;">@r.RawText</p>
<a class="btn btn-accent" asp-page="/Admin/Review" asp-route-id="@r.Id">بررسی و انتشار ←</a>
</div>
}
}
</div>
</div>
</div>