[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,40 @@
|
||||
@page
|
||||
@model JobsMedical.Web.Pages.Admin.ReviewsModel
|
||||
@{
|
||||
ViewData["Title"] = "مدیریت نظرات";
|
||||
}
|
||||
<div class="page-head">
|
||||
<div class="container">
|
||||
<h1>نظرات کاربران</h1>
|
||||
<p class="muted"><a asp-page="/Admin/Overview">← پنل مدیریت</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container section" style="max-width:820px;">
|
||||
@if (Model.Msg is not null) { <div class="alert alert-success">@Model.Msg</div> }
|
||||
@if (Model.Items.Count == 0)
|
||||
{
|
||||
<div class="card empty-state">نظری ثبت نشده است.</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var r in Model.Items)
|
||||
{
|
||||
<div class="card card-pad" style="margin-bottom:8px; @(r.IsApproved ? "" : "opacity:.6;")">
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:10px;">
|
||||
<div>
|
||||
<strong>@r.Facility.Name</strong>
|
||||
<span style="color:#f59e0b;">@(new string('★', r.Stars))</span>
|
||||
@if (!r.IsApproved) { <span class="badge badge-type">پنهان</span> }
|
||||
<div class="muted" style="font-size:13px;">@(r.User.FullName ?? "کاربر") · <span dir="ltr">@JalaliDate.ToPersianDigits(r.User.Phone)</span></div>
|
||||
@if (!string.IsNullOrWhiteSpace(r.Comment)) { <p style="margin:6px 0 0;">@r.Comment</p> }
|
||||
</div>
|
||||
<div style="display:flex; gap:6px; flex-wrap:wrap;">
|
||||
<form method="post" asp-page-handler="Toggle" asp-route-id="@r.Id"><button class="btn btn-outline" style="padding:4px 12px;">@(r.IsApproved ? "پنهانکردن" : "نمایش")</button></form>
|
||||
<form method="post" asp-page-handler="Delete" asp-route-id="@r.Id" onsubmit="return confirm('حذف شود؟');"><button class="btn btn-outline" style="padding:4px 12px; color:var(--danger); border-color:var(--danger);">حذف</button></form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,38 @@
|
||||
using JobsMedical.Web.Data;
|
||||
using JobsMedical.Web.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace JobsMedical.Web.Pages.Admin;
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
public class ReviewsModel : PageModel
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
public ReviewsModel(AppDbContext db) => _db = db;
|
||||
|
||||
public List<Review> Items { get; private set; } = new();
|
||||
[TempData] public string? Msg { get; set; }
|
||||
|
||||
public async Task OnGetAsync()
|
||||
{
|
||||
Items = await _db.Reviews.Include(r => r.Facility).Include(r => r.User)
|
||||
.OrderByDescending(r => r.CreatedAt).Take(200).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostToggleAsync(int id)
|
||||
{
|
||||
var r = await _db.Reviews.FindAsync(id);
|
||||
if (r is not null) { r.IsApproved = !r.IsApproved; await _db.SaveChangesAsync(); Msg = r.IsApproved ? "نمایش داده شد." : "پنهان شد."; }
|
||||
return RedirectToPage();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostDeleteAsync(int id)
|
||||
{
|
||||
var r = await _db.Reviews.FindAsync(id);
|
||||
if (r is not null) { _db.Reviews.Remove(r); await _db.SaveChangesAsync(); Msg = "حذف شد."; }
|
||||
return RedirectToPage();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user