implemented a marked as complete button
This commit is contained in:
@@ -134,6 +134,17 @@ function App() {
|
|||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const completeTask = async (taskId) => {
|
||||||
|
try {
|
||||||
|
await api.updateTask(taskId, { status: 'closed' });
|
||||||
|
await api.addAudit({ actor: meId, action: 'task_completed', summary: 'Marked task as completed', target: taskId });
|
||||||
|
setOpenTaskId(null);
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
alert("Failed to complete task: " + e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const dismissHU = (id) => setHeadsUp(h => h.filter(x => x.id !== id));
|
const dismissHU = (id) => setHeadsUp(h => h.filter(x => x.id !== id));
|
||||||
const openTaskFromAnywhere = (id) => { setOpenTaskId(id); setShowLogs(false); };
|
const openTaskFromAnywhere = (id) => { setOpenTaskId(id); setShowLogs(false); };
|
||||||
|
|
||||||
@@ -191,7 +202,7 @@ function App() {
|
|||||||
|
|
||||||
<AddTaskModal open={!!adding} onClose={() => setAdding(null)} onSubmit={addTask} defaultAssignee={adding} me={me} />
|
<AddTaskModal open={!!adding} onClose={() => setAdding(null)} onSubmit={addTask} defaultAssignee={adding} me={me} />
|
||||||
{mappedOpenTask && (
|
{mappedOpenTask && (
|
||||||
<TaskDetail task={mappedOpenTask} allAudit={frontendAudit} onClose={() => setOpenTaskId(null)} onMove={moveTask} onPriority={setPriority} />
|
<TaskDetail task={mappedOpenTask} allAudit={frontendAudit} onClose={() => setOpenTaskId(null)} onMove={moveTask} onPriority={setPriority} onComplete={() => completeTask(mappedOpenTask.id)} />
|
||||||
)}
|
)}
|
||||||
{showLogs && (
|
{showLogs && (
|
||||||
<Modal title="Audit log" onClose={() => setShowLogs(false)} wide>
|
<Modal title="Audit log" onClose={() => setShowLogs(false)} wide>
|
||||||
|
|||||||
+17
-4
@@ -163,7 +163,7 @@ function HeadsUp({ items, onDismiss, onOpenTask }) {
|
|||||||
|
|
||||||
function OverviewScreen({ tasks, onOpen, onAddFor, density, onMoveTask }) {
|
function OverviewScreen({ tasks, onOpen, onAddFor, density, onMoveTask }) {
|
||||||
const byUser = Object.fromEntries(USERS.map(u => [u.id, []]));
|
const byUser = Object.fromEntries(USERS.map(u => [u.id, []]));
|
||||||
tasks.forEach(t => { if (byUser[t.assignee]) byUser[t.assignee].push(t); });
|
tasks.forEach(t => { if (byUser[t.assignee] && t.status !== 'closed') byUser[t.assignee].push(t); });
|
||||||
const [draggingTask, setDraggingTask] = React.useState(null);
|
const [draggingTask, setDraggingTask] = React.useState(null);
|
||||||
const [dragOverCol, setDragOverCol] = React.useState(null);
|
const [dragOverCol, setDragOverCol] = React.useState(null);
|
||||||
return (
|
return (
|
||||||
@@ -244,13 +244,14 @@ function UserScreen({ user, tasks, onOpen, onAddFor, density }) {
|
|||||||
const mine = tasks.filter(t => t.assignee === user.id);
|
const mine = tasks.filter(t => t.assignee === user.id);
|
||||||
const open = mine.filter(t => t.status === 'open');
|
const open = mine.filter(t => t.status === 'open');
|
||||||
const flagged = mine.filter(t => t.status === 'unsuccessful' || t.status === 'billing');
|
const flagged = mine.filter(t => t.status === 'unsuccessful' || t.status === 'billing');
|
||||||
|
const closed = mine.filter(t => t.status === 'closed');
|
||||||
return (
|
return (
|
||||||
<div className="user-view">
|
<div className="user-view">
|
||||||
<div className="user-view__hero">
|
<div className="user-view__hero">
|
||||||
<Avatar user={user} size={64} />
|
<Avatar user={user} size={64} />
|
||||||
<div className="user-view__hero-meta">
|
<div className="user-view__hero-meta">
|
||||||
<h1 className="user-view__name">{user.name}</h1>
|
<h1 className="user-view__name">{user.name}</h1>
|
||||||
<p className="user-view__role">{user.role} · <span className="mono">{mine.length} tasks</span></p>
|
<p className="user-view__role">{user.role} · <span className="mono">{open.length + flagged.length} open tasks</span></p>
|
||||||
</div>
|
</div>
|
||||||
<div className="user-view__hero-cta">
|
<div className="user-view__hero-cta">
|
||||||
<button className="btn btn--primary" onClick={() => onAddFor(user.id)}>
|
<button className="btn btn--primary" onClick={() => onAddFor(user.id)}>
|
||||||
@@ -272,6 +273,14 @@ function UserScreen({ user, tasks, onOpen, onAddFor, density }) {
|
|||||||
{open.map(t => <TaskCard key={t.id} task={t} onOpen={onOpen} density={density} />)}
|
{open.map(t => <TaskCard key={t.id} task={t} onOpen={onOpen} density={density} />)}
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
|
{closed.length > 0 && (
|
||||||
|
<Section title="Completed" sub={closed.length + ' tasks'}>
|
||||||
|
<div className="user-view__grid" style={{ opacity: 0.6 }}>
|
||||||
|
{closed.map(t => <TaskCard key={t.id} task={t} onOpen={onOpen} density={density} />)}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -398,7 +407,7 @@ function Modal({ children, onClose, title, eyebrow, wide = false }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TaskDetail({ task, allAudit = [], onClose, onMove, onPriority }) {
|
function TaskDetail({ task, allAudit = [], onClose, onMove, onPriority, onComplete }) {
|
||||||
if (!task) return null;
|
if (!task) return null;
|
||||||
const assignee = findUser(task.assignee);
|
const assignee = findUser(task.assignee);
|
||||||
const author = findUser(task.addedBy);
|
const author = findUser(task.addedBy);
|
||||||
@@ -415,7 +424,7 @@ function TaskDetail({ task, allAudit = [], onClose, onMove, onPriority }) {
|
|||||||
<strong>Auto-marked Unsuccessful.</strong> Two missed bookings detected by the scheduler. Decide on next step or revert.
|
<strong>Auto-marked Unsuccessful.</strong> Two missed bookings detected by the scheduler. Decide on next step or revert.
|
||||||
<div className="detail__alert-actions">
|
<div className="detail__alert-actions">
|
||||||
<button className="btn btn--ghost btn--sm">Revert to Open</button>
|
<button className="btn btn--ghost btn--sm">Revert to Open</button>
|
||||||
<button className="btn btn--soft btn--sm">Close as unsuccessful</button>
|
<button className="btn btn--soft btn--sm" onClick={onComplete}>Close as unsuccessful</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -478,6 +487,10 @@ function TaskDetail({ task, allAudit = [], onClose, onMove, onPriority }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside className="detail__side">
|
<aside className="detail__side">
|
||||||
|
<button className="btn btn--primary btn--full" style={{ marginBottom: '1.5rem' }} onClick={onComplete}>
|
||||||
|
<Icon.Check /> Mark as completed
|
||||||
|
</button>
|
||||||
|
|
||||||
<Field label="Assigned to">
|
<Field label="Assigned to">
|
||||||
<div className="picker">
|
<div className="picker">
|
||||||
{USERS.map(u => (
|
{USERS.map(u => (
|
||||||
|
|||||||
Reference in New Issue
Block a user