diff --git a/client/src/components/ApproverMultiSelect.jsx b/client/src/components/ApproverMultiSelect.jsx index 1a4206c..ffb9bfb 100644 --- a/client/src/components/ApproverMultiSelect.jsx +++ b/client/src/components/ApproverMultiSelect.jsx @@ -3,6 +3,7 @@ import { Check, ChevronDown, X } from 'lucide-react' export default function ApproverMultiSelect({ users = [], selected = [], onChange }) { const [open, setOpen] = useState(false) + const [dropUp, setDropUp] = useState(false) const wrapperRef = useRef(null) // Close dropdown when clicking outside @@ -17,6 +18,14 @@ export default function ApproverMultiSelect({ users = [], selected = [], onChang return () => document.removeEventListener('mousedown', handleClick) }, [open]) + // Detect if dropdown should open upward + useEffect(() => { + if (!open || !wrapperRef.current) return + const rect = wrapperRef.current.getBoundingClientRect() + const spaceBelow = window.innerHeight - rect.bottom + setDropUp(spaceBelow < 220) + }, [open]) + const toggle = (userId) => { const id = String(userId) const next = selected.includes(id) ? selected.filter(s => s !== id) : [...selected, id] @@ -58,7 +67,7 @@ export default function ApproverMultiSelect({ users = [], selected = [], onChang {open && ( -
+
{users.map(u => { const uid = String(u._id || u.id || u.Id) const isSelected = selected.includes(uid) diff --git a/client/src/components/ArtefactDetailPanel.jsx b/client/src/components/ArtefactDetailPanel.jsx index b79d3a6..12d26e3 100644 --- a/client/src/components/ArtefactDetailPanel.jsx +++ b/client/src/components/ArtefactDetailPanel.jsx @@ -1,10 +1,10 @@ import { useState, useEffect, useContext } from 'react' -import { Plus, Copy, Check, ExternalLink, Upload, Globe, Trash2, FileText, Image as ImageIcon, Film, Sparkles, MessageSquare, Save } from 'lucide-react' +import { Plus, Copy, Check, ExternalLink, Upload, Globe, Trash2, FileText, Image as ImageIcon, Film, Sparkles, MessageSquare, Save, FileEdit, Layers, ShieldCheck } from 'lucide-react' import { AppContext } from '../App' import { useLanguage } from '../i18n/LanguageContext' import { api } from '../utils/api' import Modal from './Modal' -import SlidePanel from './SlidePanel' +import TabbedModal from './TabbedModal' import { useToast } from './ToastContainer' import ArtefactVersionTimeline from './ArtefactVersionTimeline' import ApproverMultiSelect from './ApproverMultiSelect' @@ -42,6 +42,7 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel const [submitting, setSubmitting] = useState(false) const [reviewUrl, setReviewUrl] = useState('') const [copied, setCopied] = useState(false) + const [activeTab, setActiveTab] = useState('details') // Editable fields const [editTitle, setEditTitle] = useState(artefact.title || '') @@ -339,418 +340,467 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel return fileId ? `https://drive.google.com/file/d/${fileId}/preview` : url } + 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 }, + ] + if (loading) { return ( - +
-
+ ) } - const TypeIcon = TYPE_ICONS[artefact.type] || Sparkles - return ( - -
-
- -
-
- 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} - + <> + +
+
+ +
+
+ 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} + + )} +
+
+
+ + } + tabs={tabs} + activeTab={activeTab} + onTabChange={setActiveTab} + footer={ + <> +
+ {onDelete && ( + )}
-
-
- {onDelete && ( - - )} -
-
-
- }> -
- {/* Description */} -
-

{t('artefacts.descriptionLabel')}

-