Files
hamkadr/src/JobsMedical.Web/Pages/Admin/Ingested.cshtml
T
soroush.asadi 4e5df73cf7
CI/CD / CI · dotnet build (push) Successful in 1m41s
CI/CD / Deploy · hamkadr (push) Has been cancelled
Add «آماده به کار» (talent) listing type — workers offering themselves
Adds a third listing kind alongside Shift/Job for healthcare staff who
advertise their own availability (very common in Iranian medical
channels, e.g. "دندانپزشک آماده همکاری… ۵۰٪ تسویه"). These have no
facility; the contact phone is the key field.

- Model: TalentListing (role, person name, years, licensed, city/district,
  area note, availability, gender, comp, phone) + ListingKind.Talent +
  RawListing.LinkedTalentId + DbSet/relations/indexes + EF migration.
- Parser: detect آماده‌به‌کار/جویای کار → Kind=Talent; extract person name,
  years of experience, licensed flag, area («منطقه ۱»), phone. Facility
  name extraction now skipped for talent.
- Validator: talent path scores role + phone + medical (no facility/pay
  required).
- Ingestion auto-publish: creates a TalentListing for talent kind.
- Review (manual publish): Talent option + talent fields; publishes a
  TalentListing without a facility. Shift/Job facility now falls back to a
  shared «نامشخص / ثبت نشده» record when the ad names none — publishing
  never fails on a missing facility.
- Browse /Talent (indexable, filters: city/district/role/gender),
  details /Talent/Details (noindex — personal contact, tel: call button),
  _TalentCard, badge-talent, nav link, home section.
- Sitemap includes /Talent; robots disallows /Talent/Details. Archiver
  expires stale talent listings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 08:01:12 +03:30

71 lines
4.0 KiB
Plaintext

@page
@model JobsMedical.Web.Pages.Admin.IngestedModel
@{
ViewData["Title"] = "نتایج جمع‌آوری";
string P(int n) => JalaliDate.ToPersianDigits(n.ToString());
int C(JobsMedical.Web.Models.RawListingStatus s) => Model.Counts.GetValueOrDefault(s);
string Pill(string key, string label, int count) =>
$"<a class=\"ing-pill {(Model.Status == key || (Model.Status is null && key == "all") ? "active" : "")}\" href=\"?status={key}\">{label} ({P(count)})</a>";
}
<div class="page-head">
<div class="container">
<h1>نتایج جمع‌آوری</h1>
<p class="muted"><a asp-page="/Admin/Index">← صف بررسی</a> — همه‌ی آگهی‌های جمع‌آوری‌شده و وضعیت هرکدام.</p>
</div>
</div>
<div class="container section">
<div class="ing-filters">
@Html.Raw(Pill("all", "همه", Model.Counts.Values.Sum()))
@Html.Raw(Pill("new", "در صف", C(JobsMedical.Web.Models.RawListingStatus.New)))
@Html.Raw(Pill("flagged", "پرچم‌خورده", C(JobsMedical.Web.Models.RawListingStatus.Flagged)))
@Html.Raw(Pill("published", "منتشرشده", C(JobsMedical.Web.Models.RawListingStatus.Normalized)))
@Html.Raw(Pill("discarded", "ردشده/اسپم", C(JobsMedical.Web.Models.RawListingStatus.Discarded)))
</div>
<p class="muted" style="font-size:13px;">@P(Model.Total) نتیجه (نمایش حداکثر ۲۰۰ مورد اخیر).</p>
@if (Model.Items.Count == 0)
{
<div class="card empty-state">موردی با این فیلتر نیست.</div>
}
else
{
foreach (var r in Model.Items)
{
var (cls, label) = r.Status switch
{
JobsMedical.Web.Models.RawListingStatus.New => ("badge-day", "در صف"),
JobsMedical.Web.Models.RawListingStatus.Flagged => ("badge-type", "پرچم‌خورده"),
JobsMedical.Web.Models.RawListingStatus.Normalized => ("badge-verified", "منتشر شد"),
_ => ("badge-gender", "رد شد"),
};
<div class="card card-pad" style="margin-bottom:10px;">
<div class="row" style="display:flex; justify-content:space-between; align-items:center; gap:8px; flex-wrap:wrap;">
<strong>@r.SourceChannel</strong>
<span style="display:flex; gap:6px; align-items:center;">
<span class="badge @cls">@label</span>
<span class="badge badge-type">اطمینان @P(r.Confidence)٪</span>
<span class="muted" style="font-size:12px;">@JalaliDate.ToLongDate(DateOnly.FromDateTime(r.FetchedAt))</span>
</span>
</div>
<p style="margin:8px 0; white-space:pre-wrap; font-size:13.5px;">@(r.RawText.Length > 320 ? r.RawText.Substring(0,320) + "…" : r.RawText)</p>
@if (!string.IsNullOrEmpty(r.ValidationNotes)) { <p class="muted" style="font-size:12px; margin:0 0 6px;">⚠ @r.ValidationNotes</p> }
@if (r.Status == JobsMedical.Web.Models.RawListingStatus.New || r.Status == JobsMedical.Web.Models.RawListingStatus.Flagged)
{
<a class="btn btn-outline" style="padding:4px 12px; font-size:13px;" asp-page="/Admin/Review" asp-route-id="@r.Id">بررسی و انتشار ←</a>
}
else if (r.LinkedShiftId is int sid)
{
<a class="btn btn-outline" style="padding:4px 12px; font-size:13px;" asp-page="/Shifts/Details" asp-route-id="@sid" target="_blank">مشاهده آگهی منتشرشده</a>
}
else if (r.LinkedTalentId is int tid)
{
<a class="btn btn-outline" style="padding:4px 12px; font-size:13px;" asp-page="/Talent/Details" asp-route-id="@tid" target="_blank">مشاهده «آماده به کار» منتشرشده</a>
}
</div>
}
}
</div>