Skip to content

Commit

Permalink
added Peer Recognition feature
Browse files Browse the repository at this point in the history
  • Loading branch information
tsu-ki committed Jan 1, 2025
1 parent e1f5034 commit a4f1c43
Show file tree
Hide file tree
Showing 6 changed files with 347 additions and 10 deletions.
2 changes: 2 additions & 0 deletions blt/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@
add_member,
create_team,
delete_team,
give_kudos,
join_requests,
kick_member,
leave_team,
Expand Down Expand Up @@ -793,6 +794,7 @@
path("teams/delete-team/", delete_team, name="delete_team"),
path("teams/leave-team/", leave_team, name="leave_team"),
path("teams/kick-member/", kick_member, name="kick_member"),
path("teams/give-kudos/", give_kudos, name="give_kudos"),
path(
"similarity-scan",
TemplateView.as_view(template_name="similarity.html"),
Expand Down
51 changes: 51 additions & 0 deletions website/migrations/0177_kudos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Generated by Django 5.1.4 on 2024-12-30 17:47

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("website", "0176_repo_contributor_repo_contributor_count_and_more"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name="Kudos",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("message", models.TextField(blank=True, null=True)),
("timestamp", models.DateTimeField(auto_now_add=True)),
(
"receiver",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="kudos_received",
to=settings.AUTH_USER_MODEL,
),
),
(
"sender",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="kudos_sent",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name_plural": "Kudos",
"ordering": ["-timestamp"],
},
),
]
14 changes: 14 additions & 0 deletions website/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1292,3 +1292,17 @@ def save(self, *args, **kwargs):

def __str__(self):
return f"{self.project.name}/{self.name}"


class Kudos(models.Model):
sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name="kudos_sent")
receiver = models.ForeignKey(User, on_delete=models.CASCADE, related_name="kudos_received")
message = models.TextField(null=True, blank=True)
timestamp = models.DateTimeField(auto_now_add=True)

class Meta:
ordering = ["-timestamp"]
verbose_name_plural = "Kudos"

def __str__(self):
return f"Kudos from {self.sender.username} to {self.receiver.username}"
Binary file added website/static/img/kudos_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
244 changes: 237 additions & 7 deletions website/templates/team_overview.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{% extends "base.html" %}
{% load static %}
{% block content %}
<style>
/* Styling for the Team Overview Page */
Expand Down Expand Up @@ -35,6 +36,7 @@
object-fit: cover;
}

