|
|
|
@@ -3,7 +3,7 @@ import { Pencil, Save, X, Plus, Trash2, Link2 } from 'lucide-react';
|
|
|
|
|
import { useUi } from '../../context/ui-context.jsx';
|
|
|
|
|
import { useAsync, useAction } from '../../hooks/useAsync.js';
|
|
|
|
|
import { updateActivity, addDependency, removeDependency } from '../../services/activities.js';
|
|
|
|
|
import { listSubtasks, createSubtask, setSubtaskDone, deleteSubtask } from '../../services/subtasks.js';
|
|
|
|
|
import { listTasks, createTask, setTaskDone, deleteTask } from '../../services/tasks.js';
|
|
|
|
|
import { listCatalog } from '../../services/catalogs.js';
|
|
|
|
|
import { listUsers } from '../../services/users.js';
|
|
|
|
|
import { Button } from '../../components/Button.jsx';
|
|
|
|
@@ -29,17 +29,17 @@ export default function ActivityDetailPanel({ activity, projectId, siblingActivi
|
|
|
|
|
const { data: priorities } = useAsync(() => listCatalog('priorities'), []);
|
|
|
|
|
const { data: statuses } = useAsync(() => listCatalog('activity-statuses'), []);
|
|
|
|
|
const { data: users } = useAsync(listUsers, []);
|
|
|
|
|
const { data: subtasks, reload: reloadSubtasks } = useAsync(() => listSubtasks(activity.id), [activity.id]);
|
|
|
|
|
const { data: tasks, reload: reloadTasks } = useAsync(() => listTasks(activity.id), [activity.id]);
|
|
|
|
|
|
|
|
|
|
const [tab, setTab] = useState('detail');
|
|
|
|
|
const [editing, setEditing] = useState(false);
|
|
|
|
|
const [form, setForm] = useState(() => toForm(activity));
|
|
|
|
|
const [newSubtask, setNewSubtask] = useState('');
|
|
|
|
|
const [newTask, setNewTask] = useState('');
|
|
|
|
|
const [addingDep, setAddingDep] = useState('');
|
|
|
|
|
|
|
|
|
|
useEffect(() => { setForm(toForm(activity)); setEditing(false); }, [activity]);
|
|
|
|
|
|
|
|
|
|
const hasSubtasks = subtasks && subtasks.length > 0;
|
|
|
|
|
const hasTasks = tasks && tasks.length > 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 }));
|
|
|
|
@@ -56,22 +56,22 @@ export default function ActivityDetailPanel({ activity, projectId, siblingActivi
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function addSubtaskItem() {
|
|
|
|
|
if (!newSubtask.trim()) return;
|
|
|
|
|
async function addTaskItem() {
|
|
|
|
|
if (!newTask.trim()) return;
|
|
|
|
|
await runAction(async () => {
|
|
|
|
|
await createSubtask(activity.id, { name: newSubtask.trim() });
|
|
|
|
|
setNewSubtask('');
|
|
|
|
|
await reloadSubtasks();
|
|
|
|
|
await createTask(activity.id, { name: newTask.trim() });
|
|
|
|
|
setNewTask('');
|
|
|
|
|
await reloadTasks();
|
|
|
|
|
onChanged();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function toggleSubtask(s) {
|
|
|
|
|
await runAction(async () => { await setSubtaskDone(s.id, !s.is_done); await reloadSubtasks(); onChanged(); });
|
|
|
|
|
async function toggleTask(task) {
|
|
|
|
|
await runAction(async () => { await setTaskDone(task.id, !task.is_done); await reloadTasks(); onChanged(); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function removeSubtaskItem(s) {
|
|
|
|
|
await runAction(async () => { await deleteSubtask(s.id); await reloadSubtasks(); onChanged(); });
|
|
|
|
|
async function removeTaskItem(task) {
|
|
|
|
|
await runAction(async () => { await deleteTask(task.id); await reloadTasks(); onChanged(); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function addDep() {
|
|
|
|
@@ -88,7 +88,7 @@ export default function ActivityDetailPanel({ activity, projectId, siblingActivi
|
|
|
|
|
<Tabs
|
|
|
|
|
tabs={[
|
|
|
|
|
{ key: 'detail', label: t('tab_info') },
|
|
|
|
|
{ key: 'subtasks', label: t('subtasks_title') },
|
|
|
|
|
{ key: 'tasks', label: t('tasks_title') },
|
|
|
|
|
{ key: 'dependencies', label: t('activity_dependencies') },
|
|
|
|
|
{ key: 'comments', label: t('tab_comments') },
|
|
|
|
|
{ key: 'documents', label: t('tab_documents') },
|
|
|
|
@@ -167,7 +167,7 @@ export default function ActivityDetailPanel({ activity, projectId, siblingActivi
|
|
|
|
|
{t('activity_milestone')}
|
|
|
|
|
</label>
|
|
|
|
|
</Field>
|
|
|
|
|
{!hasSubtasks && (
|
|
|
|
|
{!hasTasks && (
|
|
|
|
|
<Field label={t('activity_progress')}>
|
|
|
|
|
<input className="input" type="number" min={0} max={100} value={form.progressPct} onChange={(e) => setForm((f) => ({ ...f, progressPct: e.target.value }))} />
|
|
|
|
|
</Field>
|
|
|
|
@@ -178,19 +178,19 @@ export default function ActivityDetailPanel({ activity, projectId, siblingActivi
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{tab === 'subtasks' && (
|
|
|
|
|
{tab === 'tasks' && (
|
|
|
|
|
<div>
|
|
|
|
|
{(subtasks || []).map((s) => (
|
|
|
|
|
<div key={s.id} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 0' }}>
|
|
|
|
|
<input type="checkbox" checked={!!s.is_done} onChange={() => toggleSubtask(s)} />
|
|
|
|
|
<span style={{ flex: 1, fontSize: '.8rem', textDecoration: s.is_done ? 'line-through' : 'none', opacity: s.is_done ? .6 : 1 }}>{s.name}</span>
|
|
|
|
|
<button className="icon-btn" onClick={() => removeSubtaskItem(s)}><Trash2 size={15} /></button>
|
|
|
|
|
{(tasks || []).map((task) => (
|
|
|
|
|
<div key={task.id} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 0' }}>
|
|
|
|
|
<input type="checkbox" checked={!!task.is_done} onChange={() => toggleTask(task)} />
|
|
|
|
|
<span style={{ flex: 1, fontSize: '.8rem', textDecoration: task.is_done ? 'line-through' : 'none', opacity: task.is_done ? .6 : 1 }}>{task.name}</span>
|
|
|
|
|
<button className="icon-btn" onClick={() => removeTaskItem(task)}><Trash2 size={15} /></button>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
{(!subtasks || subtasks.length === 0) && <div className="text-muted" style={{ fontSize: '.78rem' }}>{t('common_empty')}</div>}
|
|
|
|
|
{(!tasks || tasks.length === 0) && <div className="text-muted" style={{ fontSize: '.78rem' }}>{t('common_empty')}</div>}
|
|
|
|
|
<div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
|
|
|
|
|
<input className="input" value={newSubtask} placeholder={t('subtask_new')} onChange={(e) => setNewSubtask(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && addSubtaskItem()} />
|
|
|
|
|
<Button variant="standard" icon={Plus} onClick={addSubtaskItem}>{t('common_new')}</Button>
|
|
|
|
|
<input className="input" value={newTask} placeholder={t('task_new')} onChange={(e) => setNewTask(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && addTaskItem()} />
|
|
|
|
|
<Button variant="standard" icon={Plus} onClick={addTaskItem}>{t('common_new')}</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|