diff --git a/accounts/templates/accounts/account_detail.html b/accounts/templates/accounts/account_detail.html index 4e6c6e3..0e277e6 100755 --- a/accounts/templates/accounts/account_detail.html +++ b/accounts/templates/accounts/account_detail.html @@ -1,11 +1,13 @@ {% extends 'base.html' %} {% block content %} -
+

{{ user.first_name }} {{ user.last_name }}

-

{{ user.email }}

-

- Update email/change password -

-
+
+

{{ user.email }}

+

+ Update email/change password +

+
+ {% endblock %} diff --git a/accounts/templates/accounts/profile.html b/accounts/templates/accounts/profile.html index 927dec5..acf3d50 100644 --- a/accounts/templates/accounts/profile.html +++ b/accounts/templates/accounts/profile.html @@ -6,5 +6,59 @@

Hi, {{user.first_name}}

+
+

Today, {% now "D, M j" %}

+
+ {% for event in today %} +
+ {{event.date|date:"D, M j"}} + {{event.name}} + {% if event.employee %} for + {{event.employee}} + {% endif %} + + {{event.time|time:"TIME_FORMAT"}} + Edit +
+ {% endfor %} +
+
+

Add event

+
+

Upcoming

+
+ {% for event in upcoming_events %} +
+ {{event.date|date:"D, M j"}} + {{event.name}} + {% if event.employee %} for + {{event.employee}} + {% endif %} + + {{event.time|time:"TIME_FORMAT"}} + Edit +
+ {% endfor %} +
+

See all events

+
+
+

Recent Activity

+ {% regroup latest_activity by created_at.date as latest_activity_re %} + {% for date in latest_activity_re %} +
{{date.grouper|date:"D, M j"}}
+ {% for entry in date.list %} +

+ {{entry.notes}} + for + {{entry.employee}} +

