diff --git a/Areas/HelpPage/Controllers/HelpController.cs b/Areas/HelpPage/Controllers/HelpController.cs index d3ab597..6c132df 100644 --- a/Areas/HelpPage/Controllers/HelpController.cs +++ b/Areas/HelpPage/Controllers/HelpController.cs @@ -59,5 +59,16 @@ namespace boilerplate.Areas.HelpPage.Controllers return View(ErrorViewName); } + + public class UserController : Controller + { + // ???? ?????? ??? ?? ??? ?????? ... + + // ??? ??? ?? ????? ???? + public ActionResult AllNews() + { + return View(); + } + } } } \ No newline at end of file diff --git a/Controllers/HomeController.cs b/Controllers/HomeController.cs index 50ec86b..e6967c6 100644 --- a/Controllers/HomeController.cs +++ b/Controllers/HomeController.cs @@ -1,6 +1,10 @@ -using System; +// Make sure these 'using' directives are at the top of your file +using boilerplate.Models; +using System; using System.Collections.Generic; +using System.Data.Entity; // This is required for .ToListAsync() using System.Linq; +using System.Threading.Tasks; using System.Web; using System.Web.Mvc; @@ -8,10 +12,24 @@ namespace boilerplate.Controllers { public class HomeController : Controller { + // Add an instance of your database context + private boilerplateEntities db = new boilerplateEntities(); + // GET: Default public ActionResult Index() { return View(); } + + // This action now fetches news from the database + public async Task AllNews() + { + // Fetch all news, order by the most recent ones + var newsList = await db.News.OrderByDescending(n => n.News_Date).ToListAsync(); + return View(newsList); + } + + + } -} \ No newline at end of file +} diff --git a/Controllers/PanelController.cs b/Controllers/PanelController.cs index cb0a6de..dbcbe7d 100644 --- a/Controllers/PanelController.cs +++ b/Controllers/PanelController.cs @@ -1,20 +1,17 @@ using boilerplate.Models; -using Microsoft.Ajax.Utilities; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Data.Entity; -using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder; -using System.Data.Entity.Infrastructure; -using System.Data.Entity.SqlServer; -using System.Globalization; +using System.Data.Entity.Validation; +using System.IO; using System.Linq; using System.Net; -using System.Reflection; +using System.Text; using System.Threading.Tasks; using System.Web; using System.Web.Mvc; -using System.Web.UI.WebControls; +using ClosedXML.Excel; namespace boilerplate.Controllers { @@ -28,236 +25,322 @@ namespace boilerplate.Controllers if (!HasSession()) { return RedirectToAction("Login"); } if (HasSession("ManageDashboard")) { - var user = Session["User"] as User; - ViewBag.Title = "داشبورد"; - ViewBag.Counter1 = db.Users.Count(); - ViewBag.Counter2 = db.Users.Count(); - ViewBag.Counter3 = db.Users.Count(); - ViewBag.Counter4 = db.Users.Count(); - ViewBag.Counter5 = db.Users.Count(); - ViewBag.Counter6 = db.Users.Count(); } return View(); } + // --- User Management Actions --- public async Task Users() { - List models = new List(); - if (!HasSession("ManageUser")) { return RedirectToAction("Index"); } - models = db.Users.AsNoTracking().Select(x => new UserModel + var models = await db.Users.AsNoTracking().Select(x => new UserModel { Id = x.User_Id, Name = x.User_Name, Family = x.User_Family, Phone = x.User_Phone, - Code = x.User_Code, - Status = x.User_Status, Role = x.User_Role, + Status = x.User_Status, CreatedDate = x.User_CreatedDateFa - }).ToList(); - + }).ToListAsync(); return View(models); } public new async Task User(int Id = 0) { if (!HasSession("ManageUser")) { return new HttpStatusCodeResult(HttpStatusCode.Unauthorized); } - var model = new User() - { - User_Id = Id, - User_Status = 1, - User_Image = "dUser.png" - }; - if (Id != 0) - { - - model = db.Users.AsNoTracking().Where(x => x.User_Id == Id).FirstOrDefault(); + User model; + if (Id == 0) + { + model = new User() { User_Id = 0, User_Status = 1, User_Image = "dUser.png" }; + } + else + { + model = await db.Users.AsNoTracking().FirstOrDefaultAsync(x => x.User_Id == Id); if (model == null) { return RedirectToAction("Users"); } - } - return View(model); } - [Route("~/panel/Setting")] - public async Task Setting() + // --- News Management Actions (Reverted to simple version) --- + public async Task NewsList() { + if (!HasSession("ManageNews")) { return RedirectToAction("Index"); } - var model = new Setting() + var news = await db.News.OrderByDescending(n => n.News_Date).ToListAsync(); + return View(news); + } + + // --- News Management Actions --- + // --- News Management Actions --- + public class AddNewsViewModel + { + public News News { get; set; } + public List AllCategories { get; set; } + public List AllTags { get; set; } + } + + public async Task AddNews(int id = 0) + { + if (!HasSession("ManageNews")) { return RedirectToAction("Index"); } + + var allItems = await db.Items.ToListAsync(); + var categories = allItems.Where(i => i.Item_Type == "1").ToList(); + var tags = allItems.Where(i => i.Item_Type == "2").ToList(); + + var viewModel = new AddNewsViewModel { - Setting_Id = 1, - Setting_Social = "[]", - Setting_Send = false, + AllCategories = categories, + AllTags = tags }; - if (!HasSession("ManageSetting")) { return RedirectToAction("Index"); } - model = db.Settings.AsNoTracking().FirstOrDefault(); - - return View(model); - } - - [Route("~/panel/SaveSettings")] - [HttpPost] - public async Task SaveSettings() - { - StatusModel statusModel = new StatusModel() { Status = "Success" }; - - try + if (id == 0) { - if (HasSession()) - { - var User = Session["User"] as User; - if (HasSession("ManageSetting") -) - { - SettingModel model = JsonConvert.DeserializeObject(Request.Form["Model"]); - var Settings = await db.Settings.FirstOrDefaultAsync(); - if (Settings != null) - { - Settings.Setting_Name = model.Name; - Settings.Setting_Title = model.Title; - Settings.Setting_Discount = model.Discount; - Settings.Setting_Description = model.Description; - Settings.Setting_Keywords = model.Keywords; - Settings.Setting_Footer = model.Footer; - Settings.Setting_Header = model.Header; - Settings.Setting_Social = JsonConvert.SerializeObject(model.Socials); - Settings.Setting_Username = model.SmsUsername; - Settings.Setting_Password = model.SmsPassword; - Settings.Setting_FromNumber = model.SmsFromNumber; - Settings.Setting_PaternLogin = model.SmsPaternLogin; - Settings.Setting_Address = model.address; - Settings.Setting_PostCode = model.postcode; - Settings.Setting_Phone = model.phone; - Settings.Setting_Website = model.website; - db.Entry(Settings).State = EntityState.Modified; - for (int i = 0; i < Request.Files.Count; i++) - { - switch (Request.Files.AllKeys[i]) - { - case "Logo": - Request.Files[i].SaveAs(Server.MapPath($"~/Media/logo.png")); - break; - case "Icon": - Request.Files[i].SaveAs(Server.MapPath($"~/Media/favicon.ico")); - break; - } - } - } - } - } - else { statusModel.Status = "Auth"; } + ViewBag.PageTitle = "افزودن خبر جدید"; + viewModel.News = new News { News_Date = DateTime.Now }; } - catch { statusModel.Status = "Error"; } - return Json(statusModel); + else + { + var news = await db.News.FindAsync(id); + if (news == null) { return HttpNotFound(); } + ViewBag.PageTitle = "ویرایش خبر"; + viewModel.News = news; + } + + return View(viewModel); } - - [HttpPost] [ValidateAntiForgeryToken] - public async Task Login(LoginModel model) + [ValidateInput(false)] + public async Task AddNews(AddNewsViewModel viewModel, HttpPostedFileBase imageFile, string[] selectedCategories, string[] selectedTags) { - StatusModel statusModel = new StatusModel() { Status = "Success" }; + if (!HasSession("ManageNews")) { return RedirectToAction("Index"); } + + // Join multiple selections into comma-separated strings + viewModel.News.News_Category = selectedCategories != null ? string.Join(",", selectedCategories) : ""; + viewModel.News.News_Tag = selectedTags != null ? string.Join(",", selectedTags) : ""; + + // *** ADDED: Server-side validation for required fields *** + if (viewModel.News.News_ID == 0 && (imageFile == null || imageFile.ContentLength == 0)) + { + ModelState.AddModelError("imageFile", "لطفاً یک تصویر برای خبر انتخاب کنید."); + } + if (selectedCategories == null || !selectedCategories.Any()) + { + ModelState.AddModelError("selectedCategories", "انتخاب حداقل یک دسته‌بندی الزامی است."); + } + if (selectedTags == null || !selectedTags.Any()) + { + ModelState.AddModelError("selectedTags", "انتخاب حداقل یک تگ الزامی است."); + } + try { if (ModelState.IsValid) { - if (!string.IsNullOrEmpty(model.Username) && !string.IsNullOrEmpty(model.Password)) + var model = viewModel.News; + if (imageFile != null && imageFile.ContentLength > 0) { - var User = await db.Users.Where(x => x.User_Phone == model.Username && x.User_Role == "Admin").FirstOrDefaultAsync(); - if (User != null) + var uploadDir = "~/Media/News"; + if (!Directory.Exists(Server.MapPath(uploadDir))) { - if (User.User_Status == 1) - { - if (User.User_Password == functions.HashPassword(model.Password + User.User_CreatedDate)) - { - DateTime Now = DateTime.Now; - string Token = functions.GenerateToken(); - User.User_LoginDate = Now; - User.User_LoginDateFa = functions.ConvertToPersianDate(Now); - User.User_Token = Token; - await db.SaveChangesAsync(); - Session["User"] = User; - SetCookie("UserId", $"{User.User_Id}"); - SetCookie("Token", Token); - - - } - else { statusModel.Status = "Wrong"; } - } - else { statusModel.Status = "Block"; } + Directory.CreateDirectory(Server.MapPath(uploadDir)); } - else { statusModel.Status = "NoExist"; } + var fileName = Guid.NewGuid().ToString() + Path.GetExtension(imageFile.FileName); + var path = Path.Combine(Server.MapPath(uploadDir), fileName); + imageFile.SaveAs(path); + model.News_IMG = fileName; } - else { statusModel.Status = "Empty"; } + + if (model.News_ID == 0) + { + model.News_CreateDate = DateTime.Now; + model.News_Date = DateTime.Now; + db.News.Add(model); + } + else + { + model.News_EditDate = DateTime.Now; + db.Entry(model).State = EntityState.Modified; + if (imageFile == null) + { + db.Entry(model).Property(x => x.News_IMG).IsModified = false; + } + } + + await db.SaveChangesAsync(); + return RedirectToAction("NewsList"); } - else { statusModel.Status = "NotValid"; } } - catch { statusModel.Status = "Error"; } - return Json(statusModel); + catch (Exception ex) + { + ModelState.AddModelError("", "یک خطای پیش‌بینی نشده رخ داد: " + ex.Message); + } + + // If we get here, something failed. Repopulate the dropdowns. + var allItems = await db.Items.ToListAsync(); + viewModel.AllCategories = allItems.Where(i => i.Item_Type == "1").ToList(); + viewModel.AllTags = allItems.Where(i => i.Item_Type == "2").ToList(); + return View(viewModel); } - [Route("~/Panel/Logout")] - public async Task Logout() + + [HttpPost] + public async Task DeleteNews(int id) { - if (Session["User"] != null) + if (!HasSession("ManageNews")) { return Json(new { success = false }); } + + var news = await db.News.FindAsync(id); + if (news != null) { - var UserModel = Session["User"] as User; - var Single = await db.Users.FindAsync(UserModel.User_Id); - if (Single != null) + var imagePath = Server.MapPath("~/Media/News/" + news.News_IMG); + if (System.IO.File.Exists(imagePath)) { - Single.User_Token = null; + System.IO.File.Delete(imagePath); + } + + db.News.Remove(news); + await db.SaveChangesAsync(); + return Json(new { success = true }); + } + return Json(new { success = false }); + } + + // --- Item (Category/Tag) Management Actions --- + public async Task Items(int id) + { + if (!HasSession("ManageItems")) { return RedirectToAction("Index"); } + + string itemTypeString = id.ToString(); + ViewBag.ItemType = id; + ViewBag.PageTitle = (id == 1) ? "مدیریت دسته‌بندی‌ها" : "مدیریت تگ‌ها"; + ViewBag.NewItemLabel = (id == 1) ? "نام دسته‌بندی جدید" : "نام تگ جدید"; + + var items = await db.Items.Where(i => i.Item_Type == itemTypeString).ToListAsync(); + return View(items); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task AddItem(Item model) + { + if (!HasSession("ManageItems")) { return RedirectToAction("Index"); } + + if (ModelState.IsValid && !string.IsNullOrWhiteSpace(model.Item_Title)) + { + bool exists = await db.Items.AnyAsync(i => i.Item_Title == model.Item_Title && i.Item_Type == model.Item_Type); + if (!exists) + { + db.Items.Add(model); await db.SaveChangesAsync(); } } - Session.Clear(); - Session.Abandon(); - SetCookie("UserId", "", -1); - SetCookie("UserToken", "", -1); - Response.Redirect("~/Panel/Login"); + return RedirectToAction("Items", new { id = Convert.ToInt32(model.Item_Type) }); } - [Route("~/Login")] - [Route("Login")] - public async Task Login(string Return = "") + + [HttpPost] + public async Task DeleteItem(int id) { - ViewBag.Return = Return; - if (Session["User"] != null) + if (!HasSession("ManageItems")) { return Json(new { success = false }); } + + var item = await db.Items.FindAsync(id); + if (item != null) { - Response.Redirect("~/panel/index"); + db.Items.Remove(item); + await db.SaveChangesAsync(); + return Json(new { success = true }); } - else if (UserSessionExists()) { Response.Redirect("~/panel/Index"); } - return View(); + return Json(new { success = false }); + } + + // --- Settings Action --- + [Route("~/panel/Setting")] + public async Task Setting() + { + if (!HasSession("ManageSetting")) { return RedirectToAction("Index"); } + + var model = await db.Settings.AsNoTracking().FirstOrDefaultAsync(); + + if (model == null) + { + model = new Setting(); + } + + return View(model); + } + + // --- Authentication and Session Management --- + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Login(LoginModel model) + { + StatusModel statusModel = new StatusModel() { Status = "Success", RedirectUrl = "" }; + try + { + if (ModelState.IsValid && !string.IsNullOrEmpty(model.Username) && !string.IsNullOrEmpty(model.Password)) + { + var user = await db.Users.FirstOrDefaultAsync(x => x.User_Phone == model.Username); + + if (user != null && user.User_Status == 1) + { + if (string.Equals(user.User_Role, "Admin", StringComparison.OrdinalIgnoreCase) && + user.User_Password == functions.HashPassword(model.Password + user.User_CreatedDate)) + { + Session["User"] = user; + SetCookie("UserId", $"{user.User_Id}"); + SetCookie("Token", functions.GenerateToken()); + statusModel.RedirectUrl = "/Panel/Index"; + } + else { statusModel.Status = "Wrong"; } + } + else if (user != null) { statusModel.Status = "Block"; } + else { statusModel.Status = "NoExist"; } + } + else { statusModel.Status = "Empty"; } + } + catch { statusModel.Status = "Error"; } + + return Json(statusModel); } bool UserSessionExists() { try { - string Token = GetCookie("Token"); - decimal.TryParse(GetCookie("UserId"), out decimal UserId); - if (!string.IsNullOrEmpty(Token) && UserId != 0) + string tokenFromCookie = GetCookie("Token"); + string userIdFromCookie = GetCookie("UserId"); + + if (string.IsNullOrEmpty(tokenFromCookie) || string.IsNullOrEmpty(userIdFromCookie)) { - var Single = db.Users.Where(x => x.User_Id == UserId && x.User_Token == Token && x.User_Status == 1 && x.User_Role == "Admin").FirstOrDefault(); - if (Single != null) - { - Session["User"] = Single; - } - else - { - SetCookie("Token", "", -1); - SetCookie("UserId", "", -1); - } - if (Session["User"] != null) { return true; } + return false; } - SetCookie("Token", "", -1); - SetCookie("UserId", "", -1); + + if (!int.TryParse(userIdFromCookie, out int userId)) + { + return false; + } + + var user = db.Users.FirstOrDefault(x => x.User_Id == userId && x.User_Token == tokenFromCookie && x.User_Status == 1); + + if (user != null && user.User_Role != null && string.Equals(user.User_Role.Trim(), "Admin", StringComparison.OrdinalIgnoreCase)) + { + Session["User"] = user; + return true; + } + else + { + SetCookie("Token", "", -1); + SetCookie("UserId", "", -1); + return false; + } + } + catch + { return false; } - catch { return false; } } bool HasSession(string Access = "") @@ -266,107 +349,92 @@ namespace boilerplate.Controllers return string.IsNullOrEmpty(Access) || functions.HasAccess((Session["User"] as User).User_Access, Access); } - [Route("~/Verify")] - [Route("Verify")] - public ActionResult Verify(string Phone = "", string Return = "") + [Route("~/Panel/Logout")] + public async Task Logout() + { + Session.Clear(); + Session.Abandon(); + SetCookie("UserId", "", -1); + SetCookie("Token", "", -1); + Response.Redirect("~/Panel/Login"); + } + + [Route("~/Login")] + [Route("Login")] + public ActionResult Login(string Return = "") { ViewBag.Return = Return; - if (Session["User"] != null) - { - Response.Redirect("~/Index"); - } - if (Phone == "") - { - Response.Redirect("~/Login"); - } - else - { - ViewBag.Phone = Phone; - } + if (UserSessionExists()) { Response.Redirect("~/panel/Index"); } return View(); } - //set session and cookies afte login + [HttpGet] - [Route("Complete")] - [Route("~/Complete")] - public ActionResult Complete() + public ActionResult Complete(int User, string Token) { - try + var user = db.Users.FirstOrDefault(u => u.User_Id == User && u.User_Token == Token); + if (user != null) { - // Log query parameters - var user = Request.QueryString["User"]; - var token = Request.QueryString["Token"]; - - if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(token)) - { - Console.WriteLine("User or Token is missing in the query string."); - return new HttpStatusCodeResult(400, "Bad Request: User or Token missing."); - } - - // Log the query parameters - Console.WriteLine($"User: {user}, Token: {token}"); - - var currentuser = db.Users - .Where(x => x.User_Id.ToString() == user && x.User_Token.ToString() == token) - .FirstOrDefault(); - - if (currentuser != null) - { - // Perform necessary actions, like setting session and cookies - Session["User"] = currentuser; - SetCookie("UserId", $"{currentuser.User_Id}"); - SetCookie("Token", currentuser.User_Token); - - // Log success - Console.WriteLine("User authenticated and session updated."); - } - else - { - // Log if user is not found - Console.WriteLine("User not found."); - return new HttpStatusCodeResult(404, "User not found."); - } + Session["User"] = user; + SetCookie("UserId", user.User_Id.ToString()); + SetCookie("Token", user.User_Token); + return new HttpStatusCodeResult(HttpStatusCode.OK); } - catch (Exception ex) - { - // Log error details - Console.WriteLine($"Error in Complete: {ex.Message}"); - return new HttpStatusCodeResult(500, "Internal Server Error: " + ex.Message); - } - - // Return a success response or redirect - return new HttpStatusCodeResult(200, "Request completed successfully."); + return new HttpStatusCodeResult(HttpStatusCode.Unauthorized); } string SetCookie(string Key, string Value, int ExpireDay = 365) { - try + var cookie = new HttpCookie(Key, Value) { - Response.Cookies.Add(new HttpCookie(Key, Value) { Expires = DateTime.Now.AddDays(ExpireDay) }); - } - catch { } + Expires = DateTime.Now.AddDays(ExpireDay), + Path = "/", + SameSite = SameSiteMode.Lax + }; + Response.Cookies.Add(cookie); return Value; } string GetCookie(string Key) { - string value = string.Empty; - try + var cookie = Request.Cookies[Key]; + return cookie?.Value ?? string.Empty; + } + + [HttpPost] + public async Task UpdateItem(int id, string newTitle) + { + if (!HasSession("ManageItems")) { return Json(new { success = false, message = "Unauthorized" }); } + + if (string.IsNullOrWhiteSpace(newTitle)) { - var cookie = Request.Cookies[Key]; - if (cookie != null) - { - if (string.IsNullOrWhiteSpace(cookie.Value)) - { - return value; - } - value = cookie.Value; - } + return Json(new { success = false, message = "نام آیتم نمی‌تواند خالی باشد." }); } - catch { } - return value; + + var item = await db.Items.FindAsync(id); + if (item != null) + { + // بررسی برای جلوگیری از ثبت نام تکراری + bool exists = await db.Items.AnyAsync(i => i.Item_Title == newTitle && i.Item_Type == item.Item_Type && i.Item_Id != id); + if (exists) + { + return Json(new { success = false, message = "این نام قبلاً ثبت شده است." }); + } + + item.Item_Title = newTitle; + await db.SaveChangesAsync(); + return Json(new { success = true }); + } + return Json(new { success = false, message = "آیتم یافت نشد." }); } + + public async Task ClientManagement() + { + if (!HasSession("ManageClients")) { return RedirectToAction("Index"); } + var clients = await db.Clients.ToListAsync(); + return View(clients); + } + } } diff --git a/Controllers/UserController.cs b/Controllers/UserController.cs index 070b7cb..98339dc 100644 --- a/Controllers/UserController.cs +++ b/Controllers/UserController.cs @@ -1,4 +1,5 @@ using boilerplate.Models; +using ClosedXML.Excel; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -9,11 +10,13 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Threading.Tasks; using System.Web; using System.Web.Hosting; using System.Web.Http; + namespace boilerplate.Controllers { public class UserController : ApiController @@ -212,7 +215,7 @@ namespace boilerplate.Controllers user.User_Token = token; user.User_LoginDate = nowTime; user.User_LoginDateFa = functions.ConvertToPersianDate(nowTime); - + db.SaveChanges(); @@ -275,6 +278,7 @@ namespace boilerplate.Controllers Token = Token, Id = user.User_Id, Phone = user.User_Phone, + role = user.User_Role, }); } else @@ -319,5 +323,237 @@ namespace boilerplate.Controllers } return statusModel; } + + + [Route("api/Client/Import")] + [HttpPost] + public async Task ImportClients() + { + var statusModel = new StatusModel { Status = "Success" }; + try + { + if (!Request.Content.IsMimeMultipartContent()) + { + statusModel.Status = "InvalidRequest"; + return statusModel; + } + + var provider = await Request.Content.ReadAsMultipartAsync(new InMemoryMultipartFormDataStreamProvider()); + var formData = provider.FormData; + + // ۱. احراز هویت و بررسی دسترسی + var authModel = JsonConvert.DeserializeObject(formData.Get("Auth")); + var userAccessModel = functions.CheckUserAccess(authModel, new string[] { "Admin" }, new string[] { "All", "ManageClients" }); + + if (userAccessModel.Id == 0) + { + statusModel.Status = "Auth"; + return statusModel; + } + + // ۲. دریافت فایل اکسل + if (provider.Files.Count == 0) + { + statusModel.Status = "NoFile"; + return statusModel; + } + + HttpContent excelFile = provider.Files[0]; + Stream excelStream = await excelFile.ReadAsStreamAsync(); + + // ۳. پردازش فایل اکسل با ClosedXML + using (var workbook = new XLWorkbook(excelStream)) + { + var worksheet = workbook.Worksheet(1); + var rows = worksheet.RowsUsed().Skip(1); // نادیده گرفتن هدر + + var clientsToAdd = new List(); + foreach (var row in rows) + { + if (row.Cell(1).IsEmpty()) continue; + clientsToAdd.Add(new Client + { + Client_Name = row.Cell(1).GetValue(), + Client_LastName = row.Cell(2).GetValue(), + Client_PhoneNumber = row.Cell(3).GetValue(), + Client_UserName = row.Cell(4).GetValue(), + Client_Email = row.Cell(5).GetValue() + }); + } + + // ۴. ذخیره در دیتابیس + if (clientsToAdd.Any()) + { + db.Clients.AddRange(clientsToAdd); + await db.SaveChangesAsync(); + statusModel.Message = $"تعداد {clientsToAdd.Count} مشتری جدید با موفقیت اضافه شد."; + } + else + { + statusModel.Status = "EmptyFile"; + statusModel.Message = "فایل اکسل خالی است یا ردیف معتبری ندارد."; + } + } + } + catch (Exception ex) + { + statusModel.Status = "Error"; + statusModel.Message = ex.ToString(); // برگرداندن خطای کامل برای دیباگ + } + return statusModel; + } + + [Route("api/Client/Export")] + [HttpPost] + public async Task ExportClients([FromBody] AuthModel authModel) + { + // ۱. احراز هویت و بررسی دسترسی + var userAccessModel = functions.CheckUserAccess(authModel, new string[] { "Admin" }, new string[] { "All", "ManageClients" }); + if (userAccessModel.Id == 0) + { + return Request.CreateResponse(HttpStatusCode.Unauthorized); + } + + // ۲. دریافت لیست مشتریان + var clients = await db.Clients.ToListAsync(); + + // ۳. ساخت فایل اکسل در حافظه + using (var workbook = new XLWorkbook()) + { + var worksheet = workbook.Worksheets.Add("Clients"); + worksheet.Cell(1, 1).Value = "نام"; + worksheet.Cell(1, 2).Value = "نام خانوادگی"; + worksheet.Cell(1, 3).Value = "شماره تلفن"; + worksheet.Cell(1, 4).Value = "نام کاربری"; + worksheet.Cell(1, 5).Value = "ایمیل"; + worksheet.Row(1).Style.Font.Bold = true; + + worksheet.Cell(2, 1).InsertData(clients); + + // ۴. آماده‌سازی پاسخ برای دانلود + using (var stream = new MemoryStream()) + { + workbook.SaveAs(stream); + var content = stream.ToArray(); + + var result = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new ByteArrayContent(content) + }; + result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") + { + FileName = $"Clients_{DateTime.Now:yyyyMMddHHmmss}.xlsx" + }; + return result; + } + } + } + + + [Route("api/Client/Delete")] + [HttpPost] + public async Task DeleteClient(AuthModel model) + { + StatusModel statusModel = new StatusModel() { Status = "Success" }; + try + { + // Check for admin privileges + var userAccessModel = functions.CheckUserAccess(model, new string[] { "Admin" }, new string[] { "All", "ManageClients" }); + if (userAccessModel.Id == 0) + { + statusModel.Status = "Auth"; + return statusModel; + } + + // Find and delete the client + var clientToDelete = await db.Clients.FindAsync(model.Id); + if (clientToDelete != null) + { + db.Clients.Remove(clientToDelete); + await db.SaveChangesAsync(); + } + else + { + statusModel.Status = "NotFound"; + } + } + catch (Exception ex) + { + statusModel.Status = "Error"; + statusModel.Message = ex.Message; + } + return statusModel; + } + + + // --- این کلاس مدل را به فایل Models/UserModel.cs یا یک فایل جدید اضافه کنید --- + public class ClientUpdateModel + { + public AuthModel Auth { get; set; } + public Client Client { get; set; } + } + + // --- این متدها را به UserController.cs اضافه کنید --- + + // API برای دریافت اطلاعات یک مشتری خاص (برای نمایش در فرم ویرایش) + [Route("api/Client/Get/{id}")] + [HttpPost] + public async Task GetClient(int id, [FromBody] AuthModel authModel) + { + var userAccessModel = functions.CheckUserAccess(authModel, new string[] { "Admin" }, new string[] { "All", "ManageClients" }); + if (userAccessModel.Id == 0) + { + return Request.CreateResponse(HttpStatusCode.Unauthorized); + } + + var client = await db.Clients.FindAsync(id); + if (client == null) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + return Request.CreateResponse(HttpStatusCode.OK, client); + } + + // API برای آپدیت اطلاعات یک مشتری + [Route("api/Client/Update")] + [HttpPost] + public async Task UpdateClient([FromBody] ClientUpdateModel model) + { + var statusModel = new StatusModel { Status = "Success" }; + try + { + var userAccessModel = functions.CheckUserAccess(model.Auth, new string[] { "Admin" }, new string[] { "All", "ManageClients" }); + if (userAccessModel.Id == 0) + { + statusModel.Status = "Auth"; + return statusModel; + } + + var clientToUpdate = await db.Clients.FindAsync(model.Client.Client_Id); + if (clientToUpdate != null) + { + // آپدیت کردن فیلدها + clientToUpdate.Client_Name = model.Client.Client_Name; + clientToUpdate.Client_LastName = model.Client.Client_LastName; + clientToUpdate.Client_PhoneNumber = model.Client.Client_PhoneNumber; + clientToUpdate.Client_UserName = model.Client.Client_UserName; + clientToUpdate.Client_Email = model.Client.Client_Email; + + await db.SaveChangesAsync(); + } + else + { + statusModel.Status = "NotFound"; + } + } + catch (Exception ex) + { + statusModel.Status = "Error"; + statusModel.Message = ex.Message; + } + return statusModel; + } + } } \ No newline at end of file diff --git a/Global.asax.cs b/Global.asax.cs index f6df3e5..3e61527 100644 --- a/Global.asax.cs +++ b/Global.asax.cs @@ -13,6 +13,8 @@ namespace boilerplate { protected void Application_Start() { + // خط کد قبلی که باعث خطا می‌شد، حذف شده است. + AreaRegistration.RegisterAllAreas(); GlobalConfiguration.Configure(WebApiConfig.Register); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); diff --git a/Media/News/36ff946d-e1c1-4d40-8018-149f3a5e06d8.png b/Media/News/36ff946d-e1c1-4d40-8018-149f3a5e06d8.png new file mode 100644 index 0000000..4a954d6 Binary files /dev/null and b/Media/News/36ff946d-e1c1-4d40-8018-149f3a5e06d8.png differ diff --git a/Media/News/45669dc1-2d99-4dcc-88cb-13651ebc8ce4.jpg b/Media/News/45669dc1-2d99-4dcc-88cb-13651ebc8ce4.jpg new file mode 100644 index 0000000..ed4d3bb Binary files /dev/null and b/Media/News/45669dc1-2d99-4dcc-88cb-13651ebc8ce4.jpg differ diff --git a/Media/News/ffcb03be-514d-4056-8438-e15833ada13c.png b/Media/News/ffcb03be-514d-4056-8438-e15833ada13c.png new file mode 100644 index 0000000..4a954d6 Binary files /dev/null and b/Media/News/ffcb03be-514d-4056-8438-e15833ada13c.png differ diff --git a/Media/User/1404_5_22_10_56_20_416_20506.jpg b/Media/User/1404_5_22_10_56_20_416_20506.jpg new file mode 100644 index 0000000..9b2f6bc Binary files /dev/null and b/Media/User/1404_5_22_10_56_20_416_20506.jpg differ diff --git a/Media/User/1404_5_25_14_30_20_274_38765.png b/Media/User/1404_5_25_14_30_20_274_38765.png new file mode 100644 index 0000000..747db0b Binary files /dev/null and b/Media/User/1404_5_25_14_30_20_274_38765.png differ diff --git a/Media/User/1404_5_25_15_24_9_560_51457.png b/Media/User/1404_5_25_15_24_9_560_51457.png new file mode 100644 index 0000000..747db0b Binary files /dev/null and b/Media/User/1404_5_25_15_24_9_560_51457.png differ diff --git a/Media/User/1404_5_25_15_6_1_747_47110.png b/Media/User/1404_5_25_15_6_1_747_47110.png new file mode 100644 index 0000000..747db0b Binary files /dev/null and b/Media/User/1404_5_25_15_6_1_747_47110.png differ diff --git a/Media/User/dUser.png b/Media/User/dUser.png new file mode 100644 index 0000000..e3612d5 Binary files /dev/null and b/Media/User/dUser.png differ diff --git a/Models/Client.cs b/Models/Client.cs new file mode 100644 index 0000000..9c23868 --- /dev/null +++ b/Models/Client.cs @@ -0,0 +1,24 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace boilerplate.Models +{ + using System; + using System.Collections.Generic; + + public partial class Client + { + public int Client_Id { get; set; } + public string Client_Name { get; set; } + public string Client_LastName { get; set; } + public string Client_PhoneNumber { get; set; } + public string Client_UserName { get; set; } + public string Client_Email { get; set; } + } +} diff --git a/Models/Item.cs b/Models/Item.cs new file mode 100644 index 0000000..926934d --- /dev/null +++ b/Models/Item.cs @@ -0,0 +1,21 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace boilerplate.Models +{ + using System; + using System.Collections.Generic; + + public partial class Item + { + public int Item_Id { get; set; } + public string Item_Title { get; set; } + public string Item_Type { get; set; } + } +} diff --git a/Models/Model1.Context.cs b/Models/Model1.Context.cs index bff1a17..b10a8d2 100644 --- a/Models/Model1.Context.cs +++ b/Models/Model1.Context.cs @@ -25,6 +25,9 @@ namespace boilerplate.Models throw new UnintentionalCodeFirstException(); } + public virtual DbSet Clients { get; set; } + public virtual DbSet Items { get; set; } + public virtual DbSet News { get; set; } public virtual DbSet Settings { get; set; } public virtual DbSet Users { get; set; } } diff --git a/Models/Model1.Designer.cs b/Models/Model1.Designer.cs index 0f4fc70..9dffa4c 100644 --- a/Models/Model1.Designer.cs +++ b/Models/Model1.Designer.cs @@ -1,4 +1,4 @@ -// T4 code generation is enabled for model 'D:\Users\l\source\repos\boilerplate\Models\Model1.edmx'. +// T4 code generation is enabled for model 'C:\Users\Senat\source\repos\boilerPlate - Test\new-boilerplate\Models\Model1.edmx'. // To enable legacy code generation, change the value of the 'Code Generation Strategy' designer // property to 'Legacy ObjectContext'. This property is available in the Properties Window when the model // is open in the designer. diff --git a/Models/Model1.edmx b/Models/Model1.edmx index 8bf78a3..20436dc 100644 --- a/Models/Model1.edmx +++ b/Models/Model1.edmx @@ -4,7 +4,41 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -53,14 +87,57 @@ + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -94,76 +171,109 @@ - + - - + + - + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/Models/Model1.edmx.diagram b/Models/Model1.edmx.diagram index aefab2d..71d659d 100644 --- a/Models/Model1.edmx.diagram +++ b/Models/Model1.edmx.diagram @@ -5,8 +5,11 @@ - - + + + + + diff --git a/Models/News.cs b/Models/News.cs new file mode 100644 index 0000000..5596cae --- /dev/null +++ b/Models/News.cs @@ -0,0 +1,28 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace boilerplate.Models +{ + using System; + using System.Collections.Generic; + + public partial class News + { + public int News_ID { get; set; } + public string News_Title { get; set; } + public string News_Description { get; set; } + public string News_Summary { get; set; } + public string News_Category { get; set; } + public string News_Tag { get; set; } + public System.DateTime News_Date { get; set; } + public System.DateTime News_CreateDate { get; set; } + public Nullable News_EditDate { get; set; } + public string News_IMG { get; set; } + } +} diff --git a/Models/NewsModel.cs b/Models/NewsModel.cs new file mode 100644 index 0000000..8dd969a --- /dev/null +++ b/Models/NewsModel.cs @@ -0,0 +1,41 @@ +namespace boilerplate.Models +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.Web.Mvc; + + // This is the metadata class where we define display names and validation rules. + public class NewsMetadata + { + [Display(Name = "عنوان خبر")] + [Required(ErrorMessage = "لطفاً عنوان خبر را وارد کنید.")] + public string News_Title { get; set; } + + [Display(Name = "توضیحات کامل")] + [AllowHtml] + [Required(ErrorMessage = "لطفاً توضیحات خبر را وارد کنید.")] + public string News_Description { get; set; } + + [Display(Name = "خلاصه خبر")] + public string News_Summary { get; set; } + + [Display(Name = "دسته بندی")] + public string News_Category { get; set; } + + [Display(Name = "تگ‌ها")] + public string News_Tag { get; set; } + + [Display(Name = "تاریخ نمایش")] + public Nullable News_Date { get; set; } + + [Display(Name = "تصویر شاخص")] + public string News_IMG { get; set; } + } + + [MetadataType(typeof(NewsMetadata))] + public partial class News + { + // This part remains empty. + } +} diff --git a/Models/StatusModel.cs b/Models/StatusModel.cs index 3194394..bbbcfd3 100644 --- a/Models/StatusModel.cs +++ b/Models/StatusModel.cs @@ -6,5 +6,7 @@ public string Message { get; set; } public string Result { get; set; } public string FileContent { get; set; } + + public string RedirectUrl { get; set; } // این خط رو به کلاس اضافه کن } } \ No newline at end of file diff --git a/Views/Home/AllNews.cshtml b/Views/Home/AllNews.cshtml new file mode 100644 index 0000000..e311d21 --- /dev/null +++ b/Views/Home/AllNews.cshtml @@ -0,0 +1,83 @@ +@model IEnumerable + +@{ + ViewBag.Title = "آخرین اخبار"; + +} + +
+

@ViewBag.Title

+
+
+ @if (!Model.Any()) + { +
+

در حال حاضر خبری برای نمایش وجود ندارد.

+
+ } + else + { + foreach (var item in Model) + { +
+
+ @item.News_Title +
+
@item.News_Title
+ +

@item.News_Summary

+ + @* --- New section for Category and Tags --- *@ +
+ @if (!string.IsNullOrEmpty(item.News_Category)) + { +
+ دسته بندی: + @* --- FIXED: Loop through categories to create separate badges --- *@ + @{ + var categories = item.News_Category.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var category in categories) + { + @category.Trim() + } + } +
+ } + + @if (!string.IsNullOrEmpty(item.News_Tag)) + { +
+ تگ ها: + @{ + var tags = item.News_Tag.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var tag in tags) + { + @tag.Trim() + } + } +
+ } +
+ + @**@ +
+ +
+
+ } + } +
+
diff --git a/Views/Panel/AddNews.cshtml b/Views/Panel/AddNews.cshtml new file mode 100644 index 0000000..241b97c --- /dev/null +++ b/Views/Panel/AddNews.cshtml @@ -0,0 +1,116 @@ +@model boilerplate.Controllers.PanelController.AddNewsViewModel +@{ + ViewBag.Title = Model.News.News_ID == 0 ? "افزودن خبر جدید" : "ویرایش خبر"; + Layout = "~/Views/_PanelSideBar.cshtml"; +} + +@section Heads { + + + +} + +
+
+

