Search: Elasticsearch-style highlighted match snippets (results + typeahead)
CI/CD / CI · dotnet build (push) Successful in 6m9s
CI/CD / Deploy · hamkadr (push) Has been cancelled

- SearchHighlight.Snippet: extracts a ±70-char window around the first
  matching term and marks it (with ellipses) — the ES "highlight" fragment.
- Result cards (shift/job/talent) now show that snippet from the matched
  description/tags when a query is present, so you SEE where the term hit
  (e.g. «…دارای مدرک <mark>mmt</mark>…») instead of just the role.
- Typeahead suggestions gain a highlighted "sub" line (talent→tags,
  shift→city·specialty, job→facility·city) so matches show in the dropdown too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-08 21:43:50 +03:30
parent bd8d754ee8
commit 8b0b21f24d
7 changed files with 59 additions and 7 deletions
+6 -5
View File
@@ -373,17 +373,17 @@ app.MapGet("/search/suggest", async (string? q, AppDbContext db) =>
.Where(s => s.Status == ShiftStatus.Open && s.Date >= today &&
(EF.Functions.ILike(s.Facility.Name, like) || EF.Functions.ILike(s.Role.Name, like) || EF.Functions.ILike(s.SpecialtyRequired, like)))
.OrderByDescending(s => s.CreatedAt).Take(5)
.Select(s => new SuggestItem("شیفت", s.Role.Name + " — " + s.Facility.Name, "/Shifts/Details/" + s.Id)).ToListAsync();
.Select(s => new SuggestItem("شیفت", s.Role.Name + " — " + s.Facility.Name, "/Shifts/Details/" + s.Id, s.Facility.City.Name + " · " + s.SpecialtyRequired)).ToListAsync();
var jobs = await db.JobOpenings
.Where(j => j.Status == ShiftStatus.Open && j.CreatedAt >= jobCut &&
(EF.Functions.ILike(j.Title, like) || EF.Functions.ILike(j.Facility.Name, like) || EF.Functions.ILike(j.Role.Name, like)))
.OrderByDescending(j => j.CreatedAt).Take(5)
.Select(j => new SuggestItem("استخدام", j.Title, "/Jobs/Details/" + j.Id)).ToListAsync();
.Select(j => new SuggestItem("استخدام", j.Title, "/Jobs/Details/" + j.Id, j.Facility.Name + " · " + j.Facility.City.Name)).ToListAsync();
var talent = await db.TalentListings
.Where(t => t.Status == ShiftStatus.Open && t.CreatedAt >= talentCut &&
(EF.Functions.ILike(t.Tags ?? "", like) || EF.Functions.ILike(t.Role.Name, like) || EF.Functions.ILike(t.City.Name, like) || EF.Functions.ILike(t.PersonName ?? "", like)))
.OrderByDescending(t => t.CreatedAt).Take(5)
.Select(t => new SuggestItem("آماده‌به‌کار", (t.PersonName ?? t.Role.Name) + " — " + t.City.Name, "/Talent/Details/" + t.Id)).ToListAsync();
.Select(t => new SuggestItem("آماده‌به‌کار", (t.PersonName ?? t.Role.Name) + " — " + t.City.Name, "/Talent/Details/" + t.Id, t.Tags)).ToListAsync();
// round-robin merge so all three types appear, capped at 5
var merged = new List<SuggestItem>();
@@ -398,5 +398,6 @@ app.MapGet("/search/suggest", async (string? q, AppDbContext db) =>
app.Run();
/// <summary>One typeahead suggestion row (lowercase props → camelCase JSON for the client).</summary>
public record SuggestItem(string type, string label, string url);
/// <summary>One typeahead suggestion row (lowercase props → camelCase JSON for the client).
/// <c>sub</c> is the matched-context line (tags/city/specialty) shown highlighted under the label.</summary>
public record SuggestItem(string type, string label, string url, string? sub = null);