Skip to content
Extraits de code Groupes Projets
Valider 452020a1 rédigé par youssef.achkir's avatar youssef.achkir
Parcourir les fichiers

sale session export as csv file

parent 6905ac3c
Branches
2 requêtes de fusion!337Feature/MYD-696 - "Orders exporting as csv file",!336fEATURE/MYD-696 - "orders exporting without filters"
Ce diff est replié.
......@@ -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",
......@@ -55,6 +56,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",
......@@ -92,6 +94,7 @@
"devDependencies": {
"@types/autosuggest-highlight": "^3.2.3",
"@types/crypto-js": "^4.2.2",
"@types/file-saver": "^2.0.7",
"@types/json2csv": "^5.0.7",
"@types/lodash.debounce": "^4.0.9",
"@types/node": "20.11.30",
......
......@@ -304,7 +304,7 @@ export function useAddProductToUnregisteredCartFromLive() {
return memoizedValue;
}
export function useGetOrdersBySessionId(sessionId: string) {
export function useGetOrdersBySessionId(sessionId: string ) {
const URL = endpoints.salleSession.getOrdersBySessionId(sessionId);
const { data, error, isLoading, isValidating } = useSWR<ISessionOrder[]>(URL, fetcher, {
......
......@@ -181,6 +181,7 @@ export default function LiveCard({ liveId }: Props) {
title="Nombre de produits vendus"
total={SaleSessionData?.orderProductsNumber || 0}
icon={<img alt="icon" src="/assets/icons/glass/ic_glass_bag.png" />}
onClick={ () => console.log(SaleSessionData)}
/>
</Grid>
......
......@@ -46,6 +46,7 @@ import {
import SalesSessionTableRow from "./SalesSession-table-row";
import SalesSessionTableToolbar from "./SalesSession-table-toolbar";
import SalesSessionTableFiltersResult from "./SalesSession-table-filters-result";
import { exportSessionsToCSV } from '@/utils/export-service';
import {
Autocomplete,
Box,
......@@ -70,6 +71,10 @@ import { useGetLives } from "@/shared/api/live";
import { getTokenInfo } from "@/utils/token";
import LoadingButton from "@mui/lab/LoadingButton";
import EditSalesSessionDialog from "./EditSalesSessionDialog";
import { getAllOrders } from "@/shared/api/main-order";
import { useGetUsers } from "@/shared/api/user";
import { IClientItem } from "@/shared/types/user";
import ExportSessionsDialog from "./ExportSessionsDialog";
// ----------------------------------------------------------------------
......@@ -112,8 +117,18 @@ export default function AllSalesSessionView() {
const [formData, setFormData] = useState<ILiveItem>({} as ILiveItem);
const [formErrors, setFormErrors] = useState<FormErrors>({});
const [openEditDialog, setOpenEditDialog] = useState(false);
const [openExportDialog, setOpenExportDialog] = useState(false);
const [selectedRow, setSelectedRow] = useState<ILiveItem | null>(null);
const { updateSaleSession } = useUpdateSaleSession();
const { ordersData, ordersLoading, mutateOrders } = getAllOrders();
const { userData } = useGetUsers();
const [clientData, setClientData] = useState<IClientItem[]>([]);
useEffect(() => {
if (userData && Array.isArray(userData)) {
const clients = userData.filter(user => user.roles && user.roles.name === "CLIENT") as IClientItem[];
setClientData(clients);
}
}, [userData]);
const handleFormChange = (
e: React.ChangeEvent<
......@@ -250,6 +265,13 @@ export default function AllSalesSessionView() {
setFormErrors({});
};
const handleOpenExportDialog = () => {
setOpenExportDialog(true);
};
const handleCloseExportDialog = () => {
setOpenExportDialog(false);
};
const validationSchema = Yup.object().shape({
name: Yup.string().required("Le nom est requis"),
startedDate: Yup.date().required("La date de début est requise"),
......@@ -409,6 +431,12 @@ export default function AllSalesSessionView() {
});
}
};
//Exporter les sessions de vente en CSV FORMAT
const handleExport = () => {
// exportSessionsToCSV(sessions);
console.log(ordersData);
console.log(clientData);
};
const token = localStorage.getItem("token");
const storedUserInfo = token ? getTokenInfo(token) : null;
const isAdmin =
......@@ -417,26 +445,28 @@ export default function AllSalesSessionView() {
return (
<>
<Container maxWidth={settings.themeStretch ? false : "xl"}>
<CustomBreadcrumbs
heading="Session de Vente"
links={[
{ name: "Tableau de bord", href: paths.dashboard.root },
{ name: "Vente", href: paths.dashboard.admin.live.saleSession },
{ name: "Listes" },
]}
action={
<Button
variant="contained"
startIcon={<Iconify icon="mingcute:add-line" />}
onClick={handleOpenAddDialog}
>
Ajouter
</Button>
}
sx={{
mb: { xs: 3, md: 5 },
}}
/>
<CustomBreadcrumbs
heading="Session de Vente"
links={[
{ name: "Tableau de bord", href: paths.dashboard.root },
{ name: "Vente", href: paths.dashboard.admin.live.saleSession },
{ name: "Listes" },
]}
action={
<Button
variant="contained"
startIcon={<Iconify icon="mingcute:add-line" />}
onClick={handleOpenAddDialog}
>
Ajouter
</Button>
}
sx={{
mb: { xs: 3, md: 5 },
}}
/>
<Dialog
open={openAddDialog}
onClose={handleCloseAddDialog}
......@@ -607,46 +637,69 @@ export default function AllSalesSessionView() {
/>
<Card>
<Tabs
value={filters.type}
onChange={handleFiltertype}
sx={{
px: 2.5,
boxShadow: (theme) =>
`inset 0 -2px 0 0 ${alpha(theme.palette.grey[500], 0.08)}`,
}}
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between', // Space between Tabs and Button
px: 2.5,
boxShadow: (theme) =>
`inset 0 -2px 0 0 ${alpha(theme.palette.grey[500], 0.08)}`,
}}
>
{/* Tabs Component */}
<Tabs
value={filters.type}
onChange={handleFiltertype}
sx={{
px: 0, // Adjust padding if needed
}}
>
{Type_OPTIONS.map((tab) => (
<Tab
key={tab.value}
iconPosition="end"
value={tab.value}
label={tab.label}
icon={
<Label
variant={
((tab.value === "all" || tab.value === filters.type) &&
"filled") ||
"soft"
}
color={
(tab.value === "Live" && "success") ||
(tab.value === "Demande_MP" && "warning") ||
(tab.value === "Article_a_controle" && "error") ||
(tab.value === "Autre" && "default") ||
"default"
}
>
{Type_OPTIONS.map((tab) => (
<Tab
key={tab.value}
iconPosition="end"
value={tab.value}
label={tab.label}
icon={
<Label
variant={
((tab.value === "all" || tab.value === filters.type) &&
"filled") ||
"soft"
}
color={
(tab.value === "Live" && "success") ||
(tab.value === "Demande_MP" && "warning") ||
(tab.value === "Article_a_controle" && "error") ||
(tab.value === "Autre" && "default") ||
"default"
}
>
{tab.value === "all"
? tableData.length
: tableData.filter(
(session) => session.type === tab.value
).length}
</Label>
}
/>
))}
</Tabs>
{tab.value === "all"
? tableData.length
: tableData.filter(
(session) => session.type === tab.value
).length}
</Label>
}
/>
))}
</Tabs>
{/* Button Component */}
<Button
variant="outlined"
onClick={handleOpenExportDialog}
sx={{
color: '#000',
border : 'none'
}}
>
Exporter
</Button>
</Box>
<ExportSessionsDialog open={openExportDialog} onClose={handleCloseExportDialog}/>
<SalesSessionTableToolbar
filters={{
......
import React, { useState, useEffect, useCallback } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
LinearProgress,
Typography,
Box,
} from '@mui/material';
import { useGetSaleSessions } from '@/shared/api/saleSession';
import { useGetUsers } from '@/shared/api/user';
import { IClientItem } from '@/shared/types/user';
import { ILiveItem as ISession, ISessionOrder as IOrder } from '@/shared/types/saleSession';
import Papa from 'papaparse';
import { saveAs } from 'file-saver';
import { useOrdersFetcher } from './fetcher';
const MAX_RETRIES = 3;
const RETRY_DELAY = 2000;
const ExportSaleSessionsDialog: React.FC<{ open: boolean; onClose: () => void }> = ({ open, onClose }) => {
const { sessions, sessionsLoading, sessionsError } = useGetSaleSessions();
const { userData } = useGetUsers();
const { fetchOrdersForSession } = useOrdersFetcher();
const [clientData, setClientData] = useState<IClientItem[]>([]);
const [progress, setProgress] = useState(0);
const [currentSessionIndex, setCurrentSessionIndex] = useState(0);
const [exportData, setExportData] = useState<any[]>([]);
const [isExporting, setIsExporting] = useState(false);
const [isComplete, setIsComplete] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isDownloading, setIsDownloading] = useState(false);
useEffect(() => {
if (userData && Array.isArray(userData)) {
const clients = userData.filter(user => user.roles && user.roles.name === "CLIENT") as IClientItem[];
setClientData(clients);
}
}, [userData]);
const getOrdersWithRetry = useCallback(async (sessionId: string, retries = 0): Promise<IOrder[]> => {
try {
return fetchOrdersForSession(sessionId);
} catch (error) {
if (retries < MAX_RETRIES) {
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
return getOrdersWithRetry(sessionId, retries + 1);
}
throw error;
}
}, [fetchOrdersForSession]);
const processSession = useCallback(async (session: ISession) => {
try {
const orders = await getOrdersWithRetry(session.id);
console.log(`Processing session ${session.id} with ${orders.length} orders`);
const transformedOrders = orders.map((order: IOrder) => {
const client = clientData.find(c =>
c.uid?.toString() === order.client?.uid?.toString() ||
c.id?.toString() === order.client?.id?.toString()
);
return {
sessionId: session.id,
sessionName: session.name,
orderId: order.id.toString(),
id: order.client?.uid?.toString() || order.client?.id?.toString() || "Unknown ID",
name: order.client?.firstName && order.client?.lastName
? `${order.client.firstName} ${order.client.lastName}`
: order.client?.pseudo || '',
email: order.client?.email || order.client?.pseudo || '',
title: order.itemCartDTO.product ? order.itemCartDTO.product.name : '',
UGS: order.itemCartDTO.product ? order.itemCartDTO.product.ugs : '',
quantity: order.itemCartDTO.quantity,
pseudo: order.client ? order.client.pseudo : '',
CreatedAt: order.createdDate,
EditDate: order.lastModification,
price: order.price,
statutPanier: order.itemCartDTO.status,
statutOrder: order.status,
clientDetails: client || {},
};
});
return transformedOrders;
} catch (error) {
console.error(`Failed to process session ${session.id}:`, error);
return [];
}
}, [clientData, getOrdersWithRetry]);
const exportSessions = useCallback(async () => {
if (!sessions || sessions.length === 0) {
setError("No sessions available to export.");
return;
}
setIsExporting(true);
setError(null);
setProgress(0);
setCurrentSessionIndex(0);
setExportData([]);
const allSessionData = [];
for (let i = 0; i < sessions.length; i++) {
try {
const sessionData = await processSession(sessions[i]);
allSessionData.push(...sessionData);
setCurrentSessionIndex(i + 1);
setProgress(((i + 1) / sessions.length) * 100);
} catch (error) {
console.error(`Error processing session ${sessions[i].id}:`, error);
}
}
setExportData(allSessionData);
setIsComplete(true);
setIsExporting(false);
console.log(`Export complete. Total rows: ${allSessionData.length}`);
}, [sessions, processSession]);
const downloadCSV = useCallback(() => {
setIsDownloading(true);
if (exportData.length === 0) {
setError("No data to export. Please try the export process again.");
setIsDownloading(false);
return;
}
try {
const csv = Papa.unparse(exportData);
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
saveAs(blob, 'sale_sessions_export.csv');
console.log(`CSV file created with ${exportData.length} rows`);
} catch (error) {
console.error('Error creating CSV:', error);
setError("An error occurred while creating the CSV file.");
} finally {
setIsDownloading(false);
}
}, [exportData]);
return (
<Dialog open={open} onClose={onClose} fullWidth maxWidth="sm">
<DialogTitle>Exporter les sessions de vente en format CSV</DialogTitle>
<DialogContent>
{sessionsLoading ? (
<Typography>Chargement des sessions...</Typography>
) : sessionsError ? (
<Typography color="error">Erreur lors du chargement des sessions.</Typography>
) : (
<>
<Box sx={{ width: '100%', mb: 2 }}>
<Typography variant="body2" color="text.secondary" gutterBottom>
{isExporting ? `${currentSessionIndex} sur ${sessions?.length}` : ''}
</Typography>
<LinearProgress
variant="determinate"
value={progress}
sx={{ height: 10, borderRadius: 5 }}
/>
</Box>
{error && (
<Typography color="error" variant="body2" gutterBottom>
{error}
</Typography>
)}
{isComplete && (
<Typography variant="body2" color="success.main" gutterBottom>
Export completed. {exportData.length} rows ready for download.
</Typography>
)}
</>
)}
</DialogContent>
<DialogActions>
{isComplete ? (
<Button
onClick={downloadCSV}
color="primary"
variant="contained"
disabled={isDownloading || exportData.length === 0}
>
{isDownloading ? 'Téléchargement...' : 'Télécharger CSV'}
</Button>
) : (
<Button
onClick={exportSessions}
disabled={isExporting || sessionsLoading || !sessions?.length}
color="primary"
variant="contained"
>
{isExporting ? 'Exportation en cours...' : 'Commencer l\'exportation'}
</Button>
)}
<Button onClick={onClose} color="secondary">
Fermer
</Button>
</DialogActions>
</Dialog>
);
};
export default ExportSaleSessionsDialog;
import { useGetOrdersBySessionId } from '@/shared/api/saleSession';
import { ISessionOrder as IOrder } from '@/shared/types/saleSession';
export const useOrdersFetcher = () => {
const fetchOrdersForSession = (sessionId: string) => {
const { orders, ordersError } = useGetOrdersBySessionId(sessionId);
if (ordersError) {
throw new Error(ordersError);
}
return orders;
};
return { fetchOrdersForSession };
};
import { saveAs } from 'file-saver'; // For downloading the file
import Papa from 'papaparse'; // For CSV generation
import {ILiveItem} from "@/shared/types/saleSession"
export const exportSessionsToCSV = (sessions : ILiveItem) => {
if (!sessions || sessions.length === 0) {
console.error('No sessions to export');
return;
}
// Convert sessions to CSV
const csv = Papa.unparse(sessions);
// Create a Blob object from the CSV string
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
// Trigger file download
saveAs(blob, 'saleSessions.csv');
};
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