diff --git a/DrSousan.Api/Data/AppDbContext.cs b/DrSousan.Api/Data/AppDbContext.cs index f478716..7080a09 100644 --- a/DrSousan.Api/Data/AppDbContext.cs +++ b/DrSousan.Api/Data/AppDbContext.cs @@ -12,7 +12,10 @@ public class AppDbContext(DbContextOptions options) : DbContext(op public DbSet BlogCategories => Set(); public DbSet BlogPosts => Set(); public DbSet Comments => Set(); - public DbSet Faqs => Set(); + public DbSet Faqs => Set(); + public DbSet Patients => Set(); + public DbSet PatientVisits => Set(); + public DbSet HealthRequests => Set(); protected override void OnModelCreating(ModelBuilder mb) { diff --git a/DrSousan.Api/Models/Models.cs b/DrSousan.Api/Models/Models.cs index ca0aefd..61aee68 100644 --- a/DrSousan.Api/Models/Models.cs +++ b/DrSousan.Api/Models/Models.cs @@ -130,6 +130,56 @@ public class Faq public DateTime CreatedAt { get; set; } = DateTime.UtcNow; } +// ─── Patient ────────────────────────────────────────────────────────────────── +public class Patient +{ + public int Id { get; set; } + [MaxLength(150)] public string FullName { get; set; } = ""; + [MaxLength(20)] public string PhoneNumber { get; set; } = ""; + [MaxLength(200)] public string Email { get; set; } = ""; + public int Age { get; set; } + public decimal Weight { get; set; } // kg + public decimal Height { get; set; } // cm + [MaxLength(10)] public string Gender { get; set; } = ""; // مرد / زن + [MaxLength(10)] public string BloodType { get; set; } = ""; + public string DiseaseHistory { get; set; } = ""; + public string Allergies { get; set; } = ""; + public string Medications { get; set; } = ""; + public string Notes { get; set; } = ""; + [MaxLength(20)] public string Category { get; set; } = "beauty"; // beauty | health + public bool IsActive { get; set; } = true; + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public ICollection Visits { get; set; } = new List(); +} + +// ─── Patient Visit / Doctor Note ────────────────────────────────────────────── +public class PatientVisit +{ + public int Id { get; set; } + public int PatientId { get; set; } + public Patient? Patient { get; set; } + [MaxLength(300)] public string Title { get; set; } = ""; + public string Content { get; set; } = ""; + public string Prescription { get; set; } = ""; // دارو / تجویز + [MaxLength(50)] public string VisitType { get; set; } = "ویزیت"; // ویزیت | آزمایش | پروسیجر + public DateTime VisitDate { get; set; } = DateTime.UtcNow; + public DateTime? NextVisitDate { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} + +// ─── Health / Appointment Request (public form) ─────────────────────────────── +public class HealthRequest +{ + public int Id { get; set; } + [MaxLength(150)] public string FullName { get; set; } = ""; + [MaxLength(20)] public string PhoneNumber { get; set; } = ""; + [MaxLength(200)] public string Email { get; set; } = ""; + public string Message { get; set; } = ""; + [MaxLength(20)] public string Category { get; set; } = "beauty"; // beauty | health + public bool IsHandled { get; set; } = false; + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} + // ─── DTOs ───────────────────────────────────────────────────────────────────── public record LoginRequest(string Username, string Password); public record ChangePasswordRequest(string CurrentPassword, string NewPassword); diff --git a/DrSousan.Api/Pages/Index.cshtml b/DrSousan.Api/Pages/Index.cshtml index b07ffb3..c7b1eb7 100644 --- a/DrSousan.Api/Pages/Index.cshtml +++ b/DrSousan.Api/Pages/Index.cshtml @@ -172,6 +172,29 @@ .faq-item[open] summary::after { content:"-"; } .faq-answer { padding:0 1.5rem 1.25rem; color:var(--mid); font-size:0.92rem; line-height:1.85; border-top:1px solid var(--border); } /* ─── Contact ──────────────────────────────────────────────── */ + /* ─── Health Care Landing ─────────────────────────────────── */ + #health-care { background:var(--section-bg); } + .health-header { text-align:center; margin-bottom:3rem; } + .health-cats { display:grid; grid-template-columns:1fr 1fr; gap:2rem; margin-bottom:2.5rem; } + .health-cat-card { background:var(--white); border-radius:24px; padding:2.2rem; border:1px solid var(--border); display:flex; flex-direction:column; gap:1rem; } + .health-cat-icon { width:60px; height:60px; border-radius:18px; display:flex; align-items:center; justify-content:center; } + .health-cat-icon svg { width:28px; height:28px; } + .health-cat-icon.beauty { background:#FCE4EC; color:#C2185B; } + .health-cat-icon.health { background:#E3F2FD; color:#1565C0; } + .health-cat-card h3 { font-size:1.15rem; font-weight:700; color:var(--dark); } + .health-cat-card p { font-size:.88rem; color:var(--mid); line-height:1.8; } + .health-cat-list { list-style:none; padding:0; display:flex; flex-direction:column; gap:.45rem; flex:1; } + .health-cat-list li { font-size:.85rem; color:var(--mid); padding-right:1.2rem; position:relative; } + .health-cat-list li::before { content:"✓"; position:absolute; right:0; color:var(--gold); font-weight:700; } + .health-cta-btn { background:var(--gold); color:#fff; border:none; border-radius:12px; padding:.85rem 1.5rem; font-family:'Vazirmatn',sans-serif; font-size:.9rem; font-weight:600; cursor:pointer; transition:background .25s; margin-top:auto; } + .health-cta-btn:hover { background:#a07840; } + .health-cta-btn.health-btn { background:#1565C0; } + .health-cta-btn.health-btn:hover { background:#0d47a1; } + .health-form-wrap { margin-top:1rem; } + .health-form-card { background:var(--white); border-radius:24px; padding:2.5rem; border:1px solid var(--border); max-width:680px; margin:0 auto; } + .health-form-card h3 { font-size:1.1rem; font-weight:700; margin-bottom:.5rem; } + #health-care .hidden { display:none; } + /* ─── Contact ─────────────────────────────────────────────── */ #contact { background:var(--white); } .contact-grid { display:grid; grid-template-columns:1fr 1.4fr; gap:4rem; align-items:start; } .contact-info-list { display:flex; flex-direction:column; gap:1.5rem; margin-top:2rem; } @@ -227,6 +250,7 @@ .about-image-wrap { text-align:center; } .testimonials-grid { grid-template-columns:1fr; } .section-title { font-size:1.5rem; } + .health-cats { grid-template-columns:1fr; } .faq-item summary { font-size:.9rem; padding:1rem 1.2rem; } .faq-answer { padding:0 1.2rem 1rem; font-size:.88rem; } } @@ -581,6 +605,72 @@ + +
+
+
+ +

خدمات پزشکی دکتر آل‌طه

+
+

ما در دو حوزه تخصصی زیبایی پوست و سلامت عمومی در کنار شما هستیم. از مشاوره اولیه تا پیگیری درمان، تیم ما آماده پاسخگویی است.

+
+ + +
+
+
+ +
+

زیبایی پوست

+

بوتاکس، فیلر، لیزر، مزوتراپی، پاکسازی عمیق پوست و درمان تخصصی انواع مشکلات پوستی توسط متخصص.

+
    +
  • تزریق بوتاکس و فیلر
  • +
  • لیزر موهای زائد و جوانسازی
  • +
  • مزوتراپی و PRP
  • +
  • درمان جای جوش و لک
  • +
  • پاکسازی عمیق پوست
  • +
+ +
+ +
+
+ +
+

سلامت عمومی

+

معاینه، تشخیص و پیگیری بیماری‌های عمومی، مشاوره تغذیه، مدیریت وزن و برنامه‌ریزی سلامت فردی.

+
    +
  • معاینه و ویزیت تخصصی
  • +
  • مشاوره و مدیریت وزن
  • +
  • پیگیری بیماری‌های مزمن
  • +
  • برنامه سلامت شخصی‌سازی‌شده
  • +
  • آزمایشات تخصصی
  • +
+ +
+
+ + + +
+
+
@@ -758,6 +848,52 @@ }, 3000); } + // Health care request form + function openHealthForm(category) { + document.getElementById('hf-category').value = category; + document.getElementById('healthFormTitle').textContent = + category === 'health' ? 'درخواست مراقبت سلامت عمومی' : 'درخواست مشاوره زیبایی پوست'; + const wrap = document.getElementById('healthFormWrap'); + wrap.classList.remove('hidden'); + setTimeout(() => wrap.scrollIntoView({behavior:'smooth', block:'center'}), 100); + } + async function submitHealthForm() { + const name = document.getElementById('hf-name').value.trim(); + const phone = document.getElementById('hf-phone').value.trim(); + if (!name || !phone) { alert('نام و شماره تلفن الزامی است'); return; } + const btn = document.getElementById('hf-submit'); + btn.textContent = '...در حال ارسال'; + btn.disabled = true; + try { + const body = { + fullName: name, + phoneNumber: phone, + email: document.getElementById('hf-email').value, + message: document.getElementById('hf-message').value, + category: document.getElementById('hf-category').value + }; + const res = await fetch('/api/health-request', { + method: 'POST', + headers: {'Content-Type':'application/json'}, + body: JSON.stringify(body) + }); + if (!res.ok) throw new Error(); + btn.textContent = '✓ درخواست شما ثبت شد'; + btn.style.background = '#2D7A4F'; + ['hf-name','hf-phone','hf-email','hf-message'].forEach(id => document.getElementById(id).value=''); + setTimeout(() => { + document.getElementById('healthFormWrap').classList.add('hidden'); + btn.textContent = 'ارسال درخواست'; + btn.style.background = ''; + btn.disabled = false; + }, 3000); + } catch { + btn.textContent = 'خطا — دوباره تلاش کنید'; + btn.style.background = '#c62828'; + setTimeout(() => { btn.textContent='ارسال درخواست'; btn.style.background=''; btn.disabled=false; }, 2500); + } + } + // Active nav link on scroll const sections = document.querySelectorAll('section[id]'); window.addEventListener('scroll', () => { diff --git a/DrSousan.Api/Program.cs b/DrSousan.Api/Program.cs index e35ebdd..acea375 100644 --- a/DrSousan.Api/Program.cs +++ b/DrSousan.Api/Program.cs @@ -89,6 +89,60 @@ await using (var scope = app.Services.CreateAsyncScope()) """); } catch { /* already exists */ } + // Ensure Patient tables exist + try { + await db.Database.ExecuteSqlRawAsync(""" + CREATE TABLE IF NOT EXISTS "Patients" ( + "Id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "FullName" TEXT NOT NULL DEFAULT '', + "PhoneNumber" TEXT NOT NULL DEFAULT '', + "Email" TEXT NOT NULL DEFAULT '', + "Age" INTEGER NOT NULL DEFAULT 0, + "Weight" REAL NOT NULL DEFAULT 0, + "Height" REAL NOT NULL DEFAULT 0, + "Gender" TEXT NOT NULL DEFAULT '', + "BloodType" TEXT NOT NULL DEFAULT '', + "DiseaseHistory" TEXT NOT NULL DEFAULT '', + "Allergies" TEXT NOT NULL DEFAULT '', + "Medications" TEXT NOT NULL DEFAULT '', + "Notes" TEXT NOT NULL DEFAULT '', + "Category" TEXT NOT NULL DEFAULT 'beauty', + "IsActive" INTEGER NOT NULL DEFAULT 1, + "CreatedAt" TEXT NOT NULL DEFAULT '' + ) + """); + } catch { } + try { + await db.Database.ExecuteSqlRawAsync(""" + CREATE TABLE IF NOT EXISTS "PatientVisits" ( + "Id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "PatientId" INTEGER NOT NULL, + "Title" TEXT NOT NULL DEFAULT '', + "Content" TEXT NOT NULL DEFAULT '', + "Prescription" TEXT NOT NULL DEFAULT '', + "VisitType" TEXT NOT NULL DEFAULT 'ویزیت', + "VisitDate" TEXT NOT NULL DEFAULT '', + "NextVisitDate" TEXT, + "CreatedAt" TEXT NOT NULL DEFAULT '', + FOREIGN KEY ("PatientId") REFERENCES "Patients"("Id") ON DELETE CASCADE + ) + """); + } catch { } + try { + await db.Database.ExecuteSqlRawAsync(""" + CREATE TABLE IF NOT EXISTS "HealthRequests" ( + "Id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "FullName" TEXT NOT NULL DEFAULT '', + "PhoneNumber" TEXT NOT NULL DEFAULT '', + "Email" TEXT NOT NULL DEFAULT '', + "Message" TEXT NOT NULL DEFAULT '', + "Category" TEXT NOT NULL DEFAULT 'beauty', + "IsHandled" INTEGER NOT NULL DEFAULT 0, + "CreatedAt" TEXT NOT NULL DEFAULT '' + ) + """); + } catch { } + await SeedAsync(db); } @@ -566,6 +620,121 @@ app.MapDelete("/api/upload/{filename}", (string filename, IWebHostEnvironment en return Results.NoContent(); }).RequireAuthorization(); +// ── Patients (admin) ────────────────────────────────────────────────────────── +var patientsGroup = app.MapGroup("/api/patients").RequireAuthorization(); + +patientsGroup.MapGet("/", async (string? category, string? search, AppDbContext db) => +{ + var q = db.Patients.Where(p => p.IsActive); + if (!string.IsNullOrEmpty(category)) q = q.Where(p => p.Category == category); + if (!string.IsNullOrEmpty(search)) + q = q.Where(p => p.FullName.Contains(search) || p.PhoneNumber.Contains(search)); + return Results.Ok(await q.OrderByDescending(p => p.CreatedAt) + .Select(p => new { p.Id, p.FullName, p.PhoneNumber, p.Email, p.Age, p.Gender, + p.Category, p.BloodType, p.CreatedAt, + VisitCount = db.PatientVisits.Count(v => v.PatientId == p.Id) }) + .ToListAsync()); +}); + +patientsGroup.MapGet("/{id:int}", async (int id, AppDbContext db) => +{ + var p = await db.Patients.Include(x => x.Visits.OrderByDescending(v => v.VisitDate)) + .FirstOrDefaultAsync(x => x.Id == id); + return p is null ? Results.NotFound() : Results.Ok(p); +}); + +patientsGroup.MapPost("/", async (Patient patient, AppDbContext db) => +{ + patient.CreatedAt = DateTime.UtcNow; + db.Patients.Add(patient); + await db.SaveChangesAsync(); + return Results.Created($"/api/patients/{patient.Id}", patient); +}); + +patientsGroup.MapPut("/{id:int}", async (int id, Patient updated, AppDbContext db) => +{ + var p = await db.Patients.FindAsync(id); + if (p is null) return Results.NotFound(); + p.FullName = updated.FullName; p.PhoneNumber = updated.PhoneNumber; + p.Email = updated.Email; p.Age = updated.Age; p.Weight = updated.Weight; + p.Height = updated.Height; p.Gender = updated.Gender; p.BloodType = updated.BloodType; + p.DiseaseHistory = updated.DiseaseHistory; p.Allergies = updated.Allergies; + p.Medications = updated.Medications; p.Notes = updated.Notes; + p.Category = updated.Category; p.IsActive = updated.IsActive; + await db.SaveChangesAsync(); + return Results.Ok(p); +}); + +patientsGroup.MapDelete("/{id:int}", async (int id, AppDbContext db) => +{ + var p = await db.Patients.FindAsync(id); + if (p is null) return Results.NotFound(); + p.IsActive = false; // soft delete + await db.SaveChangesAsync(); + return Results.NoContent(); +}); + +// Patient visits/notes +patientsGroup.MapGet("/{id:int}/visits", async (int id, AppDbContext db) => + Results.Ok(await db.PatientVisits.Where(v => v.PatientId == id) + .OrderByDescending(v => v.VisitDate).ToListAsync())); + +patientsGroup.MapPost("/{id:int}/visits", async (int id, PatientVisit visit, AppDbContext db) => +{ + var patient = await db.Patients.FindAsync(id); + if (patient is null) return Results.NotFound(); + visit.PatientId = id; + visit.CreatedAt = DateTime.UtcNow; + if (visit.VisitDate == default) visit.VisitDate = DateTime.UtcNow; + db.PatientVisits.Add(visit); + await db.SaveChangesAsync(); + return Results.Created($"/api/patients/{id}/visits/{visit.Id}", visit); +}); + +patientsGroup.MapDelete("/visits/{visitId:int}", async (int visitId, AppDbContext db) => +{ + var v = await db.PatientVisits.FindAsync(visitId); + if (v is null) return Results.NotFound(); + db.PatientVisits.Remove(v); + await db.SaveChangesAsync(); + return Results.NoContent(); +}); + +// ── Health Requests (public submit / admin manage) ──────────────────────────── +app.MapPost("/api/health-request", async (HealthRequest req, AppDbContext db) => +{ + req.CreatedAt = DateTime.UtcNow; + req.IsHandled = false; + db.HealthRequests.Add(req); + await db.SaveChangesAsync(); + return Results.Ok(new { message = "درخواست شما ثبت شد" }); +}); + +app.MapGet("/api/health-requests", async (bool? handled, AppDbContext db) => +{ + var q = db.HealthRequests.AsQueryable(); + if (handled.HasValue) q = q.Where(r => r.IsHandled == handled); + return Results.Ok(await q.OrderByDescending(r => r.CreatedAt).ToListAsync()); +}).RequireAuthorization(); + +app.MapPut("/api/health-requests/{id:int}", async (int id, AppDbContext db) => +{ + var r = await db.HealthRequests.FindAsync(id); + if (r is null) return Results.NotFound(); + r.IsHandled = true; + await db.SaveChangesAsync(); + return Results.Ok(r); +}).RequireAuthorization(); + +app.MapDelete("/api/health-requests/{id:int}", async (int id, AppDbContext db) => +{ + var r = await db.HealthRequests.FindAsync(id); + if (r is null) return Results.NotFound(); + db.HealthRequests.Remove(r); + await db.SaveChangesAsync(); + return Results.NoContent(); +}).RequireAuthorization(); + // ── Sitemap ─────────────────────────────────────────────────────────────────── app.MapGet("/sitemap.xml", async (AppDbContext db, HttpContext ctx) => { diff --git a/DrSousan.Api/wwwroot/admin/index.html b/DrSousan.Api/wwwroot/admin/index.html index bd49f24..d3cf229 100644 --- a/DrSousan.Api/wwwroot/admin/index.html +++ b/DrSousan.Api/wwwroot/admin/index.html @@ -269,6 +269,15 @@ tr:hover td{background:#FAFBFC} گزارش SEO
+ + + + +
+
+
+
پرونده بیماران
+
+ + + +
+
+
+ + + +
نامتلفنسنجنسیتدستهویزیت‌هاعملیات
+
+
+
+ + +
+
+ +

+
+
+ +
+
اطلاعات شخصی
+ +
+ +
+ +
+
سابقه پزشکی
+ +
+
+ +
+
+
سوابق ویزیت
+ +
+
+
+
+ + +
+
+
+
درخواست‌های مراقبت سلامت
+
+ + +
+
+
+ + + +
نامتلفندستهپیامتاریخوضعیتعملیات
+
+
+
+
@@ -640,6 +727,71 @@ tr:hover td{background:#FAFBFC}
+ + + + + +