[Facilities] Public facility pages + ratings & reviews
New /Facilities/Details public page: verified badge, info, Neshan map + directions, the facility's open shifts & jobs, and a complaint form; facility cards on /Facilities link to it. Ratings & reviews: Review model (1-5 stars + comment, one per user/facility, unique index, migration); logged-in users rate/review on the facility page; average + count shown in the header and the review list; admins moderate (hide/delete) at /Admin/Reviews. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
using System.Security.Claims;
|
||||
using JobsMedical.Web.Data;
|
||||
using JobsMedical.Web.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace JobsMedical.Web.Pages.Facilities;
|
||||
|
||||
public class DetailsModel : PageModel
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
private readonly JobsMedical.Web.Services.Scraping.SettingsService _settings;
|
||||
|
||||
public DetailsModel(AppDbContext db, JobsMedical.Web.Services.Scraping.SettingsService settings)
|
||||
{
|
||||
_db = db;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public Facility? Facility { get; private set; }
|
||||
public List<Shift> Shifts { get; private set; } = new();
|
||||
public List<JobOpening> Jobs { get; private set; } = new();
|
||||
public string? MapKey { get; private set; }
|
||||
public bool Reported { get; private set; }
|
||||
|
||||
public record ReviewRow(string Who, int Stars, string? Comment, DateTime When);
|
||||
public List<ReviewRow> Reviews { get; private set; } = new();
|
||||
public double AvgRating { get; private set; }
|
||||
public int RatingCount { get; private set; }
|
||||
public bool CanReview { get; private set; } // logged in & not yet reviewed
|
||||
public bool AlreadyReviewed { get; private set; }
|
||||
[TempData] public string? ReviewMsg { get; set; }
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(int id)
|
||||
{
|
||||
Facility = await _db.Facilities.Include(f => f.City).Include(f => f.District)
|
||||
.FirstOrDefaultAsync(f => f.Id == id);
|
||||
if (Facility is null) return NotFound();
|
||||
|
||||
var today = DateOnly.FromDateTime(DateTime.UtcNow);
|
||||
Shifts = await _db.Shifts.Include(s => s.Role)
|
||||
.Where(s => s.FacilityId == id && s.Status == ShiftStatus.Open && s.Date >= today)
|
||||
.OrderBy(s => s.Date).Take(12).ToListAsync();
|
||||
Jobs = await _db.JobOpenings.Include(j => j.Role)
|
||||
.Where(j => j.FacilityId == id && j.Status == ShiftStatus.Open)
|
||||
.OrderByDescending(j => j.CreatedAt).Take(12).ToListAsync();
|
||||
|
||||
MapKey = (await _settings.GetAsync()).NeshanMapKey;
|
||||
Reported = Request.Query["reported"] == "1";
|
||||
|
||||
await LoadReviewsAsync(id);
|
||||
return Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostReviewAsync(int id, int stars, string? comment)
|
||||
{
|
||||
if (User.Identity?.IsAuthenticated != true)
|
||||
return RedirectToPage("/Account/Login", new { returnUrl = $"/Facilities/Details/{id}" });
|
||||
|
||||
var uid = int.Parse(User.FindFirstValue(System.Security.Claims.ClaimTypes.NameIdentifier)!);
|
||||
if (!await _db.Facilities.AnyAsync(f => f.Id == id)) return NotFound();
|
||||
stars = Math.Clamp(stars, 1, 5);
|
||||
|
||||
var existing = await _db.Reviews.FirstOrDefaultAsync(r => r.FacilityId == id && r.UserId == uid);
|
||||
if (existing is null)
|
||||
_db.Reviews.Add(new Review { FacilityId = id, UserId = uid, Stars = stars, Comment = comment?.Trim() });
|
||||
else { existing.Stars = stars; existing.Comment = comment?.Trim(); existing.CreatedAt = DateTime.UtcNow; }
|
||||
await _db.SaveChangesAsync();
|
||||
ReviewMsg = "نظر شما ثبت شد. متشکریم.";
|
||||
return RedirectToPage(new { id });
|
||||
}
|
||||
|
||||
private async Task LoadReviewsAsync(int id)
|
||||
{
|
||||
var rows = await _db.Reviews.Include(r => r.User)
|
||||
.Where(r => r.FacilityId == id && r.IsApproved)
|
||||
.OrderByDescending(r => r.CreatedAt).ToListAsync();
|
||||
RatingCount = rows.Count;
|
||||
AvgRating = rows.Count > 0 ? Math.Round(rows.Average(r => r.Stars), 1) : 0;
|
||||
Reviews = rows.Take(20).Select(r => new ReviewRow(
|
||||
r.User.FullName ?? "کاربر", r.Stars, r.Comment, r.CreatedAt)).ToList();
|
||||
|
||||
if (User.Identity?.IsAuthenticated == true &&
|
||||
int.TryParse(User.FindFirstValue(System.Security.Claims.ClaimTypes.NameIdentifier), out var uid))
|
||||
{
|
||||
AlreadyReviewed = rows.Any(r => r.UserId == uid)
|
||||
|| await _db.Reviews.AnyAsync(r => r.FacilityId == id && r.UserId == uid);
|
||||
CanReview = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user