diff --git a/Areas/Inventory/Models/ProductModel.cs b/Areas/Inventory/Models/ProductModel.cs index 2c5ba93..cc889c5 100644 --- a/Areas/Inventory/Models/ProductModel.cs +++ b/Areas/Inventory/Models/ProductModel.cs @@ -17,5 +17,8 @@ namespace PSTW_CentralSystem.Areas.Inventory.Models [ForeignKey("ManufacturerId")] public virtual ManufacturerModel? Manufacturer { get; set; } public virtual ICollection? Items { get; set; } // Navigation property> + + [System.ComponentModel.DataAnnotations.Schema.NotMapped] + public string? ImageFileName { get; set; } } } diff --git a/Areas/Inventory/Views/InventoryMaster/ProductRegistration.cshtml b/Areas/Inventory/Views/InventoryMaster/ProductRegistration.cshtml index dcf29ae..251fe86 100644 --- a/Areas/Inventory/Views/InventoryMaster/ProductRegistration.cshtml +++ b/Areas/Inventory/Views/InventoryMaster/ProductRegistration.cshtml @@ -70,7 +70,10 @@
- +
@@ -238,6 +241,17 @@ existingFileName: null, } }, + watch: { + category(newVal) { + // If user clicks Disposable, auto-fill N/A + if (newVal === 'Disposable') { + this.modelNo = 'N/A'; + } else { + // Clear it if they switch back to Asset/Part so they can type + if (this.modelNo === 'N/A') this.modelNo = ''; + } + } + }, mounted() { // Fetch companies, depts, and products from the API this.fetchManufactures(); @@ -387,7 +401,8 @@ ManufacturerId: this.manufacturer, category: this.category, ModelNo: this.modelNo, - ImageProduct: this.imageProduct + ImageProduct: this.imageProduct, + ImageFileName: this.existingFileName }; try { @@ -441,7 +456,8 @@ manufacturerId: this.manufacturer, category: this.category, modelNo: this.modelNo, - imageProduct: this.imageProduct + imageProduct: this.imageProduct, + ImageFileName: this.existingFileName }; try { @@ -512,21 +528,20 @@ } }, - // User Inserting an Image previewImage(event) { if (typeof event === "string") { this.imageSrc = event; - this.existingFileName = event.split('/').pop(); // Ambil nama fail daripada URL + this.existingFileName = event.split('/').pop(); return; } const file = event.target.files[0]; if (file) { + this.existingFileName = file.name; const reader = new FileReader(); reader.onload = (e) => { this.imageSrc = e.target.result; this.imageProduct = e.target.result.split(',')[1]; - this.existingFileName = file.name; // Simpan nama fail }; reader.readAsDataURL(file); } else { diff --git a/Controllers/API/Inventory/InvMainAPI.cs b/Controllers/API/Inventory/InvMainAPI.cs index 95eed8a..d1dfa93 100644 --- a/Controllers/API/Inventory/InvMainAPI.cs +++ b/Controllers/API/Inventory/InvMainAPI.cs @@ -207,29 +207,69 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory } + // HELPER FUNCTION TO HANDLE DUPLICATE NAMES e.g. image(1).jpg + private string GetUniqueFilePath(string folderPath, string fileName) + { + string nameWithoutExt = Path.GetFileNameWithoutExtension(fileName); + string extension = Path.GetExtension(fileName); + string fullPath = Path.Combine(folderPath, fileName); + + int count = 1; + while (System.IO.File.Exists(fullPath)) + { + string tempFileName = $"{nameWithoutExt}({count}){extension}"; + fullPath = Path.Combine(folderPath, tempFileName); + count++; + } + return fullPath; + } + [HttpPost("AddProduct")] public async Task AddProduct([FromBody] ProductModel product) { - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } - if (product == null) - { - return NotFound("Product is null"); - } + if (!ModelState.IsValid) return BadRequest(ModelState); + if (product == null) return NotFound("Product is null"); try { product.QuantityProduct = 0; - var productImage = product.ImageProduct; - if (!string.IsNullOrEmpty(product.ImageProduct)) + + // --- LOGIC START --- + if (product.Category == "Disposable") { - var bytes = Convert.FromBase64String(product.ImageProduct); - var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/images", product.ModelNo + ".jpg"); - await System.IO.File.WriteAllBytesAsync(filePath, bytes); - product.ImageProduct = "/media/inventory/images/" + product.ModelNo + ".jpg"; + // 1. Force ModelNo to N/A for Disposable + product.ModelNo = "N/A"; + + // 2. Save using Original Filename with (1) logic + if (!string.IsNullOrEmpty(product.ImageProduct) && !string.IsNullOrEmpty(product.ImageFileName)) + { + var bytes = Convert.FromBase64String(product.ImageProduct); + var folderPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/images"); + if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath); + + // Get unique path (e.g., mouse(1).png) + string fullPath = GetUniqueFilePath(folderPath, product.ImageFileName); + + await System.IO.File.WriteAllBytesAsync(fullPath, bytes); + + // Save relative path to DB + product.ImageProduct = "/media/inventory/images/" + Path.GetFileName(fullPath); + } } + else + { + // OLD LOGIC FOR ASSETS/PARTS (Preserved) + if (!string.IsNullOrEmpty(product.ImageProduct)) + { + var bytes = Convert.FromBase64String(product.ImageProduct); + + var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/images", product.ModelNo + ".jpg"); + await System.IO.File.WriteAllBytesAsync(filePath, bytes); + product.ImageProduct = "/media/inventory/images/" + product.ModelNo + ".jpg"; + } + } + + _centralDbContext.Products.Add(product); await _centralDbContext.SaveChangesAsync(); var updatedList = await _centralDbContext.Products.Include("Manufacturer").Where(x => x.ManufacturerId == x.ManufacturerId).ToListAsync(); @@ -244,25 +284,39 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory [HttpPost("EditProduct")] public async Task EditProduct([FromBody] ProductModel editedProduct) { - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } + if (!ModelState.IsValid) return BadRequest(ModelState); + var product = await _centralDbContext.Products.FindAsync(editedProduct.ProductId); - if (product == null) - { - return NotFound("Product is null"); - } + if (product == null) return NotFound("Product is null"); try { - var productImage = editedProduct.ImageProduct; // Save image to wwwroot/media/inventory/images | Images name is product.ModelNo | product.ImageProduct is in base64 string - if (product.ImageProduct != editedProduct.ImageProduct) + if (editedProduct.Category == "Disposable") { - var bytes = Convert.FromBase64String(editedProduct.ImageProduct); - var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/images", editedProduct.ModelNo + ".jpg"); - await System.IO.File.WriteAllBytesAsync(filePath, bytes); - editedProduct.ImageProduct = "/media/inventory/images/" + editedProduct.ModelNo + ".jpg"; + editedProduct.ModelNo = "N/A"; + + if (product.ImageProduct != editedProduct.ImageProduct && !string.IsNullOrEmpty(editedProduct.ImageFileName)) + { + var bytes = Convert.FromBase64String(editedProduct.ImageProduct); + var folderPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/images"); + if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath); + + string fullPath = GetUniqueFilePath(folderPath, editedProduct.ImageFileName); + await System.IO.File.WriteAllBytesAsync(fullPath, bytes); + + editedProduct.ImageProduct = "/media/inventory/images/" + Path.GetFileName(fullPath); + } + } + else + { + // OLD LOGIC FOR ASSETS/PARTS (Preserved) + if (product.ImageProduct != editedProduct.ImageProduct) + { + var bytes = Convert.FromBase64String(editedProduct.ImageProduct); + var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/images", editedProduct.ModelNo + ".jpg"); + await System.IO.File.WriteAllBytesAsync(filePath, bytes); + editedProduct.ImageProduct = "/media/inventory/images/" + editedProduct.ModelNo + ".jpg"; + } } product.ProductName = editedProduct.ProductName; @@ -272,7 +326,6 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory product.ModelNo = editedProduct.ModelNo; product.ImageProduct = editedProduct.ImageProduct; - _centralDbContext.Products.Update(product); await _centralDbContext.SaveChangesAsync(); diff --git a/appsettings.json b/appsettings.json index 7f5d7f6..e749a63 100644 --- a/appsettings.json +++ b/appsettings.json @@ -2,8 +2,8 @@ "ConnectionStrings": { //"DefaultConnection": "Server=localhost;uid=root;Password='';Database=web_interface;" //"DefaultConnection": "server=175.136.244.102;user id=root;password=tw_mysql_root;port=3306;database=web_interface" - //"CentralConnnection": "Server=192.168.12.12;Port=3306;uid=installer;password='pstw_mysql_installer';database=pstw_cs;", //DB_dev Local connection - "CentralConnnection": "Server=219.92.7.60;Port=3307;uid=installer;password='pstw_mysql_installer';database=pstw_cs;", //DB_dev dev Public connection + "CentralConnnection": "Server=192.168.12.12;Port=3306;uid=installer;password='pstw_mysql_installer';database=pstw_cs;", //DB_dev Local connection + //"CentralConnnection": "Server=219.92.7.60;Port=3307;uid=installer;password='pstw_mysql_installer';database=pstw_cs;", //DB_dev dev Public connection //"CentralConnnection": "Server=219.92.7.60;Port=3307;uid=installer;password='pstw_mysql_installer';database=pstw_cs_prod;", //DB_dev prod connection //"DefaultConnection": "Server=219.92.7.60;Port=3307;uid=intern;password='intern_mysql_acct';database=web_interface;",//DB_dev connection //"CentralConnnection": "Server=192.168.12.13;Port=3306;uid=installer;password='pstw_mysql_installer';database=pstw_cs_prod;", // DB_prod live connection diff --git a/wwwroot/Media/Inventory/Images/213.jpg b/wwwroot/Media/Inventory/Images/213.jpg new file mode 100644 index 0000000..4e6ffb5 Binary files /dev/null and b/wwwroot/Media/Inventory/Images/213.jpg differ diff --git a/wwwroot/Media/Inventory/Images/logo PSTW(1).jpg b/wwwroot/Media/Inventory/Images/logo PSTW(1).jpg new file mode 100644 index 0000000..df8596e Binary files /dev/null and b/wwwroot/Media/Inventory/Images/logo PSTW(1).jpg differ diff --git a/wwwroot/Media/Inventory/Images/logo PSTW.jpg b/wwwroot/Media/Inventory/Images/logo PSTW.jpg new file mode 100644 index 0000000..df8596e Binary files /dev/null and b/wwwroot/Media/Inventory/Images/logo PSTW.jpg differ