[Applicant+Admin] Withdraw application, delete account, admin analytics dashboard
CI/CD / CI · dotnet build (push) Successful in 47s
CI/CD / Deploy · hamkadr (push) Successful in 1m28s

Applicant: 'انصراف از درخواست' on /Me removes the Apply event for that shift/job. Account: 'حذف حساب من' on /Me/Profile permanently deletes the user + cascades (profile, alerts, reviews, applications), detaches anonymous visitor history, and signs out (per privacy policy). Admin: /Admin/Analytics dashboard — totals (users, facilities/verified, open shifts/jobs, applications, reviews), 7-day deltas, and a 14-day applications bar chart; linked from Overview alongside the new نظرات moderation page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-07 07:52:49 +03:30
parent d87afb577c
commit aa61efd46f
7 changed files with 143 additions and 0 deletions
@@ -0,0 +1,56 @@
using JobsMedical.Web.Data;
using JobsMedical.Web.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
namespace JobsMedical.Web.Pages.Admin;
[Authorize(Roles = "Admin")]
public class AnalyticsModel : PageModel
{
private readonly AppDbContext _db;
public AnalyticsModel(AppDbContext db) => _db = db;
public int Users { get; private set; }
public int Facilities { get; private set; }
public int VerifiedFacilities { get; private set; }
public int OpenShifts { get; private set; }
public int OpenJobs { get; private set; }
public int Applications { get; private set; }
public int Reviews { get; private set; }
public int NewUsers7 { get; private set; }
public int NewApps7 { get; private set; }
public record DayBar(DateOnly Day, int Count);
public List<DayBar> ApplyByDay { get; private set; } = new();
public int MaxBar { get; private set; } = 1;
public async Task OnGetAsync()
{
var today = DateOnly.FromDateTime(DateTime.UtcNow);
Users = await _db.Users.CountAsync();
Facilities = await _db.Facilities.CountAsync();
VerifiedFacilities = await _db.Facilities.CountAsync(f => f.IsVerified);
OpenShifts = await _db.Shifts.CountAsync(s => s.Status == ShiftStatus.Open && s.Date >= today);
OpenJobs = await _db.JobOpenings.CountAsync(j => j.Status == ShiftStatus.Open);
Applications = await _db.InterestEvents.CountAsync(e => e.EventType == InterestEventType.Apply);
Reviews = await _db.Reviews.CountAsync();
var since7 = DateTime.UtcNow.AddDays(-7);
NewUsers7 = await _db.Users.CountAsync(u => u.CreatedAt >= since7);
NewApps7 = await _db.InterestEvents.CountAsync(e => e.EventType == InterestEventType.Apply && e.CreatedAt >= since7);
var since14 = DateTime.UtcNow.Date.AddDays(-13);
var stamps = await _db.InterestEvents
.Where(e => e.EventType == InterestEventType.Apply && e.CreatedAt >= since14)
.Select(e => e.CreatedAt).ToListAsync();
var byDay = stamps.GroupBy(d => DateOnly.FromDateTime(d.Date)).ToDictionary(g => g.Key, g => g.Count());
for (var i = 0; i < 14; i++)
{
var day = DateOnly.FromDateTime(since14).AddDays(i);
ApplyByDay.Add(new DayBar(day, byDay.GetValueOrDefault(day)));
}
MaxBar = Math.Max(1, ApplyByDay.Count > 0 ? ApplyByDay.Max(b => b.Count) : 1);
}
}