@ViewBag.Title

+
+
+ @using (Html.BeginForm("AddNews", "Panel", FormMethod.Post, new { enctype = "multipart/form-data" })) + { + @Html.AntiForgeryToken() + @Html.HiddenFor(model => model.News.News_ID) + @Html.HiddenFor(model => model.News.News_IMG) + + if (!ViewData.ModelState.IsValid) + { +
+ @Html.ValidationSummary(false) +
+ } + +
+ + @Html.EditorFor(model => model.News.News_Title, new { htmlAttributes = new { @class = "form-control" } }) +
+ +
+ + +
+ +
+ + +
+ +
+ + @Html.TextAreaFor(model => model.News.News_Summary, new { @class = "form-control", rows = 3 }) +
+ +
+ + @Html.TextAreaFor(model => model.News.News_Description, new { @class = "form-control", id = "newsDescriptionEditor" }) +
+ +
+ + + @if (!string.IsNullOrEmpty(Model.News.News_IMG)) + { +
+ تصویر فعلی: + +
+ } +
+ + + } +
+
+ +@section Script { + + + +} diff --git a/Views/Panel/ClientManagement.cshtml b/Views/Panel/ClientManagement.cshtml new file mode 100644 index 0000000..dcdd867 --- /dev/null +++ b/Views/Panel/ClientManagement.cshtml @@ -0,0 +1,300 @@ +@model IEnumerable +@{ + ViewBag.Title = "مدیریت مشتریان"; + Layout = "~/Views/_PanelSideBar.cshtml"; + var currentUser = Session["User"] as boilerplate.Models.User; +} + +
+
+

