react-version #1
5
.gitignore
vendored
5
.gitignore
vendored
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
@ -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>
|
||||
|
BIN
GrossesMitaines/GrossesMitainesAPI/Images/default.jpg
Normal file
BIN
GrossesMitaines/GrossesMitainesAPI/Images/default.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
BIN
GrossesMitaines/GrossesMitainesAPI/Images/default_thumbnail.jpg
Normal file
BIN
GrossesMitaines/GrossesMitainesAPI/Images/default_thumbnail.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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;
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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}
|
||||
|
@ -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) => {
|
||||
|
@ -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">
|
||||
|
Loading…
Reference in New Issue
Block a user