diff --git a/.DS_Store b/.DS_Store index f29bb9ef..cf57988c 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/osinaweb/db.sqlite3 b/osinaweb/db.sqlite3 index 42a14def..1cae8828 100644 Binary files a/osinaweb/db.sqlite3 and b/osinaweb/db.sqlite3 differ diff --git a/osinaweb/osichat/__init__.py b/osinaweb/osichat/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/osinaweb/osichat/__pycache__/__init__.cpython-310.pyc b/osinaweb/osichat/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 00000000..852ea410 Binary files /dev/null and b/osinaweb/osichat/__pycache__/__init__.cpython-310.pyc differ diff --git a/osinaweb/osichat/__pycache__/admin.cpython-310.pyc b/osinaweb/osichat/__pycache__/admin.cpython-310.pyc new file mode 100644 index 00000000..ff08f7b4 Binary files /dev/null and b/osinaweb/osichat/__pycache__/admin.cpython-310.pyc differ diff --git a/osinaweb/osichat/__pycache__/apps.cpython-310.pyc b/osinaweb/osichat/__pycache__/apps.cpython-310.pyc new file mode 100644 index 00000000..e3562eb8 Binary files /dev/null and b/osinaweb/osichat/__pycache__/apps.cpython-310.pyc differ diff --git a/osinaweb/osichat/__pycache__/models.cpython-310.pyc b/osinaweb/osichat/__pycache__/models.cpython-310.pyc new file mode 100644 index 00000000..52e4d923 Binary files /dev/null and b/osinaweb/osichat/__pycache__/models.cpython-310.pyc differ diff --git a/osinaweb/osichat/admin.py b/osinaweb/osichat/admin.py new file mode 100644 index 00000000..a615015f --- /dev/null +++ b/osinaweb/osichat/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin +from .models import * +# Register your models here. + +admin.site.register(ChatRoom) +admin.site.register(ChatMember) +admin.site.register(ChatProject) +admin.site.register(ChatMessage) +admin.site.register(ChatMessageAttachment) +admin.site.register(ChatMessageReaction) +admin.site.register(ChatMessageSeen) \ No newline at end of file diff --git a/osinaweb/osichat/apps.py b/osinaweb/osichat/apps.py new file mode 100644 index 00000000..20395e7e --- /dev/null +++ b/osinaweb/osichat/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class OsichatConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'osichat' diff --git a/osinaweb/osichat/migrations/0001_initial.py b/osinaweb/osichat/migrations/0001_initial.py new file mode 100644 index 00000000..c7ba5ad5 --- /dev/null +++ b/osinaweb/osichat/migrations/0001_initial.py @@ -0,0 +1,79 @@ +# Generated by Django 4.2.5 on 2024-07-16 10:34 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('osinacore', '0097_remove_status_date_remove_status_time'), + ] + + operations = [ + migrations.CreateModel( + name='ChatMessage', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('content', models.TimeField(blank=True, null=True)), + ('date_sent', models.DateTimeField()), + ('member', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='ChatRoom', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50)), + ('date_created', models.DateTimeField()), + ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='ChatProject', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('public', models.BooleanField()), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='osinacore.project')), + ('room', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='osichat.chatroom')), + ], + ), + migrations.CreateModel( + name='ChatMessageSeen', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('message', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='osichat.chatmessage')), + ], + ), + migrations.CreateModel( + name='ChatMessageReaction', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('reaction', models.CharField(max_length=200)), + ('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('message', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='osichat.chatmessage')), + ], + ), + migrations.CreateModel( + name='ChatMessageAttachment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('attachment', models.TextField()), + ('message', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='osichat.chatmessage')), + ], + ), + migrations.CreateModel( + name='ChatMember', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_joined', models.DateTimeField()), + ('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='osichat.chatroom')), + ], + ), + ] diff --git a/osinaweb/osichat/migrations/__init__.py b/osinaweb/osichat/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/osinaweb/osichat/migrations/__pycache__/0001_initial.cpython-310.pyc b/osinaweb/osichat/migrations/__pycache__/0001_initial.cpython-310.pyc new file mode 100644 index 00000000..ed0a1abf Binary files /dev/null and b/osinaweb/osichat/migrations/__pycache__/0001_initial.cpython-310.pyc differ diff --git a/osinaweb/osichat/migrations/__pycache__/__init__.cpython-310.pyc b/osinaweb/osichat/migrations/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 00000000..492fc086 Binary files /dev/null and b/osinaweb/osichat/migrations/__pycache__/__init__.cpython-310.pyc differ diff --git a/osinaweb/osichat/models.py b/osinaweb/osichat/models.py new file mode 100644 index 00000000..80a5eab8 --- /dev/null +++ b/osinaweb/osichat/models.py @@ -0,0 +1,42 @@ +from django.db import models +from osinacore.models import * +# Create your models here. + +class ChatRoom(models.Model): + name = models.CharField(max_length=50) + created_by = models.ForeignKey(User, null=True, on_delete=models.SET_NULL) + date_created = models.DateTimeField() + + +class ChatMember(models.Model): + member = models.ForeignKey(User, on_delete=models.CASCADE) + room = models.ForeignKey(ChatRoom, on_delete=models.CASCADE) + date_joined = models.DateTimeField() + + +class ChatProject(models.Model): + room = models.OneToOneField(ChatRoom, on_delete=models.CASCADE) + project = models.ForeignKey(Project, on_delete=models.CASCADE) + public = models.BooleanField() + + +class ChatMessage(models.Model): + member = models.ForeignKey(User, null=True, on_delete=models.SET_NULL) + content = models.TimeField(null=True, blank=True) + date_sent = models.DateTimeField() + + +class ChatMessageAttachment(models.Model): + message = models.ForeignKey(ChatMessage, on_delete=models.CASCADE) + attachment = models.TextField() + + +class ChatMessageReaction(models.Model): + message = models.ForeignKey(ChatMessage, on_delete=models.CASCADE) + member = models.ForeignKey(User, on_delete=models.CASCADE) + reaction = models.CharField(max_length=200) + + +class ChatMessageSeen(models.Model): + message = models.ForeignKey(ChatMessage, on_delete=models.CASCADE) + member = models.ForeignKey(User, on_delete=models.CASCADE) \ No newline at end of file diff --git a/osinaweb/osichat/tests.py b/osinaweb/osichat/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/osinaweb/osichat/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/osinaweb/osichat/views.py b/osinaweb/osichat/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/osinaweb/osichat/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/osinaweb/osinaweb/__pycache__/settings.cpython-310.pyc b/osinaweb/osinaweb/__pycache__/settings.cpython-310.pyc index e9f04285..ddc7ebc0 100644 Binary files a/osinaweb/osinaweb/__pycache__/settings.cpython-310.pyc and b/osinaweb/osinaweb/__pycache__/settings.cpython-310.pyc differ diff --git a/osinaweb/osinaweb/settings.py b/osinaweb/osinaweb/settings.py index a36e9f7e..74450788 100644 --- a/osinaweb/osinaweb/settings.py +++ b/osinaweb/osinaweb/settings.py @@ -40,6 +40,7 @@ LOGIN_URL = 'signin' # Application definition INSTALLED_APPS = [ + 'osichat', 'daphne', 'support', 'rest_framework', diff --git a/osinaweb/static/js/tickets/tickets-room.js b/osinaweb/static/js/tickets/tickets-room.js index 8736bd93..5d372d50 100644 --- a/osinaweb/static/js/tickets/tickets-room.js +++ b/osinaweb/static/js/tickets/tickets-room.js @@ -63,7 +63,7 @@ function app(socket) { function initializeWebSocket() { const ticketId = document.getElementById('ticketId').textContent.trim(); - const wsUrl = `wss://${window.location.host}/ws/ticketroom/${ticketId}/`; + const wsUrl = `ws://${window.location.host}/ws/ticketroom/${ticketId}/`; const socket = new WebSocket(wsUrl); socket.onopen = () => { @@ -118,12 +118,25 @@ function initializeWebSocket() { button.classList.remove('hidden'); } }); - + } else if (data.event_type === 'user_status') { const toponlineUsersDiv = document.getElementById('top-online-users'); const fixedonlineUsersDiv = document.getElementById('fixed-online-users'); toponlineUsersDiv.innerHTML = data.html; fixedonlineUsersDiv.innerHTML = data.html; + + } else if (data.event_type === 'ticket_status') { + const ticketStatusDivs = document.querySelectorAll('.ticket-status'); + ticketStatusDivs.forEach(div => { + div.innerHTML = data.html; + }); + + } else if (data.event_type === 'ticket_department') { + const ticketStatusDivs = document.querySelectorAll('.ticket-department'); + ticketStatusDivs.forEach(div => { + div.innerHTML = data.html; + }); + } else { messagesDiv.insertAdjacentHTML('beforeend', data.html); typingDiv.innerHTML = ''; diff --git a/osinaweb/support/__pycache__/consumers.cpython-310.pyc b/osinaweb/support/__pycache__/consumers.cpython-310.pyc index 82847a45..1f0e2ca5 100644 Binary files a/osinaweb/support/__pycache__/consumers.cpython-310.pyc and b/osinaweb/support/__pycache__/consumers.cpython-310.pyc differ diff --git a/osinaweb/support/__pycache__/models.cpython-310.pyc b/osinaweb/support/__pycache__/models.cpython-310.pyc index 58a77998..4a4da824 100644 Binary files a/osinaweb/support/__pycache__/models.cpython-310.pyc and b/osinaweb/support/__pycache__/models.cpython-310.pyc differ diff --git a/osinaweb/support/__pycache__/views.cpython-310.pyc b/osinaweb/support/__pycache__/views.cpython-310.pyc index 855b0af3..a7d237dd 100644 Binary files a/osinaweb/support/__pycache__/views.cpython-310.pyc and b/osinaweb/support/__pycache__/views.cpython-310.pyc differ diff --git a/osinaweb/support/consumers.py b/osinaweb/support/consumers.py index 77b3b6de..b2f09388 100644 --- a/osinaweb/support/consumers.py +++ b/osinaweb/support/consumers.py @@ -167,6 +167,32 @@ class TicketRoomConsumer(WebsocketConsumer): 'reaction': new_reaction.reaction if new_reaction else None })) + def new_status_handler(self, event): + last_ticket_status = TicketStatus.objects.filter(ticket=self.ticket).last() + context = { + 'last_ticket_status': last_ticket_status, + 'new': True + } + html = render_to_string("details_templates/partials/ticket-status.html", context=context) + self.send(text_data=json.dumps({ + 'event_type': 'ticket_status', + 'html': html + })) + + + def new_department_handler(self, event): + last_ticket_department = TicketDepartment.objects.filter(ticket=self.ticket).last() + context = { + 'last_ticket_department': last_ticket_department, + 'new': True + } + html = render_to_string("details_templates/partials/ticket-department.html", context=context) + self.send(text_data=json.dumps({ + 'event_type': 'ticket_department', + 'html': html + })) + + def modify_online_user(self): connections = TicketConnection.objects.filter(ticket=self.ticket, terminated_at__isnull=True) event = { diff --git a/osinaweb/support/models.py b/osinaweb/support/models.py index ba4f3a67..eda0dfa9 100644 --- a/osinaweb/support/models.py +++ b/osinaweb/support/models.py @@ -165,6 +165,17 @@ class TicketDepartment(models.Model): date_added = models.DateTimeField() +@receiver(post_save, sender=TicketDepartment) +def new_ticket_department_handler(sender, instance, created, **kwargs): + if created: + channel_layer = get_channel_layer() + event = { + 'type': 'new_department_handler', + } + ticket_number = instance.ticket.ticket_number + async_to_sync(channel_layer.group_send)(ticket_number, event) + + class TicketStaff(models.Model): staff = models.ForeignKey(StaffProfile, on_delete=models.CASCADE) ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE) @@ -205,6 +216,19 @@ class TicketStatus(models.Model): date_added = models.DateTimeField() +@receiver(post_save, sender=TicketStatus) +def new_ticket_status_handler(sender, instance, created, **kwargs): + if created: + channel_layer = get_channel_layer() + event = { + 'type': 'new_status_handler', + } + ticket_number = instance.ticket.ticket_number + async_to_sync(channel_layer.group_send)(ticket_number, event) + + + + class TicketTask(models.Model): ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE) task = models.ForeignKey(Task, on_delete=models.CASCADE) diff --git a/osinaweb/support/templates/details_templates/partials/new-ticket-message.html b/osinaweb/support/templates/details_templates/partials/new-ticket-message.html index 4ee0b692..bc34258a 100644 --- a/osinaweb/support/templates/details_templates/partials/new-ticket-message.html +++ b/osinaweb/support/templates/details_templates/partials/new-ticket-message.html @@ -1,4 +1,4 @@ -
+
{% include 'details_templates/partials/ticket-message.html' %} diff --git a/osinaweb/support/templates/details_templates/partials/ticket-department.html b/osinaweb/support/templates/details_templates/partials/ticket-department.html new file mode 100644 index 00000000..c8b2108f --- /dev/null +++ b/osinaweb/support/templates/details_templates/partials/ticket-department.html @@ -0,0 +1,11 @@ +{{last_ticket_department.department}} + + diff --git a/osinaweb/support/templates/details_templates/partials/ticket-status.html b/osinaweb/support/templates/details_templates/partials/ticket-status.html new file mode 100644 index 00000000..b0367837 --- /dev/null +++ b/osinaweb/support/templates/details_templates/partials/ticket-status.html @@ -0,0 +1,30 @@ +{% if last_ticket_status.status == 'Open' %} +
+
+

