import { useState, useEffect, useRef } from 'react' import { X, Trash2, AlertCircle, Upload, FileText, Star } from 'lucide-react' import { PRIORITY_CONFIG, getBrandColor, api } from '../utils/api' import { useLanguage } from '../i18n/LanguageContext' import CommentsSection from './CommentsSection' import Modal from './Modal' import SlidePanel from './SlidePanel' import CollapsibleSection from './CollapsibleSection' const API_BASE = '/api' export default function TaskDetailPanel({ task, onClose, onSave, onDelete, projects, users, brands }) { const { t } = useLanguage() const fileInputRef = useRef(null) const [form, setForm] = useState({ title: '', description: '', project_id: '', assigned_to: '', priority: 'medium', status: 'todo', start_date: '', due_date: '', }) const [dirty, setDirty] = useState(false) const [saving, setSaving] = useState(false) const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) // Attachments state const [attachments, setAttachments] = useState([]) const [pendingFiles, setPendingFiles] = useState([]) // for create mode (no task ID yet) const [uploading, setUploading] = useState(false) const [maxSizeMB, setMaxSizeMB] = useState(50) const [uploadError, setUploadError] = useState(null) const [currentThumbnail, setCurrentThumbnail] = useState(null) const taskId = task?._id || task?.id const isCreateMode = !taskId useEffect(() => { api.get('/settings/app').then(s => setMaxSizeMB(s.uploadMaxSizeMB || 50)).catch(() => {}) }, []) const taskIdRef = useRef(taskId) useEffect(() => { // Only reset form when switching to a different task (or initial mount) const switched = taskIdRef.current !== taskId taskIdRef.current = taskId if (task && (switched || !form.title)) { setForm({ title: task.title || '', description: task.description || '', project_id: task.project_id || task.projectId || '', assigned_to: task.assigned_to || task.assignedTo || '', priority: task.priority || 'medium', status: task.status || 'todo', start_date: task.start_date || task.startDate || '', due_date: task.due_date || task.dueDate || '', }) setDirty(isCreateMode) if (switched) setPendingFiles([]) setCurrentThumbnail(task.thumbnail || null) if (!isCreateMode) loadAttachments() } }, [task]) if (!task) return null const dueDate = task.due_date || task.dueDate const isOverdue = dueDate && new Date(dueDate) < new Date() && task.status !== 'done' const creatorName = task.creator_user_name || task.creatorUserName const priority = PRIORITY_CONFIG[form.priority] || PRIORITY_CONFIG.medium const statusOptions = [ { value: 'todo', label: t('tasks.todo') }, { value: 'in_progress', label: t('tasks.in_progress') }, { value: 'done', label: t('tasks.done') }, ] const priorityOptions = [ { value: 'low', label: t('tasks.priority.low') }, { value: 'medium', label: t('tasks.priority.medium') }, { value: 'high', label: t('tasks.priority.high') }, { value: 'urgent', label: t('tasks.priority.urgent') }, ] const update = (field, value) => { setForm(f => ({ ...f, [field]: value })) setDirty(true) } const handleSave = async () => { setSaving(true) try { const data = { title: form.title, description: form.description, project_id: form.project_id || null, assigned_to: form.assigned_to || null, priority: form.priority, status: form.status, start_date: form.start_date || null, due_date: form.due_date || null, } await onSave(isCreateMode ? null : taskId, data, pendingFiles) setDirty(false) setPendingFiles([]) if (isCreateMode) onClose() } finally { setSaving(false) } } const handleDelete = () => { setShowDeleteConfirm(true) } const confirmDelete = () => { onDelete(taskId) setShowDeleteConfirm(false) onClose() } // ─── Attachments ────────────────────────────── async function loadAttachments() { if (!taskId) return try { const data = await api.get(`/tasks/${taskId}/attachments`) setAttachments(Array.isArray(data) ? data : (data.data || [])) } catch { setAttachments([]) } } const handleFileUpload = async (files) => { if (!files?.length) return setUploadError(null) const maxBytes = maxSizeMB * 1024 * 1024 const tooBig = Array.from(files).find(f => f.size > maxBytes) if (tooBig) { setUploadError(t('tasks.fileTooLarge') .replace('{name}', tooBig.name) .replace('{size}', (tooBig.size / 1024 / 1024).toFixed(1)) .replace('{max}', maxSizeMB)) return } setUploading(true) for (const file of files) { const fd = new FormData() fd.append('file', file) try { await api.upload(`/tasks/${taskId}/attachments`, fd) } catch (err) { console.error('Upload failed:', err) setUploadError(err.message || 'Upload failed') } } setUploading(false) loadAttachments() } const handleDeleteAttachment = async (attId) => { try { await api.delete(`/task-attachments/${attId}`) loadAttachments() } catch (err) { console.error('Delete attachment failed:', err) } } const handleSetThumbnail = async (attachment) => { try { const attId = attachment._id || attachment.id || attachment.Id await api.patch(`/tasks/${taskId}/thumbnail`, { attachment_id: attId }) const url = attachment.url || `/api/uploads/${attachment.filename}` setCurrentThumbnail(url) } catch (err) { console.error('Set thumbnail failed:', err) } } const handleRemoveThumbnail = async () => { try { await api.patch(`/tasks/${taskId}/thumbnail`, { attachment_id: null }) setCurrentThumbnail(null) } catch (err) { console.error('Remove thumbnail failed:', err) } } // Get brand for the selected project const selectedProject = projects?.find(p => String(p._id || p.id) === String(form.project_id)) const brandName = selectedProject ? (selectedProject.brand_name || selectedProject.brandName) : (task.brand_name || task.brandName) const header = (
{/* Thumbnail banner */} {currentThumbnail && (
)}
update('title', e.target.value)} className="w-full text-lg font-semibold text-text-primary bg-transparent border-0 p-0 focus:outline-none focus:ring-0" placeholder={t('tasks.taskTitle')} />
{priorityOptions.find(p => p.value === form.priority)?.label} {statusOptions.find(s => s.value === form.status)?.label} {isOverdue && !isCreateMode && ( {t('tasks.overdue')} )}
) return ( <> {/* Details Section */}
{/* Description */}