mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-23 23:18:12 +02:00
3 files changed
+90
-4
No files matched your search
@@ -42,8 +42,15 @@ export default function CalendarListView({ scheduledPosts, onEditPost, onDeleteP
|
|||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
// Sort dates
|
// Sort dates - put today first, then chronological order
|
||||||
const sortedDates = Object.keys(groupedPosts).sort();
|
const today = format(new Date(), "yyyy-MM-dd");
|
||||||
|
const sortedDates = Object.keys(groupedPosts).sort((a, b) => {
|
||||||
|
// Today always comes first
|
||||||
|
if (a === today && b !== today) return -1;
|
||||||
|
if (b === today && a !== today) return 1;
|
||||||
|
// Otherwise chronological order
|
||||||
|
return a.localeCompare(b);
|
||||||
|
});
|
||||||
|
|
||||||
const toggleDate = (date: string) => {
|
const toggleDate = (date: string) => {
|
||||||
const newExpanded = new Set(expandedDates);
|
const newExpanded = new Set(expandedDates);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
|||||||
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||||
import type { SocialPage, Media } from "@shared/schema";
|
import type { SocialPage, Media, ScheduledPost } from "@shared/schema";
|
||||||
import { PreviewModal } from "@/components/preview-modal";
|
import { PreviewModal } from "@/components/preview-modal";
|
||||||
|
|
||||||
function SortableMediaItem({
|
function SortableMediaItem({
|
||||||
@@ -127,6 +127,10 @@ export default function NewPost() {
|
|||||||
queryKey: ['/api/media'],
|
queryKey: ['/api/media'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: scheduledPosts = [] } = useQuery<ScheduledPost[]>({
|
||||||
|
queryKey: ['/api/scheduled-posts'],
|
||||||
|
});
|
||||||
|
|
||||||
const uploadMutation = useMutation({
|
const uploadMutation = useMutation({
|
||||||
mutationFn: async (file: File) => {
|
mutationFn: async (file: File) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@@ -707,7 +711,7 @@ export default function NewPost() {
|
|||||||
<CardDescription>Programmez la publication (optionnel)</CardDescription>
|
<CardDescription>Programmez la publication (optionnel)</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="space-y-2">
|
<div className="space-y-3">
|
||||||
<Label htmlFor="scheduledDate">Date et heure</Label>
|
<Label htmlFor="scheduledDate">Date et heure</Label>
|
||||||
<Input
|
<Input
|
||||||
id="scheduledDate"
|
id="scheduledDate"
|
||||||
@@ -719,6 +723,80 @@ export default function NewPost() {
|
|||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Laissez vide pour publier immédiatement
|
Laissez vide pour publier immédiatement
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{(() => {
|
||||||
|
// Get dates with scheduled posts
|
||||||
|
const datesWithPosts = scheduledPosts
|
||||||
|
.filter(post => post.scheduledAt)
|
||||||
|
.map(post => {
|
||||||
|
const date = new Date(post.scheduledAt!);
|
||||||
|
return date.toISOString().split('T')[0];
|
||||||
|
})
|
||||||
|
.filter((date, index, self) => self.indexOf(date) === index)
|
||||||
|
.sort();
|
||||||
|
|
||||||
|
if (datesWithPosts.length > 0) {
|
||||||
|
return (
|
||||||
|
<div className="p-3 bg-red-500/10 rounded-lg border border-red-500/20">
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-red-500"></div>
|
||||||
|
<span className="text-xs font-semibold text-red-600 dark:text-red-400">
|
||||||
|
Dates avec publications programmées
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{datesWithPosts.slice(0, 5).map((date) => {
|
||||||
|
const dateObj = new Date(date + 'T00:00:00');
|
||||||
|
const formatted = dateObj.toLocaleDateString('fr-FR', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'short'
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
key={date}
|
||||||
|
className="text-xs px-2 py-1 bg-red-500/20 text-red-700 dark:text-red-300 rounded-md font-medium"
|
||||||
|
>
|
||||||
|
{formatted}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{datesWithPosts.length > 5 && (
|
||||||
|
<span className="text-xs px-2 py-1 text-red-600 dark:text-red-400 font-medium">
|
||||||
|
+{datesWithPosts.length - 5} autre{datesWithPosts.length - 5 > 1 ? 's' : ''}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="text-[10px] text-red-600/80 dark:text-red-400/80 mt-2">
|
||||||
|
Ces dates ont déjà des publications planifiées
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})()}
|
||||||
|
|
||||||
|
<div className="p-3 bg-green-500/10 rounded-lg border border-green-500/20">
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-green-500"></div>
|
||||||
|
<span className="text-xs font-semibold text-green-600 dark:text-green-400">
|
||||||
|
Heures optimales pour l'audience
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<span className="text-xs px-2 py-1 bg-green-500/20 text-green-700 dark:text-green-300 rounded-md font-medium">
|
||||||
|
8h - 9h
|
||||||
|
</span>
|
||||||
|
<span className="text-xs px-2 py-1 bg-green-500/20 text-green-700 dark:text-green-300 rounded-md font-medium">
|
||||||
|
12h - 14h
|
||||||
|
</span>
|
||||||
|
<span className="text-xs px-2 py-1 bg-green-500/20 text-green-700 dark:text-green-300 rounded-md font-medium">
|
||||||
|
18h - 21h
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-[10px] text-green-600/80 dark:text-green-400/80 mt-2">
|
||||||
|
Ces créneaux maximisent la portée de vos publications
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Preferred communication style: Simple, everyday language.
|
|||||||
## Recent Changes
|
## Recent Changes
|
||||||
|
|
||||||
### October 28, 2025
|
### October 28, 2025
|
||||||
|
- **Calendar Mobile UX Improvements**: Enhanced mobile calendar and publication planning interface with three visual improvements: (1) Mobile calendar list now displays today's date at the top, before chronological ordering, for quick access to current day's publications; (2) Added green visual indicators in new-post page showing optimal posting times (8h-9h, 12h-14h, 18h-21h) to maximize audience reach; (3) Added red visual indicators showing dates with existing scheduled posts to prevent scheduling conflicts. Both indicators appear dynamically below the datetime selector, displaying up to 5 dates with option to view more.
|
||||||
- **Facebook Video Story Publishing Fix**: Fixed critical bug preventing video story publication. Facebook Graph API requires a 3-phase upload process for video stories: (1) START phase - initialize upload session and get `video_id` + `upload_url`, (2) UPLOAD phase - POST video to rupload.facebook.com endpoint with `file_url` header and OAuth authorization, (3) FINISH phase - finalize and publish story with `video_id`. Previous implementation incorrectly attempted direct upload to `/video_stories` endpoint with `file_url` parameter, causing "#100 The parameter upload_phase is required" error. Fixed by implementing proper 3-phase workflow using remote URL upload method (file_url header), avoiding need for binary chunked uploads.
|
- **Facebook Video Story Publishing Fix**: Fixed critical bug preventing video story publication. Facebook Graph API requires a 3-phase upload process for video stories: (1) START phase - initialize upload session and get `video_id` + `upload_url`, (2) UPLOAD phase - POST video to rupload.facebook.com endpoint with `file_url` header and OAuth authorization, (3) FINISH phase - finalize and publish story with `video_id`. Previous implementation incorrectly attempted direct upload to `/video_stories` endpoint with `file_url` parameter, causing "#100 The parameter upload_phase is required" error. Fixed by implementing proper 3-phase workflow using remote URL upload method (file_url header), avoiding need for binary chunked uploads.
|
||||||
|
|
||||||
### October 17, 2025
|
### October 17, 2025
|
||||||
|
|||||||
Reference in new issue
Block a user