@ViewBag.Title

+
+ + +
+
+
+ + + + + + + + + + + + + @foreach (var client in Model) + { + + + + + + + + + } + +
نامنام خانوادگیشماره تلفننام کاربریایمیلعملیات
@client.Client_Name@client.Client_LastName@client.Client_PhoneNumber@client.Client_UserName@client.Client_Email + + +
+
+
+ + + + + + + + +@section Script { + +} diff --git a/Views/Panel/Index.cshtml b/Views/Panel/Index.cshtml index f3dd396..8141f84 100644 --- a/Views/Panel/Index.cshtml +++ b/Views/Panel/Index.cshtml @@ -70,22 +70,22 @@ -
- *@ @*
@@ -118,6 +118,21 @@
+
+
+
+
+
+ +
+ +
+
+
+
@@ -128,4 +143,5 @@ } @section Script{ + } diff --git a/Views/Panel/Items.cshtml b/Views/Panel/Items.cshtml new file mode 100644 index 0000000..2c3a23c --- /dev/null +++ b/Views/Panel/Items.cshtml @@ -0,0 +1,122 @@ +@model IEnumerable +@{ + ViewBag.Title = ViewBag.PageTitle; + Layout = "~/Views/_PanelSideBar.cshtml"; + var itemType = (int)ViewBag.ItemType; +} + +
+ +
+
+
+

