Skip to content
Extraits de code Groupes Projets
Valider 6ba6955a rédigé par yasserAbassi's avatar yasserAbassi
Parcourir les fichiers

custom products csv export

parent 6905ac3c
Branches
1 requête de fusion!342MYD-697/add export to csv for products and users
......@@ -37,6 +37,7 @@
"date-fns": "^2.29.3",
"dayjs": "^1.11.13",
"dayjs-plugin-utc": "^0.1.2",
"file-saver": "^2.0.5",
"firebase": "^10.14.1",
"formik": "^2.4.6",
"framer-motion": "^11.0.6",
......@@ -47,6 +48,7 @@
"json2csv": "^6.0.0-alpha.2",
"jwt-decode": "^4.0.0",
"lodash": "^4.17.21",
"lucide-react": "^0.469.0",
"mui": "^0.0.1",
"mui-image-crop": "^1.1.0",
"mui-one-time-password-input": "^2.0.2",
......@@ -55,6 +57,7 @@
"next": "^14.1.4",
"notistack": "^3.0.1",
"nprogress": "^0.2.0",
"papaparse": "^5.4.1",
"react": "^18",
"react-apexcharts": "^1.4.1",
"react-avatar-editor": "^13.0.2",
......@@ -85,6 +88,7 @@
"stompjs": "^2.3.3",
"stylis-plugin-rtl": "^2.1.1",
"swr": "^2.2.4",
"use-debounce": "^10.0.4",
"uuid": "^9.0.1",
"yet-another-react-lightbox": "^3.17.1",
"yup": "^1.4.0"
......
import { useState } from 'react';
import { TextField, IconButton } from '@mui/material';
import { Search } from 'lucide-react';
const ProductSearch = ({ onSearch }) => {
const [searchTerm, setSearchTerm] = useState('');
const handleSearch = () => {
onSearch(searchTerm);
};
const handleKeyPress = (event) => {
if (event.key === 'Enter') {
handleSearch();
}
};
return (
<div className="flex items-center gap-2">
<TextField
size="small"
placeholder="Recherche..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
onKeyPress={handleKeyPress}
className="min-w-[200px]"
/>
<IconButton onClick={handleSearch} size="small" className="bg-gray-100">
<Search className="h-5 w-5" />
</IconButton>
</div>
);
};
export default ProductSearch;
\ No newline at end of file
......@@ -17,6 +17,7 @@ import {
GridToolbarFilterButton,
GridToolbarColumnsButton,
GridColumnVisibilityModel,
GridFilterModel,
} from "@mui/x-data-grid";
import Snackbar from "@mui/material/Snackbar";
import Alert from "@mui/material/Alert";
......@@ -57,6 +58,8 @@ import {
RenderCellPuchasePrice,
} from "./product-table-row";
import ProductSearch from "./SearchField";
const stockOptions = [
{ value: "In Stock", label: "In Stock" },
{ value: "Out of Stock", label: "Out of Stock" },
......@@ -86,15 +89,17 @@ export default function ProductListView() {
const { products, productsLoading, revalidate } = useAllGetProducts();
const [tableData, setTableData] = useState<IProductListItem[]>([]);
const [filters, setFilters] = useState(defaultFilters);
const [selectedRowIds, setSelectedRowIds] = useState<GridRowSelectionModel>(
[]
);
const [selectedRowIds, setSelectedRowIds] = useState<GridRowSelectionModel>([]);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [forceUpdate, setForceUpdate] = useState(0);
const [columnVisibilityModel, setColumnVisibilityModel] =
useState<GridColumnVisibilityModel>(HIDE_COLUMNS);
const [columnVisibilityModel, setColumnVisibilityModel] = useState<GridColumnVisibilityModel>(HIDE_COLUMNS);
const [confirmDeleteRow, setConfirmDeleteRow] = useState<number | null>(null);
const [searchTerm, setSearchTerm] = useState("");
const [filterModel, setFilterModel] = useState<GridFilterModel>({
items: [],
quickFilterValues: [],
});
useEffect(() => {
if (products.length) {
......@@ -105,6 +110,8 @@ export default function ProductListView() {
const dataFiltered = applyFilter({
inputData: tableData,
filters,
searchTerm,
filterModel,
});
const canReset = !isEqual(defaultFilters, filters);
......@@ -121,8 +128,61 @@ export default function ProductListView() {
const handleResetFilters = useCallback(() => {
setFilters(defaultFilters);
setFilterModel({
items: [],
quickFilterValues: [],
});
setSearchTerm("");
}, []);
const handleExport = async () => {
try {
const exportPayload = {
customFilters: filters,
searchTerm: searchTerm,
gridFilters: filterModel.items,
quickFilter: filterModel.quickFilterValues,
};
console.log("Export payload:", exportPayload);
const dataToExport = dataFiltered;
const arrayId = dataToExport.map((product) => product.id);
console.log("Array ID:", arrayId);
const response = await fetch("http://localhost:8080/api/stock/products/products/export", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(arrayId),
});
if (!response.ok) {
throw new Error("Failed to export products.");
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'products.csv';
a.click();
window.URL.revokeObjectURL(url);
} catch (error) {
console.error(error);
setErrorMessage(
typeof error === "string"
? error
: "An error occurred while exporting products."
);
}
};
const handleSearch = (term: string) => {
setSearchTerm(term);
};
const handleDeleteRow = async (id: number) => {
try {
const response = await useDeleteProduct(id.toString());
......@@ -210,12 +270,6 @@ export default function ProductListView() {
}
};
useEffect(() => {
if (products.length) {
setTableData(products);
}
}, [products]);
const columns: GridColDef[] = [
{
field: "categoryNames",
......@@ -288,18 +342,18 @@ export default function ProductListView() {
{
field: "purchasePrice",
headerName: "Prix d'achat",
width:80,
width: 80,
renderCell: (params) => <RenderCellPuchasePrice params={params} />,
},
{
field: "supplierName",
headerName: "Fournisseur",
width:100,
width: 100,
},
{
field: "supplierRef",
headerName: "Référence",
width:70,
width: 70,
},
{
type: "actions",
......@@ -402,6 +456,11 @@ export default function ProductListView() {
loading={productsLoading}
getRowHeight={() => "auto"}
pageSizeOptions={[5, 10, 25]}
filterModel={filterModel}
onFilterModelChange={(newModel) => {
setFilterModel(newModel);
console.log("Filter model updated:", newModel);
}}
initialState={{
pagination: {
paginationModel: { pageSize: 10 },
......@@ -418,17 +477,17 @@ export default function ProductListView() {
setColumnVisibilityModel(newModel)
}
localeText={{
toolbarColumns: "Colonnes",
toolbarColumnsLabel:"chercher ",
toolbarColumns: "Colonnes",
toolbarColumnsLabel: "Chercher",
toolbarFilters: "Filtrer",
toolbarExport: "Exporter",
toolbarDensity: "Densité",
filterPanelInputPlaceholder: "Valeur à filtrer",
filterPanelColumns: "Colonnes",
toolbarDensity: "Densité",
filterPanelInputPlaceholder: "Valeur à filtrer",
filterPanelColumns: "Colonnes",
toolbarExportCSV: "Télécharger au format CSV",
toolbarExportPrint: "Imprimer",
filterPanelOperator:"Opérateur",
filterPanelInputLabel:"Valeur",
filterPanelOperator: "Opérateur",
filterPanelInputLabel: "Valeur",
}}
slots={{
toolbar: () => (
......@@ -440,7 +499,7 @@ export default function ProductListView() {
stockOptions={stockOptions}
publishOptions={publishOptions}
/>
<GridToolbarQuickFilter placeholder="Recherche..." />
<ProductSearch onSearch={handleSearch} />
<Stack
spacing={1}
flexGrow={1}
......@@ -453,9 +512,7 @@ export default function ProductListView() {
<Button
size="small"
color="error"
startIcon={
<Iconify icon="solar:trash-bin-trash-bold" />
}
startIcon={<Iconify icon="solar:trash-bin-trash-bold" />}
onClick={confirmRows.onTrue}
>
Supprimer ({selectedRowIds.length})
......@@ -469,6 +526,12 @@ export default function ProductListView() {
csvOptions={{ label: "Télécharger au format CSV" }}
printOptions={{ label: "Imprimer" }}
/>
<Button
onClick={handleExport}
startIcon={<Iconify icon="material-symbols:download" />}
>
Exporter filtré
</Button>
</Stack>
</GridToolbarContainer>
{canReset && (
......@@ -489,8 +552,6 @@ export default function ProductListView() {
},
}}
/>
</Box>
</Container>
......@@ -573,18 +634,22 @@ export default function ProductListView() {
function applyFilter({
inputData,
filters,
searchTerm,
filterModel,
}: {
inputData: IProductListItem[];
filters: IProductTableFilters;
searchTerm: string;
filterModel: GridFilterModel;
}) {
const { stock, publish, stockLive } = filters;
let filteredData = [...inputData];
// Apply custom filters
if (stock.length) {
filteredData = filteredData.filter((product) => {
const stockStatus =
product.stockWebQuantity > 0 ? "In Stock" : "Out of Stock";
const stockStatus = product.stockWebQuantity > 0 ? "In Stock" : "Out of Stock";
return stock.includes(stockStatus);
});
}
......@@ -598,15 +663,51 @@ function applyFilter({
if (stockLive.length) {
filteredData = filteredData.filter((product) => {
const stockLiveStatus =
product.stockLiveQuantity > 0 ? "In Stock" : "Out of Stock";
const stockLiveStatus = product.stockLiveQuantity > 0 ? "In Stock" : "Out of Stock";
return stockLive.includes(stockLiveStatus);
});
}
// Apply DataGrid's built-in filters
if (filterModel.items.length > 0) {
filteredData = filteredData.filter((row) => {
return filterModel.items.every((filterItem) => {
const value = row[filterItem.field] ;
const filterValue = filterItem.value;
switch (filterItem.operator) {
case 'equals':
return value === filterValue;
case 'contains':
return String(value).toLowerCase().includes(String(filterValue).toLowerCase());
case 'startsWith':
return String(value).toLowerCase().startsWith(String(filterValue).toLowerCase());
case 'endsWith':
return String(value).toLowerCase().endsWith(String(filterValue).toLowerCase());
case 'isEmpty':
return !value;
case 'isNotEmpty':
return !!value;
default:
return true;
}
});
});
}
// Apply search term
if (searchTerm) {
filteredData = filteredData.filter((product) =>
Object.values(product).some((value) =>
value && value.toString().toLowerCase().includes(searchTerm.toLowerCase())
)
);
}
// Sort by creation date
filteredData.sort(
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
return filteredData;
}
}
\ No newline at end of file
0% ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter