From d164f512ccee2da8b44a0a6b95761fd651434b82 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Thu, 9 Oct 2025 08:12:53 +0000 Subject: [PATCH] Improve calendar view by adding a responsive list layout Introduce a new component for a responsive calendar list view and update the existing calendar view to conditionally render based on screen size. 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-list-view.tsx | 168 +++++++++++++++++++ client/src/components/calendar-view.tsx | 65 +++---- client/src/hooks/use-media-query.ts | 30 ++++ 4 files changed, 235 insertions(+), 32 deletions(-) create mode 100644 client/src/components/calendar-list-view.tsx create mode 100644 client/src/hooks/use-media-query.ts diff --git a/.replit b/.replit index bd88b27..d51ca2b 100644 --- a/.replit +++ b/.replit @@ -34,10 +34,6 @@ externalPort = 4200 localPort = 40537 externalPort = 3000 -[[ports]] -localPort = 40905 -externalPort = 6800 - [[ports]] localPort = 42161 externalPort = 6000 diff --git a/client/src/components/calendar-list-view.tsx b/client/src/components/calendar-list-view.tsx new file mode 100644 index 0000000..edac5de --- /dev/null +++ b/client/src/components/calendar-list-view.tsx @@ -0,0 +1,168 @@ +import { format, isSameDay, parseISO } from "date-fns"; +import { fr } from "date-fns/locale"; +import { Edit, Trash2, ChevronDown, ChevronUp } from "lucide-react"; +import { SiFacebook, SiInstagram } from "react-icons/si"; +import { Button } from "@/components/ui/button"; +import { useState } from "react"; + +interface CalendarListViewProps { + scheduledPosts: any[]; + onEditPost: (post: any) => void; + onDeletePost: (postId: string) => void; +} + +export default function CalendarListView({ scheduledPosts, onEditPost, onDeletePost }: CalendarListViewProps) { + const [expandedDates, setExpandedDates] = useState>(new Set()); + + // Group posts by date + const groupedPosts = scheduledPosts.reduce((acc: { [key: string]: any[] }, post) => { + if (!post.scheduledAt) return acc; + const date = format(parseISO(post.scheduledAt), "yyyy-MM-dd"); + if (!acc[date]) { + acc[date] = []; + } + acc[date].push(post); + return acc; + }, {}); + + // Sort dates + const sortedDates = Object.keys(groupedPosts).sort(); + + const toggleDate = (date: string) => { + const newExpanded = new Set(expandedDates); + if (newExpanded.has(date)) { + newExpanded.delete(date); + } else { + newExpanded.add(date); + } + setExpandedDates(newExpanded); + }; + + const isToday = (dateStr: string) => { + return isSameDay(parseISO(dateStr), new Date()); + }; + + return ( +
+ {sortedDates.length === 0 ? ( +
+ Aucune publication programmée +
+ ) : ( + sortedDates.map((dateStr) => { + const posts = groupedPosts[dateStr]; + const isExpanded = expandedDates.has(dateStr); + const dateObj = parseISO(dateStr); + const isDateToday = isToday(dateStr); + + return ( +
+ + + {isExpanded && ( +
+ {posts.map((post: any) => { + 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} + + {isPending ? 'Programmé' : 'Publié'} + +
+
+ + {pageName} +
+
+ + {isPending && ( +
+ + +
+ )} +
+
+ ); + })} +
+ )} +
+ ); + }) + )} +
+ ); +} diff --git a/client/src/components/calendar-view.tsx b/client/src/components/calendar-view.tsx index 39b6b97..1292da5 100644 --- a/client/src/components/calendar-view.tsx +++ b/client/src/components/calendar-view.tsx @@ -7,12 +7,15 @@ 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"; +import CalendarListView from "./calendar-list-view"; +import { useMediaQuery } from "@/hooks/use-media-query"; export default function CalendarView() { const [currentDate, setCurrentDate] = useState(new Date()); const [editDialogOpen, setEditDialogOpen] = useState(false); const [selectedPost, setSelectedPost] = useState(null); const { toast } = useToast(); + const isDesktop = useMediaQuery("(min-width: 768px)"); const { data: scheduledPosts = [] } = useQuery({ queryKey: ["/api/scheduled-posts"], @@ -37,15 +40,15 @@ export default function CalendarView() { }, }); - const handleDeletePost = (postId: string, e: React.MouseEvent) => { - e.stopPropagation(); + const handleDeletePost = (postId: string, e?: React.MouseEvent) => { + if (e) e.stopPropagation(); if (confirm("Êtes-vous sûr de vouloir supprimer cette publication programmée ?")) { deletePostMutation.mutate(postId); } }; - const handleEditPost = (post: any, e: React.MouseEvent) => { - e.stopPropagation(); + const handleEditPost = (post: any, e?: React.MouseEvent) => { + if (e) e.stopPropagation(); setSelectedPost(post); setEditDialogOpen(true); }; @@ -147,16 +150,17 @@ export default function CalendarView() { -
-
- {daysOfWeek.map((day) => ( -
- {day} -
- ))} -
+ {isDesktop ? ( +
+
+ {daysOfWeek.map((day) => ( +
+ {day} +
+ ))} +
-
+
{days.map((day, index) => (
-
-
-
- Programmé -
-
-
- Publié +
+
+
+ Programmé +
+
+
+ Publié +
-
- - {selectedPost && ( - )} + +
); } diff --git a/client/src/hooks/use-media-query.ts b/client/src/hooks/use-media-query.ts new file mode 100644 index 0000000..abb69b4 --- /dev/null +++ b/client/src/hooks/use-media-query.ts @@ -0,0 +1,30 @@ +import { useState, useEffect } from "react"; + +export function useMediaQuery(query: string): boolean { + const [matches, setMatches] = useState(false); + + useEffect(() => { + const media = window.matchMedia(query); + + // Set initial value + setMatches(media.matches); + + // Define callback for changes + const listener = (e: MediaQueryListEvent) => { + setMatches(e.matches); + }; + + // Modern browsers + if (media.addEventListener) { + media.addEventListener('change', listener); + return () => media.removeEventListener('change', listener); + } + // Legacy browsers + else { + media.addListener(listener); + return () => media.removeListener(listener); + } + }, [query]); + + return matches; +}