افزودن آیتم جدید

+
+
+ @using (Html.BeginForm("AddItem", "Panel", FormMethod.Post)) + { + @Html.AntiForgeryToken() + @Html.Hidden("Item_Type", itemType) +
+ + +
+ + } +
+
+
+ + +
+
+
+

@ViewBag.PageTitle

+
+
+ + + + + + + + + @foreach (var item in Model) + { + + + + + } + +
نامعملیات
+ @item.Item_Title + + + + + +
+
+
+
+
+ +@section Script { + +} diff --git a/Views/Panel/Login.cshtml b/Views/Panel/Login.cshtml index 289e024..5318428 100644 --- a/Views/Panel/Login.cshtml +++ b/Views/Panel/Login.cshtml @@ -128,7 +128,12 @@ toastr.success('ورود با موفقیت انجام شد !!!'); setTimeout(function () { if (Return == '') { - location.href = "Index"; + if (data.role == 'Admin') { + location.href = "Index"; + } + else { + location.href = '@Url.Action("AllNews", "Home")'; + } } else { location.href = Return; } @@ -150,7 +155,6 @@ }).always(function () { }); } else { - toastr.error('کد تایید را وارد کنید.'); } } diff --git a/Views/Panel/NewsList.cshtml b/Views/Panel/NewsList.cshtml new file mode 100644 index 0000000..bed24ad --- /dev/null +++ b/Views/Panel/NewsList.cshtml @@ -0,0 +1,77 @@ +@model IEnumerable +@{ + ViewBag.Title = "لیست اخبار"; + Layout = "~/Views/_PanelSideBar.cshtml"; +} + +
+
+
+