+ {% endfor %} + {% endfor %} +
{% endblock %} + + + + diff --git a/accounts/views.py b/accounts/views.py index b13a258..1d0ffad 100755 --- a/accounts/views.py +++ b/accounts/views.py @@ -1,5 +1,6 @@ import pytz from django.utils import timezone +from datetime import date, timedelta from django.shortcuts import render, reverse, redirect from django.urls import reverse_lazy from django.views.generic.base import TemplateView @@ -12,14 +13,25 @@ from django.contrib.auth.models import User from .models import Profile from .forms import AccountUpdateForm, ProfileUpdateForm +from board.models import LogEntry, Event + class ProfileView(LoginRequiredMixin, TemplateView): template_name = 'accounts/profile.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) + today = date.today() + tomorrow = today + timedelta(days=1) + enddate = today + timedelta(days=6) context['profile'] = self.request.user.profile - + context['latest_activity'] = LogEntry.objects.all()[:10] + context['today'] = Event.objects.filter( + date=today + ) + context['upcoming_events'] = Event.objects.filter( + date__range=[tomorrow, enddate] + ) return context class ProfileUpdateView(LoginRequiredMixin, UpdateView): diff --git a/board/admin.py b/board/admin.py index fa58b84..2bbcd67 100644 --- a/board/admin.py +++ b/board/admin.py @@ -4,8 +4,10 @@ from .models import ( Employee, LogEntry, Todo, + Event, ) admin.site.register(Employee) admin.site.register(LogEntry) admin.site.register(Todo) +admin.site.register(Event) diff --git a/board/forms.py b/board/forms.py index d32ebe2..0305e3c 100644 --- a/board/forms.py +++ b/board/forms.py @@ -1,6 +1,7 @@ from datetime import datetime from django import forms -from .models import Employee, LogEntry, Todo +from django.utils import timezone +from .models import Employee, LogEntry, Todo, Event from .regex import process_regex class EmployeePreCreateForm(forms.Form): @@ -89,3 +90,25 @@ class TodoCreateForm(forms.ModelForm): 'autofocus': 'autofocus' }) } + +class EventForm(forms.ModelForm): + class Meta: + model = Event + fields = ( + 'name', + 'date', + 'time', + 'employee', + ) + widgets = { + 'name': forms.TextInput(attrs = { + 'autofocus': 'autofocus' + }), + 'date': forms.DateInput(attrs = { + 'type': 'date', + 'value': timezone.now().date(), + }), + 'time': forms.DateInput(attrs = { + 'type': 'time', + }), + } diff --git a/board/migrations/0003_auto_20210727_0006.py b/board/migrations/0003_auto_20210727_0006.py new file mode 100644 index 0000000..07ee0d7 --- /dev/null +++ b/board/migrations/0003_auto_20210727_0006.py @@ -0,0 +1,70 @@ +# Generated by Django 3.2.5 on 2021-07-27 00:06 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('board', '0002_auto_20210709_0103'), + ] + + operations = [ + migrations.AlterModelOptions( + name='employee', + options={'ordering': ('-hire_date',)}, + ), + migrations.AlterModelOptions( + name='logentry', + options={'ordering': ('-created_at',)}, + ), + migrations.AlterField( + model_name='employee', + name='department', + field=models.CharField(blank=True, max_length=64, null=True), + ), + migrations.AlterField( + model_name='employee', + name='first_name', + field=models.CharField(max_length=64), + ), + migrations.AlterField( + model_name='employee', + name='last_name', + field=models.CharField(max_length=64), + ), + migrations.AlterField( + model_name='employee', + name='manager', + field=models.CharField(blank=True, max_length=64, null=True), + ), + migrations.AlterField( + model_name='employee', + name='title', + field=models.CharField(blank=True, max_length=64, null=True), + ), + migrations.AlterField( + model_name='logentry', + name='notes', + field=models.CharField(max_length=500), + ), + migrations.AlterField( + model_name='todo', + name='description', + field=models.CharField(max_length=64), + ), + migrations.CreateModel( + name='Event', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('description', models.CharField(max_length=64)), + ('date', models.DateField()), + ('time', models.TimeField(blank=True, null=True)), + ('employee', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='board.employee')), + ], + options={ + 'ordering': ('date', 'time'), + }, + ), + ] diff --git a/board/migrations/0004_rename_description_event_name.py b/board/migrations/0004_rename_description_event_name.py new file mode 100644 index 0000000..dc8a6d3 --- /dev/null +++ b/board/migrations/0004_rename_description_event_name.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.5 on 2021-07-27 01:53 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('board', '0003_auto_20210727_0006'), + ] + + operations = [ + migrations.RenameField( + model_name='event', + old_name='description', + new_name='name', + ), + ] diff --git a/board/models.py b/board/models.py index 1d3ac53..b56eeba 100644 --- a/board/models.py +++ b/board/models.py @@ -57,3 +57,19 @@ class Todo(models.Model): def get_absolute_url(self): return reverse('todo-detail', kwargs={'pk': self.employee.pk, 'todo_pk': self.pk}) + + +class Event(models.Model): + class Meta: + ordering = ('date', 'time') + + name = models.CharField(max_length=64) + date = models.DateField() + time = models.TimeField(blank=True, null=True) + employee = models.ForeignKey(Employee, on_delete=models.CASCADE, blank=True, null=True) + + def __str__(self): + return f"{self.description} on {self.date} @ {self.time}" + + def get_absolute_url(self): + return reverse('event-detail', kwargs={'pk': self.pk}) diff --git a/board/templates/board/employee_detail.html b/board/templates/board/employee_detail.html index 4874dd1..f5eb452 100644 --- a/board/templates/board/employee_detail.html +++ b/board/templates/board/employee_detail.html @@ -31,6 +31,17 @@

Initial comments:

{{employee.initial_comments|linebreaksbr}}
+
+

Employee Events

+ {% for event in employee.event_set.all %} +
+ {{event.date|date:"D, M j"}} + {{event.name}} + {{event.time|time:"TIME_FORMAT"}} + Edit +
+ {% endfor %} +

To-do's

{% include "board/todo_list.html" with todo_list=employee.todo_set.all %} @@ -45,11 +56,14 @@

Add an Entry

- {% for entry in employee.logentry_set.all %} -

