diff --git a/routes/announcement.js b/routes/announcement.js index 6300c1422610b5c40224e7a0a22038fc69ad3e6f..b1afa6775de36ea9d7177f962caba0daca1fc8ef 100644 --- a/routes/announcement.js +++ b/routes/announcement.js @@ -2,7 +2,9 @@ const express = require('express'); const router = express.Router(); const models = require('../schemas/models'); const routes = require('./index'); -const upload = require('../multer-config') +const upload = require('../multer-config'); +const fs = require('fs'); +const path = require('path'); const Announcement = models.Announcement; @@ -40,12 +42,12 @@ router.get('/add-announcement', isAuthenticated, isAgentAddAnnouncements, (req, res.render('add_announcement',{ user : req.user }); }); -// details de l'annonce +// Details de l'annonce router.get('/:id', async (req, res) => { try { const announcementId = req.params.id; const announcementDetails = await Announcement.findById(announcementId); - res.render('announcement_details', { announcement: announcementDetails ,user : req.user }); + res.render('announcement_details', { announcement: announcementDetails, user: req.user }); } catch (error) { res.status(500).json({ message: error.message }); } @@ -147,7 +149,7 @@ router.get('/update/:id', isAuthenticated, isAgent, async (req, res) => { const announcementId = req.params.id; const announcementDetails = await Announcement.findById(announcementId); - + console.log(' announcement object :', announcementDetails) if (!announcementDetails) { return res.status(404).json({ message: 'Announcement not found' }); } @@ -168,9 +170,14 @@ router.post('/update/:id', isAuthenticated, isAgent, upload.array('photos', ), a const user = req.user; const { title, propertyType, publicationStatus, propertyStatus, description, price, availabilityDate } = req.body; let photos = []; - if (req.files && req.files.length > 0) { - photos = req.files.map(file => file.filename); - } + + const oldPhotos = req.body.oldPhotos || []; + + req.files.forEach(file => { + photos.push(file.filename); + }); + + photos = photos.concat(oldPhotos); try { const updatedAnnouncement = await Announcement.findByIdAndUpdate( @@ -183,7 +190,7 @@ router.post('/update/:id', isAuthenticated, isAgent, upload.array('photos', ), a description, price, availabilityDate, - photos, + photos, // we combine old and new photos }, { new: true } ); @@ -220,5 +227,48 @@ router.get('/delete/:id', isAuthenticated, isAgent, async (req, res) => { }); +// Route to delete a photo +router.delete('/delete-photo/:announcementId/:filename', isAuthenticated, async (req, res) => { + try { + const filename = req.params.filename; + const announcementId = req.params.announcementId; + const announcement = await Announcement.findById(announcementId); + + if (!announcement) { + return res.status(404).json({ message: 'Annonce non trouvée' }); + } + + if (!announcement.photos.includes(filename)) { + return res.status(404).json({ message: 'Photo non trouvée' }); + } + + const filePath = path.join('public', 'uploads', filename); + + console.log('filePath:', filePath); + console.log('File exists:', fs.existsSync(filePath)); + + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + + let updatedPhotos = [...announcement.photos]; + + updatedPhotos = updatedPhotos.filter((photo) => photo !== filename); + announcement.photos = updatedPhotos; + console.log(announcement.photos,'photos !!'); + await announcement.save(); + + return res.status(200).json({ message: 'Photo supprimée avec succès' }); + } else { + return res.status(404).json({ message: 'Fichier introuvable' }); + } + } catch (error) { + console.error('Une erreur s\'est produite :', error); + return res.status(500).json({ message: 'Erreur interne du serveur' }); + } +}); + + + + -module.exports = router; +module.exports = router; \ No newline at end of file diff --git a/views/add_announcement.pug b/views/add_announcement.pug index 9f227d6d040225bddaa454c21571e7d5670540de..edfd02ad74f70cb8451c47d44d2f8ce42e677545 100644 --- a/views/add_announcement.pug +++ b/views/add_announcement.pug @@ -2,50 +2,53 @@ extends layout block content .container.mt-5 - - h1 Ajouter une Annonce - - form(action="/announcements/add-announcement", method="post", enctype="multipart/form-data") - .form-group.mb-4 - label(for="title") Titre - input#title.form-control(type="text", name="title", required) - - .form-group.mb-4 - label(for="propertyType") Type de Bien - select#propertyType.form-control(name="propertyType", required) - option(value="À la vente") À la vente - option(value="À la location") À la location - - .form-group.mb-4 - label(for="price") Prix - input#price.form-control(type="text", name="price", required, pattern="[0-9]+(\.[0-9]{1,2})?", title="Veuillez entrer un nombre") - - .form-group.mb-4 - label(for="availabilityDate") Date de disponibilité - input#availabilityDate.form-control(type="date", name="availabilityDate", required) - - - .form-group.mb-4 - label(for="description") Description - textarea#description.form-control(name="description", required) - - .form-group.mb-4 - label(for="publicationStatus") Statut Publication - select#publicationStatus.form-control(name="publicationStatus", required) - option(value="Publiée") Publiée - option(value="Non publiée") Non publiée - - .form-groupc - label(for="propertyStatus") Statut Bien - select#propertyStatus.form-control(name="propertyStatus", required) - option(value="Disponible") Disponible - option(value="Loué") Loué - option(value="Vendu") Vendu - - .form-group.mb-4 - label(for="photos") Photos - input#photos.form-control(type="file", name="photos", accept="image/*", multiple) - - button.btn.btn-primary(type="submit") Ajouter l'annonce - a.btn.btn-secondary(href="/announcements") Annuler - + h1 Ajouter une Annonce + + form(action="/announcements/add-announcement", method="post", enctype="multipart/form-data") + .form-group.mb-4 + label(for="title") Titre + input#title.form-control(type="text", name="title", required) + + .form-group.mb-4 + label(for="propertyType") Type de Bien + select#propertyType.form-control(name="propertyType", required) + option(value="À la vente") À la vente + option(value="À la location") À la location + + .form-group.mb-4 + label(for="price") Prix + input#price.form-control(type="text", name="price", required, pattern="[0-9]+(\.[0-9]{1,2})?", title="Veuillez entrer un nombre") + + .form-group.mb-4 + label(for="availabilityDate") Date de disponibilité + input#availabilityDate.form-control(type="date", name="availabilityDate", required) + + .form-group.mb-4 + label(for="description") Description + textarea#description.form-control(name="description", required) + + .form-group.mb-4 + label(for="publicationStatus") Statut Publication + select#publicationStatus.form-control(name="publicationStatus", required) + option(value="Publiée") Publiée + option(value="Non publiée") Non publiée + + .form-group.mb-4 + label(for="propertyStatus") Statut Bien + select#propertyStatus.form-control(name="propertyStatus", required) + option(value="Disponible") Disponible + option(value="Loué") Loué + option(value="Vendu") Vendu + + .form-group.mb-4 + label(for="photos") Photos (Glisser-déposer) + .control(style="border: 3px #0a0a0a dotted; padding: 20px") + #photo-dropzone.dropzone Glissez-déposez des fichiers ici ou cliquez pour sélectionner. + input#file-input.form-control(type="file", name="photos", multiple) + + #selected-photos + + button.btn.btn-primary(type="submit") Ajouter l'annonce + a.btn.btn-secondary(href="/announcements") Annuler + + include drag_drop_script.pug diff --git a/views/announcement_details.pug b/views/announcement_details.pug index 7a7c87f2921f09a0313727a82d6d8921d7bbd192..d96b1b49fb81147e5695a294d5a274e7b7540b0d 100644 --- a/views/announcement_details.pug +++ b/views/announcement_details.pug @@ -21,51 +21,7 @@ block content each photo in announcement.photos img.card-img-top(src=`/uploads/${photo}`, alt=announcement.title) - .mt-3 - - if user && user.isAgent - a.btn.btn-primary(href=`/announcements/update/${announcement._id}`) Modifier l'annonce - a.btn.btn-danger(href=`/announcements/delete/${announcement._id}`) Supprimer l'annonce - a.btn.btn-secondary(href="/announcements") Annuler - - .mt-3 - - if user && !user.isAgent - h2.card-title.mb-4 Mes Questions et Réponses - each question in announcement.questions - if question.user === user.username - .card.mb-3 - .card-body - h5.card-title Question - p.card-text= question.question - if question.answers.length > 0 - h5.card-title Réponses - each answer in question.answers - p.card-text Réponse : #{answer.answer} - - if announcement.propertyStatus == 'Disponible' - form(action=`/announcements/${announcement._id}/ask`, method="post") - .form-group.mt-3 - label(for="question") Poser une question - input#question.form-control(type="text", name="question", required) - .mt-3 - button.btn.btn-primary(type="submit") Poser la question - - if user && user.isAgent && announcement.questions.length > 0 - h2.card-title.mb-4 Questions et Réponses - each question in announcement.questions - .card.mb-3 - .card-body - h5.card-title Question de #{question.user} - p.card-text Question : #{question.question} - each answer in question.answers - h5.card-title Réponse - p.card-text Réponse : #{answer.answer} - - form(action=`/announcements/${announcement._id}/question/${question._id}/answer`, method="post") - .form-group.mt-3 - label(for="answer") Répondre - input#answer.form-control(type="text", name="answer", required) - .mt-3 - button.btn.btn-primary(type="submit") Répondre - + // Add spacing between buttons + div(style="margin-top: 10px;") + a.btn.btn-primary.m-3(href=`/announcements/update/${announcement._id}`) Modifier l'annonce + a.btn.btn-danger.m-3(href=`/announcements/delete/${announcement._id}`) Supprimer l'annonce diff --git a/views/drag_drop_script.pug b/views/drag_drop_script.pug new file mode 100644 index 0000000000000000000000000000000000000000..5721eb7df8b790ad8aa1c8b7dc3d8bddd2b1cd6c --- /dev/null +++ b/views/drag_drop_script.pug @@ -0,0 +1,103 @@ +script. + const dropzone = document.getElementById("photo-dropzone"); + const fileInput = document.getElementById("file-input"); + const selectedPhotosDiv = document.getElementById("selected-photos"); + const deleteButtons = document.querySelectorAll('.delete-photo'); // Select all delete buttons + + // Add dragover event listener + dropzone.addEventListener("dragover", (e) => { + e.preventDefault(); + dropzone.classList.add("dragover"); + }); + + // Add dragleave event listener + dropzone.addEventListener("dragleave", () => { + dropzone.classList.remove("dragover"); + }); + + const newInputDataTransfer = new DataTransfer(); + + // Add drop event listener + dropzone.addEventListener("drop", async (e) => { + e.preventDefault(); + e.stopPropagation(); + dropzone.classList.remove("dragover"); + + const droppedFiles = e.dataTransfer.files; + console.log('dropped: ', droppedFiles); + + for (let i = 0; i < droppedFiles.length; i++) { + newInputDataTransfer.items.add(droppedFiles[i]); + addImageWithDeleteButton(droppedFiles[i], selectedPhotosDiv); + } + + + fileInput.files = newInputDataTransfer.files; + + deleteButtons.forEach(button => { + button.addEventListener("click", (e) => { + handleDeleteButtonClick(e, selectedPhotosDiv); + }); + }); + }); + + dropzone.addEventListener("click", () => { + fileInput.click(); + }); + + // Add change event listener to the file input + fileInput.addEventListener("change", () => { + const files = Array.from(fileInput.files); + console.log("Selected Files:", files); + + files.forEach((file) => { + addImageWithDeleteButton(file, selectedPhotosDiv); + }); + }); + + function addImageWithDeleteButton(file, selectedPhotosDiv) { + const img = document.createElement("img"); + const deleteButton = document.createElement("button"); + img.src = URL.createObjectURL(file); + img.classList.add("selected-image"); + img.style.width = "250px"; + img.style.height = "250px"; + img.style.marginRight = "10px"; + img.style.marginBottom = "10px"; + img.style.border = "2px solid #ccc"; + deleteButton.innerHTML = ''; + + deleteButton.addEventListener("click", (e) => { + img.remove(); + deleteButton.remove(); + handleDeleteButtonClick(e, selectedPhotosDiv); + }); + + selectedPhotosDiv.appendChild(img); + selectedPhotosDiv.appendChild(deleteButton); + } + + // Function to handle delete photo click + async function handleDeleteButtonClick(event, selectedPhotosDiv) { + event.preventDefault(); + + const photoFilename = event.target.dataset.photoFilename; + const announcementId = event.target.dataset.announcementId; + + try { + const response = await fetch(`/announcements/delete-photo/${announcementId}/${photoFilename}`, { + method: 'DELETE', + }); + + if (response.ok) { + const deletedPhoto = selectedPhotosDiv.querySelector(`[src*="${photoFilename}"]`); + if (deletedPhoto) { + deletedPhoto.remove(); + } + } else { + console.error('Failed to delete photo'); + } + } catch (error) { + console.error('An error occurred:', error); + } + } \ No newline at end of file diff --git a/views/update_announcement.pug b/views/update_announcement.pug index e5c34bb356b6032a7b36b14ab06294c46a6aa33f..470ab9be49dbce1a7da0ab9cd267d75e0bcc9ee8 100644 --- a/views/update_announcement.pug +++ b/views/update_announcement.pug @@ -2,7 +2,6 @@ extends layout block content .container.mt-5 - h1 Modifier une Annonce form(action=`/announcements/update/${announcement._id}`, method="post", enctype="multipart/form-data") @@ -22,7 +21,7 @@ block content .form-group.mb-4 label(for="availabilityDate") Date de disponibilité - input#availabilityDate.form-control(type="date", name="availabilityDate", required, value=announcement.availabilityDate) + input#availabilityDate.form-control(type="date", name="availabilityDate", required, value=announcement.availabilityDate.toISOString().split('T')[0]) .form-group.mb-4 label(for="description") Description @@ -41,9 +40,54 @@ block content option(value="Loué", selected=announcement.propertyStatus === "Loué") Loué option(value="Vendu", selected=announcement.propertyStatus === "Vendu") Vendu + each oldPhoto in announcement.photos + input(type="hidden", name="oldPhotos[]", value=oldPhoto) + .form-group.mb-4 - label(for="photos") Photos - input#photos.form-control(type="file", name="photos", accept="image/*", multiple) + label(for="photos") Photos (Glisser-déposer) + .control(style="border: 3px #0a0a0a dotted; padding: 20px") + #photo-dropzone.dropzone + | Glissez-déposez des fichiers ici ou cliquez pour sélectionner. + input#file-input.form-control(type="file", name="photos", multiple) + + #selected-photos + each photo in announcement.photos + .selected-photo.mb-3 + img(src=`/uploads/${photo}`, alt="Announcement Photo", class="img-thumbnail") + button.delete-photo.btn.btn-danger(type="button", data-photo-filename=`${photo}`, data-announcement-id=`${announcement._id}`) Supprimer button.btn.btn-primary(type="submit") Modifier l'annonce - a.btn.btn-secondary(href=`/announcements/${announcement._id}`) Annuler + a.btn.btn-secondary(href="/announcements") Annuler + + include drag_drop_script.pug + + script. + document.addEventListener('DOMContentLoaded', function () { + const deleteButtons = document.querySelectorAll('.delete-photo'); + deleteButtons.forEach((button) => { + button.addEventListener('click', async (event) => { + event.preventDefault(); + const photoFilename = button.dataset.photoFilename; + const announcementId = button.dataset.announcementId; + + try { + const response = await fetch(`/announcements/delete-photo/${announcementId}/${photoFilename}`, { + method: 'DELETE', + }); + + if (response.ok) { + button.parentNode.remove(); + + const oldPhotosInput = document.querySelector('input[name="oldPhotos[]"][value="' + photoFilename + '"]'); + if (oldPhotosInput) { + oldPhotosInput.remove(); + } + } else { + console.error('Failed'); + } + } catch (error) { + console.error('error : ', error); + } + }); + }); + });