@ViewBag.Title

+
+ +
+
+
+ + + + + + + + + + + + @foreach (var item in Model) + { + + + + + + + + } + +
تصویرعنوان خبرخلاصهتاریخ ایجادعملیات
+ News Image + @item.News_Title@item.News_Summary + @(item.News_CreateDate > DateTime.MinValue ? new boilerplate.Models.Functions().ConvertToPersianDate(item.News_CreateDate) : "") + + + + + +
+
+
+
+ +@section Script { + +} diff --git a/Views/Panel/User.cshtml b/Views/Panel/User.cshtml index 9f6786a..2e18809 100644 --- a/Views/Panel/User.cshtml +++ b/Views/Panel/User.cshtml @@ -6,7 +6,6 @@ ViewBag.Title = Model.User_Id == 0 ? "کاربر جدید" : Model.User_Name + " " + Model.User_Family; Layout = "~/Views/_PanelSideBar.cshtml"; Functions functions = new Functions(); - }
@@ -165,6 +164,13 @@ تنظیمات
+
+ +
@@ -219,6 +225,14 @@ }); function Save() { + // --- ADDED: Client-side validation for image on new users --- + var isNew = (Id === 0); + var fileInput = $("#fuImage")[0]; + if (isNew && fileInput.files.length === 0) { + toastr.error("لطفاً یک تصویر برای کاربر انتخاب کنید."); + return; // Stop the function if image is missing + } + let Phone = $("#txtPhone").val().trim(); if (Phone.length) { let Access = ""; @@ -238,7 +252,7 @@ $("#btnSave").css("opacity", 0.5); $("#btnSave").attr("onclick", ""); let model = { - Auth: { UserId: UserId, Token: Token }, + Auth: { UserId: @User.User_Id, Token: "@User.User_Token" }, Id: Id, Name: $("#txtName").val().trim(), Family: $("#txtFamily").val().trim(), @@ -255,12 +269,18 @@ ajax.addEventListener("load", function (event) { $("#btnSave").css("opacity", 1); let data = JSON.parse(event.target.responseText); + if (data.Status == "Success") { toastr.success("ذخیره سازی با موفقیت انجام شد"); setTimeout(function () { location.href = `../Panel/User/${data.Result}`; }, 1500); - } else { + } + else if (data.Status == "DuplicatePhone") { + $("#btnSave").attr("onclick", "Save()"); // Re-enable button + toastr.error("این شماره تلفن قبلاً در سیستم ثبت شده است!"); + } + else { $("#btnSave").attr("onclick", "Save()"); console.log(data); toastr.warning("خطا هنگام ذخیره سازی اطلاعات"); @@ -291,7 +311,7 @@ confirmButtonText: 'بله' }).then((result) => { if (result.value) { - $.post("../api/User/Delete", { Id: Id, UserId: UserId, Token: Token }).done(function (data) { + $.post("../api/User/Delete", { Id: Id, UserId: @User.User_Id, Token: "@User.User_Token" }).done(function (data) { if (data.Status == "Success") { $(".btns").remove(); toastr.success("حذف با موفقیت انجام شد"); diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index 6e0b4c2..bf65344 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -9,7 +9,7 @@ -