added parent delete popup + endpoint

This commit is contained in:
Louis Heredero 2025-02-02 22:43:52 +01:00
parent 8b0b56cb7b
commit 35d5a81e6f
Signed by: HEL
GPG Key ID: 8D83DE470F8544E7
10 changed files with 160 additions and 5 deletions

View File

@ -30,6 +30,7 @@ urlpatterns = [
path("table/<date:start_date>/<date:end_date>/", views.get_table_data, name="table_data"), path("table/<date:start_date>/<date:end_date>/", views.get_table_data, name="table_data"),
path("table/", views.table_view, name="table"), path("table/", views.table_view, name="table"),
path("parents/new/", views.new_parent_view, name="new_parent"), path("parents/new/", views.new_parent_view, name="new_parent"),
path("parents/<int:id>/on_delete/", views.parent_on_delete_view, name="parent_on_delete"),
path("parents/<int:id>/", views.parent_view, name="parent"), path("parents/<int:id>/", views.parent_view, name="parent"),
path("projects/<int:id>/", views.project_view, name="project"), path("projects/<int:id>/", views.project_view, name="project"),
path("projects/<int:id>/set_parent", views.set_parent, name="set_parent"), path("projects/<int:id>/set_parent", views.set_parent, name="set_parent"),

View File

@ -108,4 +108,8 @@ button, select {
a { a {
color: var(--accent); color: var(--accent);
}
ul {
padding-left: 2em;
} }

View File

@ -72,7 +72,7 @@ function formatPercentage(ratio) {
return percentage + "%" return percentage + "%"
} }
function req(url, options) { function req(url, options = {}) {
let headers = options.headers || {} let headers = options.headers || {}
let csrftoken = document.querySelector("input[name='csrfmiddlewaretoken']").value let csrftoken = document.querySelector("input[name='csrfmiddlewaretoken']").value
headers["X-CSRFToken"] = csrftoken headers["X-CSRFToken"] = csrftoken

View File

@ -21,6 +21,7 @@
display: flex; display: flex;
gap: 0.8em; gap: 0.8em;
flex-direction: column; flex-direction: column;
padding: 0;
} }
.list li { .list li {
@ -34,4 +35,61 @@
.list li .actions { .list li .actions {
margin-left: auto; margin-left: auto;
}
.popup {
background-color: #00000040;
position: fixed;
inset: 0;
display: grid;
place-items: center;
}
.popup:not(.show) {
display: none;
}
.popup .popup-container {
display: flex;
flex-direction: column;
gap: 1em;
padding: 2em;
background-color: var(--dark1);
border-radius: 1em;
width: 100%;
max-width: 40em;
box-shadow: 0 0.2em 0.8em var(--dark3);
}
.popup .popup-container .elmt-name {
text-decoration: underline;
}
.popup .popup-container .actions {
display: flex;
justify-content: space-evenly;
gap: 1em;
}
.popup .popup-container .actions button {
padding: 0.4em 1.2em;
}
.popup .popup-container .actions .cancel {
background-color: var(--light2);
}
.popup .popup-container .actions .cancel:hover {
background-color: var(--light3);
}
.popup .popup-container .actions .delete {
--col: #f95f4b;
color: var(--col);
border-color: var(--col);
background-color: var(--dark1);
}
.popup .popup-container .actions .delete:hover {
background-color: var(--dark2);
} }

View File

@ -1,3 +1,31 @@
function showDeletePopup(id, name) {
let popup = document.getElementById("delete-popup")
popup.dataset.id = id
popup.querySelectorAll(".elmt-name").forEach(elmt => {
elmt.innerText = name
})
popup.classList.add("show")
}
function deleteElement(id) {
let url = window.location.href
if (!url.endsWith("/")) {
url += "/"
}
url += `${id}/`
req(url, {
method: "DELETE"
}).then(res => {
return res.json()
}).then(res => {
if (res.status === "success") {
window.location.reload()
}
})
}
let onBeforeDelete = null
window.addEventListener("load", () => { window.addEventListener("load", () => {
document.querySelector("button.new").addEventListener("click", () => { document.querySelector("button.new").addEventListener("click", () => {
window.location.href = "new/" window.location.href = "new/"
@ -8,5 +36,20 @@ window.addEventListener("load", () => {
row.querySelector("button.edit").addEventListener("click", () => { row.querySelector("button.edit").addEventListener("click", () => {
window.location.href = `${id}/` window.location.href = `${id}/`
}) })
row.querySelector("button.delete")?.addEventListener("click", async () => {
if (onBeforeDelete) {
await onBeforeDelete(row)
}
showDeletePopup(id, row.dataset.name)
})
})
let deletePopup = document.getElementById("delete-popup")
deletePopup.querySelector(".actions .cancel").addEventListener("click", () => {
deletePopup.classList.remove("show")
})
deletePopup.querySelector(".actions .delete").addEventListener("click", () => {
deleteElement(deletePopup.dataset.id)
}) })
}) })

View File

@ -0,0 +1,11 @@
onBeforeDelete = async row => {
await req(`${row.dataset.id}/on_delete/`).then(res => {
return res.json()
}).then(res => {
if (res.status === "success") {
let popup = document.getElementById("delete-popup")
popup.querySelector(".project-count").innerText = res.projects
popup.querySelector(".task-count").innerText = res.tasks
}
})
}

View File

@ -5,7 +5,7 @@ window.addEventListener("load", () => {
selector.addEventListener("change", () => { selector.addEventListener("change", () => {
let fd = new FormData() let fd = new FormData()
fd.set("parent_id", selector.value) fd.set("parent_id", selector.value)
req(`${id}/set_parent`, { req(`${id}/set_parent/`, {
method: "POST", method: "POST",
body: fd body: fd
}) })

View File

@ -10,7 +10,7 @@ from django.views.decorators.http import require_POST
from dispatcher.core import import_tasks from dispatcher.core import import_tasks
from dispatcher.forms import ProjectForm, ImportForm, ParentForm from dispatcher.forms import ProjectForm, ImportForm, ParentForm
from dispatcher.models import Project, Parent, Clocking from dispatcher.models import Project, Parent, Clocking, Task
from dispatcher.serializers import ClockingSerializer from dispatcher.serializers import ClockingSerializer
@ -32,6 +32,11 @@ def projects_view(request):
def parent_view(request, id): def parent_view(request, id):
parent = get_object_or_404(Parent, id=id) parent = get_object_or_404(Parent, id=id)
if request.method == "DELETE":
parent.delete()
return JsonResponse({"status": "success"})
context = { context = {
"class": "parent", "class": "parent",
"id": id "id": id
@ -43,6 +48,17 @@ def parent_view(request, id):
context["form"] = ParentForm(instance=parent) context["form"] = ParentForm(instance=parent)
return render(request, "edit.html", context) return render(request, "edit.html", context)
def parent_on_delete_view(request, id):
parent = get_object_or_404(Parent, id=id)
projects = parent.project_set.count()
tasks = Task.objects.filter(project__parent=parent).count()
return JsonResponse({
"status": "success",
"projects": projects,
"tasks": tasks
})
def new_parent_view(request): def new_parent_view(request):
context = { context = {
"class": "parent" "class": "parent"

View File

@ -6,13 +6,14 @@
{% block extra-head %}{% endblock %} {% block extra-head %}{% endblock %}
{% endblock %} {% endblock %}
{% block main %} {% block main %}
{% csrf_token %}
<div class="list-wrapper"> <div class="list-wrapper">
<div class="list-header"> <div class="list-header">
<button class="new">New {{ class_name }}</button> <button class="new">New {{ class_name }}</button>
</div> </div>
<ul class="list"> <ul class="list">
{% for element in elements %} {% for element in elements %}
<li data-id="{{ element.id }}" class="{% block extra-class %}{% endblock %}"> <li data-id="{{ element.id }}" data-name="{% block element-name %}{% endblock %}" class="{% block extra-class %}{% endblock %}">
{% block element %}{% endblock %} {% block element %}{% endblock %}
<div class="actions"> <div class="actions">
{% block actions %}{% endblock %} {% block actions %}{% endblock %}
@ -23,4 +24,14 @@
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
<div class="popup" id="delete-popup">
<div class="popup-container">
<h2 class="title">Delete <span class="elmt-name"></span> ?</h2>
<p class="desc">{% block delete-desc %}{% endblock %}</p>
<div class="actions">
<button class="cancel">Cancel</button>
<button class="delete">Delete</button>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -3,8 +3,10 @@
{% block title %}Parents{% endblock %} {% block title %}Parents{% endblock %}
{% block extra-head %} {% block extra-head %}
<link rel="stylesheet" href="{% static "parents.css" %}"> <link rel="stylesheet" href="{% static "parents.css" %}">
<script src="{% static "parents.js" %}"></script>
{% endblock %} {% endblock %}
{% block extra-class %}{% if element.is_productive %}productive{% endif %}{% endblock %} {% block extra-class %}{% if element.is_productive %}productive{% endif %}{% endblock %}
{% block element-name %}{{ element.name }}{% endblock %}
{% block element %} {% block element %}
<div class="name">{{ element.name }}</div> <div class="name">{{ element.name }}</div>
{% if element.project_num %} {% if element.project_num %}
@ -13,4 +15,13 @@
{% endblock %} {% endblock %}
{% block actions %} {% block actions %}
<button class="edit">Edit</button> <button class="edit">Edit</button>
{% endblock %} <button class="delete">Delete</button>
{% endblock %}
{% block delete-desc %}
Are sure you want to delete <span class="elmt-name"></span> ?
This action is <u>IRREVERSIBLE</u> ! By deleting this element, you will also delete:
<ul>
<li><span class="project-count"></span> project(s)</li>
<li><span class="task-count"></span> task imputation(s)</li>
</ul>
{% endblock %}