E6-FStockSeeker/src/views/Dashboard.vue
2025-02-07 18:00:33 +01:00

229 lines
9.3 KiB
Vue

<script setup>
import { ref, onMounted} from 'vue';
import Chart from 'primevue/chart';
import Fieldset from 'primevue/fieldset';
import MeterGroup from 'primevue/metergroup';
import Carousel from 'primevue/carousel';
import { getProducts,getWarehouses, modifyProduct } from '../api.js';
import Tag from 'primevue/tag';
import AutoComplete from 'primevue/autocomplete';
import InputNumber from 'primevue/inputnumber';
import IftaLabel from 'primevue/iftalabel';
import Button from 'primevue/button'
import { useToast } from 'primevue/usetoast';
import Divider from 'primevue/divider';
import ScrollPanel from 'primevue/scrollpanel';
import Chip from 'primevue/chip';
import PMessage from 'primevue/message';
const toast = useToast();
const products = ref([]);
const warehouses = ref([]);
const selectedRef = ref()
const filteredRef = ref();
const newValue = ref();
const modifyErrors = ref({});
const chartProductsData = ref();
const chartWarehousesData = ref();
const chartOptions = {plugins:{legend:{labels:{usePointStyle:true,}}}};
const productColors = {};
const warehouseColors ={};
const colorsSchemes = [
"#40407a", "#706fd3", "#f7f1e3", "#34ace0",
"#33d9b2", "#2c2c54", "#474787", "#aaa69d",
"#227093", "#218c74", "#ff5252", "#ff793f",
"#d1ccc0", "#ffb142", "#ffda79", "#b33939",
"#cd6133", "#84817a", "#cc8e35", "#ccae62"
];
const getProductValues = (warehouse) => {
if (!products.value) return [];
return products.value
.filter(product => product.warehouses.includes(warehouse.id))
.map(product => ({
label: product.name,
color: getColorForProduct(product.name),
value: (product.quantity * 100) / warehouse.max_capacity
}));
};
const getColorForProduct = (productName) => {
if (!productColors[productName]) {
productColors[productName] = colorsSchemes[Object.keys(productColors).length % colorsSchemes.length];
}
return productColors[productName];
};
const getColorForWarehouse = (warehouseName) => {
if (!warehouseColors[warehouseName]) {
warehouseColors[warehouseName] = colorsSchemes[Object.keys(warehouseColors).length % colorsSchemes.length];
}
return warehouseColors[warehouseName];
};
const setChartProductsData = () => {
return {
labels: products.value.map(product => product.name),
datasets: [
{
label: 'Quantité',
backgroundColor: products.value.map(product => `${getColorForProduct(product.name)}80`),
borderColor : products.value.map(product => getColorForProduct(product.name)),
data: products.value.map(product => product.quantity),
borderWidth: 1,
}
]
};
};
const setChartWarehousesData = () =>{
return {
labels: warehouses.value.map(warehouse => warehouse.name),
datasets: [
{
label: 'Capacité',
backgroundColor: warehouses.value.map(warehouse => `${getColorForWarehouse(warehouse.name)}80`),
borderColor : warehouses.value.map(warehouse => getColorForWarehouse(warehouse.name)),
data: warehouses.value.map(warehouse => warehouse.max_capacity),
borderWidth: 1
}
]
}
};
const search = (event) => {
filteredRef.value = products.value.filter(product =>
product.reference.toLowerCase().includes(event.query.toLowerCase())
);
}
onMounted(async () => {
warehouses.value = await getWarehouses();
products.value = await getProducts();
makeAlert(products.value.filter(productsAlerts => productsAlerts.is_stock_low && productsAlerts.alert_enabled))
chartProductsData.value = setChartProductsData();
chartWarehousesData.value = setChartWarehousesData();
})
const productValueModifier = async () => {
if (!selectedRef.value || !newValue.value) return;
const product = products.value.find(p => p.reference === selectedRef.value.reference);
if (product) {
modifyErrors.value = {};
const newQuantity = product.quantity + newValue.value
try{
await modifyProduct({
quantity: newQuantity,
warehouses: product.warehouses
}, product.id);
warehouses.value = await getWarehouses();
products.value = await getProducts();
chartProductsData.value = setChartProductsData();
chartWarehousesData.value = setChartWarehousesData();
} catch (error){
if (error.response && error.response.data) {
const data = error.response.data;
if (data.detail) {
toast.add({ severity: 'error',life: 2500, summary: 'Erreur', detail: data.detail });
};
modifyErrors.value = { quantity: data.quantity ? data.quantity[0]:"" }
};
}
} else {
toast.add({ severity: 'error',life: 2500, summary: 'Erreur', detail: 'Une erreur est survenue.' });
}
};
function makeAlert(productsAlerts){
productsAlerts.forEach(product => toast.add({ severity: 'warn', life: 4500, summary:`Attention ${product.name} est dessous du seuil définie`}));
}
</script>
<template>
<div class="fieldset-container">
<Fieldset class="fieldset-section fieldset-p-graph" legend="Produits stockés" >
<Chart type="pie" :data="chartProductsData" :options="chartOptions"></Chart>
</Fieldset>
<Fieldset class="fieldset-section fieldset-wh-graph" legend="Listes des Entrepôts" >
<Chart type="bar" :data="chartWarehousesData" :options="chartOptions"></Chart>
</Fieldset>
<Fieldset class="fieldset-section fieldset-capacity-usage" legend=" Capacité utilisé" >
<ScrollPanel style="width: 100%; height: 20rem">
<ul>
<li v-for="warehouse in warehouses" :key="warehouse.id">
<h4>{{ warehouse.name }}</h4>
<MeterGroup :value="getProductValues(warehouse)"></MeterGroup>
<Divider />
</li>
</ul>
</ScrollPanel>
</Fieldset>
<Fieldset class="fieldset-section fieldset-quickadd" legend="Ajout Rapide" >
<form class="form-update-user" @submit.prevent="productValueModifier">
<IftaLabel>
<AutoComplete inputId="reference" name="reference" optionLabel="reference" :suggestions="filteredRef" v-model="selectedRef" @complete="search" fluid></AutoComplete>
<label for="reference">Réference :</label>
</IftaLabel>
<IftaLabel>
<InputNumber inputId="number-value" showButtons v-model="newValue" mode="decimal" fluid></InputNumber>
<label for="number-value">Ajouter/Soustraire :</label>
</IftaLabel>
<p-message v-if="modifyErrors.quantity" severity="error">{{ modifyErrors.quantity }}</p-message>
<Button
label="Modifier"
icon="pi pi-file-edit
"
type="submit"
class="p-button-primary"> </Button>
</form>
</Fieldset>
<Fieldset class="fieldset-section fieldset-list-wh-p" legend="Products et Entrepôt">
<h4 v-if="products.length === 0">
<i class="pi pi-exclamation-circle"></i>
Aucun produit renseigné
</h4>
<Carousel v-else :value="products"
:numVisible="2"
:numScroll="1"
class="carousmex"
>
<template #item="slotProps">
<div class="carousel-products">
<span v-if="slotProps.data.alert_enabled && slotProps.data.is_stock_low">
<Tag icon="pi pi-box"severity="danger" value="STOCK FAIBLE" rounded> </Tag>
</span>
<span v-else>
<Tag icon="pi pi-box" severity="info" value="STOCK OK" rounded> </Tag>
</span>
<img v-if="slotProps.data.image" :src="slotProps.data.image" alt="" style="border-radius: 8px; width: 6rem; height: 6rem;">
<i v-if="!slotProps.data.image" class="pi pi-eye-slash" style="font-size: 6rem"></i>
<Chip icon="pi pi-box" :label="String(slotProps.data.quantity)"/>
<Chip icon="pi pi-hashtag" :label="String(slotProps.data.reference)"></Chip>
</div>
</template>
</Carousel>
<Divider />
<h4 v-if="warehouses.length === 0">
<i class="pi pi-exclamation-circle"></i>
Aucun entrepôt renseigné
</h4>
<Carousel v-else :value="warehouses"
:numVisible="2"
:numScroll="1"
class="carousmex"
>
<template #item="slotProps" >
<div class="carousel-products">
<h4>{{ slotProps.data.name }}</h4>
<Chip icon="pi pi-map-marker" :label="slotProps.data.location"></Chip>
<Chip icon="pi pi-shop" :label="`${slotProps.data.max_capacity}`"></Chip>
</div>
</template>
</Carousel>
</Fieldset>
</div>
</template>