import { useState } from 'react'; import { Plus, Save, Trash2 } from 'lucide-react'; import { useUi } from '../../context/ui-context.jsx'; import { useAuth } from '../../context/auth-context.jsx'; import { useAsync, useAction } from '../../hooks/useAsync.js'; import { listActivitiesByProject, createActivity, getActivity, deleteActivity, reorderActivities } from '../../services/activities.js'; import { listCatalog } from '../../services/catalogs.js'; import { listUsers } from '../../services/users.js'; import { ListView } from '../../components/ListView.jsx'; import { SearchInput } from '../../components/SearchInput.jsx'; import { Button } from '../../components/Button.jsx'; import { Field, FieldRow } from '../../components/Field.jsx'; import { ComboBox } from '../../components/ComboBox.jsx'; import { StatusBadge, ProgressBar, DueBadge } from '../../components/StatusBadge.jsx'; import { dueStatus } from '../../utils/dueStatus.js'; import { DetailPopup } from '../../components/DetailPopup.jsx'; import ActivityDetailPanel from './ActivityDetailPanel.jsx'; const EMPTY_FORM = { name: '', description: '', responsibleUserId: '', priorityId: '', statusId: '', startDate: '', dueDate: '', isMilestone: false }; export default function ProjectActivitiesTab({ projectId }) { const { t, pushToast, openDialog } = useUi(); const { user: currentUser } = useAuth(); const runAction = useAction(); const { data: activities, reload } = useAsync(() => listActivitiesByProject(projectId), [projectId]); const { data: priorities } = useAsync(() => listCatalog('priorities'), []); const { data: statuses } = useAsync(() => listCatalog('activity-statuses'), []); const { data: users } = useAsync(listUsers, []); const [search, setSearch] = useState(''); const [creating, setCreating] = useState(false); const [form, setForm] = useState(EMPTY_FORM); const [selected, setSelected] = useState(null); 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 })); async function save() { if (!form.name.trim() || !form.priorityId || !form.statusId) return; await runAction(async () => { await createActivity(projectId, { ...form, responsibleUserId: form.responsibleUserId || null }); setCreating(false); setForm(EMPTY_FORM); pushToast({ tone: 'success', message: t('common_saved') }); await reload(); }); } async function openActivity(row) { const full = await getActivity(row.id); setSelected(full); } async function refreshAll() { await reload(); if (selected) setSelected(await getActivity(selected.id)); } async function handleReorder(ids) { await reorderActivities(projectId, ids); await reload(); } function confirmDelete(row) { openDialog({ tone: 'warning', title: t('common_confirm_delete_title'), message: t('common_confirm_delete_msg'), actions: [ { label: t('common_cancel'), variant: 'standard' }, { label: t('common_delete'), variant: 'danger', onClick: () => runAction(async () => { await deleteActivity(row.id); await reload(); pushToast({ tone: 'success', message: t('common_saved') }); }), }, ], }); } const columns = [ { key: 'code', label: t('activity_code') }, { key: 'name', label: t('common_name') }, { key: 'responsible_name', label: t('common_responsible'), render: (r) => r.responsible_name || '—' }, { key: 'status_name', label: t('common_status'), render: (r) => }, { key: 'priority_name', label: t('common_priority'), render: (r) => }, { key: 'due_date', label: t('activity_due_date'), render: (r) => (
{r.due_date || '—'}
) }, { key: 'progress_pct', label: t('activity_progress'), render: (r) =>
}, { key: 'updated_at', label: t('activity_updated_at'), render: (r) => {String(r.updated_at).slice(0, 16)} }, ]; if (!activities) return
{t('common_loading')}
; return (
`${a.code} ${a.name} ${a.responsible_name || ''}`} getRowKey={(a) => a.id} onRowClick={openActivity} onReorder={currentUser?.roleCode !== 'viewer' ? handleReorder : undefined} rowActions={currentUser?.roleCode !== 'viewer' ? (a) => [ { label: t('common_delete'), icon: Trash2, danger: true, onClick: () => confirmDelete(a) }, ] : undefined} /> setCreating(false)} width={640} height={560}> setForm((f) => ({ ...f, name: e.target.value }))} autoFocus />