summaryrefslogtreecommitdiffstats
path: root/restaurant_orders/dashboard
diff options
context:
space:
mode:
Diffstat (limited to 'restaurant_orders/dashboard')
-rw-r--r--restaurant_orders/dashboard/__init__.py0
-rw-r--r--restaurant_orders/dashboard/admin.py3
-rw-r--r--restaurant_orders/dashboard/apps.py6
-rw-r--r--restaurant_orders/dashboard/consumers.py49
-rw-r--r--restaurant_orders/dashboard/forms.py30
-rw-r--r--restaurant_orders/dashboard/templates/dashboard/dashboard.html152
-rw-r--r--restaurant_orders/dashboard/templates/dashboard/dashboard_order.html89
-rw-r--r--restaurant_orders/dashboard/urls.py17
-rw-r--r--restaurant_orders/dashboard/views.py95
9 files changed, 441 insertions, 0 deletions
diff --git a/restaurant_orders/dashboard/__init__.py b/restaurant_orders/dashboard/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/restaurant_orders/dashboard/__init__.py
diff --git a/restaurant_orders/dashboard/admin.py b/restaurant_orders/dashboard/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/restaurant_orders/dashboard/admin.py
@@ -0,0 +1,3 @@
1from django.contrib import admin
2
3# Register your models here.
diff --git a/restaurant_orders/dashboard/apps.py b/restaurant_orders/dashboard/apps.py
new file mode 100644
index 0000000..7b1cc05
--- /dev/null
+++ b/restaurant_orders/dashboard/apps.py
@@ -0,0 +1,6 @@
1from django.apps import AppConfig
2
3
4class DashboardConfig(AppConfig):
5 default_auto_field = 'django.db.models.BigAutoField'
6 name = 'dashboard'
diff --git a/restaurant_orders/dashboard/consumers.py b/restaurant_orders/dashboard/consumers.py
new file mode 100644
index 0000000..17a5d34
--- /dev/null
+++ b/restaurant_orders/dashboard/consumers.py
@@ -0,0 +1,49 @@
1from channels.generic.websocket import AsyncWebsocketConsumer
2from channels.exceptions import StopConsumer
3import channels.layers
4
5from asgiref.sync import sync_to_async, async_to_sync
6
7from core.models import Restaurant, Order
8
9from django.db.models import signals
10from django.dispatch import receiver
11from django.core import serializers
12
13
14class OrderConsumer(AsyncWebsocketConsumer):
15 async def connect(self):
16 restaurant_pk = self.scope['url_route']['kwargs']['restaurant_pk']
17 self.restaurant = sync_to_async(Restaurant.get_user_restaurant_or_404)(restaurant_pk, self.scope['user'])
18 if not self.restaurant:
19 return
20
21 self.room_name = str(restaurant_pk)
22 self.room_group_name = f'restaurant_{self.room_name}'
23
24 # Join room group
25 await self.channel_layer.group_add(
26 self.room_group_name,
27 self.channel_name
28 )
29
30 await self.accept()
31
32 async def disconnect(self, close_code):
33 raise StopConsumer
34
35 async def new_order(self, event):
36 await self.send(event['data'])
37
38 @staticmethod
39 @receiver(signals.post_save, sender=Order)
40 def order_observer(sender, instance, **kwargs):
41 if not instance.can_display:
42 return
43
44 layer = channels.layers.get_channel_layer()
45
46 async_to_sync(layer.group_send)(f'restaurant_{instance.restaurant.pk}', {
47 'type': 'new.order',
48 'data': serializers.serialize('json', (instance, ))
49 })
diff --git a/restaurant_orders/dashboard/forms.py b/restaurant_orders/dashboard/forms.py
new file mode 100644
index 0000000..23937ed
--- /dev/null
+++ b/restaurant_orders/dashboard/forms.py
@@ -0,0 +1,30 @@
1from django import forms
2
3from core.models import Order
4
5
6FORM_TAILWIND_CLASSES = 'form-control block w-full px-3 py-1.5 text-base font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none'
7
8class OrderStatusForm(forms.ModelForm):
9 class Meta:
10 model = Order
11 fields = ('wp_status', )
12
13 def __init__(self, *args, **kwargs):
14 super().__init__(*args, **kwargs)
15 self.fields['wp_status'].label = 'Przenies do:'
16
17class AddToBillForm(forms.Form):
18 send_mail = forms.BooleanField(label='Wyslij maila', initial=False, required=False)
19 send_sms = forms.BooleanField(label='Wyslij sms', initial=True, required=False)
20
21 def __init__(self, pk, user, *args, **kwargs):
22 super().__init__(*args, **kwargs)
23 order = Order.get_order(pk, user)
24
25 for item in order.line_items:
26 index = item['product_id']
27 self.fields[index] = forms.IntegerField(required=False, label=item['name'])
28
29 for index in self.fields.keys():
30 self.fields[index].widget.attrs.update({'class': FORM_TAILWIND_CLASSES})
diff --git a/restaurant_orders/dashboard/templates/dashboard/dashboard.html b/restaurant_orders/dashboard/templates/dashboard/dashboard.html
new file mode 100644
index 0000000..0d2bf11
--- /dev/null
+++ b/restaurant_orders/dashboard/templates/dashboard/dashboard.html
@@ -0,0 +1,152 @@
1{% extends "base.html" %}
2
3{% block title %}Dashboard{% endblock %}
4
5{% block content %}
6 {% include '_pagination.html' %}
7 <form method="get" id="" action="" class="font-bold">
8 <select name="status" class="bg-gray-50 rounded-lg p-2 pl-4 pr-4" onchange="this.form.submit()" id="id_status">
9
10 <option value="" selected>Wszystkie</option>
11 <option value="pending">Oczekujace</option>
12 <option value="processing">Przetwarzane</option>
13 <option value="on-hold">Wstrzymane</option>
14 <option value="completed">Zakonczone</option>
15 <option value="cancelled">Anulowane</option>
16 <option value="refunded">Zwrocone</option>
17 <option value="failed">Nie powiodlo sie</option>
18 <option value="trash">Usuniete</option>
19 </select>
20 </form>
21
22 <script type="text/javascript">
23 const $select = document.querySelector('#id_status');
24 const paramsString = window.location.search;
25 const searchParams = new URLSearchParams(paramsString);
26 let status = searchParams.get('status');
27 if(status != null)
28 $select.value = searchParams.get('status');
29 </script>
30
31 <div id="orders" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 max-h-0 text-center h-full bg-gray-50 text-gray-600">
32 {% for object in object_list %}
33 <div id="order_{{object.pk}}" class='order'>
34 <a href="{% url 'dashboard:order_dashboard' object.pk %}">
35 <div class="flex m-10">
36 <div class="bg-local block rounded-lg shadow-lg p-10 max-w-sm bg-gray-50 hover:bg-gray-100 hover:shadow-lg focus:bg-gray-200 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-gray-200 active:shadow-lg transition duration-150 ease-in-out">
37 <h1 class="text-2xl leading-tight w-max font-bold">#{{ object.wp_id}} - {{ object.billing.first_name }} {{ object.billing.last_name }}</h1>
38 <h2 class="text-lg font-bold w-max mb-4">{{object.get_wp_status_display}}</h2>
39 <ul class="list-decimal">
40 {% for item in object.line_items %}
41 <li class="text-sm leading-tight font-bold pt-1 float-left">{{ item.name }} x{{item.quantity}}szt.</li>
42 {% endfor %}
43 </ul>
44 </div>
45 </div>
46 </a>
47 </div>
48 {% endfor %}
49 </div>
50 <h3 id="empty_orders_list_message" class="hidden text-3xl font-bold py-20 mb-8">Na poczatku byla ciemnosc...</h3>
51
52 <script type="text/javascript">
53 function setup_message_if_orders_empty() {
54 let orders = document.getElementById(`orders`)
55 let message = document.getElementById(`empty_orders_list_message`)
56
57 if (orders.getElementsByClassName('order').length == 0)
58 message.classList.remove('hidden')
59 else
60 message.classList.add('hidden')
61 }
62
63 function get_status_display(status) {
64 console.log(status)
65 let status_map = {
66 'pending': 'Oczekujace',
67 'processing': 'Przetwarzane',
68 'on-hold': 'Wstrzymane',
69 'completed': 'Zakonczone',
70 'cancelled': 'Anulowane',
71 'refunded': 'Zwrocone',
72 'failed': 'Nie powiodlo sie',
73 'trash': 'Usuniete'
74 }
75 return status_map[status]
76 }
77 function get_url(id) {
78 return `/dashboard/restaurant/order/${id}/`
79 }
80
81 function get_line_items_html(line_items) {
82 let data = ''
83 for (i in line_items) {
84 let item = line_items[i];
85 data += `<li class="text-sm leading-tight font-bold pt-1 float-left">${item.name} x${item.quantity}szt.</li>`
86 }
87 return data
88 }
89
90 function add_order(data) {
91 let order = `
92 <div id="order_${data.pk}" class='order'>
93 <a href="${get_url(data.pk)}">
94 <div class="flex m-10">
95 <div class="bg-local block rounded-lg shadow-lg p-10 max-w-sm bg-gray-50 hover:bg-gray-100 hover:shadow-lg focus:bg-gray-200 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-gray-200 active:shadow-lg transition duration-150 ease-in-out">
96 <h1 class="text-2xl leading-tight w-max font-bold">#${data.fields.wp_id} - ${data.fields.billing.first_name} ${data.fields.billing.last_name}</h1>
97 <h2 class="text-lg font-bold w-max mb-4">${get_status_display(data.fields.wp_status)}</h2>
98 <ul class="list-decimal">
99 ${get_line_items_html(data.fields.line_items)}
100 </ul>
101 </div>
102 </div>
103 </a>
104 </div>
105 `
106
107
108 let order_with_pk_from_data = document.getElementById(`order_${data.pk}`)
109
110 const paramsString = window.location.search;
111 const searchParams = new URLSearchParams(paramsString);
112 status = searchParams.get('status')
113 if(status != data.fields.status && status) {
114 let order_container = document.getElementById(`orders`)
115 order_container.removeChild(order_with_pk_from_data)
116 setup_message_if_orders_empty()
117 return // TODO: add notification in future
118 }
119 if (order_with_pk_from_data)
120 order_with_pk_from_data.innerHTML = order
121 else {
122 let orders_container = document.getElementById('orders');
123 orders_container.innerHTML += order;
124 }
125 setup_message_if_orders_empty()
126 return order
127 }
128 const socket = new WebSocket(
129 'ws://'
130 + window.location.host
131 + '/ws/dashboard/orders/{{view.kwargs.restaurant_pk}}/'
132 );
133
134 socket.onopen = function(e) {
135 console.log("[open] Connection established");
136 };
137
138 socket.onmessage = function(event) {
139 console.log('onmessage')
140 let data=JSON.parse(event.data);
141 console.log(data)
142 console.log(add_order(data[0]));
143 };
144 socket.onclose = function(e) {
145 console.error('Chat socket closed unexpectedly');
146 const data = JSON.parse(e.data);
147 console.log(data)
148 };
149
150 setup_message_if_orders_empty();
151 </script>
152 {% endblock %}
diff --git a/restaurant_orders/dashboard/templates/dashboard/dashboard_order.html b/restaurant_orders/dashboard/templates/dashboard/dashboard_order.html
new file mode 100644
index 0000000..f245843
--- /dev/null
+++ b/restaurant_orders/dashboard/templates/dashboard/dashboard_order.html
@@ -0,0 +1,89 @@
1{% extends "base.html" %}
2
3{% block title %}Order #{{order.wp_id}}{% endblock %}
4
5{% block content %}
6 <div>
7 <a href="javascript:window.history.back();">Wroc</a>
8 <h1 class="text-2xl leading-tight mb-4 pt-20 font-bold"><a href="{{order.restaurant.wordpress_url}}/wp-admin/post.php?post={{ order.wp_id}}&action=edit" target="_blank">#{{ order.wp_id}} - {{ order.billing.first_name }} {{ order.billing.last_name }} - {{order.get_wp_status_display}}</a></h1>
9 <div class="grid grid-cols-1 lg:grid-cols-2 md:grid-cols-1 max-h-0 text-center h-full bg-gray-50 text-gray-600 ">
10 <div class="flex m-10">
11 <div class="bg-local block rounded-lg shadow-lg p-10 w-full bg-gray-50">
12 <h1 class="text-2xl leading-tight mb-4 font-bold">Zamowienie</h1>
13
14 <table class="table-auto w-full">
15 <thead>
16 <tr>
17 <th>#</th>
18 <th>Produkt</th>
19 <th>Cena za produkt</th>
20 <th>Cena za produkty</th>
21 <th>Ilosc</th>
22 </tr>
23 </thead>
24 <tbody>
25 {% for item in order.line_items %}
26 <tr>
27 <td>{{ forloop.counter }}</td>
28 <td>{{ item.name }}</td>
29 <td>{{item.price}} zl</td>
30 <td>{{item.subtotal}} zl</td>
31 <td>{{item.quantity}}</td>
32 </tr>
33 {% endfor %}
34 </tbody>
35 </table>
36
37 <ul class="list-decimal m-2">
38 </ul>
39 </div>
40 </div>
41
42 <div class="flex m-10">
43 <div class="bg-local block rounded-lg shadow-lg p-10 w-full bg-gray-50">
44 <h1 class="text-2xl leading-tight mb-4 font-bold">Adres dostawy</h1>
45 <ul class="list-decimal m-2">
46 <li class="m-2 w-full text-sm leading-tight font-bold float-left">
47 <span class="float-left">Imie i Nazwisko</span>
48 <span class="float-right">{{order.billing.first_name}} {{order.billing.last_name}}</span>
49 </li>
50 <li class="m-2 w-full text-sm leading-tight font-bold float-left">
51 <span class="float-left">Numer stolika</span>
52 <span class="float-right">{{order.billing.address_1}}</span>
53 </li>
54 <li class="m-2 w-full text-sm leading-tight font-bold float-left">
55 <span class="float-left">Telefon</span>
56 <span class="float-right">{{order.billing.phone}}</span>
57 </li>
58 <li class="m-2 w-full text-sm leading-tight font-bold float-left">
59 <span class="float-left">Email</span>
60 <span class="float-right">{{order.billing.email}}</span>
61 </li>
62 </ul>
63 </div>
64 </div>
65
66 <div class="flex m-10">
67 <div class="bg-local block rounded-lg shadow-lg p-10 w-full bg-gray-50">
68 <h1 class="text-2xl leading-tight mb-4 font-bold">Doliczenie do zamowienia</h1>
69 <form method="POST" id="" action="{% url 'dashboard:order_add_to_bill' order.pk %}">
70 {% csrf_token %}
71 {{addToBillForm}}
72 <input type="submit" value="Dolicz" class="mt-2 inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out"/>
73 </form>
74 </div>
75 </div>
76
77 <div class="flex m-10">
78 <div class="bg-local block rounded-lg shadow-lg p-10 w-full bg-gray-50">
79 <h1 class="text-2xl leading-tight mb-4 font-bold">Akcje</h1>
80 <form method="POST" id="" action="{% url 'dashboard:order_status_change' order.pk %}">
81 {% csrf_token %}
82 {{orderStatusForm}}
83 <input type="submit" value="Zapisz" class="ml-2 inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out"/>
84 </form>
85 </div>
86 </div>
87 </div>
88 </div>
89{% endblock %}
diff --git a/restaurant_orders/dashboard/urls.py b/restaurant_orders/dashboard/urls.py
new file mode 100644
index 0000000..cd67ce3
--- /dev/null
+++ b/restaurant_orders/dashboard/urls.py
@@ -0,0 +1,17 @@
1from django.urls import path
2from dashboard.views import Home, DashboardView, DashboardOrderView, ChangeOrderStatusView, AddToBillView
3from dashboard.consumers import OrderConsumer
4
5app_name="dashboard"
6
7urlpatterns = [
8 path('', Home.as_view(), name="home"),
9 path('restaurant/<int:restaurant_pk>/', DashboardView.as_view(), name='restaurant_dashboard'),
10 path('restaurant/order/<int:pk>/', DashboardOrderView.as_view(), name='order_dashboard'),
11 path('restaurant/order/<int:pk>/change/status/', ChangeOrderStatusView.as_view(), name='order_status_change'),
12 path('restaurant/order/<int:pk>/add_to_bill/', AddToBillView.as_view(), name='order_add_to_bill'),
13]
14
15websocket_urlpatterns = [
16 path('orders/<int:restaurant_pk>/', OrderConsumer.as_asgi()),
17]
diff --git a/restaurant_orders/dashboard/views.py b/restaurant_orders/dashboard/views.py
new file mode 100644
index 0000000..cfbc757
--- /dev/null
+++ b/restaurant_orders/dashboard/views.py
@@ -0,0 +1,95 @@
1from django.shortcuts import render, redirect, reverse
2from django.contrib.auth.mixins import LoginRequiredMixin
3from django.views.generic.list import ListView, View
4from django.views.generic.edit import UpdateView
5from django.contrib.messages.views import SuccessMessageMixin
6from django.core import serializers
7from django.contrib import messages
8
9
10from dashboard.forms import OrderStatusForm, AddToBillForm
11from core.tasks import create_order_and_send_notification
12from core.models import Restaurant, Order
13
14class Home(LoginRequiredMixin, View):
15 def get(self, request):
16 redirect_url = 'dashboard:restaurant_dashboard'
17 restaurants = Restaurant.get_user_restaurants(request.user)
18
19 if len(restaurants) == 1:
20 return redirect(redirect_url, restaurant_pk=restaurants[0].pk)
21
22 return render(request, template_name='restaurants_choice.html', context={
23 'title': 'Dashboard',
24 'restaurants': restaurants,
25 'redirect_url': redirect_url
26 })
27
28class DashboardView(LoginRequiredMixin, ListView):
29 template_name = 'dashboard/dashboard.html'
30 model = Order
31 paginate_by = 4
32
33 def get_queryset(self, *args, **kwargs):
34 restaurant = Restaurant.get_user_restaurant_or_404(pk=self.kwargs.get('restaurant_pk'),
35 user=self.request.user)
36
37 status = self.request.GET.get('status')
38 queryset = {}
39 if status:
40 queryset['wp_status'] = status
41
42 obj = super().get_queryset(*args, **kwargs).filter(
43 restaurant=restaurant,
44 can_display=True,
45 **queryset
46 ).order_by('-wp_id')
47
48 return obj
49
50
51class DashboardOrderView(LoginRequiredMixin, View):
52 def get(self, request, pk):
53 order = Order.get_order(pk, request.user)
54 orderStatusForm = OrderStatusForm(instance=order)
55 addToBillForm = AddToBillForm(pk, request.user)
56
57 return render(request, 'dashboard/dashboard_order.html', context={
58 'order': order,
59 'orderStatusForm': orderStatusForm,
60 'addToBillForm': addToBillForm
61 })
62
63
64class ChangeOrderStatusView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
65 form_class = OrderStatusForm
66 model = Order
67 success_message = 'Zapisano!'
68 slug_field='order_pk'
69
70 def get_queryset(self, *args, **kwargs):
71 return super().get_queryset(*args, **kwargs).filter(
72 pk=self.kwargs['pk'],
73 can_display=True,
74 restaurant__users=self.request.user.pk
75 )
76
77 def get_success_url(self):
78 return reverse('dashboard:order_dashboard', args=(self.kwargs['pk'], ))
79
80class AddToBillView(LoginRequiredMixin, View):
81 def post(self, request, pk, *args, **kwargs):
82 addToBillForm = AddToBillForm(pk, request.user, request.POST)
83
84 if addToBillForm.is_valid():
85 order = Order.get_order(pk, request.user)
86 order = serializers.serialize('json', (order, ))
87 email = True if addToBillForm.data.get('send_mail') else False
88 phone = True if addToBillForm.data.get('send_sms') else False
89 items = [(wp_pk, price) for wp_pk, price in request.POST.items() if price.isdigit()]
90
91 # TODO: Za duzo tych jebanych argumentow !
92 create_order_and_send_notification.delay(order, items, email, phone, request.user.pk)
93
94
95 return redirect('dashboard:order_dashboard', pk)