Opened by {{last_ticket_status.added_by.first_name}} on + {{ last_ticket_status.date_added|date:"d F Y, h:i A" }}

+
+{% elif last_ticket_status.status == 'Working On' %} +
+
+

Updated to 'Working On' by + {{last_ticket_status.added_by.first_name}} on {{ last_ticket_status.date_added|date:"d F Y, h:i A" }} +

+
+{% elif last_ticket_status.status == 'Closed' %} +
+
+

Closed by {{last_ticket_status.added_by.first_name}} on + {{ last_ticket_status.date_added|date:"d F Y, h:i A" }}

+
+{% endif %} + + diff --git a/osinaweb/support/templates/details_templates/ticket-room.html b/osinaweb/support/templates/details_templates/ticket-room.html index fb0332e1..987353df 100644 --- a/osinaweb/support/templates/details_templates/ticket-room.html +++ b/osinaweb/support/templates/details_templates/ticket-room.html @@ -21,8 +21,7 @@

Ticket #{{ticket.ticket_number}} - {{ticket.ticketdepartment_set.all.last.department}}

+ class="font-semibold">#{{ticket.ticket_number}} - {% include 'details_templates/partials/ticket-department.html' %}

@@ -58,29 +57,9 @@
- - - {% if last_ticket_status.status == 'Open' %} -
-
-

Opened by {{last_ticket_status.added_by.first_name}} on - {{ last_ticket_status.date_added|date:"d F Y, h:i A" }}

-
- {% elif last_ticket_status.status == 'Working On' %} -
-
-

Updated to 'Working On' by - {{last_ticket_status.added_by.first_name}} on {{ last_ticket_status.date_added|date:"d F Y, h:i A" }} -

-
- {% elif last_ticket_status.status == 'Closed' %} -
-
-

Closed by {{last_ticket_status.added_by.first_name}} on - {{ last_ticket_status.date_added|date:"d F Y, h:i A" }}

+
+ {% include 'details_templates/partials/ticket-status.html' %}
- {% endif %} -

{{ticket.description}}

@@ -118,30 +97,13 @@