diff --git a/src/app/dashboard/product/category/[id]/edit/page.tsx b/src/app/dashboard/product/category/[id]/edit/page.tsx index f1884db00b8ed5975cc2d9ca14b53294709e0e49..ddbc034493302a2790521e20636a9a5ba0f2ce32 100644 --- a/src/app/dashboard/product/category/[id]/edit/page.tsx +++ b/src/app/dashboard/product/category/[id]/edit/page.tsx @@ -13,5 +13,5 @@ type Props = { export default function CategoryEditViewpage({ params }: Props) { const { id } = params; - return <CategoryEditView id={id} />; + return <CategoryEditView id={id.toString()} />; } diff --git a/src/shared/api/categorie.ts b/src/shared/api/categorie.ts index 175cda984df8adad012794e7eee3d47a5b778c23..e263d6aa5d7662df1cc2e25ea04ea5b58d0dcfa3 100644 --- a/src/shared/api/categorie.ts +++ b/src/shared/api/categorie.ts @@ -101,7 +101,7 @@ export async function useUpdateCategory(category: ICategoryRequest,categoryId: s return response } catch (error) { - console.error("Erreur lors de l'ajout de la catégorie:", error); + console.error("Erreur lors de la mise à jour de la catégorie:", error); throw error; } @@ -127,7 +127,7 @@ export async function useDeleteCategories(categoryIds: string[]) { }); return response } catch (error) { - console.error("Erreur lors de la suppression de la catégorie:", error); + console.error("Erreur lors de la suppression des catégories:", error); throw error; } } \ No newline at end of file diff --git a/src/shared/api/tag.ts b/src/shared/api/tag.ts index fc14b8f6fd980d6af042d3f2c4e03dda907ddd33..3a03c96cb0b66fd4f1bdba2bc85ad074b4ff9ea9 100644 --- a/src/shared/api/tag.ts +++ b/src/shared/api/tag.ts @@ -1,4 +1,4 @@ -import useSWR,{ mutate } from 'swr' +import useSWR from 'swr' import { useMemo } from "react"; import { endpoints } from "./server"; import axios, { AxiosRequestConfig, AxiosResponse } from "axios"; @@ -43,7 +43,7 @@ axiosInstance.interceptors.response.use( export function useGetTags(){ const URL = [endpoints.stock_management.tags.getAll()] - const {data, error, isLoading, isValidating} = useSWR<ITag[]>(URL, fetcher) + const {data, error, isLoading, isValidating, mutate} = useSWR<ITag[]>(URL, fetcher) const memoizedValue = useMemo( ()=>({ @@ -51,6 +51,7 @@ export function useGetTags(){ tagsLoading: isLoading, tagsError: error, tagsValidating: isValidating, + tagsMutate: mutate } ) @@ -58,3 +59,72 @@ export function useGetTags(){ ,[data, error]); return memoizedValue } + +export function useGetTag(tagId: string){ + const URL = [endpoints.stock_management.tags.getById(tagId)] + const {data, error, isLoading, isValidating} = useSWR<ITag>(URL, fetcher) + + const memoizedValue = useMemo( + ()=>({ + tagData : (data as ITag) || null, + tagLoading: isLoading, + tagError: error, + tagValidating: isValidating, + + + } + ) + + ,[data, error]); + return memoizedValue +} + +export async function useAddTag(tag: ITag) { + try { + const URL = endpoints.stock_management.tags.add() + const response: AxiosResponse<ITag> = await axiosInstance.post(URL, tag); + + } catch (error) { + console.error("Erreur lors de l'ajout de l'étiquette:", error); + throw error; + } +} + +export async function useUpdateTag(tag: ITag,tagId: string ){ + + try { + const URL = endpoints.stock_management.tags.edit(tagId) + const response: AxiosResponse<ITag> = await axiosInstance.put(URL, tag); + return response + + } catch (error) { + console.error("Erreur lors de la mise à jour de l'étiquette:", error); + throw error; + } + +} + +export async function useDeleteTag(tagId: string) { + try { + const URL = endpoints.stock_management.tags.delete(tagId) + const response = await axiosInstance.delete(URL); + return response; + } catch (error) { + console.error("Erreur lors de la suppression de l'étiquette:", error); + throw error; + } +} + + +export async function useDeleteTags(tagIds: string[]) { + try { + const URL = endpoints.stock_management.tags.deleteBunch() + const response = await axiosInstance.delete(URL, { + data: tagIds, + }); + return response + } catch (error) { + console.error("Erreur lors de la suppression des étiquettes:", error); + throw error; + } +} \ No newline at end of file diff --git a/src/shared/sections/product/add-edit-category/product-create-category.tsx b/src/shared/sections/product/add-edit-category/product-create-category.tsx index 2b0842c43bb3a09e6adeab915f7fa4c9394abcbd..72fae3ff0d2fdc1cd7615c089c466598ff994c67 100644 --- a/src/shared/sections/product/add-edit-category/product-create-category.tsx +++ b/src/shared/sections/product/add-edit-category/product-create-category.tsx @@ -25,7 +25,7 @@ import { GridRowSelectionModel, GridToolbarFilterButton, } from "@mui/x-data-grid"; -import { _categories } from "@/shared/_mock/_categories"; + import { Avatar, CircularProgress, Typography, Alert } from "@mui/material"; export default function ProductCategory() { @@ -105,10 +105,13 @@ export default function ProductCategory() { mutateCategories(); if (response.status === 200) { - // Logique après succès + setSelectedRowIds([]); + setSuccessMessage("Catégorie supprimée avec succès."); } } catch (error) { setErrorMessage("Erreur lors de la suppression de la catégorie."); + + }finally{ const timeoutId = setTimeout(() => { setErrorMessage(null); clearTimeout(timeoutId); diff --git a/src/shared/sections/product/add-list-tags/product-create-tags.tsx b/src/shared/sections/product/add-list-tags/product-create-tags.tsx index 857e0bfe2c5900d0cac370ab031f936ce1fff3bf..e0ea7772db7cf1c9b74c19124f576c5b01f33ee9 100644 --- a/src/shared/sections/product/add-list-tags/product-create-tags.tsx +++ b/src/shared/sections/product/add-list-tags/product-create-tags.tsx @@ -1,23 +1,18 @@ "use client"; -import { useState } from "react"; +import { useState, useCallback } from "react"; import Container from "@mui/material/Container"; - import Stack from "@mui/material/Stack"; - import Grid from "@mui/material/Grid"; - import Button from "@mui/material/Button"; import Iconify from "@/shared/components/iconify"; import { paths } from "@/routes/paths"; import CustomBreadcrumbs from "@/shared/components/custom-breadcrumbs"; import Card from "@mui/material/Card"; - import { useBoolean } from "@/hooks/use-boolean"; import ConfirmDialog from "@/shared/components/confirm-dialog"; -import ProductTagsForm from "./tags-edit-form"; +import ProductTagsAddForm from "./tags-add-form"; import { useRouter } from "@/hooks"; -import { useCallback } from "react"; import { DataGrid, GridColDef, @@ -28,65 +23,83 @@ import { GridRowSelectionModel, GridToolbarFilterButton, } from "@mui/x-data-grid"; +import { useDeleteTag, useDeleteTags, useGetTags } from "@/shared/api/tag"; +import { CircularProgress, Typography, Alert } from "@mui/material"; export default function ProductTags() { - 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 router = useRouter(); + const confirmRows = useBoolean(); + + const { tagsData, tagsLoading, tagsError, tagsMutate } = useGetTags(); + const handleEditRow = useCallback( (id: string) => { router.push(paths.dashboard.admin.product.editTag(id)); }, [router] ); - const confirmRows = useBoolean(); - const Initialtags = [ - { - id: 1, - name: "Robes", - description: "-", - slug: "robes", - total: 35, - }, - { - id: 2, - name: "Chemisiers", - slug: "chemisiers", - description: "-", - total: 20, - }, - { - id: 3, - name: "Pantalons", - slug: "pantalons", - description: "-", - total: 25, - }, - { - id: 4, - name: "Jupes", - slug: "jupes", - description: "-", - total: 30, - }, - { - id: 5, - name: "Vestes", - slug: "vestes", - description: "-", - total: 15, - }, - { - id: 6, - name: "Chaussures", - slug: "chaussures", - description: "-", - total: 40, - }, - ]; - const handleDeleteRows = () => {}; + const handleRetry = async () => { + try { + tagsMutate(); + } catch (err) { + setErrorMessage("Erreur lors du rechargement des étiquettes."); + const timeoutId = setTimeout(() => { + setErrorMessage(null); + clearTimeout(timeoutId); + }, 5000); + } + }; + const handleDeleteRows = async () => { + try { + if (selectedRowIds.length > 0) { + const idsToDelete = selectedRowIds.map((id) => id.toString()); + const response = await useDeleteTags(idsToDelete); + console.log("Delete response:", response); + tagsMutate(); + + if (response.status === 204) { + setSelectedRowIds([]); + setSuccessMessage("Étiquette(s) supprimée(s) avec succès."); + + } + } + } catch (error) { + + setErrorMessage("Erreur lors de la suppression des étiquettes."); + } finally { + setTimeout(() => { + setSuccessMessage(null); + setErrorMessage(null); + }, 5000); + } + }; + + const handleDeleteRow = async (id: number) => { + try { + const response = await useDeleteTag(id.toString()); + console.log("Delete response:", response); // Ajouté pour débogage + tagsMutate(); + + if (response.status === 204) { + setSelectedRowIds([]); + setSuccessMessage("Étiquette supprimée avec succès."); + console.log("Success message set."); // Ajouté pour débogage + } + } catch (error) { + console.error("Error during delete:", error); // Ajouté pour débogage + setErrorMessage("Erreur lors de la suppression de l'étiquette."); + } finally { + setTimeout(() => { + setSuccessMessage(null); + setErrorMessage(null); + }, 5000); // 5 secondes pour nettoyer les messages + } + }; + const columns: GridColDef[] = [ { @@ -130,13 +143,12 @@ export default function ProductTags() { label="modifier" onClick={() => handleEditRow(params.row.id)} />, - <GridActionsCellItem showInMenu icon={<Iconify icon="solar:trash-bin-trash-bold" />} label="Supprimer" onClick={() => { - //handleDeleteRow(params.row.id); + handleDeleteRow(params.row.id); }} sx={{ color: "error.main" }} />, @@ -149,13 +161,12 @@ export default function ProductTags() { }, ]; - const rows = Initialtags.map((tags) => ({ - id: tags.id, - - name: tags.name, - slug: tags.slug, - description: tags.description, - total: tags.total, + const rows = tagsData.map((tag) => ({ + id: tag.id, + name: tag.nom, + slug: tag.slug, + description: tag.description, + total: tag.total, })); return ( @@ -171,10 +182,7 @@ export default function ProductTags() { heading="Étiquettes" links={[ { name: "Dashboard", href: paths.dashboard.root }, - { - name: "Product", - href: paths.dashboard.admin.product.root, - }, + { name: "Product", href: paths.dashboard.admin.product.root }, { name: "Étiquettes" }, ]} sx={{ @@ -187,25 +195,51 @@ export default function ProductTags() { <Grid container spacing={3}> <Grid item xs={4}> - <ProductTagsForm /> + <ProductTagsAddForm /> </Grid> <Grid item xs={8}> - <Card variant="outlined"> - <DataGrid - checkboxSelection - autoHeight - getRowHeight={() => "auto"} - rows={rows} - columns={columns} - disableColumnSelector - disableDensitySelector - onRowSelectionModelChange={(newSelectionModel) => { - setSelectedRowIds(newSelectionModel); - }} - slots={{ - toolbar: () => ( - <> + {tagsLoading ? ( + <Stack + direction="row" + spacing={2} + justifyContent="center" + alignItems="center" + sx={{ height: '100%', width: '100%' }} + > + <CircularProgress /> + <Typography>Chargement des étiquettes...</Typography> + </Stack> + ) : tagsError ? ( + <Container> + <Alert severity="error" sx={{ mb: 2 }}> + Impossible de charger la liste des étiquettes + </Alert> + <Button variant="contained" color="primary" onClick={handleRetry}> + Réessayer + </Button> + </Container> + ) : ( + <Card variant="outlined"> + <DataGrid + checkboxSelection + autoHeight + getRowHeight={() => "auto"} + rows={rows} + columns={columns} + disableColumnSelector + disableDensitySelector + pageSizeOptions={[5, 10, 25]} + initialState={{ + pagination: { + paginationModel: { pageSize: 10 }, + }, + }} + onRowSelectionModelChange={(newSelectionModel) => { + setSelectedRowIds(newSelectionModel); + }} + slots={{ + toolbar: () => ( <GridToolbarContainer> <GridToolbarFilterButton /> <GridToolbarExport /> @@ -227,22 +261,35 @@ export default function ProductTags() { } onClick={confirmRows.onTrue} > - Delete ({selectedRowIds.length}) + Supprimer ({selectedRowIds.length}) </Button> + </Stack> )} </Stack> </GridToolbarContainer> - </> - ), - }} - slotProps={{ - toolbar: { - showQuickFilter: true, - }, - }} - /> - </Card> + ), + }} + slotProps={{ + toolbar: { + showQuickFilter: true, + }, + }} + /> + {errorMessage && ( + <Alert severity="error" onClose={() => setErrorMessage(null)}> + {errorMessage} + </Alert> + )} + + {successMessage && ( + <Alert severity="success" onClose={() => setSuccessMessage(null)}> + {successMessage} + </Alert> + )} + + </Card> + )} <ConfirmDialog open={confirmRows.value} onClose={confirmRows.onFalse} diff --git a/src/shared/sections/product/add-list-tags/tag-edit-view.tsx b/src/shared/sections/product/add-list-tags/tag-edit-view.tsx index 73f2a496321bc1b266af96a299d3fb95b44c4634..0bdfb92e8351ae43fe844ab71930c0a921c1dfe9 100644 --- a/src/shared/sections/product/add-list-tags/tag-edit-view.tsx +++ b/src/shared/sections/product/add-list-tags/tag-edit-view.tsx @@ -7,7 +7,7 @@ import { paths } from "@/routes/paths"; import { useSettingsContext } from "@/shared/components/settings"; import CustomBreadcrumbs from "@/shared/components/custom-breadcrumbs"; -import ProductTagsForm from "./tags-edit-form"; +import ProductTagsEditForm from "./tags-edit-form"; // ---------------------------------------------------------------------- @@ -35,7 +35,7 @@ export default function TagEditView({ id }: Props) { }} /> - <ProductTagsForm /> + <ProductTagsEditForm id={id} /> </Container> ); } diff --git a/src/shared/sections/product/add-list-tags/tags-add-form.tsx b/src/shared/sections/product/add-list-tags/tags-add-form.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6a8d9a0adb1afae3b4913057ed5e2ebbfd1ce4ba --- /dev/null +++ b/src/shared/sections/product/add-list-tags/tags-add-form.tsx @@ -0,0 +1,115 @@ +"use client"; +import { useState } from "react"; +import TextField from "@mui/material/TextField"; +import Button from "@mui/material/Button"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import Card from "@mui/material/Card"; +import { Alert, Snackbar } from "@mui/material"; +import { ITag } from "@/shared/types/tag"; +import { useAddTag, useGetTags } from "@/shared/api/tag"; + +export default function ProductTagsAddForm() { + const {tagsMutate} =useGetTags() + + const [nom, setNom] = useState<string>(''); + const [slug, setSlug] = useState<string>(''); + const [description, setDescription] = useState<string>(''); + + // States for Snackbar + const [openSnackbar, setOpenSnackbar] = useState<boolean>(false); + const [snackbarMessage, setSnackbarMessage] = useState<string>(""); + const [showSuccessMessage, setShowSuccessMessage] = useState(false); + + const validateForm = (): boolean => { + if (!nom || !slug || !description ) { + setSnackbarMessage("Tous les champs requis doivent être remplis."); + setOpenSnackbar(true); + return false; + } + return true; + }; + + const handleCloseSnackbar = () => { + setOpenSnackbar(false); + }; + + const handleOnSubmit = async (event: React.FormEvent<HTMLFormElement>) => { + event.preventDefault(); + if (!validateForm()) { + return; + } + const tagRequest: ITag = { + nom, + slug, + description, + }; + + try { + await useAddTag(tagRequest); + tagsMutate(); + setShowSuccessMessage(true); + const timeoutId = setTimeout(() => { + setShowSuccessMessage(false); + clearTimeout(timeoutId); + }, 5000); + } catch (error) { + setSnackbarMessage("l'Étiquette n'a pas pu être créée."); + setOpenSnackbar(true); + } + }; + + return ( + <Card variant="outlined"> + <form onSubmit={handleOnSubmit}> + <Stack spacing={1.5} sx={{ p: 3 }}> + <Typography variant="subtitle2">Ajouter une Étiquette</Typography> + + <TextField + name="nom" + label="Nom" + InputLabelProps={{ shrink: true }} + value={nom} + onChange={(e) => setNom(e.target.value)} + /> + + <TextField + label="Slug" + name="slug" + InputLabelProps={{ shrink: true }} + value={slug} + onChange={(e) => setSlug(e.target.value)} + /> + + <TextField + name="description" + label="Description" + multiline + rows={4} + InputLabelProps={{ shrink: true }} + value={description} + onChange={(e) => setDescription(e.target.value)} + /> + + <Button type="submit" variant="contained" color="primary"> + Enregistrer + </Button> + </Stack> + <Snackbar + open={openSnackbar} + autoHideDuration={6000} + onClose={handleCloseSnackbar} + > + <Alert onClose={handleCloseSnackbar} severity="error"> + {snackbarMessage} + </Alert> + </Snackbar> + {showSuccessMessage && ( + <Alert severity="success" onClose={() => setShowSuccessMessage(false)}> + l'Étiquette ajoutée avec succès + </Alert> + )} + </form> + </Card> + ); +} diff --git a/src/shared/sections/product/add-list-tags/tags-edit-form.tsx b/src/shared/sections/product/add-list-tags/tags-edit-form.tsx index d89400f6fe8be139cb0fe65482cfbecac92ef252..eddd554ede82362c795ee0500b2c886bdc6c0777 100644 --- a/src/shared/sections/product/add-list-tags/tags-edit-form.tsx +++ b/src/shared/sections/product/add-list-tags/tags-edit-form.tsx @@ -1,37 +1,79 @@ -"use client"; +import React, { useState, useEffect } from 'react'; +import { useGetTag, useGetTags, useUpdateTag } from '@/shared/api/tag'; +import { ITag } from '@/shared/types/tag'; +import { TextField, Button, Stack, Typography, Card, Alert, Snackbar } from "@mui/material"; -import TextField from "@mui/material/TextField"; -import Button from "@mui/material/Button"; -import Stack from "@mui/material/Stack"; -import Typography from "@mui/material/Typography"; -import Card from "@mui/material/Card"; +export default function ProductTagsEditForm({ id }: { id: string }) { + const [nom, setNom] = useState<string>(''); + const [slug, setSlug] = useState<string>(''); + const [description, setDescription] = useState<string>(''); + const [openSnackbar, setOpenSnackbar] = useState<boolean>(false); + const [snackbarMessage, setSnackbarMessage] = useState<string>(""); + const [showSuccessMessage, setShowSuccessMessage] = useState(false); + + // Use hooks directly + const { tagData, } = useGetTag(id); + const { tagsMutate } = useGetTags(); + + useEffect(() => { + if (tagData) { + setNom(tagData.nom); + setSlug(tagData.slug); + setDescription(tagData.description); + } + }, [tagData]); + + const validateForm = (): boolean => { + if (!nom || !slug || !description) { + setSnackbarMessage("Tous les champs requis doivent être remplis."); + setOpenSnackbar(true); + return false; + } + return true; + }; + + const handleCloseSnackbar = () => { + setOpenSnackbar(false); + }; + + const handleOnSubmit = async (event: React.FormEvent<HTMLFormElement>) => { + event.preventDefault(); + if (!validateForm()) { + return; + } + const tagRequest: ITag = { nom, slug, description }; + + try { + await useUpdateTag(tagRequest, id); + tagsMutate(); + setShowSuccessMessage(true); + const timeoutId =setTimeout(() => {setShowSuccessMessage(false) + clearTimeout(timeoutId) + }, 5000); + } catch (error) { + setSnackbarMessage("l'Étiquette n'a pas pu être modifiée."); + setOpenSnackbar(true); + const timeoutId =setTimeout(() => {setOpenSnackbar(false); + clearTimeout(timeoutId) + }, 5000); + } + }; -export default function ProductTagsForm() { return ( <Card variant="outlined"> - <Stack spacing={1.5} sx={{ p: 3 }}> - <Typography variant="subtitle2">Ajouter une Étiquette</Typography> - - <TextField name="nom" label="Nom" InputLabelProps={{ shrink: true }} /> - - <TextField - label="Slug" - name="slug" - InputLabelProps={{ shrink: true }} - /> - - <TextField - name="Description" - label="Description " - multiline - rows={4} - InputLabelProps={{ shrink: true }} - /> - - <Button type="submit" variant="contained" color="primary"> - Enregister - </Button> - </Stack> + <form onSubmit={handleOnSubmit}> + <Stack spacing={1.5} sx={{ p: 3 }}> + <Typography variant="subtitle2">Ajouter une Étiquette</Typography> + <TextField name="nom" label="Nom" InputLabelProps={{ shrink: true }} value={nom} onChange={(e) => setNom(e.target.value)} /> + <TextField label="Slug" name="slug" InputLabelProps={{ shrink: true }} value={slug} onChange={(e) => setSlug(e.target.value)} /> + <TextField name="description" label="Description" multiline rows={4} InputLabelProps={{ shrink: true }} value={description} onChange={(e) => setDescription(e.target.value)} /> + <Button type="submit" variant="contained" color="primary">Enregistrer</Button> + </Stack> + <Snackbar open={openSnackbar} autoHideDuration={6000} onClose={handleCloseSnackbar}> + <Alert onClose={handleCloseSnackbar} severity="error">{snackbarMessage}</Alert> + </Snackbar> + {showSuccessMessage && <Alert severity="success" onClose={() => setShowSuccessMessage(false)}>l'Étiquette ajoutée avec succès</Alert>} + </form> </Card> ); } diff --git a/src/shared/types/tag.ts b/src/shared/types/tag.ts index 15efe4bf25736bff2144fd371d9d1f08dc422461..7e7ee70e1a6d4102dc0717ff37ff87d159f41346 100644 --- a/src/shared/types/tag.ts +++ b/src/shared/types/tag.ts @@ -1,7 +1,7 @@ export type ITag = { - id : string; - name : string; + id? : string; + nom : string; description : string ; slug : string; - total : string + total ?: string } \ No newline at end of file