931b7b6ffb
Scrape engine (Services/Scraping/): pluggable IListingSource (working sample + Telegram/Divar credential-ready stubs) → IngestionService (content-hash dedupe → parse → validate → review queue) → ListingValidator (completeness score + spam screen) → IngestionWorker (config-gated hosted service). RawListing gains ContentHash/Confidence/ValidationNotes; RawListingStatus.Flagged. Admin /Admin gets run-now, source list, confidence + flagged queue. Hour-range viz: _HourBar 24h timeline bar (colored by type, overnight wrap) on shift cards, recommendation cards, and detail. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
43 lines
1.5 KiB
Plaintext
43 lines
1.5 KiB
Plaintext
@model JobsMedical.Web.Models.Shift
|
||
@using System.Globalization
|
||
@{
|
||
var s = Model;
|
||
var ci = CultureInfo.InvariantCulture;
|
||
int sm = s.StartTime.Hour * 60 + s.StartTime.Minute;
|
||
int em = s.EndTime.Hour * 60 + s.EndTime.Minute;
|
||
var typeClass = s.ShiftType switch
|
||
{
|
||
ShiftType.Day => "day",
|
||
ShiftType.Evening => "evening",
|
||
ShiftType.Night => "night",
|
||
_ => "oncall",
|
||
};
|
||
|
||
// Build one or two segments (overnight shifts wrap past midnight). On-call = whole day.
|
||
var segs = new List<(double left, double width)>();
|
||
if (s.ShiftType == ShiftType.OnCall || em == sm)
|
||
segs.Add((0, 100));
|
||
else if (em > sm)
|
||
segs.Add((sm / 1440.0 * 100, (em - sm) / 1440.0 * 100));
|
||
else
|
||
{
|
||
segs.Add((sm / 1440.0 * 100, (1440 - sm) / 1440.0 * 100));
|
||
segs.Add((0, em / 1440.0 * 100));
|
||
}
|
||
string Pct(double v) => v.ToString("0.##", ci);
|
||
}
|
||
<div class="hourbar-wrap" title="@JalaliDate.Time(s.StartTime) تا @JalaliDate.Time(s.EndTime)">
|
||
<div class="hourbar">
|
||
<span class="hourbar-grid" style="left:25%"></span>
|
||
<span class="hourbar-grid" style="left:50%"></span>
|
||
<span class="hourbar-grid" style="left:75%"></span>
|
||
@foreach (var seg in segs)
|
||
{
|
||
<span class="hourbar-fill @typeClass" style="left:@Pct(seg.left)%; width:@Pct(seg.width)%"></span>
|
||
}
|
||
</div>
|
||
<div class="hourbar-axis">
|
||
<span>۰</span><span>۶</span><span>۱۲</span><span>۱۸</span><span>۲۴</span>
|
||
</div>
|
||
</div>
|