[Map] Render real Neshan map on shift/job detail pages
The detail pages showed a 'map coming later' placeholder. Add a read-only Neshan web map (Leaflet SDK) showing the facility marker when NeshanMapKey is set, plus a 'مسیریابی در نشان' directions link; falls back to coordinates when no key. New _NeshanMap shared partial loads the SDK and inits #facmap. Shift/Job Details models now expose MapKey via SettingsService. Coordinates are emitted with InvariantCulture so the decimal point/digits don't break JS. The facility registration picker already used Neshan; this reuses the same key. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -122,6 +122,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (j.Facility?.Lat is not null && j.Facility?.Lng is not null)
|
||||||
|
{
|
||||||
|
var latS = j.Facility.Lat.Value.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
var lngS = j.Facility.Lng.Value.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
<div class="card card-pad" style="margin-top:16px;">
|
||||||
|
<h3 style="margin-top:0;">موقعیت مکانی</h3>
|
||||||
|
@if (!string.IsNullOrEmpty(Model.MapKey))
|
||||||
|
{
|
||||||
|
<div id="facmap" data-lat="@latS" data-lng="@lngS" style="height:200px; border-radius:10px; overflow:hidden; border:1px solid var(--line);"></div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div style="background:var(--primary-soft); border-radius:10px; height:140px; display:grid; place-items:center; color:var(--primary-dark); text-align:center; padding:10px;">
|
||||||
|
🗺️<br /><small class="muted" dir="ltr">@latS، @lngS</small>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<a class="btn btn-outline btn-block" style="margin-top:8px;" target="_blank" rel="noopener"
|
||||||
|
href="https://neshan.org/maps/@(latS),@(lngS),16z">مسیریابی در نشان</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(Model.MapKey) && Model.Job?.Facility?.Lat is not null)
|
||||||
|
{
|
||||||
|
<partial name="_NeshanMap" model="Model.MapKey" />
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,14 +11,18 @@ public class DetailsModel : PageModel
|
|||||||
{
|
{
|
||||||
private readonly AppDbContext _db;
|
private readonly AppDbContext _db;
|
||||||
private readonly InterestService _interest;
|
private readonly InterestService _interest;
|
||||||
|
private readonly JobsMedical.Web.Services.Scraping.SettingsService _settings;
|
||||||
|
|
||||||
public DetailsModel(AppDbContext db, InterestService interest)
|
public DetailsModel(AppDbContext db, InterestService interest,
|
||||||
|
JobsMedical.Web.Services.Scraping.SettingsService settings)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_interest = interest;
|
_interest = interest;
|
||||||
|
_settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JobOpening? Job { get; private set; }
|
public JobOpening? Job { get; private set; }
|
||||||
|
public string? MapKey { get; private set; }
|
||||||
public bool ShowContact { get; private set; }
|
public bool ShowContact { get; private set; }
|
||||||
public bool Saved { get; private set; }
|
public bool Saved { get; private set; }
|
||||||
public bool Reported { get; private set; }
|
public bool Reported { get; private set; }
|
||||||
@@ -27,6 +31,7 @@ public class DetailsModel : PageModel
|
|||||||
{
|
{
|
||||||
await LoadAsync(id);
|
await LoadAsync(id);
|
||||||
if (Job is null) return NotFound();
|
if (Job is null) return NotFound();
|
||||||
|
MapKey = (await _settings.GetAsync()).NeshanMapKey;
|
||||||
Reported = Request.Query["reported"] == "1";
|
Reported = Request.Query["reported"] == "1";
|
||||||
await _interest.LogJobAsync(InterestEventType.View, id);
|
await _interest.LogJobAsync(InterestEventType.View, id);
|
||||||
return Page();
|
return Page();
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
@model string
|
||||||
|
@*
|
||||||
|
Read-only Neshan map. Render once per page AFTER an element <div id="facmap"
|
||||||
|
data-lat="…" data-lng="…"> exists. Pass the Neshan web key as the model.
|
||||||
|
The SDK is a synchronous script, so the init below runs once L is defined.
|
||||||
|
*@
|
||||||
|
<link rel="stylesheet" href="https://static.neshan.org/sdk/leaflet/1.4.0/neshan-sdk/v1.0.8/index.css" />
|
||||||
|
<script src="https://static.neshan.org/sdk/leaflet/1.4.0/neshan-sdk/v1.0.8/index.js"></script>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var el = document.getElementById('facmap');
|
||||||
|
if (!el || !window.L) return;
|
||||||
|
var lat = parseFloat(el.dataset.lat), lng = parseFloat(el.dataset.lng);
|
||||||
|
if (isNaN(lat) || isNaN(lng)) return;
|
||||||
|
var map = new L.Map('facmap', {
|
||||||
|
key: '@Model', maptype: 'neshan', poi: true, traffic: false,
|
||||||
|
center: [lat, lng], zoom: 15
|
||||||
|
});
|
||||||
|
L.marker([lat, lng]).addTo(map);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
@@ -143,11 +143,20 @@
|
|||||||
<h3 style="margin-top:0;">موقعیت مکانی</h3>
|
<h3 style="margin-top:0;">موقعیت مکانی</h3>
|
||||||
@if (f.Lat is not null && f.Lng is not null)
|
@if (f.Lat is not null && f.Lng is not null)
|
||||||
{
|
{
|
||||||
<div style="background:var(--primary-soft); border-radius:10px; height:170px; display:grid; place-items:center; color:var(--primary-dark); text-align:center; padding:10px;">
|
var latS = f.Lat.Value.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||||
🗺️<br />نقشه نشان/بلد<br />
|
var lngS = f.Lng.Value.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||||
<small class="muted">@f.Lat، @f.Lng</small>
|
@if (!string.IsNullOrEmpty(Model.MapKey))
|
||||||
</div>
|
{
|
||||||
<p class="muted" style="font-size:12px; margin-bottom:0;">نقشه تعاملی در فاز بعد اضافه میشود (Neshan/Balad).</p>
|
<div id="facmap" data-lat="@latS" data-lng="@lngS" style="height:200px; border-radius:10px; overflow:hidden; border:1px solid var(--line);"></div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div style="background:var(--primary-soft); border-radius:10px; height:140px; display:grid; place-items:center; color:var(--primary-dark); text-align:center; padding:10px;">
|
||||||
|
🗺️<br /><small class="muted" dir="ltr">@latS، @lngS</small>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<a class="btn btn-outline btn-block" style="margin-top:8px;" target="_blank" rel="noopener"
|
||||||
|
href="https://neshan.org/maps/@(latS),@(lngS),16z">مسیریابی در نشان</a>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -157,3 +166,8 @@
|
|||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(Model.MapKey) && Model.Shift?.Facility?.Lat is not null)
|
||||||
|
{
|
||||||
|
<partial name="_NeshanMap" model="Model.MapKey" />
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,15 +11,19 @@ public class DetailsModel : PageModel
|
|||||||
{
|
{
|
||||||
private readonly AppDbContext _db;
|
private readonly AppDbContext _db;
|
||||||
private readonly InterestService _interest;
|
private readonly InterestService _interest;
|
||||||
|
private readonly JobsMedical.Web.Services.Scraping.SettingsService _settings;
|
||||||
|
|
||||||
public DetailsModel(AppDbContext db, InterestService interest)
|
public DetailsModel(AppDbContext db, InterestService interest,
|
||||||
|
JobsMedical.Web.Services.Scraping.SettingsService settings)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_interest = interest;
|
_interest = interest;
|
||||||
|
_settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Shift? Shift { get; private set; }
|
public Shift? Shift { get; private set; }
|
||||||
public List<Shift> MoreAtFacility { get; private set; } = new();
|
public List<Shift> MoreAtFacility { get; private set; } = new();
|
||||||
|
public string? MapKey { get; private set; }
|
||||||
|
|
||||||
// Set after the visitor taps "interested" — reveals the facility contact (handoff model).
|
// Set after the visitor taps "interested" — reveals the facility contact (handoff model).
|
||||||
public bool ShowContact { get; private set; }
|
public bool ShowContact { get; private set; }
|
||||||
@@ -30,6 +34,7 @@ public class DetailsModel : PageModel
|
|||||||
{
|
{
|
||||||
await LoadAsync(id);
|
await LoadAsync(id);
|
||||||
if (Shift is null) return NotFound();
|
if (Shift is null) return NotFound();
|
||||||
|
MapKey = (await _settings.GetAsync()).NeshanMapKey;
|
||||||
Reported = Request.Query["reported"] == "1";
|
Reported = Request.Query["reported"] == "1";
|
||||||
await _interest.LogAsync(InterestEventType.View, id); // behavioral signal for recommendations
|
await _interest.LogAsync(InterestEventType.View, id); // behavioral signal for recommendations
|
||||||
return Page();
|
return Page();
|
||||||
|
|||||||
Reference in New Issue
Block a user