emile 10 months ago
parent cd43080d10
commit 2e044ece76

Binary file not shown.

@ -77,3 +77,30 @@ class OnlineUserConsumer(WebsocketConsumer):
'html': html,
'online_users_ids': event.get('online_users_ids', [])
}))
class NewStatusConsumer(WebsocketConsumer):
def connect(self):
self.user = self.scope['user']
async_to_sync(self.channel_layer.group_add)(
"new_status_group",
self.channel_name
)
self.accept()
def disconnect(self, close_code):
async_to_sync(self.channel_layer.group_discard)(
"new_status_group",
self.channel_name
)
def new_status_event(self, event):
status_id = event['status_id']
status = Status.objects.get(id=status_id)
context = {'status': status, 'new': True}
html = render_to_string("details_templates/partials/new-status-activity.html", context)
self.send(text_data=json.dumps({
'event_type': 'new_status',
'html': html,
}))

@ -0,0 +1,18 @@
# Generated by Django 4.2.5 on 2024-07-09 07:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('osinacore', '0095_delete_customuser'),
]
operations = [
migrations.AddField(
model_name='status',
name='date_time',
field=models.DateTimeField(blank=True, null=True),
),
]

@ -6,6 +6,10 @@ from django.db.models import Max
from django.utils import timezone
from django.db.models import Sum, F
from datetime import timedelta
from django.db.models.signals import post_save
from django.dispatch import receiver
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
# Create your models here.
@ -407,10 +411,23 @@ class Status(models.Model):
time = models.CharField(max_length=40)
staff = models.ForeignKey(StaffProfile, on_delete=models.CASCADE, null=True,blank=True, related_name='staff')
task = models.ForeignKey(Task, on_delete=models.SET_NULL ,null=True, blank=True, related_name='reference_task')
date_time = models.DateTimeField(null=True, blank=True)
def __str__(self):
return self.text
@receiver(post_save, sender=Status)
def new_ticket_update_handler(sender, instance, created, **kwargs):
if created:
channel_layer = get_channel_layer()
event = {
'type': 'new_status_event',
'status_id': instance.id,
}
async_to_sync(channel_layer.group_send)("new_status_group", event)
class Reaction(models.Model):
status = models.ForeignKey(Status, on_delete=models.CASCADE)
emoji = models.CharField(max_length=15)

@ -3,6 +3,8 @@ from .consumers import *
websocket_urlpatterns = [
path("ws/online-users/", OnlineUserConsumer.as_asgi()),
path("ws/new-statuses/", NewStatusConsumer.as_asgi()),
]

