import { useState, useEffect, useContext } from 'react' import { Plus, LayoutGrid, List, Search, X, FileText, Filter } from 'lucide-react' import { AppContext } from '../App' import { useAuth } from '../contexts/AuthContext' import { useLanguage } from '../i18n/LanguageContext' import { api, PLATFORMS } from '../utils/api' import KanbanBoard from '../components/KanbanBoard' import KanbanCard from '../components/KanbanCard' import PostCard from '../components/PostCard' import PostDetailPanel from '../components/PostDetailPanel' import DatePresetPicker from '../components/DatePresetPicker' import { SkeletonKanbanBoard, SkeletonTable } from '../components/SkeletonLoader' import EmptyState from '../components/EmptyState' import BulkSelectBar from '../components/BulkSelectBar' import Modal from '../components/Modal' import { useToast } from '../components/ToastContainer' const EMPTY_POST = { title: '', description: '', brand_id: '', platforms: [], status: 'draft', assigned_to: '', scheduled_date: '', notes: '', campaign_id: '', publication_links: [], } export default function PostProduction() { const { t, lang } = useLanguage() const { teamMembers, brands, getBrandName } = useContext(AppContext) const { canEditResource } = useAuth() const toast = useToast() const [posts, setPosts] = useState([]) const [loading, setLoading] = useState(true) const [view, setView] = useState('kanban') const [panelPost, setPanelPost] = useState(null) const [campaigns, setCampaigns] = useState([]) const [filters, setFilters] = useState({ brand: '', platform: '', assignedTo: '', campaign: '', periodFrom: '', periodTo: '' }) const [searchTerm, setSearchTerm] = useState('') const [activePreset, setActivePreset] = useState('') const [moveError, setMoveError] = useState('') const [selectedIds, setSelectedIds] = useState(new Set()) const [showBulkDeleteConfirm, setShowBulkDeleteConfirm] = useState(false) const [showFilters, setShowFilters] = useState(false) const [showCreateModal, setShowCreateModal] = useState(false) const [createForm, setCreateForm] = useState({ ...EMPTY_POST }) const [createSaving, setCreateSaving] = useState(false) useEffect(() => { loadPosts() api.get('/campaigns').then(r => setCampaigns(Array.isArray(r) ? r : [])).catch(() => {}) }, []) const loadPosts = async () => { try { const res = await api.get('/posts') setPosts(Array.isArray(res) ? res : []) } catch (err) { console.error('Failed to load posts:', err) } finally { setLoading(false) } } const handleMovePost = async (postId, newStatus) => { // Optimistic update — move the card instantly const prev = posts setPosts(posts.map(p => p._id === postId ? { ...p, status: newStatus } : p)) try { await api.patch(`/posts/${postId}`, { status: newStatus }) toast.success(t('posts.statusUpdated')) } catch (err) { console.error('Move failed:', err) setPosts(prev) if (err.message?.includes('Cannot publish')) { setMoveError(t('posts.publishRequired')) setTimeout(() => setMoveError(''), 5000) toast.error(t('posts.publishRequired')) } else { toast.error(t('common.updateFailed')) } } } const handlePanelSave = async (postId, data) => { if (postId) { await api.patch(`/posts/${postId}`, data) toast.success(t('posts.updated')) } else { await api.post('/posts', data) toast.success(t('posts.created')) } loadPosts() } const handlePanelDelete = async (postId) => { try { await api.delete(`/posts/${postId}`) toast.success(t('posts.deleted')) loadPosts() } catch (err) { console.error('Delete failed:', err) toast.error(t('common.deleteFailed')) } } const handleBulkDelete = async () => { try { await api.post('/posts/bulk-delete', { ids: [...selectedIds] }) toast.success(t('posts.deleted')) setSelectedIds(new Set()) setShowBulkDeleteConfirm(false) loadPosts() } catch (err) { console.error('Bulk delete failed:', err) toast.error(t('common.deleteFailed')) } } const toggleSelect = (id) => { setSelectedIds(prev => { const next = new Set(prev) if (next.has(id)) next.delete(id) else next.add(id) return next }) } const toggleSelectAll = () => { if (selectedIds.size === filteredPosts.length) setSelectedIds(new Set()) else setSelectedIds(new Set(filteredPosts.map(p => p._id || p.id || p.Id))) } const openEdit = (post) => { if (!canEditResource('post', post)) { toast.error(t('posts.canOnlyEditOwn')) return } setPanelPost(post) } const openNew = () => { setCreateForm({ ...EMPTY_POST }) setShowCreateModal(true) } const handleCreate = async () => { setCreateSaving(true) try { const data = { title: createForm.title, brand_id: createForm.brand_id ? Number(createForm.brand_id) : null, campaign_id: createForm.campaign_id ? Number(createForm.campaign_id) : null, assigned_to: createForm.assigned_to ? Number(createForm.assigned_to) : null, status: 'draft', } const created = await api.post('/posts', data) setShowCreateModal(false) toast.success(t('posts.created')) loadPosts() // Open the detail panel for further editing if (created) setPanelPost(created) } catch (err) { console.error('Create post failed:', err) toast.error(t('common.saveFailed')) } finally { setCreateSaving(false) } } const filteredPosts = posts.filter(p => { if (filters.brand && String(p.brandId || p.brand_id) !== filters.brand) return false if (filters.platform && !(p.platforms || []).includes(filters.platform) && p.platform !== filters.platform) return false if (filters.assignedTo && String(p.assignedTo || p.assigned_to) !== filters.assignedTo) return false if (filters.campaign && String(p.campaignId || p.campaign_id) !== filters.campaign) return false if (searchTerm && !p.title?.toLowerCase().includes(searchTerm.toLowerCase())) return false if (filters.periodFrom || filters.periodTo) { const postDate = p.scheduledDate || p.scheduled_date || p.published_date || p.publishedDate if (!postDate) return false const d = new Date(postDate).toISOString().slice(0, 10) if (filters.periodFrom && d < filters.periodFrom) return false if (filters.periodTo && d > filters.periodTo) return false } return true }) if (loading) { return view === 'kanban' ? : } return (
{/* Toolbar */}
setSearchTerm(e.target.value)} className="w-full pl-10 pr-4 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary bg-white" />
{showFilters && (
{ setFilters(f => ({ ...f, periodFrom: from, periodTo: to })); setActivePreset(key) }} onClear={() => { setFilters(f => ({ ...f, periodFrom: '', periodTo: '' })); setActivePreset('') }} />
{ setFilters(f => ({ ...f, periodFrom: e.target.value })); setActivePreset('') }} title={t('posts.periodFrom')} className="text-xs border border-border rounded-lg px-2 py-1.5 bg-white text-text-secondary focus:outline-none focus:ring-2 focus:ring-brand-primary/20" /> { setFilters(f => ({ ...f, periodTo: e.target.value })); setActivePreset('') }} title={t('posts.periodTo')} className="text-xs border border-border rounded-lg px-2 py-1.5 bg-white text-text-secondary focus:outline-none focus:ring-2 focus:ring-brand-primary/20" />
)}
{moveError && (
{moveError}
)} {view === 'kanban' ? ( p._id} onMove={(id, status) => handleMovePost(id, status)} renderCard={(post) => { const brandName = getBrandName(post.brand_id || post.brandId) || post.brand_name || post.brand const assignee = post.assignedToName || post.assignedName || post.assigned_name return ( openEdit(post)} /> ) }} /> ) : (
{filteredPosts.length === 0 ? ( 0 ? t('common.clearFilters') : null} onSecondaryAction={() => { setFilters({ brand: '', platform: '', assignedTo: '', campaign: '', periodFrom: '', periodTo: '' }) setSearchTerm('') }} /> ) : ( <> {selectedIds.size > 0 && (
setShowBulkDeleteConfirm(true)} onClear={() => setSelectedIds(new Set())} />
)} {filteredPosts.map(post => { const postId = post._id || post.id || post.Id return ( openEdit(post)} checkboxSlot={ toggleSelect(postId)} className="rounded border-border" />} /> ) })}
e.stopPropagation()}> 0} onChange={toggleSelectAll} className="rounded border-border" /> {t('posts.postTitle')} {t('posts.brand')} {t('posts.status')} {t('posts.platforms')} {t('posts.assignTo')} {t('posts.scheduledDate')}
)}
)} {/* Bulk Delete Confirmation */} setShowBulkDeleteConfirm(false)} title={t('common.bulkDeleteConfirm').replace('{count}', selectedIds.size)} isConfirm danger confirmText={t('common.deleteSelected')} onConfirm={handleBulkDelete} > {t('common.bulkDeleteDesc')} {/* Create Post Modal */} setShowCreateModal(false)} title={t('posts.newPost')} size="md">
setCreateForm(f => ({ ...f, title: e.target.value }))} className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary" autoFocus />
{/* Post Detail Panel (edit only) */} {panelPost && ( setPanelPost(null)} onSave={handlePanelSave} onDelete={handlePanelDelete} brands={brands} teamMembers={teamMembers} campaigns={campaigns} /> )}
) }