react-version #1

Merged
memartel_loc merged 290 commits from react-version into main 2023-11-04 09:48:15 -04:00
47 changed files with 198 additions and 34 deletions
Showing only changes of commit 9df28d3eed - Show all commits

5
.gitignore vendored
View File

@ -349,3 +349,8 @@ MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# ignore les images pour ne pas avoir pleins d'image non-lié
GrossesMitaines/GrossesMitainesAPI/Images/*
!GrossesMitaines/GrossesMitainesAPI/Images/default.jpg
!GrossesMitaines/GrossesMitainesAPI/Images/default_thumbnail.jpg

View File

@ -0,0 +1,67 @@
using GrossesMitainesAPI.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace GrossesMitainesAPI.Controllers {
[EnableCors("_myAllowSpecificOrigins"), ApiController, Route("api/[controller]"), Authorize(AuthenticationSchemes = "Identity.Application")]
public class ImageController : ControllerBase {
private readonly InventoryContext _context;
private readonly IWebHostEnvironment _hostEnvironment;
private readonly ILogger<ProductController> _logger;
public ImageController(ILogger<ProductController> logger, InventoryContext context, IWebHostEnvironment hostEnvironment) {
_context = context;
_hostEnvironment = hostEnvironment;
_logger = logger;
}
[EnableCors("_myAllowSpecificOrigins"), HttpGet(Name = "Image"), AllowAnonymous]
public IActionResult Get(int id, bool? thumbnail = false) {
string path;
string filename;
string filetype;
try {
var prod = _context.Products.Where(x => x.Id == id).First();
filename = prod.ImageName ?? throw new Exception("Unable to find product image name. Sending default.jpg instead...");
}
catch (Exception e) {
_logger.LogError(8, e.Message);
filename = "default.jpg";
}
path = thumbnail == true ? Path.Combine(_hostEnvironment.ContentRootPath, "Images", Path.GetFileNameWithoutExtension(filename) + "_thumbnail" + Path.GetExtension(filename))
: Path.Combine(_hostEnvironment.ContentRootPath, "Images", filename);
if (!System.IO.File.Exists(path)) {
_logger.LogError(8, "Unable to find image. Sending default image instead...");
path = Path.Combine(_hostEnvironment.ContentRootPath, "Images", "default.jpg");
}
switch (Path.GetExtension(path)) {
case ".jpg":
case ".jpeg":
filetype = "image/jpeg";
break;
case ".png":
filetype = "image/png";
break;
default:
filetype = "image/jpeg";
break;
}
byte[] imageData = System.IO.File.ReadAllBytes(path);
return File(imageData, filetype);
}
}
}

View File

@ -9,10 +9,10 @@ using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Cors;
using GrossesMitainesAPI.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Hosting;
#endregion
[EnableCors("_myAllowSpecificOrigins"), ApiController, Route("api/[controller]"),
[EnableCors("_myAllowSpecificOrigins"), ApiController, Route("api/[controller]"),
Authorize(AuthenticationSchemes = "Identity.Application")]
public class InventoryController : Controller {
#region Constants
@ -24,14 +24,16 @@ public class InventoryController : Controller {
private readonly ILogger<InventoryController> _logger;
private readonly InventoryContext _context;
private readonly DatabaseCacheService _cache;
private readonly IWebHostEnvironment _hostEnvironment;
#endregion
#region Ctor
public InventoryController(ILogger<InventoryController> logger, InventoryContext context, DatabaseCacheService cache) {
public InventoryController(ILogger<InventoryController> logger, InventoryContext context, DatabaseCacheService cache, IWebHostEnvironment hostEnvironment) {
_context = context;
_logger = logger;
_cache = cache;
_hostEnvironment = hostEnvironment;
}
#endregion
@ -41,7 +43,7 @@ public class InventoryController : Controller {
public IEnumerable<ProductViewModel> Get(int? lastId, string? order, string? filterPrice, string? filterState, bool? all) {
bool iscache = false;
IQueryable<ProductViewModel> ret;
if (_cache.isOk()) {
if (_cache.isOk()) {
ret = _cache.queryCache().Select(x => new ProductViewModel(x));
iscache = true;
}
@ -78,20 +80,20 @@ public class InventoryController : Controller {
ret = ret.Where(x => x.Status == ProductModel.States.Discontinued);
break;
case "isPromoted":
ret = ret.Where(x => x.Status == ProductModel.States.Clearance ||
ret = ret.Where(x => x.Status == ProductModel.States.Clearance ||
x.Status == ProductModel.States.Promotion);
break;
default: break;
}
switch (order) {
case "Price":
ret = ret.OrderBy(x => x.Status == ProductModel.States.Promotion ||
x.Status == ProductModel.States.Clearance ?
ret = ret.OrderBy(x => x.Status == ProductModel.States.Promotion ||
x.Status == ProductModel.States.Clearance ?
x.PromoPrice : x.Price);
break;
case "PriceDesc":
ret = ret.OrderByDescending(x => x.Status == ProductModel.States.Promotion ||
x.Status == ProductModel.States.Clearance ?
ret = ret.OrderByDescending(x => x.Status == ProductModel.States.Promotion ||
x.Status == ProductModel.States.Clearance ?
x.PromoPrice : x.Price);
break;
case "Title":
@ -114,9 +116,10 @@ public class InventoryController : Controller {
try {
if (!lastId.HasValue || lastId == 0)
yup = true;
foreach (ProductViewModel prod in ret.ToList()) {
if (yup && add < AMOUNT_SCROLL || (all.HasValue && all == true)) {
lst.Add(prod);
add++;
}
@ -124,13 +127,15 @@ public class InventoryController : Controller {
yup = true;
}
return lst;
} catch (Exception e) {
}
catch (Exception e) {
if (iscache)
_logger.LogError(e, "Erreur d'appel de cache.");
else _logger.LogError(e, "Erreur d'appel d'API.");
return new List<ProductViewModel>();
}
}
// Inventory/Delete => Décrémenter un produit. Va aller chercher directement dans la BD.
//[EnableCors("_myAllowSpecificOrigins"), HttpDelete(Name = "Inventory"), AllowAnonymous]
//public ActionResult<int> Delete(int? id) {

View File

@ -10,7 +10,9 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using GrossesMitainesAPI.Services;
using System.Drawing;
using System.IO;
using Microsoft.EntityFrameworkCore.Storage;
#endregion
/// <summary>
@ -57,7 +59,7 @@ public class ProductController : ControllerBase {
}
[EnableCors("_myAllowSpecificOrigins"), HttpPost(Name = "Product")]
public async Task<ActionResult<ProductModel>> Post(ProductModel prod) {
public async Task<ActionResult<ProductModel>> Post([FromForm] ProductModel prod) {
if (prod.Price <= prod.PromoPrice)
prod.PromoPrice = prod.Price - 0.01M;
try {
@ -110,10 +112,33 @@ public class ProductController : ControllerBase {
string imageName = new String(Path.GetFileNameWithoutExtension(imageFile.FileName).Take(10).ToArray()).Replace(' ', '-');
imageName = imageName + DateTime.Now.ToString("yymmssfff") + Path.GetExtension(imageFile.FileName);
var imagePath = Path.Combine(_hostEnvironment.ContentRootPath, "Images", imageName);
using (var fileStream = new FileStream(imagePath, FileMode.Create)) {
await imageFile.CopyToAsync(fileStream);
SaveImageThumbnail(fileStream, imageName);
}
return imageName;
}
private void SaveImageThumbnail(FileStream stream, string imageName) {
try {
const float maxSize = 200f;
Image image = Image.FromStream(stream);
//Choisi le bon ratio de division pour ne pas dépasser le 200px ni dans height ni dans width
float ratio = image.Width / (image.Height / maxSize) <= maxSize ? image.Height / maxSize : image.Width / maxSize;
Bitmap resize = new Bitmap(image, new Size((int)(image.Width / ratio), (int)(image.Height / ratio)));
string path = Path.Combine(_hostEnvironment.ContentRootPath, "Images", Path.GetFileNameWithoutExtension(imageName) + "_thumbnail" + Path.GetExtension(imageName));
resize.Save(path);
}
catch (Exception ex) {
_logger.LogError(8, ex.Message);
}
}
#endregion
}

View File

@ -22,6 +22,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
</ItemGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -13,6 +13,8 @@ public class ProductViewModel {
public States Status { get; set; } = States.Available;
public string? ImageName { get; set; }
public string? ImageData { get; set; }
public ProductViewModel(ProductModel prod) {
this.Id = prod.Id;
this.Title = prod.Title;

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1,26 @@
import { useState, useEffect } from "react";
const FeaturedImage = ({ productId }) => {
const [imageSrc, setImageSrc] = useState("/images/default_thumbnail.jpg");
useEffect(() => {
fetch(`https://localhost:7292/api/Image?id=${productId}`)
.then(response => response.blob())
.then(blob => {
const imageUrl = URL.createObjectURL(blob);
setImageSrc(imageUrl);
})
},[]);
return (
<img
className="featured-img"
src={imageSrc}
alt={productId + "_image"}
/>
);
}
export default FeaturedImage;

View File

@ -1,5 +1,7 @@
import { Carousel } from "react-bootstrap";
import { Link } from "react-router-dom";
import FeaturedImage from "./FeaturedImage";
const FeaturedList = ({ products }) => {
if (products === null)
return (<></>);
@ -9,10 +11,7 @@ const FeaturedList = ({ products }) => {
<Carousel.Item key={product.id} className="featured-itm">
<Link key={product.id} to={`/morceaux/${product.id}`}>
<img
className="featured-img"
src={`/images/${product.imageName}.jpg`}
/>
<FeaturedImage productId={product.id}/>
<Carousel.Caption className="featured-info">
<h3>{product.title}</h3>
<p>{product.description}</p>

View File

@ -1,13 +1,7 @@
import { Card } from "react-bootstrap";
import { useState, useEffect } from "react";
// public enum States {
// Available,
// BackOrder,
// Unavailable,
// Clearance,
// Promotion,
// Discontinued
// }
function renderStatus(statusCode) {
if (statusCode !== undefined) {
@ -62,7 +56,7 @@ function renderPrice(price, newPrice, status) {
if (price !== undefined) {
if (status != 3 && status != 4) {
if (status !== 3 && status !== 4) {
return (
<Card.Text className="item-price-container">
<span className="item-price">
@ -87,12 +81,25 @@ function renderPrice(price, newPrice, status) {
}
const Item = ({ imageUrl, name, price, newPrice, status }) => {
const Item = ({ productId, name, price, newPrice, status }) => {
const [imageSrc, setImageSrc] = useState("/images/default_thumbnail.jpg");
useEffect(() => {
fetch(`https://localhost:7292/api/Image?id=${productId}&thumbnail=true`)
.then(response => response.blob())
.then(blob => {
const imageUrl = URL.createObjectURL(blob);
setImageSrc(imageUrl);
})
}, []);
if (name !== undefined) {
return (
<Card className="item">
<Card.Img className="item-img" variant="top" src={`/images/${imageUrl}_thumbnail.jpg`} />
<Card.Img className="item-img" variant="top" src={imageSrc} />
<Card.Body className="item-info">
<div className="item-name-container">
<Card.Title className="item-name">

View File

@ -9,7 +9,8 @@ const ItemList = ({ items }) => {
{items.length <= 0 && <p>Aucun morceaux à montrer...</p>}
{items.map((item) =>
<Link key={item.id} className='item-link' to={`/morceaux/${item.id}`}>
<Item imageUrl={item.imageName}
<Item
productId={item.id}
name={item.title}
price={item.price}
status={item.status}

View File

@ -20,14 +20,24 @@ const Inventaire = () => {
}, []);
const handleAddItem = async (morceau) => {
console.log(morceau);
let formData = new FormData();
Object.keys(morceau).map((k) => {
formData.set(k,morceau[k]);
});
console.log(formData);
const response = await fetch(`https://localhost:7292/api/Product`, {
method: 'POST',
credentials: 'include',
mode: 'cors',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
'accept': 'application/json',
},
body: JSON.stringify(morceau)
body: formData
})
const newMorceau = await response.json();
@ -38,7 +48,7 @@ const Inventaire = () => {
console.log("Ajout de l'item avec succès: \r\n" + newMorceau);
}
else
console.log("Erreur de creation " + morceau);
console.log("Erreur de creation " + morceau.title);
};
const handleDeleteItem = async (id) => {

View File

@ -7,6 +7,7 @@ const MorceauDetail = () => {
const { id } = useParams();
const [item, setItem] = useState({});
const [isLoading, setIsLoading] = useState(false);
const [imageSrc, setImageSrc] = useState("/images/default_thumbnail.jpg");
useEffect(() => {
document.title = 'Morceaux';
@ -16,10 +17,25 @@ const MorceauDetail = () => {
const json = await response.json();
setItem(json);
}
fetchData();
fetch(`https://localhost:7292/api/Image?id=${id}`)
.then(response => response.blob())
.then(blob => {
const imageUrl = URL.createObjectURL(blob);
setImageSrc(imageUrl);
})
setIsLoading(false);
});
useEffect(() => {
}, []);
function renderPrice(price, newPrice, status) {
if (price !== undefined) {
if (status !== 3 && status !== 4) {
@ -99,7 +115,7 @@ const MorceauDetail = () => {
<div className="detail-container">
<div className="detail-container-left">
<img className="detail-image" alt="" src={`/images/${item.imageName}.jpg`} />
<img className="detail-image" alt="" src={imageSrc} />
<p className="detail-description">{item.description}</p>
</div>
<div className="detail-container-right">