@ -0,0 +1,203 @@
{% load static %}
<link rel="stylesheet" type="text/css" href='{% static "dist/output.css" %}'>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- EMOJI PICKER POPUP CONTAINER -->
<div class="w-full h-full fixed bg-black bg-opacity-50 inset-0 flex justify-center items-center p-5 z-20 hidden"
id="emojiPickerContainer">
<div class="w-[500px] bg-gray-100 rounded-md shadow-md p-5 relative">
<div class="w-[28px] h-[28px] rounded-full bg-white border border-gray-100 shadow-md flex justify-center items-center p-2 absolute top-5 right-5 text-secondosiblue cursor-pointer hover:bg-secondosiblue hover:text-white duration-300"
id="closeEmojiPicker">
<i class="fa fa-close"></i>
</div>
<p class="text-xl text-secondosiblue text-center mt-3">Add Reaction</p>
{% csrf_token %}
<!-- EMOJIS CATEGORIES BAR -->
<div class="w-full px-3 py-1 bg-gray-200 rounded-md flex gap-2 items-center mt-5">
<div class="w-[30px] rounded-full selectedEmojiCategory p-1 emoji-category cursor-pointer hover:scale-105 duration-300"
data-category="smiley">
<img src="{% static 'images/emojis/smiley.png' %}" class="w-full">
</div>
<div class="w-[30px] rounded-full p-1 emoji-category cursor-pointer hover:scale-105 duration-300"
data-category="nature">
<img src="{% static 'images/emojis/animal.png' %}" class="w-full">
</div>
<div class="w-[30px] rounded-full p-1 emoji-category cursor-pointer hover:scale-105 duration-300"
data-category="food">
<img src="{% static 'images/emojis/food.png' %}" class="w-full">
</div>
<div class="w-[30px] rounded-full p-1 emoji-category cursor-pointer hover:scale-105 duration-300"
data-category="activities">
<img src="{% static 'images/emojis/activities.png' %}" class="w-full">
</div>
<div class="w-[30px] rounded-full p-1 emoji-category cursor-pointer hover:scale-105 duration-300"
data-category="travel">
<img src="{% static 'images/emojis/travel.png' %}" class="w-full">
</div>
<div class="w-[30px] rounded-full p-1 emoji-category cursor-pointer hover:scale-105 duration-300"
data-category="flags">
<img src="{% static 'images/emojis/flags.png' %}" class="w-full">
</div>
</div>
<!-- EMOJIS CONTAINER -->
<div class="mt-5 emoji-container">
<!-- SMILEYS -->
<div class="emoji-category-container" id="smileyContainer">
<h1 class="uppercase text-secondosiblue">Smileys & People</h1>
<div class="flex flex-wrap items-center justify-center gap-1 mt-2 emojisContainer text-[22px]">
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F604;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F605;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F601;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F923;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F602;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F60A;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F607;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F609;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F970;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F62D;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F60D;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F92A;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F914;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F60F;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F612;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F644;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F634;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F973;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F60E;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F641;</p>
</div>
</div>
<!-- NATURE -->
<div class="emoji-category-container hidden" id="natureContainer">
<h1 class="uppercase text-secondosiblue">Animals & Nature</h1>
<div class="flex flex-wrap items-center justify-center gap-1 mt-2 emojisContainer text-[22px]">
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F339;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F337;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F490;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F338;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F340;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F940;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F33C;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F33A;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F98B;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F40C;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F40E;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F406;</p>
<p class="cursor-pointer hover:scale-105 duration-500">&#x1F981;</p>
</div>
</div>
<!-- FOOD -->
<div class="emoji-category-container hidden" id="foodContainer">
</div>
<!-- ACTIVITIES -->
<div class="emoji-category-container hidden" id="activitiesContainer">
</div>
<!-- TRAVEL -->
<div class="emoji-category-container hidden" id="travelContainer">
</div>
<!-- FLAGS -->
<div class="emoji-category-container hidden" id="flagsContainer">
</div>
</div>
</div>
</div>
<div class="w-full flex flex-col py-3 users-activities fadeInAndUp" data-userId="{{ status.staff.user.id }}">
<div class="w-full flex flex-col justify-center items-start gap-3 bg-gray-50 pt-2 px-2 pb-6 rounded-md relative">
<div class="w-full flex justify-between items-center gap-2">
<div class="flex justify-start gap-2 cursor-pointer userRecentActivitiesButton"
data-modal-url="{% url 'userrecentativities' status.staff.user.id %}">
<div class="relative w-fit rounded-full">
<div class="w-[45px] h-[45px] rounded-full">
<img src='{{status.staff.image.url}}' alt="user profile"
class="w-full h-full object-cover rounded-full">
</div>
<div id="connected"
class="w-[12px] h-[12px] absolute rounded-full bg-green-600 bottom-0 right-0 border-2 border-white">
</div>
<div id="not-connected"
class="w-[12px] h-[12px] absolute rounded-full bg-red-500 bottom-0 right-0 border-2 border-white">
</div>
</div>
<div class="flex flex-col">
<h1 class="text-sm text-secondosiblue font-semibold">{{status.staff.user.first_name}}
{{status.staff.user.last_name}}</h1>
{% if status.time_ago == '0min ago' %}
<p class="text-sm text-gray-500">Just Now</p>
{%else %}
<p class="text-sm text-gray-500">{{ status.time_ago}}</p>
{%endif%}
</div>
</div>
<div class="relative">
<div class="cursor-pointer hover:scale-105 duration-500 transition-transform emojiPicker"
data-status-id="{{latest.status.id}}">
<img src="{% static 'images/icons/reactionicon.png' %}" class="w-[30px]">
</div>
</div>
</div>
<!-- Status -->
<div class="w-full">
<p class="text-sm {% if 'Completed' in status.text %} text-green-700 {% else %} text-secondosiblue {% endif %}">{{status.text}}</p>
{% if status.task.project %}
<a href="{% url 'detailed-project' status.task.project.project_id %}">
<div class="w-full flex justify-end items-center mt-1 text-gray-400 hover:text-secondosiblue duration-300 cursor-pointer">
<p class="text-xs font-light">{{status.task.project}}</p>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-3 h-3">
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
</svg>
</div>
</a>
{% endif %}
</div>
<!-- Add comment section -->
<div class="w-full h-fit flex justify-between items-center border border-gray-200 rounded-md relative">
<input type="text" placeholder="Add Comment..."
class="outline-none text-gray-500 px-3 w-full text-sm h-[40px] rounded-tl-md rounded-bl-md">
<button
class="w-fit absolute right-0 h-full px-3 py-2 bg-osiblue border border-osiblue hover:bg-white hover:text-osiblue duration-300 rounded-tr-md rounded-br-md text-white text-[12px]">
<i class="fa fa-send"></i>
</button>
</div>
</div>
</div>
<style>
@keyframes fadeInAndUp {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0px); }
}
.fade-in-up {
animation: fadeInAndUp 0.6s ease;
}
</style>

