From ce8da4dafedfa642c652de665ce67711e7aeffd1 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Thu, 9 Oct 2025 08:06:17 +0000 Subject: [PATCH] Add ability to edit scheduled social media posts Implement functionality to edit scheduled posts, including date, time, and target page. Introduces a new dialog component for editing and updates the API to support PATCH requests for scheduled posts. Replit-Commit-Author: Agent Replit-Commit-Session-Id: ae4037a0-2a6f-4530-9bac-79b543286bda Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/397bca8c-984f-43ff-841a-10897aeb8140/ae4037a0-2a6f-4530-9bac-79b543286bda/ER8Naji --- .replit | 4 + client/src/components/calendar-view.tsx | 56 +++++-- .../components/edit-scheduled-post-dialog.tsx | 139 ++++++++++++++++++ server/routes.ts | 39 ++++- 4 files changed, 226 insertions(+), 12 deletions(-) create mode 100644 client/src/components/edit-scheduled-post-dialog.tsx diff --git a/.replit b/.replit index d51ca2b..bd88b27 100644 --- a/.replit +++ b/.replit @@ -34,6 +34,10 @@ externalPort = 4200 localPort = 40537 externalPort = 3000 +[[ports]] +localPort = 40905 +externalPort = 6800 + [[ports]] localPort = 42161 externalPort = 6000 diff --git a/client/src/components/calendar-view.tsx b/client/src/components/calendar-view.tsx index 6a40ccc..39b6b97 100644 --- a/client/src/components/calendar-view.tsx +++ b/client/src/components/calendar-view.tsx @@ -1,13 +1,17 @@ import { useState } from "react"; import { Button } from "@/components/ui/button"; -import { ChevronLeft, ChevronRight, Calendar as CalendarIcon, Trash2 } from "lucide-react"; +import { ChevronLeft, ChevronRight, Calendar as CalendarIcon, Trash2, Edit } from "lucide-react"; import { useQuery, useMutation } from "@tanstack/react-query"; import { useToast } from "@/hooks/use-toast"; import { apiRequest, queryClient } from "@/lib/queryClient"; import type { ScheduledPost } from "@shared/schema"; +import { SiFacebook, SiInstagram } from "react-icons/si"; +import EditScheduledPostDialog from "./edit-scheduled-post-dialog"; export default function CalendarView() { const [currentDate, setCurrentDate] = useState(new Date()); + const [editDialogOpen, setEditDialogOpen] = useState(false); + const [selectedPost, setSelectedPost] = useState(null); const { toast } = useToast(); const { data: scheduledPosts = [] } = useQuery({ @@ -40,6 +44,12 @@ export default function CalendarView() { } }; + const handleEditPost = (post: any, e: React.MouseEvent) => { + e.stopPropagation(); + setSelectedPost(post); + setEditDialogOpen(true); + }; + const getPostsForDate = (date: Date) => { return (scheduledPosts || []).filter(post => { if (!post.scheduledAt) return false; @@ -181,10 +191,13 @@ export default function CalendarView() { return (
- {postsForDay.slice(0, 2).map((post, idx) => { + {postsForDay.slice(0, 2).map((post: any, idx) => { const time = post.scheduledAt ? new Date(post.scheduledAt).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }) : ''; const isPending = !post.publishedAt; const isPublished = !!post.publishedAt; + const pageName = post.page?.pageName || 'Page inconnue'; + const platform = post.page?.platform || 'facebook'; + const PlatformIcon = platform === 'instagram' ? SiInstagram : SiFacebook; return (
{time}
-
{post.postType || 'Post'}
+
+ + {pageName} +
{isPending && ( - +
+ + +
)}
@@ -238,6 +264,14 @@ export default function CalendarView() { + + {selectedPost && ( + + )} ); } diff --git a/client/src/components/edit-scheduled-post-dialog.tsx b/client/src/components/edit-scheduled-post-dialog.tsx new file mode 100644 index 0000000..e93338e --- /dev/null +++ b/client/src/components/edit-scheduled-post-dialog.tsx @@ -0,0 +1,139 @@ +import { useState, useEffect } from "react"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { useQuery, useMutation } from "@tanstack/react-query"; +import { useToast } from "@/hooks/use-toast"; +import { apiRequest, queryClient } from "@/lib/queryClient"; +import type { SocialPage } from "@shared/schema"; +import { format } from "date-fns"; + +interface EditScheduledPostDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + scheduledPost: any; +} + +export default function EditScheduledPostDialog({ open, onOpenChange, scheduledPost }: EditScheduledPostDialogProps) { + const { toast } = useToast(); + const [date, setDate] = useState(""); + const [time, setTime] = useState(""); + const [pageId, setPageId] = useState(""); + + const { data: pages = [] } = useQuery({ + queryKey: ["/api/pages"], + }); + + useEffect(() => { + if (scheduledPost && open) { + const scheduledAt = new Date(scheduledPost.scheduledAt); + setDate(format(scheduledAt, "yyyy-MM-dd")); + setTime(format(scheduledAt, "HH:mm")); + setPageId(scheduledPost.pageId); + } + }, [scheduledPost, open]); + + const updateMutation = useMutation({ + mutationFn: (data: { scheduledAt: string; pageId: string }) => + apiRequest('PATCH', `/api/scheduled-posts/${scheduledPost.id}`, data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['/api/scheduled-posts'] }); + toast({ + title: "Programmation modifiée", + description: "La date et l'heure ont été mises à jour avec succès", + }); + onOpenChange(false); + }, + onError: () => { + toast({ + title: "Erreur", + description: "Impossible de modifier la programmation", + variant: "destructive", + }); + }, + }); + + const handleSave = () => { + if (!date || !time || !pageId) { + toast({ + title: "Erreur", + description: "Veuillez remplir tous les champs", + variant: "destructive", + }); + return; + } + + const scheduledAt = new Date(`${date}T${time}`); + updateMutation.mutate({ scheduledAt: scheduledAt.toISOString(), pageId }); + }; + + return ( + + + + Modifier la programmation + + +
+
+ + setDate(e.target.value)} + className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" + data-testid="input-scheduled-date" + /> +
+ +
+ + setTime(e.target.value)} + className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" + data-testid="input-scheduled-time" + /> +
+ +
+ + +
+
+ + + + + +
+
+ ); +} diff --git a/server/routes.ts b/server/routes.ts index 28c51aa..6282a33 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -9,7 +9,7 @@ import { z } from "zod"; import { openRouterService } from "./services/openrouter"; import { cloudinaryService } from "./services/cloudinary"; import { insertPostSchema, insertScheduledPostSchema, insertSocialPageSchema, insertAiGenerationSchema, insertCloudinaryConfigSchema, updateCloudinaryConfigSchema, insertOpenrouterConfigSchema, updateOpenrouterConfigSchema, insertUserSchema, postMedia, type SocialPage } from "@shared/schema"; -import type { User, InsertUser } from "@shared/schema"; +import type { User, InsertUser, ScheduledPost } from "@shared/schema"; const upload = multer({ storage: multer.memoryStorage() }); @@ -696,6 +696,43 @@ export async function registerRoutes(app: Express): Promise { } }); + app.patch("/api/scheduled-posts/:id", requireAuth, async (req, res) => { + try { + const user = req.user as User; + const userId = user.id; + const { id } = req.params; + + // Verify the scheduled post belongs to the user before updating + const scheduledPost = await storage.getScheduledPost(id); + if (!scheduledPost) { + return res.status(404).json({ error: "Scheduled post not found" }); + } + + const post = await storage.getPost(scheduledPost.postId); + if (!post || post.userId !== userId) { + return res.status(403).json({ error: "Unauthorized" }); + } + + // Only allow updating scheduledAt and pageId + const { scheduledAt, pageId } = req.body; + const updateData: Partial = {}; + + if (scheduledAt) { + updateData.scheduledAt = new Date(scheduledAt); + } + + if (pageId) { + updateData.pageId = pageId; + } + + const updated = await storage.updateScheduledPost(id, updateData); + res.json(updated); + } catch (error) { + console.error("Error updating scheduled post:", error); + res.status(500).json({ error: "Failed to update scheduled post" }); + } + }); + app.post("/api/scheduled-posts", requireAuth, async (req, res) => { try { const scheduledPostData = insertScheduledPostSchema.parse(req.body);