update on timeline on portfolio view + some corrections
This commit is contained in:
@@ -9,6 +9,9 @@ import PostCard from '../components/PostCard'
|
||||
import StatusBadge from '../components/StatusBadge'
|
||||
import Modal from '../components/Modal'
|
||||
import CommentsSection from '../components/CommentsSection'
|
||||
import { SkeletonKanbanBoard, SkeletonTable } from '../components/SkeletonLoader'
|
||||
import EmptyState from '../components/EmptyState'
|
||||
import { useToast } from '../components/ToastContainer'
|
||||
|
||||
const EMPTY_POST = {
|
||||
title: '', description: '', brand_id: '', platforms: [],
|
||||
@@ -20,8 +23,10 @@ export default function PostProduction() {
|
||||
const { t } = useLanguage()
|
||||
const { teamMembers, brands } = useContext(AppContext)
|
||||
const { canEditResource, canDeleteResource } = useAuth()
|
||||
const toast = useToast()
|
||||
const [posts, setPosts] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [view, setView] = useState('kanban')
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [editingPost, setEditingPost] = useState(null)
|
||||
@@ -59,6 +64,7 @@ export default function PostProduction() {
|
||||
|
||||
const handleSave = async () => {
|
||||
setPublishError('')
|
||||
setSaving(true)
|
||||
try {
|
||||
const data = {
|
||||
title: formData.title,
|
||||
@@ -82,14 +88,17 @@ export default function PostProduction() {
|
||||
if (missingPlatforms.length > 0) {
|
||||
const names = missingPlatforms.map(p => PLATFORMS[p]?.label || p).join(', ')
|
||||
setPublishError(`${t('posts.publishMissing')} ${names}`)
|
||||
setSaving(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (editingPost) {
|
||||
await api.patch(`/posts/${editingPost._id}`, data)
|
||||
toast.success(t('posts.updated'))
|
||||
} else {
|
||||
await api.post('/posts', data)
|
||||
toast.success(t('posts.created'))
|
||||
}
|
||||
setShowModal(false)
|
||||
setEditingPost(null)
|
||||
@@ -100,19 +109,27 @@ export default function PostProduction() {
|
||||
console.error('Save failed:', err)
|
||||
if (err.message?.includes('Cannot publish')) {
|
||||
setPublishError(err.message.replace(/.*: /, ''))
|
||||
} else {
|
||||
toast.error(t('common.saveFailed'))
|
||||
}
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleMovePost = async (postId, newStatus) => {
|
||||
try {
|
||||
await api.patch(`/posts/${postId}`, { status: newStatus })
|
||||
toast.success(t('posts.statusUpdated'))
|
||||
loadPosts()
|
||||
} catch (err) {
|
||||
console.error('Move failed:', err)
|
||||
if (err.message?.includes('Cannot publish')) {
|
||||
setMoveError(t('posts.publishRequired'))
|
||||
setTimeout(() => setMoveError(''), 5000)
|
||||
toast.error(t('posts.publishRequired'))
|
||||
} else {
|
||||
toast.error(t('common.updateFailed'))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,8 +170,10 @@ export default function PostProduction() {
|
||||
try {
|
||||
await api.delete(`/attachments/${attachmentId}`)
|
||||
if (editingPost) loadAttachments(editingPost._id || editingPost.id)
|
||||
toast.success(t('posts.attachmentDeleted'))
|
||||
} catch (err) {
|
||||
console.error('Delete attachment failed:', err)
|
||||
toast.error(t('common.deleteFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,14 +263,7 @@ export default function PostProduction() {
|
||||
})
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="space-y-4 animate-pulse">
|
||||
<div className="h-12 bg-surface-tertiary rounded-xl"></div>
|
||||
<div className="flex gap-4">
|
||||
{[...Array(5)].map((_, i) => <div key={i} className="w-72 h-96 bg-surface-tertiary rounded-xl"></div>)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
return view === 'kanban' ? <SkeletonKanbanBoard /> : <SkeletonTable rows={8} cols={6} />
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -342,30 +354,38 @@ export default function PostProduction() {
|
||||
<KanbanBoard posts={filteredPosts} onPostClick={openEdit} onMovePost={handleMovePost} />
|
||||
) : (
|
||||
<div className="bg-white rounded-xl border border-border overflow-hidden">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-border bg-surface-secondary">
|
||||
<th className="text-left px-4 py-3 text-xs font-semibold text-text-tertiary uppercase tracking-wider">{t('posts.postTitle')}</th>
|
||||
<th className="text-left px-4 py-3 text-xs font-semibold text-text-tertiary uppercase tracking-wider">{t('posts.brand')}</th>
|
||||
<th className="text-left px-4 py-3 text-xs font-semibold text-text-tertiary uppercase tracking-wider">{t('posts.status')}</th>
|
||||
<th className="text-left px-4 py-3 text-xs font-semibold text-text-tertiary uppercase tracking-wider">{t('posts.platforms')}</th>
|
||||
<th className="text-left px-4 py-3 text-xs font-semibold text-text-tertiary uppercase tracking-wider">{t('posts.assignTo')}</th>
|
||||
<th className="text-left px-4 py-3 text-xs font-semibold text-text-tertiary uppercase tracking-wider">{t('posts.scheduledDate')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border-light">
|
||||
{filteredPosts.map(post => (
|
||||
<PostCard key={post._id} post={post} onClick={() => openEdit(post)} />
|
||||
))}
|
||||
{filteredPosts.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={6} className="py-12 text-center text-sm text-text-tertiary">
|
||||
{t('posts.noPostsFound')}
|
||||
</td>
|
||||
{filteredPosts.length === 0 ? (
|
||||
<EmptyState
|
||||
icon={FileText}
|
||||
title={posts.length === 0 ? t('posts.noPosts') : t('posts.noPostsFound')}
|
||||
description={posts.length === 0 ? t('posts.createFirstPost') : t('posts.tryDifferentFilter')}
|
||||
actionLabel={posts.length === 0 ? t('posts.createPost') : null}
|
||||
onAction={posts.length === 0 ? openNew : null}
|
||||
secondaryActionLabel={posts.length > 0 ? t('common.clearFilters') : null}
|
||||
onSecondaryAction={() => {
|
||||
setFilters({ brand: '', platform: '', assignedTo: '', campaign: '' })
|
||||
setSearchTerm('')
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-border bg-surface-secondary">
|
||||
<th className="text-left px-4 py-3 text-xs font-semibold text-text-tertiary uppercase tracking-wider">{t('posts.postTitle')}</th>
|
||||
<th className="text-left px-4 py-3 text-xs font-semibold text-text-tertiary uppercase tracking-wider">{t('posts.brand')}</th>
|
||||
<th className="text-left px-4 py-3 text-xs font-semibold text-text-tertiary uppercase tracking-wider">{t('posts.status')}</th>
|
||||
<th className="text-left px-4 py-3 text-xs font-semibold text-text-tertiary uppercase tracking-wider">{t('posts.platforms')}</th>
|
||||
<th className="text-left px-4 py-3 text-xs font-semibold text-text-tertiary uppercase tracking-wider">{t('posts.assignTo')}</th>
|
||||
<th className="text-left px-4 py-3 text-xs font-semibold text-text-tertiary uppercase tracking-wider">{t('posts.scheduledDate')}</th>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border-light">
|
||||
{filteredPosts.map(post => (
|
||||
<PostCard key={post._id} post={post} onClick={() => openEdit(post)} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -738,8 +758,8 @@ export default function PostProduction() {
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={!formData.title}
|
||||
className="px-5 py-2 bg-brand-primary text-white rounded-lg text-sm font-medium hover:bg-brand-primary-light disabled:opacity-50 disabled:cursor-not-allowed shadow-sm"
|
||||
disabled={!formData.title || saving}
|
||||
className={`px-5 py-2 bg-brand-primary text-white rounded-lg text-sm font-medium hover:bg-brand-primary-light disabled:opacity-50 disabled:cursor-not-allowed shadow-sm ${saving ? 'btn-loading' : ''}`}
|
||||
>
|
||||
{editingPost ? t('posts.saveChanges') : t('posts.createPost')}
|
||||
</button>
|
||||
@@ -759,11 +779,13 @@ export default function PostProduction() {
|
||||
if (editingPost) {
|
||||
try {
|
||||
await api.delete(`/posts/${editingPost._id || editingPost.id}`)
|
||||
toast.success(t('posts.deleted'))
|
||||
setShowModal(false)
|
||||
setEditingPost(null)
|
||||
loadPosts()
|
||||
} catch (err) {
|
||||
console.error('Delete failed:', err)
|
||||
toast.error(t('common.deleteFailed'))
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user