@ -166,11 +166,7 @@
<!-- Status -->
<div class="w-full">
{% if "Completed" in latest.status.text %}
<p class="text-sm text-green-700">{{latest.status.text}}</p>
{% else %}
<p class="text-sm text-secondosiblue">{{latest.status.text}}</p>
{% endif %}
<p class="text-sm {% if 'Completed' in latest.status.text %} text-green-700 {% else %} text-secondosiblue {% endif %}">{{latest.status.text}}</p>
{% if latest.status.task.project %}
<a href="{% url 'detailed-project' latest.status.task.project.project_id %}">

@ -361,4 +361,7 @@
{% endblock content %}

@ -21,20 +21,7 @@
</head>
<body class="bg-gray-200 font-poppinsLight relative">
<!-- THE POPUP THAT APPEARS WHEN THE USER GOES OFFLINE -->
{% if user_offline %}
<div class="w-full h-full inset-0 fixed bg-black bg-opacity-70 flex justify-center items-center p-5 z-50">
<div class="w-full s:w-[400px] bg-white shadow-md rounded-md flex flex-col justify-between">
<div class="w-full py-9 px-5 flex justify-center items-center text-center">
<p class="text-secondosiblue text-[18px]">You are Currently Offline</p>
</div>
<button id="goOnlineButton" data-url="{% url 'go_online' %}"
class="w-full text-white bg-green-700 py-3 px-3 rounded-b-md hover:bg-opacity-60 duration-300 cursor-pointer">Go
Online</button>
</div>
</div>
{% endif %}
<!-- USERS ACTIVITIES BUTTON ON MOBILE -->
<a href="{% url 'recentactivitiespage' %}">
@ -1222,10 +1209,13 @@
<script type="module" src='{% static "js/pop-modals.js" %}'></script>
<!---------------------- JS SCRIPTS -------------------->
<!-- ONLINE CONSUMER SCRIPT -->
<script type="text/javascript" src='{% static "js/status/new-status-consumer.js" %}'></script>
<!-- SIDE BAR SCRIPT -->
<script type="text/javascript" src='{% static "js/online/online-consumer.js" %}'></script>
<!-- SIDE BAR SCRIPT -->
<script type="text/javascript" src='{% static "js/side-bar.js" %}'></script>

