import { useEffect, useState } from 'react'; import { Pencil, Save, X, Plus, Trash2, Link2, Repeat, GripVertical } from 'lucide-react'; import { useUi } from '../../context/ui-context.jsx'; import { useAsync, useAction } from '../../hooks/useAsync.js'; import { useDragReorder } from '../../hooks/useDragReorder.js'; import { updateActivity, addDependency, removeDependency } from '../../services/activities.js'; import { listTasks, createTask, updateTask, setTaskDone, deleteTask, reorderTasks } from '../../services/tasks.js'; import { promoteTaskToRecurring } from '../../services/recurringTasks.js'; import { listCatalog } from '../../services/catalogs.js'; import { listUsers } from '../../services/users.js'; import { listComments } from '../../services/comments.js'; import { listAttachments } from '../../services/attachments.js'; import { Button } from '../../components/Button.jsx'; import { Field, FieldRow } from '../../components/Field.jsx'; import { ComboBox } from '../../components/ComboBox.jsx'; import { StatusBadge, ProgressBar } from '../../components/StatusBadge.jsx'; import { Tabs } from '../../components/Tabs.jsx'; import { CommentsPanel } from '../../components/CommentsPanel.jsx'; import { AttachmentsPanel } from '../../components/AttachmentsPanel.jsx'; import { DetailPopup } from '../../components/DetailPopup.jsx'; const HOUR_OPTIONS = Array.from({ length: 24 }, (_, h) => ({ value: h, label: `${String(h).padStart(2, '0')}:00` })); function toForm(a) { return { name: a.name, description: a.description || '', responsibleUserId: a.responsible_user_id || '', priorityId: a.priority_id, statusId: a.status_id, startDate: a.start_date || '', dueDate: a.due_date || '', actualEndDate: a.actual_end_date || '', isMilestone: !!a.is_milestone, estimatedHours: a.estimated_hours || '', progressPct: a.progress_pct, }; } export default function ActivityDetailPanel({ activity, projectId, siblingActivities, onChanged }) { const { t, pushToast } = useUi(); const runAction = useAction(); const { data: priorities } = useAsync(() => listCatalog('priorities'), []); const { data: statuses } = useAsync(() => listCatalog('activity-statuses'), []); const { data: users } = useAsync(listUsers, []); const { data: tasks, reload: reloadTasks } = useAsync(() => listTasks(activity.id), [activity.id]); const { order: orderedTasks, getHandleProps, getRowProps, isDragging } = useDragReorder(tasks || [], async (ids) => { await reorderTasks(activity.id, ids); await reloadTasks(); }); const [tab, setTab] = useState('detail'); // Recargadas al cambiar de pestaña (listas livianas) para que los contadores de las pestañas // se mantengan al dia sin necesidad de que CommentsPanel/AttachmentsPanel avisen al padre. const { data: comments } = useAsync(() => listComments('activity', activity.id), [activity.id, tab]); const { data: attachments } = useAsync(() => listAttachments('activity', activity.id), [activity.id, tab]); const [editing, setEditing] = useState(false); const [form, setForm] = useState(() => toForm(activity)); const [newTask, setNewTask] = useState(''); const [editingTaskId, setEditingTaskId] = useState(null); const [editingTaskName, setEditingTaskName] = useState(''); const [addingDep, setAddingDep] = useState(''); const [promoting, setPromoting] = useState(null); const [promoteForm, setPromoteForm] = useState(null); useEffect(() => { setForm(toForm(activity)); setEditing(false); }, [activity]); const hasTasks = tasks && tasks.length > 0; const totalTasksCount = (tasks || []).length; const pendingTasksCount = (tasks || []).filter((task) => !task.is_done).length; // "pendientes/total" (ej. "2/3") en vez de un numero solo — cambia solo cuando se agregan, // quitan o marcan/desmarcan tareas. const tasksTabCount = totalTasksCount > 0 ? `${pendingTasksCount}/${totalTasksCount}` : 0; const priorityOptions = (priorities || []).map((p) => ({ value: p.id, label: p.name })); const statusOptions = (statuses || []).map((s) => ({ value: s.id, label: s.name })); const userOptions = (users || []).map((u) => ({ value: u.id, label: u.name })); const depOptions = (siblingActivities || []) .filter((a) => a.id !== activity.id && !activity.dependencies?.some((d) => d.id === a.id)) .map((a) => ({ value: a.id, label: `${a.code} — ${a.name}` })); async function save() { await runAction(async () => { await updateActivity(activity.id, { ...form, responsibleUserId: form.responsibleUserId || null, estimatedHours: form.estimatedHours || null }); setEditing(false); pushToast({ tone: 'success', message: t('common_saved') }); onChanged(); }); } async function addTaskItem() { if (!newTask.trim()) return; await runAction(async () => { await createTask(activity.id, { name: newTask.trim() }); setNewTask(''); await reloadTasks(); onChanged(); }); } async function toggleTask(task) { await runAction(async () => { await setTaskDone(task.id, !task.is_done); await reloadTasks(); onChanged(); }); } async function removeTaskItem(task) { await runAction(async () => { await deleteTask(task.id); await reloadTasks(); onChanged(); }); } function startEditTask(task) { setEditingTaskId(task.id); setEditingTaskName(task.name); } function cancelEditTask() { setEditingTaskId(null); setEditingTaskName(''); } async function saveEditTask() { if (!editingTaskName.trim()) return; await runAction(async () => { await updateTask(editingTaskId, { name: editingTaskName.trim() }); cancelEditTask(); await reloadTasks(); onChanged(); }); } function startPromote(task) { setPromoting(task); setPromoteForm({ name: task.name, responsibleUserId: task.responsible_user_id || '', intervalUnit: 'months', intervalValue: 1, nextDueDate: task.due_date || '', endDate: '', reminderDaysBefore: 7, reminderHour: 9, }); } async function confirmPromote() { if (!promoteForm.nextDueDate) return; await runAction(async () => { await promoteTaskToRecurring(promoting.id, { ...promoteForm, responsibleUserId: promoteForm.responsibleUserId || null, endDate: promoteForm.endDate || null, }); setPromoting(null); pushToast({ tone: 'success', message: t('common_saved') }); }); } async function addDep() { if (!addingDep) return; await runAction(async () => { await addDependency(activity.id, Number(addingDep)); setAddingDep(''); onChanged(); }); } async function removeDep(depId) { await runAction(async () => { await removeDependency(activity.id, depId); onChanged(); }); } return (
{tab === 'detail' && (
{editing ? (
) : ( )}
{!editing ? (
{activity.responsible_name || '—'}
{activity.description || '—'}
{activity.start_date || '—'}
{activity.due_date || '—'}
{activity.actual_end_date || '—'}
) : (
setForm((f) => ({ ...f, name: e.target.value }))} />