Gestion de page ACF
This commit is contained in:
parent
20c2e3500d
commit
d1cee7b3ae
112
frontend/src/components/GestionPagesACF.jsx
Normal file
112
frontend/src/components/GestionPagesACF.jsx
Normal file
@ -0,0 +1,112 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
Box, Typography, Button, Table, TableBody, TableCell,
|
||||
TableContainer, TableHead, TableRow, Paper, TextField
|
||||
} from "@mui/material";
|
||||
import { Edit, ArrowBack } from "@mui/icons-material";
|
||||
import api from "../api";
|
||||
import { getToken } from "../auth";
|
||||
|
||||
const GestionPagesACF = () => {
|
||||
const [pages, setPages] = useState([]);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const navigate = useNavigate();
|
||||
|
||||
// ✅ Vérifie l'authentification
|
||||
useEffect(() => {
|
||||
if (!getToken()) {
|
||||
navigate("/admin/login");
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
// ✅ Récupère les pages contenant des champs ACF
|
||||
useEffect(() => {
|
||||
const fetchPages = async () => {
|
||||
try {
|
||||
const response = await api.get("wp/v2/pages?_fields=id,title,acf");
|
||||
const pagesData = response.data;
|
||||
|
||||
// Filtrer les pages qui ont au moins un champ ACF
|
||||
const pagesWithACF = pagesData.filter(page => page.acf && Object.keys(page.acf).length > 0);
|
||||
|
||||
setPages(pagesWithACF);
|
||||
} catch (error) {
|
||||
console.error("❌ Erreur chargement des pages :", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPages();
|
||||
}, []);
|
||||
|
||||
// ✅ Filtrage des pages par recherche
|
||||
const filteredPages = pages.filter(page =>
|
||||
page.title.rendered.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<Box sx={{ padding: "40px 20px" }}>
|
||||
{/* ✅ En-tête */}
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 4, mt:5 }}>
|
||||
<Button startIcon={<ArrowBack />} variant="outlined" color="secondary" onClick={() => navigate("/admin/dashboard")}>
|
||||
Retour au Dashboard
|
||||
</Button>
|
||||
<Typography variant="h4" sx={{ fontWeight: "bold", textAlign: "center", flexGrow: 1 }}>
|
||||
Gestion des Pages ACF
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* ✅ Recherche */}
|
||||
<TextField
|
||||
label="Rechercher une page..."
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
sx={{ mb: 3 }}
|
||||
/>
|
||||
|
||||
{/* ✅ Tableau des pages */}
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead sx={{ backgroundColor: "#0e467f" }}>
|
||||
<TableRow>
|
||||
<TableCell sx={{ color: "white", fontWeight: "bold" }}>Titre</TableCell>
|
||||
<TableCell sx={{ color: "white", fontWeight: "bold", textAlign: "center" }}>Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{filteredPages.length > 0 ? (
|
||||
filteredPages.map((page) => (
|
||||
<TableRow key={page.id}>
|
||||
{/* ✅ Titre de la page */}
|
||||
<TableCell sx={{ fontWeight: "bold" }}>{page.title.rendered}</TableCell>
|
||||
|
||||
{/* ✅ Bouton Modifier */}
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Button
|
||||
startIcon={<Edit />}
|
||||
variant="outlined"
|
||||
color="warning"
|
||||
onClick={() => navigate(`/admin/edit-page/${page.id}`)}
|
||||
>
|
||||
Modifier
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={2} sx={{ textAlign: "center", py: 2 }}>
|
||||
Aucune page avec champs ACF trouvée.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default GestionPagesACF;
|
||||
@ -34,7 +34,8 @@ function Dashboard() {
|
||||
<Box
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
backgroundImage: "url('https://source.unsplash.com/1600x900/?technology,office')",
|
||||
backgroundImage:
|
||||
"url('https://source.unsplash.com/1600x900/?technology,office')",
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
display: "flex",
|
||||
@ -141,10 +142,41 @@ function Dashboard() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{/* ✅ Gérer les Pages ACF - Nouvelle carte */}
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Card
|
||||
onClick={() => navigate("/admin/pages-acf")}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
textAlign: "center",
|
||||
padding: 2,
|
||||
transition: "0.3s",
|
||||
"&:hover": {
|
||||
backgroundColor: "#0e467f",
|
||||
color: "#ffffff",
|
||||
transform: "scale(1.05)",
|
||||
boxShadow: 8,
|
||||
},
|
||||
"&:hover svg": {
|
||||
fill: "#ffffff",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<IconButton sx={{ fontSize: 40, color: "#0e467f" }}>
|
||||
<ArticleIcon fontSize="inherit" />
|
||||
</IconButton>
|
||||
<Typography variant="h6" sx={{ fontWeight: "bold" }}>
|
||||
Gérer les Pages ACF
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Dashboard;
|
||||
export default Dashboard;
|
||||
|
||||
216
frontend/src/components/Pages/EditPageACF.jsx
Normal file
216
frontend/src/components/Pages/EditPageACF.jsx
Normal file
@ -0,0 +1,216 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { getToken } from "../../auth";
|
||||
import { getPageById, updatePageACF } from "../../wordpress";
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
Typography,
|
||||
TextField,
|
||||
Button,
|
||||
Paper,
|
||||
CircularProgress,
|
||||
} from "@mui/material";
|
||||
import { Add, Delete, ArrowBack, Save } from "@mui/icons-material";
|
||||
|
||||
function EditPageACF() {
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [acfFields, setAcfFields] = useState({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [successMessage, setSuccessMessage] = useState("");
|
||||
|
||||
// ✅ Vérifier l'authentification
|
||||
useEffect(() => {
|
||||
if (!getToken()) {
|
||||
navigate("/admin/login");
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
// ✅ Récupérer les champs ACF des pages
|
||||
useEffect(() => {
|
||||
const fetchPage = async () => {
|
||||
try {
|
||||
const pageData = await getPageById(id);
|
||||
console.log("📢 Données ACF reçues :", JSON.stringify(pageData.acf, null, 2));
|
||||
|
||||
if (!pageData.acf || Object.keys(pageData.acf).length === 0) {
|
||||
setError("❌ Aucun champ ACF disponible pour cette page.");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setAcfFields({
|
||||
...pageData.acf,
|
||||
faq_list: Array.isArray(pageData.acf?.faq_list) ? pageData.acf.faq_list : [],
|
||||
});
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error("❌ Erreur chargement des champs ACF :", error);
|
||||
setError("⚠ Impossible de charger les champs ACF.");
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPage();
|
||||
}, [id]);
|
||||
|
||||
// ✅ Mise à jour des champs ACF
|
||||
const handleUpdateACF = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const updatedACF = {
|
||||
...acfFields,
|
||||
faq_list: Array.isArray(acfFields?.faq_list) ? acfFields.faq_list : [],
|
||||
};
|
||||
|
||||
try {
|
||||
await updatePageACF(id, updatedACF);
|
||||
setSuccessMessage("✅ Modifications enregistrées !");
|
||||
setTimeout(() => navigate("/admin/pages-acf"), 2000);
|
||||
} catch (error) {
|
||||
console.error("❌ Erreur mise à jour ACF :", error);
|
||||
setError("⚠ Impossible de mettre à jour les champs ACF.");
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ Gérer la modification des champs ACF (y compris objets et tableaux)
|
||||
const handleFieldChange = (field, value) => {
|
||||
setAcfFields((prevFields) => ({
|
||||
...prevFields,
|
||||
[field]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
// ✅ Ajouter, modifier et supprimer des entrées de FAQ
|
||||
const handleFAQChange = (index, field, value) => {
|
||||
const updatedFAQ = [...acfFields.faq_list];
|
||||
updatedFAQ[index][field] = value;
|
||||
setAcfFields({ ...acfFields, faq_list: updatedFAQ });
|
||||
};
|
||||
|
||||
const addFAQ = () => {
|
||||
setAcfFields({
|
||||
...acfFields,
|
||||
faq_list: [...acfFields.faq_list, { question: "", answer: "" }],
|
||||
});
|
||||
};
|
||||
|
||||
const deleteFAQ = (index) => {
|
||||
setAcfFields({
|
||||
...acfFields,
|
||||
faq_list: acfFields.faq_list.filter((_, i) => i !== index),
|
||||
});
|
||||
};
|
||||
|
||||
if (loading) return <Typography>Chargement...</Typography>;
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundImage: "url('https://source.unsplash.com/1600x900/?technology')",
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
}}
|
||||
>
|
||||
<Container maxWidth="sm">
|
||||
<Paper elevation={6} sx={{ padding: 4, borderRadius: 3 }}>
|
||||
<Button
|
||||
startIcon={<ArrowBack />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={() => navigate("/admin/pages-acf")}
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
Retour à la gestion des pages
|
||||
</Button>
|
||||
|
||||
<Typography variant="h4" sx={{ fontWeight: "bold", textAlign: "center", mb: 3 }}>
|
||||
Modifier les Champs ACF
|
||||
</Typography>
|
||||
|
||||
{error && <Typography textAlign="center" color="error" sx={{ mb: 2 }}>{error}</Typography>}
|
||||
{successMessage && <Typography sx={{ color: "green", textAlign: "center", mb: 2 }}>{successMessage}</Typography>}
|
||||
|
||||
<form onSubmit={handleUpdateACF}>
|
||||
{/* ✅ Affichage dynamique des champs ACF */}
|
||||
{Object.keys(acfFields).map((field) => {
|
||||
const value = acfFields[field];
|
||||
|
||||
if (typeof value === "object" && !Array.isArray(value)) {
|
||||
return (
|
||||
<Box key={field} sx={{ mb: 3 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: "bold", mb: 1 }}>
|
||||
{field.replace(/_/g, " ")}
|
||||
</Typography>
|
||||
{Object.keys(value).map((subField) => (
|
||||
<TextField
|
||||
key={subField}
|
||||
label={subField.replace(/_/g, " ")}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={value[subField] || ""}
|
||||
onChange={(e) =>
|
||||
handleFieldChange(field, { ...value, [subField]: e.target.value })
|
||||
}
|
||||
sx={{ mb: 1, backgroundColor: "#fff", borderRadius: "5px" }}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
} else if (Array.isArray(value)) {
|
||||
return (
|
||||
<Box key={field} sx={{ mb: 3 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: "bold", mb: 1 }}>
|
||||
{field.replace(/_/g, " ")}
|
||||
</Typography>
|
||||
{value.map((item, index) => (
|
||||
<Box key={index} sx={{ border: "1px solid #ccc", padding: 2, borderRadius: 2, mb: 2 }}>
|
||||
{Object.keys(item).map((subField) => (
|
||||
<TextField
|
||||
key={subField}
|
||||
label={subField.replace(/_/g, " ")}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={item[subField] || ""}
|
||||
onChange={(e) => {
|
||||
const updatedArray = [...value];
|
||||
updatedArray[index][subField] = e.target.value;
|
||||
setAcfFields({ ...acfFields, [field]: updatedArray });
|
||||
}}
|
||||
sx={{ mb: 1, backgroundColor: "#fff", borderRadius: "5px" }}
|
||||
/>
|
||||
))}
|
||||
<Button startIcon={<Delete />} variant="outlined" color="error" onClick={() => deleteFAQ(index)}>
|
||||
Supprimer cette entrée
|
||||
</Button>
|
||||
</Box>
|
||||
))}
|
||||
<Button startIcon={<Add />} variant="contained" color="primary" onClick={addFAQ} sx={{ mb: 2 }}>
|
||||
Ajouter une entrée
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<TextField key={field} label={field.replace(/_/g, " ")} fullWidth variant="outlined" value={value || ""} onChange={(e) => handleFieldChange(field, e.target.value)} sx={{ mb: 2, backgroundColor: "#fff", borderRadius: "5px" }} />
|
||||
);
|
||||
}
|
||||
})}
|
||||
|
||||
<Button type="submit" variant="contained" color="primary" fullWidth startIcon={<Save />} sx={{ mt: 3 }}>
|
||||
Enregistrer les modifications
|
||||
</Button>
|
||||
</form>
|
||||
</Paper>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditPageACF;
|
||||
@ -9,17 +9,18 @@ import NotFound from "./components/Pages/NotFound.jsx";
|
||||
import GestionArticles from "./components/Pages/GestionArticles";
|
||||
import EditPost from "./components/EditPost";
|
||||
import GestionPageAccueil from "./components/Pages/GestionPageAccueil";
|
||||
|
||||
import GestionPagesACF from "./components/GestionPagesACF";
|
||||
import EditPageACF from "./components/Pages/EditPageACF";
|
||||
|
||||
// Lazy loading des pages
|
||||
const Home = lazy(() => import('./components/Pages/Home.jsx'));
|
||||
const About = lazy(() => import('./components/Pages/About'));
|
||||
const Contact = lazy(() => import('./components/Pages/Contact'));
|
||||
const Post = lazy(() => import('./components/Pages/Post'));
|
||||
const PostDetails = lazy(() => import('./components/PostDetails')) ;
|
||||
const Search = lazy(() => import('./components/Pages/Search')); // Nouveau composant pour la recherche
|
||||
const BureauEtude = lazy(() => import('./components/Pages/BureauEtude'));
|
||||
const CreatePost = lazy(() => import('./components/Pages/CreatePost'));
|
||||
const Home = lazy(() => import("./components/Pages/Home.jsx"));
|
||||
const About = lazy(() => import("./components/Pages/About"));
|
||||
const Contact = lazy(() => import("./components/Pages/Contact"));
|
||||
const Post = lazy(() => import("./components/Pages/Post"));
|
||||
const PostDetails = lazy(() => import("./components/PostDetails"));
|
||||
const Search = lazy(() => import("./components/Pages/Search")); // Nouveau composant pour la recherche
|
||||
const BureauEtude = lazy(() => import("./components/Pages/BureauEtude"));
|
||||
const CreatePost = lazy(() => import("./components/Pages/CreatePost"));
|
||||
const PostList = lazy(() => import("./components/Pages/PostList"));
|
||||
const Login = lazy(() => import("./components/Pages/Login"));
|
||||
const Dashboard = lazy(() => import("./components/Pages/Dashboard"));
|
||||
@ -32,7 +33,7 @@ import ServiceQuatre from "./components/Pages/ServiceQuatre.jsx";
|
||||
|
||||
// Import des composants globaux
|
||||
import Header from "./components/Header";
|
||||
import Footer from './components/Footer';
|
||||
import Footer from "./components/Footer";
|
||||
|
||||
function YourApp() {
|
||||
return (
|
||||
@ -46,11 +47,17 @@ function YourApp() {
|
||||
<Route path="/bureauEtude" element={<BureauEtude />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="/contact" element={<Contact />} />
|
||||
<Route path="/search" element={<Search />} /> {/* Route pour les recherches */}
|
||||
|
||||
<Route path="/search" element={<Search />} />{" "}
|
||||
{/* Route pour les recherches */}
|
||||
{/* Routes pour les services */}
|
||||
<Route path="/services/prestation-maitrise-oeuvre" element={<ServiceUn />} />
|
||||
<Route path="/services/structure-beton-charpente-metallique-bois" element={<ServiceDeux />} />
|
||||
<Route
|
||||
path="/services/prestation-maitrise-oeuvre"
|
||||
element={<ServiceUn />}
|
||||
/>
|
||||
<Route
|
||||
path="/services/structure-beton-charpente-metallique-bois"
|
||||
element={<ServiceDeux />}
|
||||
/>
|
||||
<Route path="/services/electricite" element={<ServiceTrois />} />
|
||||
<Route path="/services/ssi" element={<ServiceQuatre />} />
|
||||
<Route path="/admin/create-post" element={<CreatePost />} />
|
||||
@ -61,6 +68,8 @@ function YourApp() {
|
||||
<Route path="/admin/gestion-articles" element={<GestionArticles />} />
|
||||
<Route path="/admin/gestion-page-accueil" element={<GestionPageAccueil />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
<Route path="/admin/pages-acf" element={<GestionPagesACF />} />
|
||||
<Route path="/admin/edit-page/:id" element={<EditPageACF />} />
|
||||
</Routes>
|
||||
<Footer />
|
||||
</Router>
|
||||
|
||||
@ -211,4 +211,54 @@ export async function updateHomePageACF(pageId, newData) {
|
||||
console.error("❌ Erreur mise à jour ACF :", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔹 Récupère une page WordPress par son ID avec ses champs ACF
|
||||
* @param {number} pageId - L'ID de la page à récupérer
|
||||
* @returns {Promise<Object>} - Données de la page avec ACF
|
||||
*/
|
||||
export async function getPageById(pageId) {
|
||||
try {
|
||||
const response = await axios.get(`${API_URL}/pages/${pageId}?_fields=id,title,acf`);
|
||||
console.log("📢 Données de la page récupérées :", response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("❌ Erreur récupération page :", error.response?.data || error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔹 Met à jour les champs ACF d'une page WordPress
|
||||
* @param {number} pageId - L'ID de la page à modifier
|
||||
* @param {object} acfData - Les nouvelles données ACF à mettre à jour
|
||||
*/
|
||||
export async function updatePageACF(pageId, acfData) {
|
||||
const token = getToken();
|
||||
if (!token) {
|
||||
console.error("❌ Aucun token trouvé !");
|
||||
throw new Error("Utilisateur non authentifié.");
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`📢 Mise à jour des ACF pour la page ${pageId}...`, acfData);
|
||||
|
||||
const response = await axios.post(
|
||||
`${API_URL}/pages/${pageId}`,
|
||||
{ acf: acfData }, // Envoi uniquement les champs ACF
|
||||
{
|
||||
headers: {
|
||||
"Authorization": `Basic ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
console.log("✅ Mise à jour ACF réussie :", response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("❌ Erreur mise à jour ACF :", error.response?.data || error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user