@ -14,20 +14,7 @@
<body>
<!-- THE POPUP THAT APPEARS WHEN THE USER GOES OFFLINE -->
{% if user_offline %}
<div class="w-full h-full inset-0 fixed bg-black bg-opacity-70 flex justify-center items-center p-5 z-20">
<div class="w-full s:w-[400px] bg-white shadow-md rounded-md flex flex-col justify-between">
<div class="w-full py-9 px-5 flex justify-center items-center text-center">
<p class="text-secondosiblue text-[18px]">You are Currently Offline</p>
</div>
<button
class="w-full text-white bg-green-700 py-3 px-3 rounded-b-md hover:bg-opacity-60 duration-300 cursor-pointer">Go
Online</button>
</div>
</div>
{% endif %}
@ -723,7 +710,7 @@
<!-- RECENT ACTIVITIES -->
<div class="w-full h-fit px-3 py-5" id="activitiesContainer">
<div class="w-full">
{% include 'recent-activities.html' %}
{% include 'details_templates/partials/recent-activities.html' %}
</div>
</div>
@ -743,6 +730,9 @@
</div>
<!---------------------- JS SCRIPTS -------------------->
<!-- SIDE BAR SCRIPT -->
<script type="text/javascript" src='{% static "js/online/online-consumer.js" %}'></script>
<!-- SIDE BAR SCRIPT -->
<script type="text/javascript" src='{% static "js/side-bar.js" %}'></script>

@ -83,7 +83,7 @@ urlpatterns = [
path('projects/status/<str:status>/', views.fetch_projects_by_status, name='projects_by_status'),
path('all-projects/', views.fetch_projects_by_status, name='all_projects'),
path('delete-connections', views.delete_connections, name='delete-connections')
path('status-date/', views.update_all_date_time, name='update-status')
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

@ -879,5 +879,22 @@ def fetch_projects_by_status(request, status=None):
def delete_connections(request):
connections= Connection.objects.all().delete()
from django.shortcuts import render
from django.http import HttpResponse
from django.utils.dateparse import parse_datetime
def update_all_date_time(request):
statuses = Status.objects.all()
for status in statuses:
if status.date and status.time:
date_str = f"{status.date} {status.time}"
try:
date_time_obj = datetime.strptime(date_str, "%Y-%m-%d %I:%M %p")
status.date_time = date_time_obj
status.save()
except ValueError as e:
# Handle any errors in date/time parsing
print(f"Error parsing date/time for status {status.id}: {e}")
return JsonResponse({"status": "success", "message": "DateTime fields updated for all statuses."})

@ -160,7 +160,7 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
EMAIL_HOST = 'osinamail.ositcom.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'osina@ositcom.com'
EMAIL_HOST_USER = 'osina@eositcom.com'
DEFAULT_FROM_EMAIL = 'osina@ositcom.com'
EMAIL_HOST_PASSWORD = 'EMILEselim!@67'
EMAIL_USE_TLS = True

@ -0,0 +1,27 @@
// WebSocket connection for new statuses
const ws_theme = window.location.protocol === "https:" ? "wss" : "ws";
const newStatusesSocketUrl = `${ws_theme}://${window.location.host}/ws/new-statuses/`;
const newStatusesSocket = new WebSocket(newStatusesSocketUrl);
newStatusesSocket.onopen = () => {
console.log('WebSocket connection to new statuses established');
};
newStatusesSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
if (data.event_type === 'new_status') {
const activityDiv = document.getElementById('activitiesContainer');
console.log(activityDiv);
activityDiv.insertAdjacentHTML('afterbegin', data.html);
}
};
newStatusesSocket.onclose = () => {
console.log('WebSocket connection to new statuses closed');
};
newStatusesSocket.onerror = (error) => {
console.log('WebSocket error:', error);
};
Loading…
Cancel
Save