new
parent
8a31343fcf
commit
7eb89e5418
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,93 @@
|
||||
from channels.generic.websocket import WebsocketConsumer
|
||||
from .models import *
|
||||
import json
|
||||
from django.template.loader import render_to_string
|
||||
from asgiref.sync import async_to_sync
|
||||
import threading
|
||||
|
||||
def get_last_seen(user):
|
||||
connection = Connection.objects.filter(user=user).last()
|
||||
if not connection.exists():
|
||||
return "Not seen yet"
|
||||
|
||||
if connection.online:
|
||||
return "Online"
|
||||
last_seen_time = connection.last_seen
|
||||
now = timezone.now()
|
||||
time_diff = now - last_seen_time
|
||||
if time_diff < timedelta(days=1):
|
||||
if last_seen_time.date() == now.date():
|
||||
return f"last seen today at {last_seen_time.strftime('%I:%M %p')}"
|
||||
else:
|
||||
return f"last seen yesterday at {last_seen_time.strftime('%I:%M %p')}"
|
||||
else:
|
||||
return f"last seen on {last_seen_time.strftime('%b %d at %I:%M %p')}"
|
||||
|
||||
|
||||
|
||||
class OnlineUserConsumer(WebsocketConsumer):
|
||||
def connect(self):
|
||||
self.user = self.scope['user']
|
||||
existing_connection = Connection.objects.filter(user=self.user).last()
|
||||
if existing_connection:
|
||||
self.connection = existing_connection
|
||||
self.connection.online = True
|
||||
self.connection.disconnected = False
|
||||
self.connection.save()
|
||||
else:
|
||||
self.connection = Connection.objects.create(user=self.user, online=True)
|
||||
|
||||
async_to_sync(self.channel_layer.group_add)(
|
||||
'online_users', self.channel_name
|
||||
)
|
||||
self.accept()
|
||||
self.modify_online_user()
|
||||
|
||||
def disconnect(self, close_code):
|
||||
self.last_seen = datetime.now()
|
||||
self.connection.disconnected = True
|
||||
self.connection.save()
|
||||
timer_thread = threading.Timer(10, self.check_disconnect_status)
|
||||
timer_thread.start()
|
||||
|
||||
def check_disconnect_status(self):
|
||||
connection = Connection.objects.filter(user=self.user).last()
|
||||
if connection.disconnected:
|
||||
self.connection.last_seen = self.last_seen
|
||||
self.connection.online = False
|
||||
self.connection.save()
|
||||
self.modify_online_user()
|
||||
|
||||
def modify_online_user(self):
|
||||
connections = Connection.objects.filter(online=True)
|
||||
online_users_ids = [connection.user.id for connection in connections]
|
||||
customer_connections = []
|
||||
staff_connections = []
|
||||
for connection in connections:
|
||||
if hasattr(connection.user, 'customerprofile'):
|
||||
customer_connections.append(connection)
|
||||
elif hasattr(connection.user, 'staffprofile'):
|
||||
staff_connections.append(connection)
|
||||
|
||||
print(staff_connections)
|
||||
event = {
|
||||
'type': 'online_user_connection_handler',
|
||||
'staff_connections': staff_connections,
|
||||
'customer_connections': customer_connections,
|
||||
'online_users_ids': online_users_ids
|
||||
}
|
||||
async_to_sync(self.channel_layer.group_send)(
|
||||
'online_users', event
|
||||
)
|
||||
|
||||
def online_user_connection_handler(self, event):
|
||||
context = {
|
||||
'staff_connections': event['staff_connections'],
|
||||
'customer_connections': event['customer_connections'],
|
||||
}
|
||||
html = render_to_string("details_templates/partials/recently-online.html", context=context)
|
||||
self.send(text_data=json.dumps({
|
||||
'event_type': 'online_user_status',
|
||||
'html': html,
|
||||
'online_users_ids': event.get('online_users_ids', [])
|
||||
}))
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,22 @@
|
||||
# Generated by Django 4.2.5 on 2024-07-08 05:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('osinacore', '0085_rename_date_staffposition_start_date_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='connection',
|
||||
name='status',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='connection',
|
||||
name='terminated_at',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
]
|
@ -0,0 +1,17 @@
|
||||
# Generated by Django 4.2.5 on 2024-07-08 06:05
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('osinacore', '0086_remove_connection_status_connection_terminated_at'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='connection',
|
||||
name='date',
|
||||
),
|
||||
]
|
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.5 on 2024-07-08 06:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('osinacore', '0087_remove_connection_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='connection',
|
||||
name='date',
|
||||
field=models.DateTimeField(null=True),
|
||||
),
|
||||
]
|
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.5 on 2024-07-08 08:52
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('osinacore', '0088_connection_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='connection',
|
||||
name='connected',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.5 on 2024-07-08 08:55
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('osinacore', '0089_connection_connected'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='connection',
|
||||
old_name='terminated_at',
|
||||
new_name='last_seen',
|
||||
),
|
||||
]
|
@ -0,0 +1,21 @@
|
||||
# Generated by Django 4.2.5 on 2024-07-08 08:55
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('osinacore', '0090_rename_terminated_at_connection_last_seen'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='connection',
|
||||
name='user',
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.5 on 2024-07-08 08:57
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('osinacore', '0091_alter_connection_user'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='connection',
|
||||
old_name='connected',
|
||||
new_name='online',
|
||||
),
|
||||
]
|
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.5 on 2024-07-08 09:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('osinacore', '0092_rename_connected_connection_online'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='connection',
|
||||
name='disconnected',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,8 @@
|
||||
from django.urls import path
|
||||
from .consumers import *
|
||||
|
||||
websocket_urlpatterns = [
|
||||
path("ws/online-users/", OnlineUserConsumer.as_asgi()),
|
||||
|
||||
|
||||
]
|
@ -0,0 +1,61 @@
|
||||
{% load static %}
|
||||
|
||||
<div
|
||||
class="w-full xxlg1:flex flex-col justify-center items-center gap-3 bg-white shadow-md rounded-md p-5 logged-in-container">
|
||||
<div class="w-full flex justify-center items-center">
|
||||
<p class="text-[20px] text-secondosiblue font-bold text-center">Recently Online</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="w-full recentltLoggedStaffsContainer">
|
||||
<div class="w-full rounded-md bg-gray-200 grid grid-cols-2 shadow-sm">
|
||||
<button
|
||||
class="w-full rounded-md text-secondosiblue text-sm cursor-pointer p-2 customerButton">Customers</button>
|
||||
<button class="w-full bg-white rounded-md text-secondosiblue text-sm cursor-pointer p-2 staffButton"
|
||||
style="box-shadow: 0 0 6px rgba(88, 88, 88, 0.043), 6px 0 6px rgba(88, 88, 88, 0.043), 0 6px 6px rgba(88, 88, 88, 0.043), -6px 0 6px rgba(88, 88, 88, 0.043);">Staffs</button>
|
||||
</div>
|
||||
<div class="w-full flex flex-col gap-3 mt-3">
|
||||
{% for staff_connection in staff_connections %}
|
||||
<div class="w-full flex justify-start items-center gap-2">
|
||||
<div class="relative">
|
||||
<div class="w-[40px] h-[40px] rounded-full">
|
||||
<img src="{{staff_connection.user.staffprofile.image.url}}"
|
||||
class="w-full h-full object-cover rounded-full">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<p class="text-secondosiblue text-sm">{{staff_connection.user.first_name}}
|
||||
{{staff_connection.user.last_name}}</p>
|
||||
<p class="text-gray-500 text-sm">Online</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="w-full hidden recentltLoggedCustomersContainer">
|
||||
<div class="w-full rounded-md bg-gray-200 grid grid-cols-2 shadow-sm">
|
||||
<button class="w-full bg-white rounded-md text-secondosiblue text-sm cursor-pointer p-2 customerButton"
|
||||
style="box-shadow: 0 0 6px rgba(88, 88, 88, 0.043), 6px 0 6px rgba(88, 88, 88, 0.043), 0 6px 6px rgba(88, 88, 88, 0.043), -6px 0 6px rgba(88, 88, 88, 0.043);">Customers</button>
|
||||
<button class="w-full text-secondosiblue text-sm cursor-pointer p-2 staffButton">Staffs</button>
|
||||
</div>
|
||||
<div class="w-full mt-3 flex flex-col gap-3 recentltLoggedCustomers">
|
||||
{% for customer_connection in customer_connections %}
|
||||
<div class="w-full flex justify-start items-center gap-2">
|
||||
<div
|
||||
class="w-[40px] h-[40px] bg-secondosiblue flex justify-center items-center rounded-full text-white">
|
||||
<p>{{customer_connection.user.first_name|slice:":1"}}{{customer_connection.user.last_name|slice:":1"}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<p class="text-secondosiblue text-sm">{{recent_logged_in_customer.first_name}}
|
||||
{{recent_logged_in_customer.last_name}}</p>
|
||||
<p class="text-gray-500 text-sm">
|
||||
{{recent_logged_in_customer.last_login|date:"g:i A"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,4 @@
|
||||
from support.routing import websocket_urlpatterns as support_websocket_urlpatterns
|
||||
from osinacore.routing import websocket_urlpatterns as osinacore_websocket_urlpatterns
|
||||
|
||||
websocket_urlpatterns = support_websocket_urlpatterns + osinacore_websocket_urlpatterns
|
Binary file not shown.
After Width: | Height: | Size: 711 KiB |
@ -0,0 +1,67 @@
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const ws_scheme = window.location.protocol === "https:" ? "wss" : "ws";
|
||||
const webSocketUrl = `${ws_scheme}://${window.location.host}/ws/online-users/`;
|
||||
|
||||
const webSocket = new WebSocket(webSocketUrl);
|
||||
|
||||
webSocket.onopen = function (event) {
|
||||
console.log("WebSocket connection to online established");
|
||||
};
|
||||
|
||||
webSocket.onmessage = function (event) {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.event_type === "online_user_status") {
|
||||
// Update connected users containers
|
||||
const connectedUsersContainers = document.querySelectorAll(".connected-users");
|
||||
connectedUsersContainers.forEach(container => {
|
||||
container.innerHTML = data.html;
|
||||
});
|
||||
|
||||
// Rebind event listeners to switch between containers
|
||||
const customerButtons = document.querySelectorAll('.customerButton');
|
||||
const staffButtons = document.querySelectorAll('.staffButton');
|
||||
const customerContainers = document.querySelectorAll('.recentltLoggedCustomersContainer');
|
||||
const staffContainers = document.querySelectorAll('.recentltLoggedStaffsContainer');
|
||||
|
||||
customerButtons.forEach(button => {
|
||||
button.addEventListener('click', function () {
|
||||
customerContainers.forEach(container => container.classList.remove('hidden'));
|
||||
staffContainers.forEach(container => container.classList.add('hidden'));
|
||||
});
|
||||
});
|
||||
|
||||
staffButtons.forEach(button => {
|
||||
button.addEventListener('click', function () {
|
||||
staffContainers.forEach(container => container.classList.remove('hidden'));
|
||||
customerContainers.forEach(container => container.classList.add('hidden'));
|
||||
});
|
||||
});
|
||||
|
||||
const onlineUsersIds = data.online_users_ids || [];
|
||||
console.log(onlineUsersIds);
|
||||
|
||||
// Update user activity containers based on online status
|
||||
const userActivityContainers = document.querySelectorAll(".users-activities");
|
||||
userActivityContainers.forEach(container => {
|
||||
const userId = container.getAttribute("data-userId");
|
||||
console.log(userId);
|
||||
|
||||
if (onlineUsersIds.map(id => id.toString()).includes(userId)) {
|
||||
container.querySelector("#connected").classList.remove("hidden");
|
||||
container.querySelector("#not-connected").classList.add("hidden");
|
||||
} else {
|
||||
container.querySelector("#connected").classList.add("hidden");
|
||||
container.querySelector("#not-connected").classList.remove("hidden");
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
webSocket.onclose = function (event) {
|
||||
console.log("WebSocket connection to online closed");
|
||||
};
|
||||
|
||||
webSocket.onerror = function (error) {
|
||||
console.error("WebSocket error:", error);
|
||||
};
|
||||
});
|
@ -1,20 +0,0 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const customerButtons = document.querySelectorAll('.customerButton');
|
||||
const staffButtons = document.querySelectorAll('.staffButton');
|
||||
const customerContainers = document.querySelectorAll('.recentltLoggedCustomersContainer');
|
||||
const staffContainers = document.querySelectorAll('.recentltLoggedStaffsContainer');
|
||||
|
||||
customerButtons.forEach(button => {
|
||||
button.addEventListener('click', function () {
|
||||
customerContainers.forEach(container => container.classList.remove('hidden'));
|
||||
staffContainers.forEach(container => container.classList.add('hidden'));
|
||||
});
|
||||
});
|
||||
|
||||
staffButtons.forEach(button => {
|
||||
button.addEventListener('click', function () {
|
||||
staffContainers.forEach(container => container.classList.remove('hidden'));
|
||||
customerContainers.forEach(container => container.classList.add('hidden'));
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue