From 0578d15288fe295365ba4ef16fa9ef7261f2e8aa Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Fri, 10 Oct 2025 12:46:40 +0000 Subject: [PATCH] Add icons to distinguish between feed, story, and combined posts Integrates new icons for 'feed', 'story', and 'both' post types across CalendarListView, CalendarView, RecentPublications, and History components. Adds a post preview modal and refactors history view to display scheduled posts. Enhances CalendarView with auto-refresh. 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/d2GBAIk --- client/src/components/calendar-list-view.tsx | 19 +- client/src/components/calendar-view.tsx | 31 ++- client/src/components/recent-publications.tsx | 25 +- client/src/pages/history.tsx | 215 +++++++++++++----- 4 files changed, 229 insertions(+), 61 deletions(-) diff --git a/client/src/components/calendar-list-view.tsx b/client/src/components/calendar-list-view.tsx index 82105eb..0ca4a3e 100644 --- a/client/src/components/calendar-list-view.tsx +++ b/client/src/components/calendar-list-view.tsx @@ -1,6 +1,6 @@ import { format, isSameDay, parseISO } from "date-fns"; import { fr } from "date-fns/locale"; -import { Edit, Trash2, ChevronDown, ChevronUp, Eye } from "lucide-react"; +import { Edit, Trash2, ChevronDown, ChevronUp, Eye, Image, Smartphone } from "lucide-react"; import { SiFacebook, SiInstagram } from "react-icons/si"; import { Button } from "@/components/ui/button"; import { useState } from "react"; @@ -15,6 +15,22 @@ interface CalendarListViewProps { export default function CalendarListView({ scheduledPosts, onEditPost, onDeletePost, onPreviewPost }: CalendarListViewProps) { const [expandedDates, setExpandedDates] = useState>(new Set()); + const getPostTypeIcon = (postType: string) => { + if (postType === 'feed') { + return ; + } else if (postType === 'story') { + return ; + } else if (postType === 'both') { + return ( +
+ + +
+ ); + } + return null; + }; + // Group posts by date const groupedPosts = scheduledPosts.reduce((acc: { [key: string]: any[] }, post) => { if (!post.scheduledAt) return acc; @@ -115,6 +131,7 @@ export default function CalendarListView({ scheduledPosts, onEditPost, onDeleteP
+ {getPostTypeIcon(post.postType)} {time} ({ queryKey: ["/api/scheduled-posts"], + refetchInterval: 30000, // Auto-refresh every 30 seconds }); const deletePostMutation = useMutation({ @@ -90,6 +91,22 @@ export default function CalendarView() { } }; + const getPostTypeIcon = (postType: string) => { + if (postType === 'feed') { + return ; + } else if (postType === 'story') { + return ; + } else if (postType === 'both') { + return ( +
+ + +
+ ); + } + return null; + }; + const getPostsForDate = (date: Date) => { return (scheduledPosts || []).filter(post => { if (!post.scheduledAt) return false; @@ -256,7 +273,10 @@ export default function CalendarView() { >
-
{time}
+
+ {getPostTypeIcon(post.postType)} + {time} +
{pageName} @@ -332,7 +352,10 @@ export default function CalendarView() { >
-
{time}
+
+ {getPostTypeIcon(post.postType)} + {time} +
{pageName} diff --git a/client/src/components/recent-publications.tsx b/client/src/components/recent-publications.tsx index 550d0ab..30362be 100644 --- a/client/src/components/recent-publications.tsx +++ b/client/src/components/recent-publications.tsx @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Facebook, Instagram, Clock, CheckCircle2, XCircle, Eye } from "lucide-react"; +import { Facebook, Instagram, Clock, CheckCircle2, XCircle, Eye, Image as ImageIcon, Smartphone } from "lucide-react"; import { format } from "date-fns"; import { fr } from "date-fns/locale"; import type { ScheduledPost, SocialPage, Post, Media } from "@shared/schema"; @@ -60,6 +60,22 @@ export default function RecentPublications() { } }; + const getPostTypeIcon = (postType: string) => { + if (postType === 'feed') { + return ; + } else if (postType === 'story') { + return ; + } else if (postType === 'both') { + return ( +
+ + +
+ ); + } + return null; + }; + return ( @@ -136,9 +152,10 @@ export default function RecentPublications() {
- - {scheduledPost.postType === 'feed' ? 'Publication' : - scheduledPost.postType === 'story' ? 'Story' : scheduledPost.postType} + + {getPostTypeIcon(scheduledPost.postType)} + {scheduledPost.postType === 'feed' ? 'Feed' : + scheduledPost.postType === 'story' ? 'Story' : 'Feed & Story'} • diff --git a/client/src/pages/history.tsx b/client/src/pages/history.tsx index 37154a2..3b770fb 100644 --- a/client/src/pages/history.tsx +++ b/client/src/pages/history.tsx @@ -1,22 +1,83 @@ import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import { Clock, CheckCircle, XCircle, Calendar, History as HistoryIcon } from "lucide-react"; +import { Clock, CheckCircle, XCircle, Calendar, History as HistoryIcon, Eye, Image as ImageIcon, Smartphone } from "lucide-react"; import Sidebar from "@/components/sidebar"; import TopBar from "@/components/topbar"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; -import type { Post } from "@shared/schema"; +import { Button } from "@/components/ui/button"; +import type { ScheduledPost, SocialPage, Post, Media } from "@shared/schema"; import { format } from "date-fns"; import { fr } from "date-fns/locale"; +import { PreviewModal } from "@/components/preview-modal"; +import { useToast } from "@/hooks/use-toast"; + +type ScheduledPostWithRelations = ScheduledPost & { + post?: Post; + page?: SocialPage; +}; export default function History() { const [sidebarOpen, setSidebarOpen] = useState(false); + const [previewModalOpen, setPreviewModalOpen] = useState(false); + const [previewData, setPreviewData] = useState<{ postText: string; mediaIds: string[]; allMedia: Media[] }>({ postText: '', mediaIds: [], allMedia: [] }); + const { toast } = useToast(); - const { data: posts = [], isLoading } = useQuery({ - queryKey: ['/api/posts'], + const { data: scheduledPosts = [], isLoading } = useQuery({ + queryKey: ['/api/scheduled-posts'], }); - const publishedPosts = posts.filter(p => p.status === 'published'); + // Filter attempted posts (scheduled in the past) + const now = new Date(); + const publishedPosts = scheduledPosts + .filter(sp => sp.scheduledAt && new Date(sp.scheduledAt) <= now) + .sort((a, b) => new Date(b.scheduledAt!).getTime() - new Date(a.scheduledAt!).getTime()); + + const handlePreviewPost = async (scheduledPost: ScheduledPostWithRelations) => { + try { + const response = await fetch(`/api/posts/${scheduledPost.postId}`, { + credentials: "include", + }); + + if (!response.ok) { + throw new Error("Failed to fetch post"); + } + + const postWithMedia = await response.json(); + const mediaIds = postWithMedia.media?.map((m: Media) => m.id) || []; + const allMedia = postWithMedia.media || []; + + setPreviewData({ + postText: postWithMedia.post.content || '', + mediaIds: mediaIds, + allMedia: allMedia, + }); + + setPreviewModalOpen(true); + } catch (error) { + toast({ + title: "Erreur", + description: "Impossible de charger la prévisualisation", + variant: "destructive", + }); + } + }; + + const getPostTypeIcon = (postType: string) => { + if (postType === 'feed') { + return ; + } else if (postType === 'story') { + return ; + } else if (postType === 'both') { + return ( +
+ + +
+ ); + } + return null; + }; return (
@@ -65,60 +126,110 @@ export default function History() { ) : (
- {publishedPosts.map((post) => ( - - -
-
-
-
- + {publishedPosts.map((scheduledPost) => { + const isPending = !scheduledPost.publishedAt; + const hasError = !!scheduledPost.error; + + return ( + + +
+
+
+
+ {hasError ? ( + + ) : ( + + )} +
+ + {scheduledPost.page?.pageName || 'Page inconnue'} +
- - Publication #{post.id.substring(0, 8)} - +

+ {scheduledPost.post?.content || 'Aucun texte'} +

+
+
+ + + {hasError ? ( + <> + + Échoué + + ) : ( + <> + + Publié + + )} +
-

- {post.content} -

- - {post.status === 'published' && } - {post.status === 'failed' && } - {post.status === 'published' ? 'Publié' : 'Échoué'} - -
- - -
-
- - - {post.updatedAt - ? format(new Date(post.updatedAt), "d MMM yyyy 'à' HH:mm", { locale: fr }) - : 'Date inconnue' - } - + + +
+
+ {getPostTypeIcon(scheduledPost.postType)} + + {scheduledPost.postType === 'feed' ? 'Feed' : + scheduledPost.postType === 'story' ? 'Story' : 'Feed & Story'} + +
+
+ + + {scheduledPost.scheduledAt + ? format(new Date(scheduledPost.scheduledAt), "d MMM yyyy 'à' HH:mm", { locale: fr }) + : 'Date inconnue' + } + +
+ {scheduledPost.post?.aiGenerated && ( + + + Généré par IA + + )}
- {post.aiGenerated && ( - - - Généré par IA - - )} -
- - - ))} + + + ); + })}
)}
+ + {}} + isPublishing={false} + readOnly={true} + />
);