Compare commits
10 Commits
7c2915bb47
...
64058cadd8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64058cadd8 | ||
|
|
8ee75f7ae3 | ||
|
|
2b7b14a4ba | ||
|
|
b870a76a31 | ||
|
|
12761b3103 | ||
|
|
ae816accd1 | ||
|
|
25da0f93ab | ||
|
|
83e34f2b85 | ||
|
|
b446384d17 | ||
|
|
01ddc76720 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -102,6 +102,7 @@ venv.bak/
|
||||
|
||||
# Static CACHE
|
||||
/static/CACHE/
|
||||
/staticfiles/CACHE/
|
||||
|
||||
# sftp configuration file
|
||||
sftp-config.json
|
||||
|
||||
1
Pipfile
1
Pipfile
@ -10,6 +10,7 @@ psycopg2-binary = "*"
|
||||
django-anymail = {extras = ["mailgun"], version = "*"}
|
||||
celery = {extras = ["redis"], version = "*"}
|
||||
django-celery-beat = "*"
|
||||
django-compressor = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
|
||||
104
Pipfile.lock
generated
104
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "ffe96f34ca738a35a283e9ac98b5f1a74609c67619f18e8eb7ba62967247766f"
|
||||
"sha256": "f5ee1dc635277e81758920aeb79d3788dd518bde2e01af520b76659587f7c96c"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
@ -59,11 +59,11 @@
|
||||
},
|
||||
"charset-normalizer": {
|
||||
"hashes": [
|
||||
"sha256:88fce3fa5b1a84fdcb3f603d889f723d1dd89b26059d0123ca435570e848d5e1",
|
||||
"sha256:c46c3ace2d744cfbdebceaa3c19ae691f53ae621b39fd7570f59d14fb7f2fd12"
|
||||
"sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b",
|
||||
"sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"
|
||||
],
|
||||
"markers": "python_version >= '3'",
|
||||
"version": "==2.0.3"
|
||||
"version": "==2.0.4"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
@ -95,11 +95,11 @@
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:3da05fea54fdec2315b54a563d5b59f3b4e2b1e69c3a5841dda35019c01855cd",
|
||||
"sha256:c58b5f19c5ae0afe6d75cbdd7df561e6eb929339985dbbda2565e1cabb19a62e"
|
||||
"sha256:7f92413529aa0e291f3be78ab19be31aefb1e1c9a52cd59e130f505f27a51f13",
|
||||
"sha256:f27f8544c9d4c383bbe007c57e3235918e258364577373d4920e9162837be022"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.2.5"
|
||||
"version": "==3.2.6"
|
||||
},
|
||||
"django-anymail": {
|
||||
"extras": [
|
||||
@ -112,6 +112,13 @@
|
||||
"index": "pypi",
|
||||
"version": "==8.4"
|
||||
},
|
||||
"django-appconf": {
|
||||
"hashes": [
|
||||
"sha256:1b1d0e1069c843ebe8ae5aa48ec52403b1440402b320c3e3a206a0907e97bb06",
|
||||
"sha256:be58deb54a43d77d2e1621fe59f787681376d3cd0b8bd8e4758ef6c3a6453380"
|
||||
],
|
||||
"version": "==1.0.4"
|
||||
},
|
||||
"django-celery-beat": {
|
||||
"hashes": [
|
||||
"sha256:97ae5eb309541551bdb07bf60cc57cadacf42a74287560ced2d2c06298620234",
|
||||
@ -120,6 +127,14 @@
|
||||
"index": "pypi",
|
||||
"version": "==2.2.1"
|
||||
},
|
||||
"django-compressor": {
|
||||
"hashes": [
|
||||
"sha256:3358077605c146fdcca5f9eaffb50aa5dbe15f238f8854679115ebf31c0415e0",
|
||||
"sha256:f8313f59d5e65712fc28787d084fe834997c9dfa92d064a1a3ec3d3366594d04"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.4.1"
|
||||
},
|
||||
"django-timezone-field": {
|
||||
"hashes": [
|
||||
"sha256:6dc782e31036a58da35b553bd00c70f112d794700025270d8a6a4c1d2e5b26c6",
|
||||
@ -128,6 +143,14 @@
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==4.2.1"
|
||||
},
|
||||
"gunicorn": {
|
||||
"hashes": [
|
||||
"sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e",
|
||||
"sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==20.1.0"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
|
||||
@ -146,11 +169,46 @@
|
||||
},
|
||||
"prompt-toolkit": {
|
||||
"hashes": [
|
||||
"sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f",
|
||||
"sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88"
|
||||
"sha256:6076e46efae19b1e0ca1ec003ed37a933dc94b4d20f486235d436e64771dcd5c",
|
||||
"sha256:eb71d5a6b72ce6db177af4a7d4d7085b99756bf656d98ffcc4fecd36850eea6c"
|
||||
],
|
||||
"markers": "python_full_version >= '3.6.1'",
|
||||
"version": "==3.0.19"
|
||||
"markers": "python_full_version >= '3.6.2'",
|
||||
"version": "==3.0.20"
|
||||
},
|
||||
"psycopg2-binary": {
|
||||
"hashes": [
|
||||
"sha256:0b7dae87f0b729922e06f85f667de7bf16455d411971b2043bbd9577af9d1975",
|
||||
"sha256:0f2e04bd2a2ab54fa44ee67fe2d002bb90cee1c0f1cc0ebc3148af7b02034cbd",
|
||||
"sha256:123c3fb684e9abfc47218d3784c7b4c47c8587951ea4dd5bc38b6636ac57f616",
|
||||
"sha256:1473c0215b0613dd938db54a653f68251a45a78b05f6fc21af4326f40e8360a2",
|
||||
"sha256:14db1752acdd2187d99cb2ca0a1a6dfe57fc65c3281e0f20e597aac8d2a5bd90",
|
||||
"sha256:1e3a362790edc0a365385b1ac4cc0acc429a0c0d662d829a50b6ce743ae61b5a",
|
||||
"sha256:1e85b74cbbb3056e3656f1cc4781294df03383127a8114cbc6531e8b8367bf1e",
|
||||
"sha256:20f1ab44d8c352074e2d7ca67dc00843067788791be373e67a0911998787ce7d",
|
||||
"sha256:2f62c207d1740b0bde5c4e949f857b044818f734a3d57f1d0d0edc65050532ed",
|
||||
"sha256:3242b9619de955ab44581a03a64bdd7d5e470cc4183e8fcadd85ab9d3756ce7a",
|
||||
"sha256:35c4310f8febe41f442d3c65066ca93cccefd75013df3d8c736c5b93ec288140",
|
||||
"sha256:4235f9d5ddcab0b8dbd723dca56ea2922b485ea00e1dafacf33b0c7e840b3d32",
|
||||
"sha256:5ced67f1e34e1a450cdb48eb53ca73b60aa0af21c46b9b35ac3e581cf9f00e31",
|
||||
"sha256:7360647ea04db2e7dff1648d1da825c8cf68dc5fbd80b8fb5b3ee9f068dcd21a",
|
||||
"sha256:8c13d72ed6af7fd2c8acbd95661cf9477f94e381fce0792c04981a8283b52917",
|
||||
"sha256:988b47ac70d204aed01589ed342303da7c4d84b56c2f4c4b8b00deda123372bf",
|
||||
"sha256:995fc41ebda5a7a663a254a1dcac52638c3e847f48307b5416ee373da15075d7",
|
||||
"sha256:a36c7eb6152ba5467fb264d73844877be8b0847874d4822b7cf2d3c0cb8cdcb0",
|
||||
"sha256:aed4a9a7e3221b3e252c39d0bf794c438dc5453bc2963e8befe9d4cd324dff72",
|
||||
"sha256:aef9aee84ec78af51107181d02fe8773b100b01c5dfde351184ad9223eab3698",
|
||||
"sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773",
|
||||
"sha256:b4d7679a08fea64573c969f6994a2631908bb2c0e69a7235648642f3d2e39a68",
|
||||
"sha256:c250a7ec489b652c892e4f0a5d122cc14c3780f9f643e1a326754aedf82d9a76",
|
||||
"sha256:ca86db5b561b894f9e5f115d6a159fff2a2570a652e07889d8a383b5fae66eb4",
|
||||
"sha256:cfc523edecddaef56f6740d7de1ce24a2fdf94fd5e704091856a201872e37f9f",
|
||||
"sha256:da113b70f6ec40e7d81b43d1b139b9db6a05727ab8be1ee559f3a69854a69d34",
|
||||
"sha256:f6fac64a38f6768e7bc7b035b9e10d8a538a9fadce06b983fb3e6fa55ac5f5ce",
|
||||
"sha256:f8559617b1fcf59a9aedba2c9838b5b6aa211ffedecabca412b92a1ff75aac1a",
|
||||
"sha256:fbb42a541b1093385a2d8c7eec94d26d30437d0e77c1d25dae1dcc46741a385e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.9.1"
|
||||
},
|
||||
"python-crontab": {
|
||||
"hashes": [
|
||||
@ -173,6 +231,12 @@
|
||||
],
|
||||
"version": "==2021.1"
|
||||
},
|
||||
"rcssmin": {
|
||||
"hashes": [
|
||||
"sha256:ca87b695d3d7864157773a61263e5abb96006e9ff0e021eff90cbe0e1ba18270"
|
||||
],
|
||||
"version": "==1.0.6"
|
||||
},
|
||||
"redis": {
|
||||
"hashes": [
|
||||
"sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2",
|
||||
@ -188,6 +252,24 @@
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==2.26.0"
|
||||
},
|
||||
"rjsmin": {
|
||||
"hashes": [
|
||||
"sha256:0ab825839125eaca57cc59581d72e596e58a7a56fbc0839996b7528f0343a0a8",
|
||||
"sha256:211c2fe8298951663bbc02acdffbf714f6793df54bfc50e1c6c9e71b3f2559a3",
|
||||
"sha256:466fe70cc5647c7c51b3260c7e2e323a98b2b173564247f9c89e977720a0645f",
|
||||
"sha256:585e75a84d9199b68056fd4a083d9a61e2a92dfd10ff6d4ce5bdb04bc3bdbfaf",
|
||||
"sha256:6044ca86e917cd5bb2f95e6679a4192cef812122f28ee08c677513de019629b3",
|
||||
"sha256:714329db774a90947e0e2086cdddb80d5e8c4ac1c70c9f92436378dedb8ae345",
|
||||
"sha256:799890bd07a048892d8d3deb9042dbc20b7f5d0eb7da91e9483c561033b23ce2",
|
||||
"sha256:975b69754d6a76be47c0bead12367a1ca9220d08e5393f80bab0230d4625d1f4",
|
||||
"sha256:b15dc75c71f65d9493a8c7fa233fdcec823e3f1b88ad84a843ffef49b338ac32",
|
||||
"sha256:dd0f4819df4243ffe4c964995794c79ca43943b5b756de84be92b445a652fb86",
|
||||
"sha256:e3908b21ebb584ce74a6ac233bdb5f29485752c9d3be5e50c5484ed74169232c",
|
||||
"sha256:e487a7783ac4339e79ec610b98228eb9ac72178973e3dee16eba0e3feef25924",
|
||||
"sha256:ecd29f1b3e66a4c0753105baec262b331bcbceefc22fbe6f7e8bcd2067bcb4d7"
|
||||
],
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block head %}
|
||||
<script defer src="{% static "scripts/stimulus.umd.js" %}"></script>
|
||||
<script type="module" defer src="{% static "scripts/index.js" %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article class="panel">
|
||||
<header>
|
||||
<h1>Hi, {{user.first_name}}</h1>
|
||||
<h1>Today, {% now "D, M j" %}</h1>
|
||||
</header>
|
||||
<section>
|
||||
<h2>Today, {% now "D, M j" %}</h2>
|
||||
<div>
|
||||
<h2>Events</h2>
|
||||
{% for event in today %}
|
||||
<div class="today__event">
|
||||
<strong class="today__date">{{event.date|date:"D, M j"}}</strong>
|
||||
@ -21,12 +26,10 @@
|
||||
<span><a href="{% url 'event-update' event.pk %}">Edit</a></span>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p><em>Nothing for today.</em></p>
|
||||
<p><em>No events for today.</em></p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
<p><a href="{% url 'event-create' %}" class="action-button">Add event</a></p>
|
||||
<section>
|
||||
{% if upcoming_events %}
|
||||
<h3>Upcoming (next seven days)</h3>
|
||||
<div>
|
||||
{% for event in upcoming_events %}
|
||||
@ -42,8 +45,129 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<p><a href="{% url 'event-list' %}">See all events</a></p>
|
||||
</section>
|
||||
<p><a href="{% url 'event-create' %}" class="action-button">Add event</a></p>
|
||||
<hr>
|
||||
<section>
|
||||
<h2>To-do's</h2>
|
||||
{% if overdue_todos %}
|
||||
<div class="todos__overdue">
|
||||
<h4 class="--danger">Overdue</h4>
|
||||
<div>
|
||||
{% regroup overdue_todos by employee as overdue_list %}
|
||||
{% for employee in overdue_list %}
|
||||
<p><a href="{% url 'employee-detail' employee.grouper.pk %}">{{employee.grouper}}</a></p>
|
||||
{% for todo in employee.list %}
|
||||
<li
|
||||
class="todo__item"
|
||||
data-controller="todo"
|
||||
data-todo-url-value="{% url 'todo-update' todo.employee.pk todo.pk %}"
|
||||
data-todo-delete-url-value="{% url 'todo-delete' todo.employee.pk todo.pk %}"
|
||||
>
|
||||
<div class="todo_display"
|
||||
data-todo-target="display"
|
||||
>
|
||||
<span
|
||||
data-action="click->todo#toggle"
|
||||
class="todo__checkbox_button {% if todo.completed %}todo__checkbox_button--completed{% endif %}"></span>
|
||||
<span class="todo__description_display">{{todo.description}}</span>
|
||||
<span class="todo__due_date">{% if todo.due_date %}{{todo.due_date}}{% endif %}</span>
|
||||
<div>
|
||||
{% if not todo.employee.archived %}
|
||||
<button class="hidden_action" data-action="todo#edit" name="edit">edit</button>
|
||||
<button class="hidden_action" data-action="todo#destroy" name="destroy">delete</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<form
|
||||
class="todo_form --hidden"
|
||||
data-todo-target="form"
|
||||
data-action="todo#post"
|
||||
data-action="change->todo#change"
|
||||
action="{% url 'todo-update' todo.employee.pk todo.pk %}"
|
||||
method="POST">
|
||||
{% csrf_token %}
|
||||
<input
|
||||
data-action="todo#post"
|
||||
data-todo-target="checkbox"
|
||||
class="todo__checkbox_input"
|
||||
name="completed"
|
||||
type="checkbox"
|
||||
{% if todo.completed %}checked{% endif %}
|
||||
{% if todo.employee.archived %}disabled{% endif %}
|
||||
>
|
||||
<input name="description" type="text" value="{{todo.description}}">
|
||||
<input name="due_date" type="date" value="{% if todo.due_date %}{{todo.due_date|date:"Y-m-d"}}{% endif %}">
|
||||
<div class="form__savecancel">
|
||||
<input class="action-button" type="submit" value="Save changes">
|
||||
<a data-action="todo#cancel" href="">cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<h4>Due today</h4>
|
||||
{% regroup todos by employee as todo_list %}
|
||||
{% for employee in todo_list %}
|
||||
<p><a href="{% url 'employee-detail' employee.grouper.pk %}">{{employee.grouper}}</a></p>
|
||||
{% for todo in employee.list %}
|
||||
<li
|
||||
class="todo__item"
|
||||
data-controller="todo"
|
||||
data-todo-url-value="{% url 'todo-update' todo.employee.pk todo.pk %}"
|
||||
data-todo-delete-url-value="{% url 'todo-delete' todo.employee.pk todo.pk %}"
|
||||
>
|
||||
<div class="todo_display"
|
||||
data-todo-target="display"
|
||||
>
|
||||
<span
|
||||
data-action="click->todo#toggle"
|
||||
class="todo__checkbox_button {% if todo.completed %}todo__checkbox_button--completed{% endif %}"></span>
|
||||
<span class="todo__description_display">{{todo.description}}</span>
|
||||
<span class="todo__due_date">{% if todo.due_date %}{{todo.due_date}}{% endif %}</span>
|
||||
<div>
|
||||
{% if not todo.employee.archived %}
|
||||
<button class="hidden_action" data-action="todo#edit" name="edit">edit</button>
|
||||
<button class="hidden_action" data-action="todo#destroy" name="destroy">delete</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<form
|
||||
class="todo_form --hidden"
|
||||
data-todo-target="form"
|
||||
data-action="todo#post"
|
||||
data-action="change->todo#change"
|
||||
action="{% url 'todo-update' todo.employee.pk todo.pk %}"
|
||||
method="POST">
|
||||
{% csrf_token %}
|
||||
<input
|
||||
data-action="todo#post"
|
||||
data-todo-target="checkbox"
|
||||
class="todo__checkbox_input"
|
||||
name="completed"
|
||||
type="checkbox"
|
||||
{% if todo.completed %}checked{% endif %}
|
||||
{% if todo.employee.archived %}disabled{% endif %}
|
||||
>
|
||||
<input name="description" type="text" value="{{todo.description}}">
|
||||
<input name="due_date" type="date" value="{% if todo.due_date %}{{todo.due_date|date:"Y-m-d"}}{% endif %}">
|
||||
<div class="form__savecancel">
|
||||
<input class="action-button" type="submit" value="Save changes">
|
||||
<a data-action="todo#cancel" href="">cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% empty %}
|
||||
<p><em>No to-do's for today.</em></p>
|
||||
{% endfor %}
|
||||
</section>
|
||||
<hr>
|
||||
<section>
|
||||
<h3>Recent Activity</h3>
|
||||
{% regroup latest_activity by created_at.date as latest_activity_re %}
|
||||
|
||||
@ -13,7 +13,7 @@ from django.contrib.auth.models import User
|
||||
from .models import Profile
|
||||
from .forms import AccountUpdateForm, ProfileUpdateForm
|
||||
|
||||
from board.models import LogEntry, Event
|
||||
from board.models import LogEntry, Event, Todo
|
||||
|
||||
|
||||
class ProfileView(LoginRequiredMixin, TemplateView):
|
||||
@ -26,6 +26,14 @@ class ProfileView(LoginRequiredMixin, TemplateView):
|
||||
enddate = today + timedelta(days=7)
|
||||
context['profile'] = self.request.user.profile
|
||||
context['latest_activity'] = LogEntry.objects.all()[:10]
|
||||
context['todos'] = Todo.objects.filter(
|
||||
due_date=today,
|
||||
completed=False
|
||||
)
|
||||
context['overdue_todos'] = Todo.objects.filter(
|
||||
due_date__lt=today,
|
||||
completed=False
|
||||
)
|
||||
context['today'] = Event.objects.filter(
|
||||
date=today
|
||||
)
|
||||
|
||||
@ -71,23 +71,29 @@ class LogEntryForm(forms.ModelForm):
|
||||
class TodoForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Todo
|
||||
fields = ('completed', 'description')
|
||||
fields = ('completed', 'description', 'due_date')
|
||||
widgets = {
|
||||
'completed': forms.CheckboxInput(attrs = {
|
||||
'class': 'todo__checkbox',
|
||||
}),
|
||||
'description': forms.TextInput(attrs = {
|
||||
'autofocus': 'autofocus'
|
||||
}),
|
||||
'due_date': forms.DateInput(attrs = {
|
||||
'type': 'date'
|
||||
})
|
||||
}
|
||||
|
||||
class TodoCreateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Todo
|
||||
fields = ('description',)
|
||||
fields = ('description', 'due_date')
|
||||
widgets = {
|
||||
'description': forms.TextInput(attrs = {
|
||||
'autofocus': 'autofocus'
|
||||
}),
|
||||
'due_date': forms.DateInput(attrs = {
|
||||
'type': 'date'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
18
board/migrations/0002_todo_due_date.py
Normal file
18
board/migrations/0002_todo_due_date.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.5 on 2021-08-04 22:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('board', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='todo',
|
||||
name='due_date',
|
||||
field=models.DateField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
@ -1,5 +1,6 @@
|
||||
from django.db import models
|
||||
from django.urls import reverse_lazy, reverse
|
||||
from datetime import datetime
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
@ -48,10 +49,18 @@ class LogEntry(models.Model):
|
||||
class Todo(models.Model):
|
||||
employee = models.ForeignKey(Employee, on_delete=models.CASCADE)
|
||||
description = models.CharField(max_length=64)
|
||||
due_date = models.DateField(blank=True, null=True)
|
||||
|
||||
completed = models.BooleanField(default=False)
|
||||
completed_at = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
def is_due(self):
|
||||
today = timezone.localtime(timezone.now()).date()
|
||||
if self.due_date == today:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.employee}: {self.description}"
|
||||
|
||||
@ -69,7 +78,7 @@ class Event(models.Model):
|
||||
employee = models.ForeignKey(Employee, on_delete=models.CASCADE, blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.description} on {self.date} @ {self.time}"
|
||||
return f"{self.name} on {self.date} @ {self.time}"
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('event-detail', kwargs={'pk': self.pk})
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% load compress %}
|
||||
|
||||
{% block head %}
|
||||
<script defer src="{% static "scripts/stimulus.umd.js" %}"></script>
|
||||
<script type="module" defer src="{% static "scripts/index.js" %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
@ -40,6 +42,8 @@
|
||||
<span class="today__time">{{event.time|time:"TIME_FORMAT"}}</span>
|
||||
<span><a href="{% url 'event-update' event.pk %}">Edit</a></span>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p><em>No events for employee.</em></p>
|
||||
{% endfor %}
|
||||
</section>
|
||||
<section id="todos">
|
||||
|
||||
@ -6,6 +6,9 @@
|
||||
<section>
|
||||
<form method="POST" action="{% url 'event-update' event.pk %}">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
<a class="action-button action-button--danger" href="{% url 'event-delete' event.pk %}">Delete this event</a>
|
||||
</p>
|
||||
{{form.as_p}}
|
||||
<p>
|
||||
<input class="action-button" type="submit" value="Save changes"> or <a href="{% url 'profile-detail' %}">cancel</a>
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
{% block content %}
|
||||
<article>
|
||||
<header>
|
||||
<h1>Update Event</h1>
|
||||
<h1>Update LogEntry</h1>
|
||||
<p><a class="action-button action-button--danger" href="{% url 'entry-delete' employee.pk logentry.pk %}">Delete this entry</a></p>
|
||||
</header>
|
||||
<section>
|
||||
|
||||
@ -1,13 +1,46 @@
|
||||
<li class="todo__item">
|
||||
<form class="todo_form" action="{% url 'todo-update' employee.pk todo.pk %}" method="POST">
|
||||
<label class="todo__details">
|
||||
<input class="todo__checkbox" name="completed" type="checkbox" {% if todo.completed %}checked{% endif %} {% if employee.archived %}disabled{% endif %}>
|
||||
<input name="description" type="hidden" value="{{todo.description}}">
|
||||
<span class="todo__description_display">{{todo.description}}</span>
|
||||
</label>
|
||||
{% if not employee.archived %}
|
||||
<a class="hidden_action" data-url="{% url 'todo-update' employee.pk todo.pk %}" href="#" name="edit">Edit…</a>
|
||||
<a class="hidden_action" data-url="{% url 'todo-delete' employee.pk todo.pk %}" href="#" name="destroy">Delete…</a>
|
||||
{% endif %}
|
||||
<li
|
||||
class="todo__item"
|
||||
data-controller="todo"
|
||||
data-todo-url-value="{% url 'todo-update' employee.pk todo.pk %}"
|
||||
data-todo-delete-url-value="{% url 'todo-delete' employee.pk todo.pk %}"
|
||||
>
|
||||
<div class="todo_display"
|
||||
data-todo-target="display"
|
||||
>
|
||||
<span
|
||||
data-action="click->todo#toggle"
|
||||
class="todo__checkbox_button {% if todo.completed %}todo__checkbox_button--completed{% endif %}"></span>
|
||||
<span class="todo__description_display">{{todo.description}}</span>
|
||||
<span class="todo__due_date">{% if todo.due_date %}{{todo.due_date}}{% endif %}</span>
|
||||
<div class="todo__actions">
|
||||
{% if not employee.archived %}
|
||||
<button class="hidden_action" data-action="todo#edit" name="edit">edit</button>
|
||||
<button class="hidden_action" data-action="todo#destroy" name="destroy">delete</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<form
|
||||
class="todo_form --hidden"
|
||||
data-todo-target="form"
|
||||
data-action="todo#post"
|
||||
data-action="change->todo#change"
|
||||
action="{% url 'todo-update' employee.pk todo.pk %}"
|
||||
method="POST">
|
||||
{% csrf_token %}
|
||||
<input
|
||||
data-action="todo#post"
|
||||
data-todo-target="checkbox"
|
||||
class="todo__checkbox_input"
|
||||
name="completed"
|
||||
type="checkbox"
|
||||
{% if todo.completed %}checked{% endif %}
|
||||
{% if employee.archived %}disabled{% endif %}
|
||||
>
|
||||
<input name="description" type="text" value="{{todo.description}}">
|
||||
<input name="due_date" type="date" value="{% if todo.due_date %}{{todo.due_date|date:"Y-m-d"}}{% endif %}">
|
||||
<div>
|
||||
<input class="action-button" type="submit" value="Save changes">
|
||||
<a data-action="todo#cancel" href="">cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
@ -1,4 +1,4 @@
|
||||
<ul id="todo__list">
|
||||
<ul id="todo__list" data-controller="todo-list">
|
||||
{% for todo in todo_list %}
|
||||
{% include "board/todo_detail.html" with todo=todo %}
|
||||
{% endfor %}
|
||||
|
||||
@ -3,7 +3,7 @@ import os
|
||||
from celery import Celery
|
||||
|
||||
# Set the default Django settings module for the 'celery' program.
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'onboard.settings_dev')
|
||||
# os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'onboard.settings_dev')
|
||||
|
||||
app = Celery('onboard')
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.flatpages',
|
||||
'compressor',
|
||||
'accounts.apps.AccountsConfig',
|
||||
'board.apps.BoardConfig',
|
||||
]
|
||||
@ -132,6 +133,13 @@ STATICFILES_DIRS = [
|
||||
]
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = '/var/www/onboard.windmillapps.org/static/'
|
||||
COMPRESS_ENABLED = True
|
||||
|
||||
STATICFILES_FINDERS = (
|
||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
||||
'compressor.finders.CompressorFinder',
|
||||
)
|
||||
|
||||
|
||||
# Default primary key field type
|
||||
|
||||
@ -39,6 +39,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.flatpages',
|
||||
'compressor',
|
||||
'django_celery_beat',
|
||||
'accounts.apps.AccountsConfig',
|
||||
'board.apps.BoardConfig',
|
||||
@ -124,8 +125,15 @@ USE_TZ = True
|
||||
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||
STATICFILES_DIRS = [BASE_DIR / 'static']
|
||||
COMPRESS_ENABLED = True
|
||||
|
||||
STATICFILES_FINDERS = (
|
||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
||||
'compressor.finders.CompressorFinder',
|
||||
)
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
|
||||
|
||||
1
static/images/checkmark.svg
Normal file
1
static/images/checkmark.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg enable-background="new 0 0 50 52.5" viewBox="0 0 50 52.5" xmlns="http://www.w3.org/2000/svg"><path d="m14.4 50.4-13.3-17.7c-1.8-2.2-1.4-5.5 1-7.3 2.2-1.6 5.5-1.2 7.2 1l8.8 11.7 22.3-35.7c1.5-2.4 4.8-3.1 7.2-1.6s3.1 4.8 1.6 7.2l-26.1 42.1c-.9 1.4-2.5 2.4-4.2 2.4-.1 0-.1 0-.3 0-1.6 0-3.1-.7-4.2-2.1z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 324 B |
140
static/scripts/controllers/todo_controller.js
Normal file
140
static/scripts/controllers/todo_controller.js
Normal file
@ -0,0 +1,140 @@
|
||||
import getCookie from "../get_cookie.js";
|
||||
|
||||
export default class extends Stimulus.Controller {
|
||||
static values = { url: String, deleteUrl: String }
|
||||
static get targets() {
|
||||
return [ "completed", "form", "display", "checkbox" ]
|
||||
}
|
||||
|
||||
connect() {
|
||||
this.editing = false
|
||||
}
|
||||
|
||||
// load() {
|
||||
// fetch(`${this.urlValue}`)
|
||||
// .then((response) => response.text())
|
||||
// .then((html) => {
|
||||
// this.formTarget.innerHTML = html;
|
||||
// if (this.formTarget.querySelector("[name=destroy]")) {
|
||||
// this.destroyButton = this.formTarget.querySelector("[name=destroy]")
|
||||
// this.destroyButton.addEventListener("click", this.destroy.bind(this))
|
||||
// }
|
||||
// if (this.formTarget.querySelector("[name=edit]")) {
|
||||
// this.editButton = this.formTarget.querySelector("[name=edit]")
|
||||
// this.editButton.addEventListener("click", this.edit.bind(this))
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
validate() {
|
||||
const inputs = new Set()
|
||||
this.formTarget.querySelectorAll("input").forEach(input => {
|
||||
inputs.add(input.checkValidity())
|
||||
})
|
||||
|
||||
let valid = (inputs.has(false)) ? false : true
|
||||
|
||||
return valid
|
||||
}
|
||||
|
||||
change(event) {
|
||||
if (event.target.type === "checkbox") {
|
||||
this.post(event)
|
||||
}
|
||||
}
|
||||
|
||||
edit(event) {
|
||||
this.formTarget.classList.remove("--hidden")
|
||||
this.displayTarget.classList.add("--hidden")
|
||||
this.editing = true
|
||||
}
|
||||
|
||||
toggle(event) {
|
||||
if (this.checkboxTarget.checked == true) {
|
||||
this.checkboxTarget.checked = false
|
||||
} else {
|
||||
this.checkboxTarget.checked = true
|
||||
}
|
||||
if (this.validate()) {
|
||||
this.post()
|
||||
}
|
||||
}
|
||||
|
||||
cancel(event) {
|
||||
if (event) {
|
||||
event.preventDefault()
|
||||
}
|
||||
if (this.editing) {
|
||||
this.formTarget.classList.add("--hidden")
|
||||
this.displayTarget.classList.remove("--hidden")
|
||||
this.editing = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
post(event) {
|
||||
if (event) {
|
||||
event.preventDefault()
|
||||
}
|
||||
// construct a new FormData object from the html form
|
||||
const formData = new FormData(this.formTarget)
|
||||
|
||||
// get the csrftoken
|
||||
const csrftoken = getCookie("csrftoken")
|
||||
|
||||
const options = {
|
||||
method: "POST",
|
||||
body: new URLSearchParams(formData),
|
||||
mode: "same-origin",
|
||||
};
|
||||
|
||||
// construct a new Request passing in the csrftoken
|
||||
const request = new Request(`${this.urlValue}`, {
|
||||
headers: { "X-CSRFToken": csrftoken },
|
||||
})
|
||||
|
||||
// finally make the post request and wait for the server to respond
|
||||
fetch(request, options)
|
||||
.then((response) => response.text())
|
||||
.then((html) => {
|
||||
this.element.outerHTML = html
|
||||
})
|
||||
.catch((error) => {
|
||||
return error;
|
||||
})
|
||||
}
|
||||
|
||||
destroy(event) {
|
||||
const confirmation = confirm(
|
||||
"Are you sure you would like to delete this entry?"
|
||||
)
|
||||
|
||||
if (confirmation) {
|
||||
// get the csrftoken
|
||||
const csrftoken = getCookie("csrftoken")
|
||||
|
||||
const options = {
|
||||
method: "POST",
|
||||
mode: "same-origin",
|
||||
};
|
||||
|
||||
// construct a new Request passing in the csrftoken
|
||||
const request = new Request(`${this.deleteUrlValue}`, {
|
||||
headers: { "X-CSRFToken": csrftoken },
|
||||
})
|
||||
|
||||
// finally make the post request and wait for the server to respond
|
||||
fetch(request, options)
|
||||
.then((response) => response.text())
|
||||
.then((html) => {
|
||||
this.element.innerHTML = html;
|
||||
setTimeout(() => {
|
||||
this.element.remove()
|
||||
}, 3000)
|
||||
})
|
||||
.catch((error) => {
|
||||
return error;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,155 +0,0 @@
|
||||
import getCookie from "./get_cookie.js";
|
||||
|
||||
export default class Form {
|
||||
constructor(form, isNew=false) {
|
||||
if (typeof form === "string") {
|
||||
this.form = document.querySelector(`#${form}`)
|
||||
} else {
|
||||
this.form = form;
|
||||
}
|
||||
this.url = this.form.attributes.action.value;
|
||||
this.destroyButton = null
|
||||
if (isNew) {
|
||||
this.load()
|
||||
} else {
|
||||
if (this.form.querySelector("[name=destroy]")) {
|
||||
this.destroyButton = this.form.querySelector("[name=destroy]")
|
||||
this.destroyButton.addEventListener("click", this.destroy.bind(this))
|
||||
}
|
||||
if (this.form.querySelector("[name=edit]")) {
|
||||
this.editButton = this.form.querySelector("[name=edit]")
|
||||
this.editButton.addEventListener("click", this.edit.bind(this))
|
||||
}
|
||||
}
|
||||
this.form.addEventListener("submit", this.post.bind(this))
|
||||
this.form.addEventListener("change", this.change.bind(this))
|
||||
}
|
||||
|
||||
load() {
|
||||
fetch(`${this.url}`)
|
||||
.then((response) => response.text())
|
||||
.then((html) => {
|
||||
this.form.innerHTML = html;
|
||||
if (this.form.querySelector("[name=destroy]")) {
|
||||
this.destroyButton = this.form.querySelector("[name=destroy]")
|
||||
this.destroyButton.addEventListener("click", this.destroy.bind(this))
|
||||
}
|
||||
if (this.form.querySelector("[name=edit]")) {
|
||||
this.editButton = this.form.querySelector("[name=edit]")
|
||||
this.editButton.addEventListener("click", this.edit.bind(this))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
validate() {
|
||||
const inputs = new Set()
|
||||
this.form.querySelectorAll("input").forEach(input => {
|
||||
inputs.add(input.checkValidity())
|
||||
})
|
||||
|
||||
let valid = (inputs.has(false)) ? false : true
|
||||
|
||||
return valid
|
||||
}
|
||||
|
||||
change(event) {
|
||||
if (event.target.type === "checkbox") {
|
||||
this.post(event)
|
||||
}
|
||||
}
|
||||
|
||||
edit(event) {
|
||||
if (event) {
|
||||
event.preventDefault()
|
||||
}
|
||||
this.form.querySelector("[name=description]").type = 'text'
|
||||
let display = this.form.querySelector(".todo__description_display")
|
||||
display.classList.add('--hidden')
|
||||
}
|
||||
|
||||
|
||||
post(event) {
|
||||
if (event) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
// construct a new FormData object from the html form
|
||||
const formData = new FormData(this.form)
|
||||
|
||||
// get the csrftoken
|
||||
const csrftoken = getCookie("csrftoken")
|
||||
|
||||
const options = {
|
||||
method: "POST",
|
||||
body: new URLSearchParams(formData),
|
||||
mode: "same-origin",
|
||||
};
|
||||
|
||||
// construct a new Request passing in the csrftoken
|
||||
const request = new Request(`${this.url}`, {
|
||||
headers: { "X-CSRFToken": csrftoken },
|
||||
})
|
||||
|
||||
// finally make the post request and wait for the server to respond
|
||||
fetch(request, options)
|
||||
.then((response) => response.text())
|
||||
.then((html) => {
|
||||
var doc = new DOMParser().parseFromString(html, "text/html")
|
||||
this.url = doc.forms[0].attributes.action.value;
|
||||
this.form.innerHTML = html;
|
||||
|
||||
if (this.form.querySelector("[name=destroy]")) {
|
||||
this.destroyButton = this.form.querySelector("[name=destroy]")
|
||||
this.destroyButton.addEventListener(
|
||||
"click",
|
||||
this.destroy.bind(this)
|
||||
)
|
||||
}
|
||||
if (this.form.querySelector("[name=edit]")) {
|
||||
this.editButton = this.form.querySelector("[name=edit]")
|
||||
this.editButton.addEventListener("click", this.edit.bind(this))
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
return error;
|
||||
})
|
||||
}
|
||||
|
||||
destroy(event) {
|
||||
if (event) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
const confirmation = confirm(
|
||||
"Are you sure you would like to delete this entry?"
|
||||
)
|
||||
|
||||
if (confirmation) {
|
||||
// get the csrftoken
|
||||
const csrftoken = getCookie("csrftoken")
|
||||
|
||||
const options = {
|
||||
method: "POST",
|
||||
mode: "same-origin",
|
||||
};
|
||||
|
||||
// construct a new Request passing in the csrftoken
|
||||
const request = new Request(`${this.destroyButton.dataset.url}`, {
|
||||
headers: { "X-CSRFToken": csrftoken },
|
||||
})
|
||||
|
||||
// finally make the post request and wait for the server to respond
|
||||
fetch(request, options)
|
||||
.then((response) => response.text())
|
||||
.then((html) => {
|
||||
this.form.innerHTML = html;
|
||||
setTimeout(() => {
|
||||
this.form.remove()
|
||||
}, 3000)
|
||||
})
|
||||
.catch((error) => {
|
||||
return error;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,4 @@
|
||||
import Form from "./form.js";
|
||||
import View from "./view.js";
|
||||
import TodoController from "./controllers/todo_controller.js"
|
||||
|
||||
|
||||
// constructor(element, forms, templateName, addButton, destination)
|
||||
|
||||
const todoListView = new View(
|
||||
document.querySelector("#todos"),
|
||||
".todo_form",
|
||||
"#todo__list"
|
||||
)
|
||||
const application = Stimulus.Application.start()
|
||||
application.register("todo", TodoController)
|
||||
|
||||
2294
static/scripts/stimulus.umd.js
Normal file
2294
static/scripts/stimulus.umd.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,52 +0,0 @@
|
||||
import Form from "./form.js";
|
||||
|
||||
export default class View {
|
||||
constructor(element, forms, list) {
|
||||
this.element = element
|
||||
this.forms = this.element.querySelectorAll(forms)
|
||||
this.list = this.element.querySelector(list)
|
||||
this.observer = null
|
||||
|
||||
this.connect()
|
||||
|
||||
for (const form of this.forms) {
|
||||
new Form(form);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
connect() {
|
||||
this.observe(this.list)
|
||||
}
|
||||
|
||||
observe(list, config = { attributes: true, childList: true, subtree: true }) {
|
||||
const callback = function (mutationList, observer) {
|
||||
mutationList.forEach(mutation => {
|
||||
switch(mutation.type) {
|
||||
case 'childList':
|
||||
if (mutation.target === list) {
|
||||
mutation.addedNodes.forEach(node => {
|
||||
if (node.children) {
|
||||
let potentialForm = node.children[0].children[0]
|
||||
if (potentialForm.nodeName === "FORM") {
|
||||
new Form(potentialForm, true);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
break;
|
||||
case 'attributes':
|
||||
/* An attribute value changed on the element in
|
||||
mutation.target.
|
||||
The attribute name is in mutation.attributeName, and
|
||||
its previous value is in mutation.oldValue. */
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.observer = new MutationObserver(callback);
|
||||
this.observer.observe(this.list, config);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,10 +1,11 @@
|
||||
:root {
|
||||
--white: #f8f8fb;
|
||||
--black: #393d3f;
|
||||
--grey: #a7b4bb;
|
||||
--gray: #a7b4bb;
|
||||
--blue: #10638c;
|
||||
--light-blue: #74c0e6;
|
||||
--red: #8c1016;
|
||||
--light-brown: #a69688;
|
||||
}
|
||||
|
||||
html {
|
||||
@ -62,7 +63,7 @@ a {
|
||||
blockquote {
|
||||
margin-left: 0;
|
||||
padding-left: 2rem;
|
||||
border-left: 2px solid var(--blue);
|
||||
border-left: 0.2rem solid var(--blue);
|
||||
}
|
||||
|
||||
body > header {
|
||||
@ -73,7 +74,7 @@ article {
|
||||
max-width: 64rem;
|
||||
margin: 0 auto 2rem;
|
||||
padding: 1rem;
|
||||
border: 0.2rem solid var(--grey);
|
||||
border: 0.2rem solid var(--gray);
|
||||
}
|
||||
|
||||
@media all and (max-width: 64rem) {
|
||||
@ -154,8 +155,14 @@ article {
|
||||
}
|
||||
|
||||
.hidden_action {
|
||||
visibility: hidden;
|
||||
margin-left: 1rem;
|
||||
display: none;
|
||||
border: none;
|
||||
background-color: var(--light-brown);
|
||||
color: white;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.todo__item {
|
||||
@ -163,20 +170,83 @@ article {
|
||||
align-items: center;
|
||||
align-content: flex-start;
|
||||
}
|
||||
.todo__item:hover {
|
||||
background-color: #e3e3e3;
|
||||
}
|
||||
.todo__item:hover .hidden_action {
|
||||
display: inline-block;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.todo_display {
|
||||
display: grid;
|
||||
grid-template-columns: 0.25fr 2fr 1fr 1fr;
|
||||
align-items: center;
|
||||
gap: 0 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
.todo__actions {
|
||||
font-size: 0.889rem;
|
||||
}
|
||||
.todo__due_date {
|
||||
color: var(--light-brown);
|
||||
}
|
||||
.todo__checkbox {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.todo_form {
|
||||
display: flex;
|
||||
.todo__checkbox_input {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
z-index: -1;
|
||||
}
|
||||
.todo__details {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
.todos__overdue {
|
||||
background-color: var(--white);
|
||||
padding: 1rem;
|
||||
}
|
||||
.todos__overdue h4 {
|
||||
margin-top: 0;
|
||||
}
|
||||
h2 + h4 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.todo__checkbox_button {
|
||||
display: inline-block;
|
||||
width: 1.4em;
|
||||
height: 1.4em;
|
||||
border: 1px solid rgba(0,0,0,0.25);
|
||||
background-color: #fff;
|
||||
border-radius: 0.3em;
|
||||
vertical-align: top;
|
||||
cursor: pointer;
|
||||
}
|
||||
.todo__checkbox_button--completed {
|
||||
position: relative;
|
||||
border-color: rgba(0,0,0,0.1);
|
||||
background-color: var(--blue);
|
||||
box-shadow: none;
|
||||
}
|
||||
.todo__checkbox_button--completed::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
background: url(../images/checkmark.svg) no-repeat center center;
|
||||
background-size: auto;
|
||||
background-size: 65%;
|
||||
}
|
||||
.todo_form {
|
||||
display: grid;
|
||||
row-gap: 1rem;
|
||||
margin: 1rem 0;
|
||||
width: 100%;
|
||||
border: 0.0625rem solid rgba(0, 0, 0, 0.25);
|
||||
background-color: var(--white);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
|
||||
@ -207,17 +277,20 @@ article {
|
||||
.action-button--danger {
|
||||
background-color: var(--red);
|
||||
}
|
||||
.--danger {
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
|
||||
/* FORMS */
|
||||
input[type=text]:not([name=description]),
|
||||
input[type=text],
|
||||
input[type=number],
|
||||
input[type=date],
|
||||
input[type=time],
|
||||
input[type=password],
|
||||
input[type=search],
|
||||
textarea, select {
|
||||
border: 0.2rem solid var(--grey);
|
||||
border: 0.2rem solid var(--gray);
|
||||
padding: 0.3rem;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
@ -226,14 +299,6 @@ textarea, select {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input[name=description] {
|
||||
border: 0.2rem solid var(--grey);
|
||||
padding: 0.3rem;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input[type=radio], input[type=checkbox] {
|
||||
cursor: pointer;
|
||||
width: 1.5rem;
|
||||
@ -267,6 +332,15 @@ input:focus, textarea:focus, select:focus {
|
||||
border-color: var(--blue) !important;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 1rem;
|
||||
border: none;
|
||||
background-color: var(--light-brown);
|
||||
color: white;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -340,7 +414,7 @@ hgroup {
|
||||
column-gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 0.046rem solid var(--grey);
|
||||
border-bottom: 0.046rem solid var(--gray);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{% load static %}
|
||||
{% load compress %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@ -12,8 +13,10 @@
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
|
||||
|
||||
{% compress css %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static "styles/normalize.css" %}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static "styles/main.css" %}">
|
||||
{% endcompress %}
|
||||
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
@ -30,11 +33,7 @@
|
||||
<a href="{% url 'employee-list' %}">Employees</a>
|
||||
</div>
|
||||
<div class="nav__auth">
|
||||
{% if user.first_name or user.last_name %}
|
||||
<a href="{% url 'account-detail' user.pk %}">{{ user.first_name }} {{ user.last_name }}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'account-detail' user.pk %}">Profile</a>
|
||||
{% endif %}
|
||||
<a class="action-button" href="{% url 'logout' %}">Logout</a>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user