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' ?
| 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')} |
|---|