- {{entry.created_at|date:"SHORT_DATE_FORMAT"}}— - {{entry.notes}} -

+ {% regroup employee.logentry_set.all by created_at.date as activity %} + {% for date in activity %} +
{{date.grouper|date:"D, M j"}}
+ {% for entry in date.list %} +

+ {{entry.created_at|time:"H:i"}} + {{entry.notes}}

+ {% endfor %} {% endfor %}
diff --git a/board/templates/board/event_confirm_delete.html b/board/templates/board/event_confirm_delete.html new file mode 100644 index 0000000..831a651 --- /dev/null +++ b/board/templates/board/event_confirm_delete.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} + +{% block content %} +
+

Delete {{event}}

+
+ {% csrf_token %} +

+ or cancel +

+
+
+{% endblock %} \ No newline at end of file diff --git a/board/templates/board/event_create_form.html b/board/templates/board/event_create_form.html new file mode 100644 index 0000000..698740d --- /dev/null +++ b/board/templates/board/event_create_form.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} + +{% block content %} +
+

Create Event

+
+
+ {% csrf_token %} + {{form.as_p}} +

+ or cancel +

+
+
+
+{% endblock %} \ No newline at end of file diff --git a/board/templates/board/event_detail.html b/board/templates/board/event_detail.html new file mode 100644 index 0000000..c1934b8 --- /dev/null +++ b/board/templates/board/event_detail.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block content %} +
+

Event

+
+
+ {{event.name}} + {% if event.employee %} for + {{event.employee}} + {% endif %} + + {{event.time|time:"TIME_FORMAT"}} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/board/templates/board/event_form.html b/board/templates/board/event_form.html new file mode 100644 index 0000000..5ca737b --- /dev/null +++ b/board/templates/board/event_form.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} + +{% block content %} +
+

Update Event

+
+
+ {% csrf_token %} + {{form.as_p}} +

+ or cancel +

+
+
+
+{% endblock %} \ No newline at end of file diff --git a/board/templates/board/event_list.html b/board/templates/board/event_list.html new file mode 100644 index 0000000..5e8efb0 --- /dev/null +++ b/board/templates/board/event_list.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} + +{% block content %} +
+

Schedule

+

Add event

+
+ {% for event in event_list %} +
+ {{event.date|date:"D, M j"}} + {{event.name}} + {% if event.employee %} for + {{event.employee}} + {% endif %} + + {{event.time|time:"TIME_FORMAT"}} + Edit +
+ {% empty %} +

There are no events yet.

