Added delete permantly button

This commit is contained in:
NPS Agent
2026-05-11 16:11:20 +09:30
parent 13e92e2b27
commit df2157b6b9
6 changed files with 45 additions and 4 deletions
+2
View File
@@ -52,6 +52,8 @@
8. **Task Reopening:** Added a "Reopen task" button to restore accidentally closed tasks back to the queue. 8. **Task Reopening:** Added a "Reopen task" button to restore accidentally closed tasks back to the queue.
9. **User Management (Settings):** Built backend API endpoints (`POST`, `PATCH`, `DELETE` for `/users`) and wired up the `WorkspaceTab` allowing Admins to manage the team from the UI. 9. **User Management (Settings):** Built backend API endpoints (`POST`, `PATCH`, `DELETE` for `/users`) and wired up the `WorkspaceTab` allowing Admins to manage the team from the UI.
10. **Task Editing:** Implemented inline editing for task descriptions using an active text box state with "Save/Cancel" actions. 10. **Task Editing:** Implemented inline editing for task descriptions using an active text box state with "Save/Cancel" actions.
11. **UI Cleanup:** Removed hardcoded, prototype placeholder notes from the `TaskDetail` modal to prepare for future dynamic notes integration.
12. **Permanent Deletion:** Added a "Delete task permanently" button to the `TaskDetail` sidebar with a confirmation dialog, backed by a new `DELETE /tasks/{id}` API endpoint.
### Phase 3: Advanced Features ### Phase 3: Advanced Features
- **Real-time Notifications:** Explore WebSockets for task assignments. - **Real-time Notifications:** Explore WebSockets for task assignments.
+8
View File
@@ -118,6 +118,14 @@ class ApiService {
return data; return data;
} }
async deleteTask(id) {
const data = await this.request(`/tasks/${id}`, {
method: 'DELETE',
});
this.notify();
return data;
}
async addAudit(auditData) { async addAudit(auditData) {
const data = await this.request('/audit', { const data = await this.request('/audit', {
method: 'POST', method: 'POST',
+13 -1
View File
@@ -166,6 +166,18 @@ function App() {
} }
}; };
const deleteTask = async (taskId) => {
try {
const t = tasks.find(x => x.id === taskId);
await api.deleteTask(taskId);
await api.addAudit({ actor: meId, action: 'task_deleted', summary: 'Deleted task' + (t ? ': ' + t.title : ''), target: taskId });
setOpenTaskId(null);
} catch(e) {
console.error(e);
alert("Failed to delete 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); };
@@ -223,7 +235,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} onComplete={() => completeTask(mappedOpenTask.id)} onReopen={() => reopenTask(mappedOpenTask.id)} onEditDesc={(newDesc) => editTaskDesc(mappedOpenTask.id, newDesc)} /> <TaskDetail task={mappedOpenTask} allAudit={frontendAudit} onClose={() => setOpenTaskId(null)} onMove={moveTask} onPriority={setPriority} onComplete={() => completeTask(mappedOpenTask.id)} onReopen={() => reopenTask(mappedOpenTask.id)} onEditDesc={(newDesc) => editTaskDesc(mappedOpenTask.id, newDesc)} onDeleteTask={() => deleteTask(mappedOpenTask.id)} />
)} )}
{showLogs && ( {showLogs && (
<Modal title="Audit log" onClose={() => setShowLogs(false)} wide> <Modal title="Audit log" onClose={() => setShowLogs(false)} wide>
+11 -2
View File
@@ -118,15 +118,24 @@ def update_task(task_id: str, task_update: schemas.TaskUpdate, db: Session = Dep
db_task = db.query(models.Task).filter(models.Task.id == task_id).first() db_task = db.query(models.Task).filter(models.Task.id == task_id).first()
if not db_task: if not db_task:
raise HTTPException(status_code=404, detail="Task not found") raise HTTPException(status_code=404, detail="Task not found")
update_data = task_update.dict(exclude_unset=True) update_data = task_update.dict(exclude_unset=True)
for key, value in update_data.items(): for key, value in update_data.items():
setattr(db_task, key, value) setattr(db_task, key, value)
db.commit() db.commit()
db.refresh(db_task) db.refresh(db_task)
return db_task return db_task
@app.delete("/tasks/{task_id}")
def delete_task(task_id: str, db: Session = Depends(get_db)):
db_task = db.query(models.Task).filter(models.Task.id == task_id).first()
if not db_task:
raise HTTPException(status_code=404, detail="Task not found")
db.delete(db_task)
db.commit()
return {"message": "Task deleted"}
@app.get("/audit", response_model=List[schemas.AuditLog]) @app.get("/audit", response_model=List[schemas.AuditLog])
def read_audit(db: Session = Depends(get_db)): def read_audit(db: Session = Depends(get_db)):
return db.query(models.AuditLog).order_by(models.AuditLog.at.desc()).all() return db.query(models.AuditLog).order_by(models.AuditLog.at.desc()).all()
BIN
View File
Binary file not shown.
+11 -1
View File
@@ -407,7 +407,7 @@ function Modal({ children, onClose, title, eyebrow, wide = false }) {
); );
} }
function TaskDetail({ task, allAudit = [], onClose, onMove, onPriority, onComplete, onReopen, onEditDesc }) { function TaskDetail({ task, allAudit = [], onClose, onMove, onPriority, onComplete, onReopen, onEditDesc, onDeleteTask }) {
const [editingDesc, setEditingDesc] = React.useState(false); const [editingDesc, setEditingDesc] = React.useState(false);
const [descValue, setDescValue] = React.useState(task ? task.description || '' : ''); const [descValue, setDescValue] = React.useState(task ? task.description || '' : '');
@@ -557,6 +557,16 @@ function TaskDetail({ task, allAudit = [], onClose, onMove, onPriority, onComple
<Field label="Reminder"> <Field label="Reminder">
<div className="meta-row">Thu, May 7 · 4:00 pm</div> <div className="meta-row">Thu, May 7 · 4:00 pm</div>
</Field> </Field>
<div style={{ marginTop: '2rem', paddingTop: '1.5rem', borderTop: '1px solid var(--border-subtle)' }}>
<button
className="btn btn--ghost btn--full"
style={{ color: 'var(--prio-high-dot)', borderColor: 'transparent' }}
onClick={() => { if (confirm('Are you sure you want to permanently delete this task?')) onDeleteTask(); }}
>
<Icon.Close /> Delete task permanently
</button>
</div>
</aside> </aside>
</div> </div>
</Modal> </Modal>