/* Slider / Members */
.slider-container {
display: flex;
align-items: center;
Expand Down Expand Up @@ -348,6 +350,47 @@
.kick-button:hover {
background-color: #e53935;
}
.kudos-section {
margin-top: 20px;
padding: 15px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.kudos-list {
margin-top: 10px;
max-height: 300px;
overflow-y: auto;
}
.kudos-item {
padding: 10px;
border-bottom: 1px solid #eee;
background-color: #f8f9fa;
margin-bottom: 10px;
border-radius: 4px;

}
.kudos-count-badge {
display: inline-block;
background-color: #17a2b8;
color: white;
padding: 3px 8px;
border-radius: 12px;
font-size: 0.8em;
margin-left: 8px;
}
.kudos-submit-btn {
background-color: #28a745;
color: #fff;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}

.kudos-submit-btn:hover {
background-color: #218838;
}
</style>
{% include "includes/sidenav.html" %}
<div class="team-overview-container">
Expand All @@ -365,29 +408,87 @@
<!-- Team Leader -->
<div class="team-leader">
<strong>Team Leader:</strong> {{ user.userprofile.team.admin.username }}
{% if user.userprofile.team.admin.kudos_count %}
<span class="kudos-count-badge">{{ user.userprofile.team.admin.kudos_count }} kudos</span>
{% endif %}
</div>
<!-- Team Members Slider -->
<div class="slider-container">
<button class="slider-nav left" onclick="prevMember()"></button>
<!-- Always display the team leader -->
<div class="member-card active" id="admin-card">
<span>{{ user.userprofile.team.admin.username }}</span>
</div>
{% for member in user.userprofile.team.managers.all %}
<div class="member-card"
id="member-{{ member.username }}"
onclick="setActiveMember('{{ member.username }}')">
{# <div class="member-card active" id="admin-card">#}
{# <span>{{ user.userprofile.team.admin.username }}</span>#}
{# {% if user.userprofile.team.admin != user %}#}
{# <button class="kudos-button"#}
{# type="button"#}
{# onclick="openKudosModal('{{ user.userprofile.team.admin.username }}')">#}
{# <img src="{% static 'img/kudos_icon.png' %}" #}
{# alt="Kudos"#}
{# width="24"#}
{# height="24">#}
{# {% if user.userprofile.team.admin.kudos_count > 0 %}#}
{# <span class="kudos-count">{{ user.userprofile.team.admin.kudos_count }}</span>#}
{# {% endif %}#}
{# </button>#}
{# {% endif %}#}
{# </div>#}
{% for member in team_members %}
<div class="member-card {% if member.username == user.userprofile.team.admin.username %}active{% endif %}"
id="member-{{ member.username }}">
<span>{{ member.username }}</span>
<!-- Show "Kick" button for all members except the admin -->
{% if user.userprofile.team.admin == user and user.userprofile.team.admin != member %}
<button class="kick-button"
id="kick-{{ member.username }}"
onclick="kickMember('{{ member.username }}')">Kick</button>
{% endif %}
<!-- Kudos Button -->
{% if member != user %}
<button class="kudos-button"
type="button"
onclick="openKudosModal('{{ member.username }}')">
<img src="{% static 'img/kudos_icon.png' %}"
alt="Kudos"
width="24"
height="24">
{% if member.kudos_count > 0 %}<span class="kudos-count">{{ member.kudos_count }}</span>{% endif %}
</button>
{% endif %}
</div>
{% endfor %}
<button class="slider-nav right" onclick="nextMember()"></button>
</div>
<div class="kudos-section">
<h3>Your Received Kudos</h3>
{% if received_kudos.exists %}
<div class="kudos-list">
{% for kudos in received_kudos %}
<div class="kudos-item">
<p class="kudos-message">
<strong>{{ kudos.sender.username }}</strong>: {{ kudos.message }}
</p>
<small class="kudos-timestamp">{{ kudos.timestamp|date:"M d, Y H:i" }}</small>
</div>
{% endfor %}
</div>
{% else %}
<p class="no-kudos">No kudos received yet.</p>
{% endif %}
</div>
<!-- Display all team kudos -->
<div class="kudos-section">
<h3>Team Kudos</h3>
<div class="kudos-list">
{% for kudos in team_kudos %}
<div class="kudos-item">
<p class="kudos-message">
<strong>{{ kudos.sender.username }} → {{ kudos.receiver.username }}</strong>: {{ kudos.message }}
</p>
<small class="kudos-timestamp">{{ kudos.timestamp|date:"M d, Y H:i" }}</small>
</div>
{% endfor %}
</div>
</div>
<!-- Team Achievements -->
<div class="achievements"></div>
<!-- Team Action Buttons (Delete Team, Leave Team, Kick Member) -->
Expand Down Expand Up @@ -481,6 +582,52 @@ <h2>Add New Member</h2>
</div>
<div class="form-group">
<button type="submit">Send Join Request</button>
<button type="button" class="btn btn-warning" onclick="closeAddMemberModal()">Cancel</button>
</div>
</form>
</div>
</div>
<!-- Kudos member card that shows kudos count -->
{% for member in team_members %}
<div class="member-card" id="member-{{ member.username }}">
<div class="member-info">
<span>{{ member.username }}</span>
<span class="kudos-count-badge"
{% if not member.kudos_count %}style="display:none"{% endif %}>
{{ member.kudos_count|default:"0" }} kudos
</span>
</div>
{% if member != user %}
<button class="kudos-button"
type="button"
onclick="openKudosModal('{{ member.username }}')">
<img src="{% static 'img/kudos_icon.png' %}"
alt="Kudos"
width="24"
height="24">
</button>
{% endif %}
</div>
{% endfor %}
<!-- Add Kudos Modal and relevant scripts -->
<div id="kudosModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeKudosModal()">×</span>
<h2>Send Kudos</h2>
<form method="post" onsubmit="sendKudos(event)" class="kudos-form">
{% csrf_token %}
<input type="hidden" id="kudosReceiver" name="kudosReceiver">
<div class="form-group">
<label for="kudosMessage">Message:</label>
<textarea id="kudosMessage"
name="kudosMessage"
rows="2"
class="kudos-textarea"
required></textarea>
</div>
<div class="form-actions">
<button type="submit" class="kudos-submit-btn">Send Kudos</button>
<button type="button" class="btn-secondary" onclick="closeKudosModal()">Cancel</button>
</div>
</form>
</div>
Expand Down Expand Up @@ -721,5 +868,88 @@ <h2>Add New Member</h2>
document.getElementById('newMember').value = username;
document.getElementById('newMemberDropdown').innerHTML = "";
}

// Kudos Modal functions
function openKudosModal(username) {
document.getElementById('kudosReceiver').value = username;
document.getElementById('kudosMessage').value = '';
document.getElementById('kudosModal').style.display = 'block';
}
function closeKudosModal() {
document.getElementById('kudosModal').style.display = 'none';
document.getElementById('kudosMessage').value = '';
}
function sendKudos(e) {
e.preventDefault();
const receiver = document.getElementById('kudosReceiver').value;
const message = document.getElementById('kudosMessage').value;

if (!message.trim()) {
alert("Please enter a kudos message.");
return;
}

fetch("/teams/give-kudos/", {
method: "POST",
headers: {
"X-CSRFToken": document.querySelector("[name=csrfmiddlewaretoken]").value,
"Content-Type": "application/json"
},
body: JSON.stringify({
kudosReceiver: receiver,
kudosMessage: message
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
closeKudosModal();
updateKudosCount(receiver);
showSuccessMessage("Kudos sent successfully!");
} else {
alert(data.error || "Failed to send kudos.");
}
})
.catch(error => {
console.error('Error:', error);
alert("Failed to send kudos. Please try again.");
});
}
function updateKudosCount(username) {
const memberCard = document.getElementById(`member-${username}`);
if (!memberCard) return;

const countBadge = memberCard.querySelector('.kudos-count-badge');
const currentCount = countBadge ?
parseInt(countBadge.textContent.split(' ')[0]) : 0;

if (countBadge) {
countBadge.textContent = `${currentCount + 1} kudos`;
countBadge.style.display = 'inline-block';
} else {
const newBadge = document.createElement('span');
newBadge.className = 'kudos-count-badge';
newBadge.textContent = '1 kudos';
memberCard.querySelector('.member-info').appendChild(newBadge);
}
}

function showSuccessMessage(message) {
const alert = document.createElement('div');
alert.className = 'alert alert-success';
alert.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 10px 20px;
border-radius: 4px;
background-color: #28a745;
color: white;
z-index: 1000;
`;
alert.textContent = message;
document.body.appendChild(alert);
setTimeout(() => alert.remove(), 3000);
}
</script>
{% endblock content %}
Loading

0 comments on commit a4f1c43

Please sign in to comment.