+ {% endfor %} +
+
+{% endblock %} diff --git a/board/urls.py b/board/urls.py index 0da3ec1..0ac94c6 100644 --- a/board/urls.py +++ b/board/urls.py @@ -32,4 +32,12 @@ urlpatterns = [ ])), ])), + + path('events/', views.EventListView.as_view(), name='event-list'), + path('events/new/', views.EventCreateView.as_view(), name='event-create'), + path('events//', include([ + path('', views.EventDetailView.as_view(), name='event-detail'), + path('update/', views.EventUpdateView.as_view(), name='event-update'), + path('delete/', views.EventDeleteView.as_view(), name='event-delete'), + ])), ] diff --git a/board/views.py b/board/views.py index d0be241..0cbd418 100644 --- a/board/views.py +++ b/board/views.py @@ -7,14 +7,15 @@ from django.views.generic.list import ListView from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Q -from .models import Employee, LogEntry, Todo +from .models import Employee, LogEntry, Todo, Event from .forms import ( EmployeeForm, EmployeePreCreateForm, EmployeeArchiveForm, LogEntryForm, TodoForm, - TodoCreateForm + TodoCreateForm, + EventForm, ) class SearchResultsView(ListView): @@ -170,4 +171,27 @@ class TodoDeleteView(LoginRequiredMixin, DeleteView): return reverse('todo-deleted', kwargs={'pk': self.kwargs['pk']}) class TodoDeleteDoneView(LoginRequiredMixin, TemplateView): - template_name = 'board/item_deleted.html' \ No newline at end of file + template_name = 'board/item_deleted.html' + + +# Events +class EventListView(LoginRequiredMixin, ListView): + model = Event + +class EventCreateView(LoginRequiredMixin, CreateView): + model = Event + template_name_suffix = '_create_form' + form_class = EventForm + success_url = reverse_lazy('profile-detail') + +class EventDetailView(LoginRequiredMixin, DetailView): + model = Event + +class EventUpdateView(LoginRequiredMixin, UpdateView): + model = Event + form_class = EventForm + +class EventDeleteView(LoginRequiredMixin, DeleteView): + model = Event + success_url = reverse_lazy('profile-detail') + diff --git a/onboard/middleware.py b/onboard/middleware.py new file mode 100644 index 0000000..8260705 --- /dev/null +++ b/onboard/middleware.py @@ -0,0 +1,17 @@ +import pytz + +from django.utils import timezone + +class TimezoneMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + tzname = request.session.get('timezone') + if request.user.is_authenticated: + tzname = request.user.profile.timezone + if tzname: + timezone.activate(pytz.timezone(tzname)) + else: + timezone.deactivate() + return self.get_response(request) diff --git a/onboard/settings.py b/onboard/settings.py index 6d2e0bb..086bd93 100644 --- a/onboard/settings.py +++ b/onboard/settings.py @@ -53,6 +53,7 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'onboard.middleware.TimezoneMiddleware', ] ROOT_URLCONF = 'onboard.urls' diff --git a/onboard/settings_dev.py b/onboard/settings_dev.py index e8b8f9e..8e9fb55 100644 --- a/onboard/settings_dev.py +++ b/onboard/settings_dev.py @@ -25,7 +25,7 @@ SECRET_KEY = 'django-insecure-fdq1(f%l*__obb*zvb3gqnlki!ryr@_1_5om(_2ju3mu70mi4% # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'xcarbon.lan'] # Application definition @@ -51,6 +51,7 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'onboard.middleware.TimezoneMiddleware', ] ROOT_URLCONF = 'onboard.urls' diff --git a/static/styles/main.css b/static/styles/main.css index 07c66e4..a82652e 100644 --- a/static/styles/main.css +++ b/static/styles/main.css @@ -2,7 +2,7 @@ --white: #fdfdff; --black: #393d3f; --grey: #a7b4bb; - --blue: #2288a2; + --blue: #10638c; } html { @@ -63,7 +63,7 @@ blockquote { border-left: 2px solid var(--blue); } -header { +body > header { padding: 1rem; } @@ -95,6 +95,7 @@ article { .navbar__mobile { display: none; + font-weight: 900; } .navbar__menu { @@ -203,6 +204,8 @@ article { /* FORMS */ input[type=text]:not([name=description]), input[type=number], +input[type=date], +input[type=time], input[type=password], input[type=search], textarea, select { @@ -223,10 +226,6 @@ input[name=description] { box-sizing: border-box; } -input:focus, textarea:focus, select:focus { - border-color: var(--blue); -} - input[type=radio], input[type=checkbox] { cursor: pointer; width: 1.5rem; @@ -256,6 +255,10 @@ li label { display: inline-block; } +input:focus, textarea:focus, select:focus { + border-color: var(--blue) !important; +} + @@ -300,4 +303,27 @@ hgroup { right: 0; text-align: left; } +} + + + +.log_entry { + display: grid; + grid-template-columns: 1fr 2fr 1fr; + column-gap: 0.5rem; + margin-bottom: 1rem; +} + +.log_entry--employee { + grid-template-columns: 1fr auto; +} + + +.today__event { + display: grid; + grid-template-columns: 1fr 2fr 0.5fr 0.5fr; + column-gap: 1rem; + margin-bottom: 2rem; + padding-bottom: 0.5rem; + border-bottom: 0.046rem solid var(--grey); } \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 61f2fc5..4e7d227 100644 --- a/templates/base.html +++ b/templates/base.html @@ -26,6 +26,8 @@ {% else %} + OnBoard Login {% endif %} @@ -51,12 +54,17 @@
+
Today
+
+
Events
+
Employees

Logout
{% else %} + OnBoard Login {% endif %}