- {value}
+ {value}
{(trend !== undefined || description) && (
{trend !== undefined && (
- 0 ? 'text-green-500' : trend < 0 ? 'text-red-500' : ''}`}>
+ 0 ? 'text-success' : trend < 0 ? 'text-destructive' : ''}`}>
{trend > 0 ? : trend < 0 ? : }
{Math.abs(trend)}%
diff --git a/client/src/components/calendar-list-view.tsx b/client/src/components/calendar-list-view.tsx
index 18e450e..1143279 100644
--- a/client/src/components/calendar-list-view.tsx
+++ b/client/src/components/calendar-list-view.tsx
@@ -165,7 +165,7 @@ export default function CalendarListView({ scheduledPosts, onEditPost, onDeleteP
@@ -176,8 +176,8 @@ export default function CalendarListView({ scheduledPosts, onEditPost, onDeleteP
{time}
{isPending ? 'Programmé' : 'Publié'}
@@ -197,7 +197,7 @@ export default function CalendarListView({ scheduledPosts, onEditPost, onDeleteP
data-testid={`button-preview-post-${post.id}`}
title="Prévisualiser"
>
-
+
{isPending && (
<>
@@ -209,7 +209,7 @@ export default function CalendarListView({ scheduledPosts, onEditPost, onDeleteP
data-testid={`button-edit-post-${post.id}`}
title="Modifier"
>
-
+
>
)}
diff --git a/client/src/components/calendar-view.tsx b/client/src/components/calendar-view.tsx
index 163ff58..f0616ca 100644
--- a/client/src/components/calendar-view.tsx
+++ b/client/src/components/calendar-view.tsx
@@ -191,52 +191,47 @@ export default function CalendarView() {
};
return (
-
-
-
-
-
-
-
-
-
Calendrier
-
Visualisez vos publications planifiées
-
-
-
-
-
- {monthNames[currentDate.getMonth()]} {currentDate.getFullYear()}
-
-
-
+
+
+
+ {monthNames[currentDate.getMonth()]} {currentDate.getFullYear()}
+
+
+
+
+
{isDesktop ? (
-
+
{daysOfWeek.map((day) => (
-
@@ -246,8 +241,8 @@ export default function CalendarView() {
{isToday(day.date) && (
-
+
Aujourd'hui
)}
@@ -289,8 +284,8 @@ export default function CalendarView() {
key={idx}
className={`
px-3 py-2 rounded-lg text-xs transition-all font-medium shadow-sm group relative
- ${isPending ? 'bg-blue-500/20 text-blue-600 hover:bg-blue-500/30' : ''}
- ${isPublished ? 'bg-green-500/20 text-green-600 hover:bg-green-500/30' : ''}
+ ${isPending ? 'bg-primary/10 text-primary hover:bg-primary/20' : ''}
+ ${isPublished ? 'bg-success/15 text-success hover:bg-success/25' : ''}
`}
data-testid={`calendar-post-${post.id}`}
>
@@ -308,29 +303,29 @@ export default function CalendarView() {
{isPending && (
<>
>
)}
@@ -368,8 +363,8 @@ export default function CalendarView() {
key={idx}
className={`
px-3 py-2 rounded-lg text-xs transition-all font-medium shadow-sm group relative
- ${isPending ? 'bg-blue-500/20 text-blue-600 hover:bg-blue-500/30' : ''}
- ${isPublished ? 'bg-green-500/20 text-green-600 hover:bg-green-500/30' : ''}
+ ${isPending ? 'bg-primary/10 text-primary hover:bg-primary/20' : ''}
+ ${isPublished ? 'bg-success/15 text-success hover:bg-success/25' : ''}
`}
data-testid={`popover-post-${post.id}`}
>
@@ -387,29 +382,29 @@ export default function CalendarView() {
{isPending && (
<>
>
)}
@@ -429,13 +424,13 @@ export default function CalendarView() {
))}
-
+
diff --git a/client/src/components/datetime-picker.tsx b/client/src/components/datetime-picker.tsx
index 7113d6a..b036fb0 100644
--- a/client/src/components/datetime-picker.tsx
+++ b/client/src/components/datetime-picker.tsx
@@ -100,7 +100,7 @@ export function DateTimePicker({ value, onChange, occupiedDates = [], placeholde
occupied: (date) => isDateOccupied(date)
}}
modifiersClassNames={{
- occupied: "bg-red-500/20 text-red-700 dark:text-red-300 font-bold hover:bg-red-500/30"
+ occupied: "bg-destructive/15 text-destructive font-bold hover:bg-destructive/25"
}}
disabled={(date) => date < new Date(new Date().setHours(0, 0, 0, 0))}
data-testid="calendar-picker"
@@ -154,7 +154,7 @@ export function DateTimePicker({ value, onChange, occupiedDates = [], placeholde
className={cn(
"w-full justify-center mb-0.5 h-9 text-xs",
selectedHour === hour && "bg-primary text-primary-foreground",
- isOptimalHour(hour) && selectedHour !== hour && "bg-green-500/20 text-green-700 dark:text-green-300 font-semibold hover:bg-green-500/30"
+ isOptimalHour(hour) && selectedHour !== hour && "bg-success/15 text-success font-semibold hover:bg-success/25"
)}
onClick={() => handleTimeChange(hour, selectedMinute)}
data-testid={`hour-${hour}`}
@@ -189,8 +189,8 @@ export function DateTimePicker({ value, onChange, occupiedDates = [], placeholde
-
-
+
+
Heures optimales (8h, 12h-13h, 18h-20h)
@@ -200,10 +200,10 @@ export function DateTimePicker({ value, onChange, occupiedDates = [], placeholde
)}
{occupiedDates.length > 0 && (
-
+
-
-
+
+
Les dates en fond rouge ont déjà des publications programmées
diff --git a/client/src/components/media-upload.tsx b/client/src/components/media-upload.tsx
index 967b4ac..c05a0d2 100644
--- a/client/src/components/media-upload.tsx
+++ b/client/src/components/media-upload.tsx
@@ -5,7 +5,18 @@ import { Dialog, DialogContent } from "@/components/ui/dialog";
import { useInfiniteQuery, useMutation } from "@tanstack/react-query";
import { apiRequest, queryClient, handleUnauthorized } from "@/lib/queryClient";
import { MediaThumbnail } from "@/components/media-thumbnail";
-import { CloudUpload, Image as ImageIcon, Video, X, Upload, Loader2, ZoomIn, Camera } from "lucide-react";
+import { CloudUpload, Image as ImageIcon, X, Upload, Loader2, ZoomIn, Camera, Trash2 } from "lucide-react";
+import { EmptyState } from "@/components/empty-state";
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+} from "@/components/ui/alert-dialog";
import { useToast } from "@/hooks/use-toast";
import { SiFacebook, SiInstagram } from "react-icons/si";
@@ -16,6 +27,7 @@ export default function MediaUpload() {
const { toast } = useToast();
const [selectedFile, setSelectedFile] = useState
(null);
const [zoomImage, setZoomImage] = useState(null);
+ const [mediaToDelete, setMediaToDelete] = useState(null);
const loadMoreRef = useRef(null);
const cameraInputRef = useRef(null);
@@ -213,219 +225,203 @@ export default function MediaUpload() {
};
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
+ const formatPreview = (label: string, size: string, url: string | undefined, aspect: string, width: string) => (
+
+
+
+
+
+ {label}
+
+
{size}
+
+
+ {url ? (
+

+ ) : (
+
+
+
+ )}
+
+
+ );
+
return (
-
-
-
-
-
-
-
-
-
Médiathèque
-
Téléchargement et recadrage automatique
-
-
-
-
-
-
+
+
+
0 ? "pointer-events-none opacity-60" : ""}
+ `}
+ data-testid="dropzone-upload"
+ >
+
+
+ {uploadingCount > 0 ? : }
+
+
+ {uploadingCount > 0 ? (
+ <>
+
Import en cours…
+
{uploadingCount} fichier(s) en cours de traitement
+ >
+ ) : (
+ <>
+
{isDragActive ? "Déposez vos fichiers ici" : "Glissez-déposez vos images et vidéos"}
+
PNG, JPG ou MP4 jusqu'à 50 Mo — recadrées automatiquement pour le fil et les stories.
+ >
+ )}
+
+
e.stopPropagation()}>
+
+
-
-
-
-
0 ? "pointer-events-none opacity-50" : ""}
- `}
- data-testid="dropzone-upload"
- >
-
-
-
- {uploadingCount > 0 ? (
-
- ) : (
-
- )}
-
- {uploadingCount > 0 ? (
- <>
-
- Téléchargement en cours...
-
-
- {uploadingCount} fichier(s) en cours de traitement
-
- >
- ) : (
- <>
-
- {isDragActive ? "Déposez vos fichiers ici" : "Glissez-déposez vos fichiers"}
-
-
ou cliquez pour parcourir
-
PNG, JPG, MP4 jusqu'à 50MB
- >
- )}
-
+
+
+
+
Vos médias
+ {totalMedia} élément(s)
+
-
-
- Fichiers téléchargés
-
- {totalMedia} média(s)
-
-
-
-
-
- {mediaList.map((media: any) => (
-
setSelectedFile(media)}
- className="relative aspect-square rounded-lg overflow-hidden border-2 border-border/50 cursor-pointer hover:border-primary hover:shadow-lg transition-all group"
- data-testid={`media-item-${media.id}`}
- >
-
-
- {media.type === "image" && (
-
- )}
+ {mediaList.length === 0 ? (
+
+ ) : (
+
+ {mediaList.map((media: any) => {
+ const selected = selectedFile?.id === media.id;
+ return (
+
setSelectedFile(media)}
+ className={`group relative aspect-square cursor-pointer overflow-hidden rounded-lg border-2 transition-all ${selected ? "border-primary" : "border-transparent hover:border-primary/40"}`}
+ data-testid={`media-item-${media.id}`}
+ >
+
+
+ {media.type === "image" && (
-
+ )}
+
- ))}
-
-
- {/* Élément sentinelle pour le scroll infini */}
- {hasNextPage ? (
-
-
- ) : null}
-
+ );
+ })}
-
+ )}
-
-
Aperçu et recadrage automatique
-
-
-
-
-
-
-
- Feed
-
-
1080×1080
-
-
- {selectedFile?.facebookFeedUrl || selectedFile?.instagramFeedUrl ? (
-

- ) : (
-
-
-
- )}
-
-
-
-
-
-
-
-
- Story
-
-
1080×1920
-
-
- {selectedFile?.instagramStoryUrl ? (
-

- ) : (
-
-
-
- )}
-
-
+ {/* Élément sentinelle pour le scroll infini */}
+ {hasNextPage ? (
+
+
-
+ ) : null}
+
+
+
!o && setMediaToDelete(null)}>
+
+
+ Supprimer ce média ?
+
+ « {mediaToDelete?.fileName} » sera définitivement retiré de la médiathèque.
+
+
+
+ Annuler
+ {
+ if (mediaToDelete) {
+ deleteMutation.mutate(mediaToDelete.id);
+ if (selectedFile?.id === mediaToDelete.id) setSelectedFile(null);
+ }
+ setMediaToDelete(null);
+ }}
+ >
+ Supprimer
+
+
+
+
+