diff --git a/client/src/components/ArtefactDetailPanel.jsx b/client/src/components/ArtefactDetailPanel.jsx index 9c3b02c..af465cc 100644 --- a/client/src/components/ArtefactDetailPanel.jsx +++ b/client/src/components/ArtefactDetailPanel.jsx @@ -24,6 +24,10 @@ const TYPE_ICONS = { other: Sparkles, } +const parseApproverIds = (a) => + a.approvers?.map(u => String(u.id)) || + (a.approver_ids ? a.approver_ids.split(',').map(s => s.trim()).filter(Boolean) : []) + export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDelete, assignableUsers = [] }) { const { t } = useLanguage() const { brands } = useContext(AppContext) @@ -35,14 +39,12 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel const [submitting, setSubmitting] = useState(false) const [freshReviewUrl, setFreshReviewUrl] = useState('') const [copied, setCopied] = useState(false) - const [activeTab, setActiveTab] = useState('details') + const [activeTab, setActiveTab] = useState(artefact.type === 'copy' ? 'versions' : 'details') - // Editable fields + // Editable fields — seeded from artefact prop; component is keyed by artefact._id at call site const [editTitle, setEditTitle] = useState(artefact.title || '') const [editDescription, setEditDescription] = useState(artefact.description || '') - const [editApproverIds, setEditApproverIds] = useState( - artefact.approvers?.map(a => String(a.id)) || (artefact.approver_ids ? artefact.approver_ids.split(',').map(s => s.trim()).filter(Boolean) : []) - ) + const [editApproverIds, setEditApproverIds] = useState(() => parseApproverIds(artefact)) const reviewUrl = freshReviewUrl || (artefact.approval_token ? `${window.location.origin}/review/${artefact.approval_token}` : '') const [savingDraft, setSavingDraft] = useState(false) const [deleting, setDeleting] = useState(false) @@ -61,14 +63,6 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel loadVersions() }, [artefact.Id]) - useEffect(() => { - setEditTitle(artefact.title || '') - setEditDescription(artefact.description || '') - setEditApproverIds( - artefact.approvers?.map(a => String(a.id)) || (artefact.approver_ids ? artefact.approver_ids.split(',').map(s => s.trim()).filter(Boolean) : []) - ) - }, [artefact.Id]) - const loadVersions = async () => { try { const res = await api.get(`/artefacts/${artefact.Id}/versions`) @@ -109,13 +103,6 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel loadVersionData(version.Id) } - const handleCreateVersion = async ({ notes, copy_from_previous }) => { - await api.post(`/artefacts/${artefact.Id}/versions`, { notes, copy_from_previous }) - toast.success(t('artefacts.versionCreated')) - loadVersions() - onUpdate() - } - const handleAddLanguage = async (languageForm) => { await api.post(`/artefacts/${artefact.Id}/versions/${selectedVersion.Id}/texts`, languageForm) toast.success(t('artefacts.languageAdded')) @@ -249,6 +236,12 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel } } + const handleUpdateLanguage = async (textId, content) => { + await api.patch(`/artefact-version-texts/${textId}`, { content }) + toast.success(t('artefacts.languageAdded')) + loadVersionData(selectedVersion.Id) + } + const handleDeleteArtefact = async () => { setDeleting(true) try { @@ -282,10 +275,10 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel const TypeIcon = TYPE_ICONS[artefact.type] || Sparkles const tabs = [ - { key: 'details', label: t('artefacts.details') || 'Details', icon: FileEdit }, - { key: 'versions', label: t('artefacts.versions') || 'Versions', icon: Layers, badge: versions.length }, - { key: 'discussion', label: t('artefacts.comments') || 'Discussion', icon: MessageSquare, badge: comments.length }, - { key: 'review', label: t('artefacts.review') || 'Review', icon: ShieldCheck }, + { key: 'details', label: t('artefacts.details'), icon: FileEdit }, + { key: 'versions', label: t('artefacts.versions'), icon: Layers, badge: versions.length }, + { key: 'discussion', label: t('artefacts.comments'), icon: MessageSquare, badge: comments.length }, + { key: 'review', label: t('artefacts.review'), icon: ShieldCheck }, ] if (loading) { @@ -304,32 +297,30 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel onClose={onClose} size="xl" header={ - <> -
-
- -
-
- setEditTitle(e.target.value)} - className="w-full text-lg font-semibold text-text-primary bg-transparent border-0 border-b border-transparent hover:border-border focus:border-brand-primary focus:outline-none focus:ring-0 px-0 py-0.5 transition-colors" - /> -
- - {artefact.status?.replace('_', ' ')} +
+
+ +
+
+ setEditTitle(e.target.value)} + className="w-full text-lg font-semibold text-text-primary bg-transparent border-0 border-b border-transparent hover:border-border focus:border-brand-primary focus:outline-none focus:ring-0 px-0 py-0.5 transition-colors" + /> +
+ + {artefact.status?.replace('_', ' ')} + + {artefact.type} + {artefact.creator_name && ( + + {t('review.createdBy')} {artefact.creator_name} - {artefact.type} - {artefact.creator_name && ( - - {t('review.createdBy')} {artefact.creator_name} - - )} -
+ )}
- +
} tabs={tabs} activeTab={activeTab} @@ -349,15 +340,17 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel )}
- + {activeTab === 'details' && ( + + )} } > @@ -376,6 +369,32 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel />
+ {/* Metadata row */} +
+ {/* Brand */} + {(artefact.brand_id || artefact.brandId) && ( +
+

{t('posts.brand')}

+

+ {brands.find(b => String(b._id) === String(artefact.brand_id || artefact.brandId))?.name || `#${artefact.brand_id || artefact.brandId}`} +

+
+ )} + {/* Created date */} + {artefact.CreatedAt && ( +
+

{t('common.created')}

+

{new Date(artefact.CreatedAt).toLocaleDateString()}

+
+ )} + {/* Linked post */} + {(artefact.post_id || artefact.postId) && ( +
+

{t('artefacts.linkedPost')}

+

{t('artefacts.post')} #{artefact.post_id || artefact.postId}

+
+ )} +
)} @@ -389,8 +408,8 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel uploading={uploading} uploadProgress={uploadProgress} onSelectVersion={handleSelectVersion} - onCreateVersion={handleCreateVersion} onAddLanguage={handleAddLanguage} + onUpdateLanguage={handleUpdateLanguage} onDeleteLanguage={handleDeleteLanguage} onFileUpload={handleFileUpload} onDeleteAttachment={handleDeleteAttachment} @@ -451,7 +470,7 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel ) : (
- {t('artefacts.selectVersionFirst') || 'Select a version first to view comments.'} + {t('artefacts.selectVersionFirst')}
)} @@ -530,12 +549,10 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel )} - {/* Empty state when no review actions available */} - {!['draft', 'revision_requested', 'rejected'].includes(artefact.status) && !reviewUrl && !artefact.feedback && !(artefact.status === 'approved' && artefact.approved_by_name) && ( + {/* Empty state: pending_review or unknown status with no review info */} + {artefact.status === 'pending_review' && !reviewUrl && !artefact.feedback && (
- {artefact.status === 'pending_review' - ? t('artefacts.pendingReviewInfo') || 'This artefact is currently pending review.' - : t('artefacts.noReviewInfo') || 'No review information available.'} + {t('artefacts.pendingReviewInfo')}
)} diff --git a/client/src/components/ArtefactDetailVersionsTab.jsx b/client/src/components/ArtefactDetailVersionsTab.jsx index e5356f2..f5119a3 100644 --- a/client/src/components/ArtefactDetailVersionsTab.jsx +++ b/client/src/components/ArtefactDetailVersionsTab.jsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import { Plus, Trash2, Globe, Image as ImageIcon } from 'lucide-react' +import { Trash2, Globe, Image as ImageIcon, Pencil } from 'lucide-react' import PortalSelect from './PortalSelect' import UploadZone from './UploadZone' import { useLanguage } from '../i18n/LanguageContext' @@ -21,8 +21,8 @@ export function ArtefactDetailVersionsTab({ uploading, uploadProgress, onSelectVersion, - onCreateVersion, onAddLanguage, + onUpdateLanguage, onDeleteLanguage, onFileUpload, onDeleteAttachment, @@ -31,11 +31,6 @@ export function ArtefactDetailVersionsTab({ }) { const { t } = useLanguage() - const [showNewVersionModal, setShowNewVersionModal] = useState(false) - const [newVersionNotes, setNewVersionNotes] = useState('') - const [copyFromPrevious, setCopyFromPrevious] = useState(false) - const [creatingVersion, setCreatingVersion] = useState(false) - const [showLanguageModal, setShowLanguageModal] = useState(false) const [languageForm, setLanguageForm] = useState({ language_code: '', language_label: '', content: '' }) const [savingLanguage, setSavingLanguage] = useState(false) @@ -43,24 +38,13 @@ export function ArtefactDetailVersionsTab({ const [confirmDeleteLangId, setConfirmDeleteLangId] = useState(null) const [confirmDeleteAttId, setConfirmDeleteAttId] = useState(null) + const [editingLangText, setEditingLangText] = useState(null) // { Id, language_code, language_label, content } + const [editLangContent, setEditLangContent] = useState('') + const [savingEditLang, setSavingEditLang] = useState(false) + const [dragOver, setDragOver] = useState(false) const [driveUrl, setDriveUrl] = useState('') - const handleCreateVersion = async () => { - setCreatingVersion(true) - try { - await onCreateVersion({ - notes: newVersionNotes || `Version ${(versions[versions.length - 1]?.version_number || 0) + 1}`, - copy_from_previous: artefact.type === 'copy' ? copyFromPrevious : false, - }) - setShowNewVersionModal(false) - setNewVersionNotes('') - setCopyFromPrevious(false) - } finally { - setCreatingVersion(false) - } - } - const handleAddLanguage = async () => { if (!languageForm.language_code || !languageForm.language_label || !languageForm.content) return setSavingLanguage(true) @@ -83,6 +67,17 @@ export function ArtefactDetailVersionsTab({ setConfirmDeleteAttId(null) } + const handleSaveEditLang = async () => { + if (!editingLangText || !editLangContent.trim()) return + setSavingEditLang(true) + try { + await onUpdateLanguage(editingLangText.Id, editLangContent) + setEditingLangText(null) + } finally { + setSavingEditLang(false) + } + } + const handleVideoDrop = (e) => { e.preventDefault() setDragOver(false) @@ -101,37 +96,18 @@ export function ArtefactDetailVersionsTab({ return ( <>
- {/* Version Timeline */} -
-
-

{t('artefacts.versions')}

- {artefact.status === 'rejected' && ( - - )} + {/* Version Timeline — only shown when there are multiple rounds */} + {versions.length > 1 && ( +
+

{t('artefacts.versions')}

+
- {artefact.status === 'rejected' && ( -
- {t('artefacts.rejectedMustCreateNewVersion')} -
- )} - {artefact.status === 'revision_requested' && ( -
- {t('artefacts.revisionEditCurrentVersion')} -
- )} - -
+ )} {/* Type-specific content */} {versionData && selectedVersion && ( @@ -145,7 +121,7 @@ export function ArtefactDetailVersionsTab({ onClick={() => setShowLanguageModal(true)} className="flex items-center gap-1 px-3 py-1.5 text-xs font-medium bg-brand-primary text-white rounded-lg hover:bg-brand-primary-light transition-colors" > - + {t('artefacts.addLanguage')}
@@ -161,12 +137,21 @@ export function ArtefactDetailVersionsTab({ {text.language_label}
- +
+ + +
{text.content} @@ -345,43 +330,39 @@ export function ArtefactDetailVersionsTab({
- {/* New Version Modal */} - setShowNewVersionModal(false)} title={t('artefacts.createNewVersion')} size="sm"> + {/* Edit Language Modal */} + setEditingLangText(null)} title={t('artefacts.editLanguage')} size="md">
+ {editingLangText && ( +
+ + {editingLangText.language_code} + + {editingLangText.language_label} +
+ )}
- +