Salim Elliye 4 weeks ago
parent 73eb499e7a
commit 43d52a360c

Binary file not shown.

@ -11,6 +11,8 @@ urlpatterns = [
path('project/', views.add_project, name='addproject'), path('project/', views.add_project, name='addproject'),
path('milestone/<str:project_id>/', views.add_milestone, name='addmilestone'),
path('milestone-story/<str:milestone_id>/', views.add_user_story_modal, name='addmilestoneuserstorymodal'),
path('project-member/<str:project_id>/', views.add_project_member_modal, name='addprojectmembermodal'), path('project-member/<str:project_id>/', views.add_project_member_modal, name='addprojectmembermodal'),
path('project-story/<str:project_id>/', views.add_user_story_modal, name='adduserstorymodal'), path('project-story/<str:project_id>/', views.add_user_story_modal, name='adduserstorymodal'),
path('project-file/<str:project_id>/', views.add_file_modal, name='addfilemodal'), path('project-file/<str:project_id>/', views.add_file_modal, name='addfilemodal'),

@ -213,7 +213,8 @@ def add_project(request):
details = request.POST.get('details') details = request.POST.get('details')
membersids = request.POST.getlist('members') membersids = request.POST.getlist('members')
start_date = request.POST.get('start_date') start_date = request.POST.get('start_date')
end_date = request.POST.get('end_date') end_date = request.POST.get('end_date') if request.POST.get('end_date') else None
project = Project( project = Project(
name=name, name=name,
@ -228,13 +229,11 @@ def add_project(request):
project.project_type.set(project_type) project.project_type.set(project_type)
project.members.set(membersids) project.members.set(membersids)
requirements = request.POST.getlist('requirements') ProjectStatus.objects.create(
for requirement_content in requirements: status = "In Progress",
if requirement_content: project = project,
requirement = ProjectRequirement( date = datetime.now()
content=requirement_content, project=project, added_by=request.user) )
requirement.save()
return redirect('my-projects') return redirect('my-projects')
context = { context = {
@ -246,6 +245,34 @@ def add_project(request):
return render(request, 'add_templates/add-project.html', context) return render(request, 'add_templates/add-project.html', context)
@staff_login_required
def add_milestone(request, project_id):
project = get_object_or_404(Project, project_id=project_id)
if request.method == 'POST':
name = request.POST.get('name')
description = request.POST.get('description')
start_date = request.POST.get('start_date')
end_date = request.POST.get('end_date')
milestone = Milestone(
project=project,
name=name,
description=description,
start_date=start_date,
end_date=end_date
)
milestone.save()
redirect_url = reverse('detailed-project', args=[project.project_id])
return redirect(redirect_url)
context = {
'project': project,
}
return render(request, 'add_templates/add-milestone.html', context)
@staff_login_required @staff_login_required
def add_project_member_modal(request, project_id): def add_project_member_modal(request, project_id):
project = get_object_or_404(Project, id=project_id) project = get_object_or_404(Project, id=project_id)
@ -268,26 +295,38 @@ def add_project_member_modal(request, project_id):
return render(request, 'add_templates/add-project-member-modal.html', context) return render(request, 'add_templates/add-project-member-modal.html', context)
@staff_login_required @staff_login_required
def add_user_story_modal(request, project_id): def add_user_story_modal(request, milestone_id=None, project_id=None):
project = get_object_or_404(Project, project_id=project_id) if project_id:
project = get_object_or_404(Project, project_id=project_id)
milestone = None
elif milestone_id:
milestone = get_object_or_404(Milestone, id=milestone_id)
project = milestone.project
milestones = Milestone.objects.filter(project=project).order_by('-id')
if request.method == 'POST': if request.method == 'POST':
content = request.POST.get('content') content = request.POST.get('content')
completed = True if request.POST.get('completed') else False
story = ProjectRequirement( confirmed = True if request.POST.get('confirmed') else False
content=content, if not milestone:
milestone = Milestone.objects.get(id=request.POST.get('milestone'))
story = UserStory(
project=project, project=project,
content=content,
milestone = milestone,
completed = completed,
confirmed = confirmed,
added_by=request.user, added_by=request.user,
) )
story.save() story.save()
# Reload the parent page using JavaScript
response = HttpResponse( response = HttpResponse(
'<script>window.top.location.reload();</script>') '<script>window.top.location.reload();</script>')
return response return response
context = { context = {
'project': project, 'project': project,
'milestone': milestone,
'milestones': milestones
} }
@ -350,7 +389,7 @@ def add_task(request, project_id=None, requirement_id=None):
epics_of_my_project = Epic.objects.filter(project=project) epics_of_my_project = Epic.objects.filter(project=project)
if requirement_id: if requirement_id:
requirement = get_object_or_404( requirement = get_object_or_404(
ProjectRequirement, id=requirement_id) UserStory, id=requirement_id)
# Case where user wants to add task from tasks page(No project specified) # Case where user wants to add task from tasks page(No project specified)
else: else:
@ -386,7 +425,7 @@ def add_task(request, project_id=None, requirement_id=None):
start_date=start_date, start_date=start_date,
end_date=end_date, end_date=end_date,
assigned_to=assigned_to, assigned_to=assigned_to,
requirement=requirement, userstory=requirement,
) )
@ -459,10 +498,6 @@ def add_epic(request, project_id):
title = request.POST.get('title') title = request.POST.get('title')
status = request.POST.get('status') status = request.POST.get('status')
description = request.POST.get('description') description = request.POST.get('description')
project_id = request.POST.get('project')
project = get_object_or_404(Project, id=project_id)
start_date = request.POST.get('start_date') start_date = request.POST.get('start_date')
end_date = request.POST.get('end_date') end_date = request.POST.get('end_date')

@ -3,20 +3,12 @@ from .models import *
# Register your models here. # Register your models here.
class RequirementInline(admin.TabularInline):
model = ProjectRequirement
extra = 1
class CredentialInline(admin.TabularInline): class CredentialInline(admin.TabularInline):
model = ProjectCredential model = ProjectCredential
extra = 1 extra = 1
class ProjectAdmin(admin.ModelAdmin): class ProjectAdmin(admin.ModelAdmin):
inlines=[RequirementInline, CredentialInline] inlines=[CredentialInline]
@ -34,6 +26,8 @@ admin.site.register(Department)
admin.site.register(StaffProfile) admin.site.register(StaffProfile)
admin.site.register(ProjectType) admin.site.register(ProjectType)
admin.site.register(Project, ProjectAdmin) admin.site.register(Project, ProjectAdmin)
admin.site.register(Milestone)
admin.site.register(UserStory)
admin.site.register(ProjectStatus) admin.site.register(ProjectStatus)
admin.site.register(PinnedProject) admin.site.register(PinnedProject)
admin.site.register(Epic) admin.site.register(Epic)

@ -11,6 +11,7 @@ urlpatterns = [
path('project/<str:project_id>/', views.edit_project, name='editproject'), path('project/<str:project_id>/', views.edit_project, name='editproject'),
path('project/<int:project_id>/toggle_pin/', views.toggle_pin_project, name='toggle_pin_project'), path('project/<int:project_id>/toggle_pin/', views.toggle_pin_project, name='toggle_pin_project'),
path('projectstatus/<str:project_id>/', views.edit_project_status_modal, name='editprojectstatusmodal'), path('projectstatus/<str:project_id>/', views.edit_project_status_modal, name='editprojectstatusmodal'),
path('story/<str:story_id>/', views.edit_user_story_modal, name='edituserstorymodal'),
path('task/<str:task_id>/', views.edit_task, name='edittask'), path('task/<str:task_id>/', views.edit_task, name='edittask'),
path('task-status/<str:task_id>/', views.edit_task_status_modal, name='edittaskstatusmodal'), path('task-status/<str:task_id>/', views.edit_task_status_modal, name='edittaskstatusmodal'),
path('epic/', views.edit_epic, name='editepic'), path('epic/', views.edit_epic, name='editepic'),

@ -233,7 +233,25 @@ def toggle_pin_project(request, project_id):
return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/')) return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
@staff_login_required
def edit_user_story_modal(request, story_id):
story = get_object_or_404(UserStory, id=story_id)
milestones = Milestone.objects.filter(project=story.project)
if request.method == 'POST':
story.milestone = get_object_or_404(Milestone, id=request.POST.get('milestone'))
story.content = request.POST.get('content')
story.completed = True if request.POST.get('completed') else False
story.confirmed = True if request.POST.get('confirmed') else False
story.save()
response = HttpResponse(
'<script>window.top.location.reload();</script>')
return response
context = {
'milestones': milestones,
'story': story,
}
return render(request, 'edit_templates/edit-userstory-modal.html', context)
@staff_login_required @staff_login_required
@ -260,7 +278,7 @@ def edit_task(request, task_id):
epic = get_object_or_404(Epic, id=epic_id) epic = get_object_or_404(Epic, id=epic_id)
task.epic = epic task.epic = epic
task.requirement = request.POST.get('requirement') task.userstory = request.POST.get('requirement')
task.status = request.POST.get('status') task.status = request.POST.get('status')
# Convert assigned_to ID to a StaffProfile instance # Convert assigned_to ID to a StaffProfile instance

@ -0,0 +1,19 @@
# Generated by Django 5.1.7 on 2025-04-05 13:38
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('osinacore', '0109_remove_status_task'),
]
operations = [
migrations.AddField(
model_name='projectrequirement',
name='milestone',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='osinacore.milestone'),
),
]

@ -0,0 +1,24 @@
# Generated by Django 5.1.7 on 2025-04-05 13:40
from django.conf import settings
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('osinacore', '0110_projectrequirement_milestone'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RenameModel(
old_name='ProjectRequirement',
new_name='UserStory',
),
migrations.RenameField(
model_name='task',
old_name='requirement',
new_name='userstory',
),
]

@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-04-05 14:13
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('osinacore', '0111_rename_projectrequirement_userstory_and_more'),
]
operations = [
migrations.RenameField(
model_name='milestone',
old_name='title',
new_name='name',
),
]

@ -0,0 +1,23 @@
# Generated by Django 5.1.7 on 2025-04-05 14:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('osinacore', '0112_rename_title_milestone_name'),
]
operations = [
migrations.AddField(
model_name='userstory',
name='completed',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='userstory',
name='confirmed',
field=models.BooleanField(default=True),
),
]

@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-04-05 15:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('osinacore', '0113_userstory_completed_userstory_confirmed'),
]
operations = [
migrations.AlterField(
model_name='project',
name='end_date',
field=models.DateField(blank=True, null=True),
),
]

@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-04-05 15:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('osinacore', '0114_alter_project_end_date'),
]
operations = [
migrations.AddField(
model_name='projectstatus',
name='default_created',
field=models.BooleanField(default=False),
),
]

@ -0,0 +1,17 @@
# Generated by Django 5.1.7 on 2025-04-05 15:19
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('osinacore', '0115_projectstatus_default_created'),
]
operations = [
migrations.RemoveField(
model_name='projectstatus',
name='default_created',
),
]

@ -172,8 +172,6 @@ class StaffProfile(models.Model):
class StaffPosition(models.Model): class StaffPosition(models.Model):
staff = models.ForeignKey(StaffProfile, on_delete=models.CASCADE) staff = models.ForeignKey(StaffProfile, on_delete=models.CASCADE)
position = models.ForeignKey(JobPosition, null=True, on_delete=models.SET_NULL) position = models.ForeignKey(JobPosition, null=True, on_delete=models.SET_NULL)
@ -198,7 +196,7 @@ class Project(models.Model):
details = models.TextField() details = models.TextField()
members = models.ManyToManyField('StaffProfile', default='', related_name='members_project') members = models.ManyToManyField('StaffProfile', default='', related_name='members_project')
start_date = models.DateField() start_date = models.DateField()
end_date = models.DateField() end_date = models.DateField(null=True, blank=True)
active = models.BooleanField(default=True, null=True) active = models.BooleanField(default=True, null=True)
project_id = models.CharField(max_length=20, null=True, blank=True) project_id = models.CharField(max_length=20, null=True, blank=True)
def __str__(self): def __str__(self):
@ -259,17 +257,6 @@ class PinnedProject(models.Model):
class Milestone(models.Model):
title = models.CharField(max_length=150)
description = models.TextField()
project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True)
start_date = models.DateField()
end_date = models.DateField()
def __str__(self):
return self.title
class Epic(models.Model): class Epic(models.Model):
title = models.CharField(max_length=150) title = models.CharField(max_length=150)
STATUS_CHOICES = ( STATUS_CHOICES = (
@ -285,11 +272,26 @@ class Epic(models.Model):
return self.title return self.title
class ProjectRequirement(models.Model):
class Milestone(models.Model):
name = models.CharField(max_length=150)
description = models.TextField()
project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True)
start_date = models.DateField()
end_date = models.DateField()
def __str__(self):
return self.name
class UserStory(models.Model):
milestone = models.ForeignKey(Milestone, on_delete=models.SET_NULL, null=True)
content = models.CharField(max_length=350) content = models.CharField(max_length=350)
project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True) project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True)
date = models.DateField(null=True, auto_now=True) date = models.DateField(null=True, auto_now=True)
added_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) added_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
confirmed = models.BooleanField(default=True)
completed = models.BooleanField(default=False)
def __str__(self): def __str__(self):
return self.content return self.content
@ -339,7 +341,7 @@ class Task(models.Model):
start_date = models.DateField(null=True) start_date = models.DateField(null=True)
end_date = models.DateField(null=True) end_date = models.DateField(null=True)
assigned_to = models.ForeignKey(StaffProfile, on_delete=models.CASCADE, null=True) assigned_to = models.ForeignKey(StaffProfile, on_delete=models.CASCADE, null=True)
requirement = models.ForeignKey(ProjectRequirement, on_delete=models.SET_NULL, null=True, blank=True) userstory = models.ForeignKey(UserStory, on_delete=models.SET_NULL, null=True, blank=True)
task_id = models.CharField(max_length=20, null=True, blank=True) task_id = models.CharField(max_length=20, null=True, blank=True)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.task_id: if not self.task_id:

@ -11,18 +11,13 @@
action="{% url 'addepic' project.project_id %}"> action="{% url 'addepic' project.project_id %}">
{% csrf_token %} {% csrf_token %}
<div class="w-full"> <div class="w-full">
<label class="text-gray-500">Epic Name:</label> <label class="text-gray-500">Name:</label>
<input required name="title" type="text" <input required name="title" type="text"
class="w-full h-[50px] py-1 px-3 border border-gray-300 outline-none rounded-md mt-1"> class="w-full h-[50px] py-1 px-3 border border-gray-300 outline-none rounded-md mt-1">
</div> </div>
<select required name="project" id="project"
class="hidden w-full h-[50px] py-1 px-3 border border-gray-300 outline-none rounded-md text-gray-500">
<option value="{{ project.id }}" selected>{{ project.name }}</option>
</select>
<div class="w-full"> <div class="w-full">
<label class="text-gray-500">Epic Description:</label> <label class="text-gray-500">Description:</label>
<textarea required name="description" type="text" placeholder="Description..." rows="5" cols="5" <textarea required name="description" type="text" placeholder="Description..." rows="5" cols="5"
class="w-full py-3 px-3 border border-gray-300 outline-none rounded-md resize-none mt-1"></textarea> class="w-full py-3 px-3 border border-gray-300 outline-none rounded-md resize-none mt-1"></textarea>
</div> </div>

@ -0,0 +1,45 @@
{% extends "add-edit-main.html" %}
{%load static%}
{% block content %}
<div class="w-full px-5 s:px-9 mb-5">
<div class="w-full bg-white h-fit shadow-md rounded-md p-5">
<h1 class="text-3xl text-secondosiblue text-center font-semibold">
Create Milestone For {{project.name}}
</h1>
<form class="mx-auto w-full flex flex-col gap-5 justify-center items-center mt-5" method="POST"
action="{% url 'addmilestone' project.project_id %}">
{% csrf_token %}
<div class="w-full">
<label class="text-gray-500">Name:</label>
<input required name="name" type="text"
class="w-full h-[50px] py-1 px-3 border border-gray-300 outline-none rounded-md mt-1">
</div>
<div class="w-full">
<label class="text-gray-500">Description:</label>
<textarea required name="description" type="text" placeholder="Description..." rows="5" cols="5"
class="w-full py-3 px-3 border border-gray-300 outline-none rounded-md resize-none mt-1"></textarea>
</div>
<div class="w-full">
<label class="text-gray-500">Start Date:</label>
<input required name="start_date" type="date" id="date" name="date"
class="w-full p-3 border border-gray-300 rounded-md bg-transparent outline-none mt-1">
</div>
<div class="w-full">
<label class="text-gray-500">End Date:</label>
<input required name="end_date" type="date" id="date" name="date"
class="w-full p-3 border border-gray-300 rounded-md bg-transparent outline-none mt-1">
</div>
<div class="w-full flex justify-center items-center mt-3">
<button type="submit"
class="w-fit py-1 px-5 bg-osiblue rounded-md outline-none text-white border border-osiblue text-xl cursor-pointer hover:bg-white hover:text-osiblue duration-300">Save</button>
</div>
</form>
</div>
</div>
{% endblock content %}

@ -70,35 +70,6 @@
class="w-full py-3 px-3 border border-gray-300 outline-none rounded-md resize-none mt-1"></textarea> class="w-full py-3 px-3 border border-gray-300 outline-none rounded-md resize-none mt-1"></textarea>
</div> </div>
<div class="w-full">
<label class="text-gray-500">Requirement(s):</label>
<div class="w-full p-3 border border-gray-300 mt-1 rounded-md">
<div class="w-full mt-2" id="addReqContainer">
<input name="requirements" type="text" placeholder="Requirement"
class="w-full p-3 border border-gray-300 rounded-md bg-transparent outline-none">
</div>
<!-- THE CLONED CONTAINER -->
<div class="mt-2 hidden" id="addReqContainerTemplate">
<div class="w-full flex flex-col gap-2">
<input name="requirements" type="text" placeholder="Requirement"
class="w-full p-3 border border-gray-300 rounded-md bg-transparent outline-none">
<button
class="w-full h-[40px] s:h-[55px] rounded-md bg-gray-300 border-none outline-none shadow-md text-white text-xl cursor-pointer py-2"
id="removeReqButton" type="button">
<i class="fa fa-minus"></i>
</button>
</div>
</div>
<button
class="w-full h-[40px] s:h-[55px] rounded-md bg-gray-400 border-none outline-none shadow-md text-white text-xl cursor-pointer py-2 mt-2"
id="addReqButton" type="button">
<i class="fa fa-plus"></i>
</button>
</div>
</div>
<div class="w-full"> <div class="w-full">
<label class="text-gray-500">Start Date:</label> <label class="text-gray-500">Start Date:</label>
<input required name="start_date" type="date" id="date" name="date" <input required name="start_date" type="date" id="date" name="date"
@ -107,7 +78,7 @@
<div class="w-full"> <div class="w-full">
<label class="text-gray-500">End Date:</label> <label class="text-gray-500">End Date:</label>
<input required name="end_date" type="date" id="date" name="date" <input name="end_date" type="date" id="date" name="date"
class="w-full p-3 border border-gray-300 rounded-md bg-transparent outline-none mt-1"> class="w-full p-3 border border-gray-300 rounded-md bg-transparent outline-none mt-1">
</div> </div>
<div class="w-full flex justify-center items-center mt-3"> <div class="w-full flex justify-center items-center mt-3">
@ -118,7 +89,5 @@
</div> </div>
</div> </div>
<!-------------- JS SCRIPTS --------------->
<script type="text/javascript" src='{% static "js/projects/add-project.js" %}'></script>
{% endblock content %} {% endblock content %}

@ -13,16 +13,41 @@
</head> </head>
<body class="font-poppinsLight"> <body class="font-poppinsLight">
<form method="POST" action="{% url 'adduserstorymodal' project.project_id %}"> <form method="POST" {% if not milestone %} action="{% url 'adduserstorymodal' project.project_id %}" {% else %} action="{% url 'addmilestoneuserstorymodal' milestone.id %}" {% endif %}>
{% csrf_token %} {% csrf_token %}
<h1 class="text-secondosiblue text-2xl font-semibold text-center">Add User Story</h1> <h1 class="text-secondosiblue text-2xl font-semibold text-center">Add User Story</h1>
{% if not milestone %}
<div class="w-full">
<label class="text-gray-500">Milestone:</label>
<select required name="milestone" id=""
class="w-full h-[50px] py-1 px-3 border border-gray-300 outline-none rounded-md text-gray-500 mt-1">
{% for milestone in milestones %}
<option value="{{milestone.id}}">{{milestone.name}}</option>
{% endfor %}
</select>
</div>
{% endif %}
<div class="w-full flex justify-center items-center"> <div class="w-full flex justify-center items-center">
<input name="content" type="text" placeholder="User Story" <input name="content" type="text" placeholder="User Story"
class="w-full p-3 border border-gray-300 rounded-md bg-transparent outline-none mt-4" class="w-full p-3 border border-gray-300 rounded-md bg-transparent outline-none mt-4"
required> required>
</div> </div>
<div class="w-full flex justify-start items-center gap-2 mt-4">
<input name="completed" type="checkbox">
<p class="text-gray-500">Completed</p>
</div>
<div class="w-full flex justify-start items-center gap-2 mt-4">
<input name="confirmed" type="checkbox">
<p class="text-gray-500">Confirmed</p>
</div>
<div class="w-full flex justify-center items-center mt-4"> <div class="w-full flex justify-center items-center mt-4">
<button type="submit" <button type="submit"
class="w-fit bg-osiblue border border-osiblue rounded-md text-white text-xl px-5 py-1 hover:bg-white hover:text-osiblue duration-300">Save</button> class="w-fit bg-osiblue border border-osiblue rounded-md text-white text-xl px-5 py-1 hover:bg-white hover:text-osiblue duration-300">Save</button>

@ -0,0 +1,180 @@
{% extends "main.html" %}
{%load static%}
{% block content %}
<div class="w-full xxlg1:w-[75%] bg-white h-fit rounded-md shadow-md p-5">
<div
class="w-full rounded-md flex flex-col justify-center items-center py-3 bg-osiblue bg-opacity-70">
<h1 class="text-xl md:text-3xl text-white font-semibold text-center">{{milestone.name}}</h1>
</div>
<div class="w-full flex flex-col gap-5 mt-5">
<div>
<p class="text-gray-500 text-xl">Project: <a href="{% url 'detailed-project' milestone.project.project_id %}"
class="text-slate-800 text-xl font-semibold hover:text-secondosiblue duration-300">{{milestone.project.name}}</a></p>
</div>
<div>
<p class="text-gray-500 text-xl">Title: <span
class="text-slate-800 text-xl font-semibold">{{milestone.name}}</span></p>
</div>
<div>
<p class="text-gray-500 text-xl">Description: <span
class="text-slate-800 text-xl font-semibold">{{milestone.description}}</span></p>
</div>
<div>
<p class="text-gray-500 text-xl">Start Date: <span
class="text-slate-800 text-xl font-semibold">{{milestone.start_date}}</span></p>
</div>
<div>
<p class="text-gray-500 text-xl">End Date: <span
class="text-slate-800 text-xl font-semibold">{{milestone.end_date}}</span></p>
</div>
<!-- STORIES -->
<div class="mt-5 relative">
<div class="overflow-x-auto border border-gray-300 rounded-b-md mt-5 tableContainer">
<div
class=" bg-gray-200 rounded-t-md flex justify-between items-center text-white text-xl font-bold h-[50px]">
<div class="px-3">
<p class="text-secondosiblue uppercase font-bold">User Stories</p>
</div>
<button
class="h-full rounded-tr-md px-4 bg-secondosiblue text-gray-200 text-[18px] outline-none border-none cursor-pointer flex justify-center items-center addUserStoryButton"
data-modal-url="{% url 'addmilestoneuserstorymodal' milestone_id=milestone.id %}">
<i class="fa fa-plus"></i>
</button>
</div>
<table class="min-w-full divide-y">
<!-- TABLE HEADER -->
<thead class="bg-gray-50">
<tr>
<th scope="col"
class="px-6 py-3 text-sm font-medium text-gray-500 uppercase border-r border-gray-300 whitespace-nowrap">
Story
</th>
<th scope="col"
class="px-6 py-3 text-sm font-medium text-gray-500 uppercase border-r border-gray-300 whitespace-nowrap">
Confirmed
</th>
<th scope="col"
class="px-6 py-3 text-sm font-medium text-gray-500 uppercase border-r border-gray-300 whitespace-nowrap">
Completed
</th>
<th scope="col"
class="px-6 py-3 text-sm font-medium text-gray-500 uppercase border-r border-gray-300 whitespace-nowrap">
Related Task
</th>
<th scope="col"
class="px-6 py-3 text-sm font-medium text-gray-500 uppercase whitespace-nowrap">
Actions
</th>
</tr>
</thead>
<!-- TABLE BODY -->
<tbody class="bg-white divide-y divide-gray-200">
<!-- 1st row -->
{% if stories %}
{% for story in stories %}
<tr>
<td class="px-6 py-4 text-center text-sm border-r border-gray-300">
<p class="text-secondosiblue">{{story.content}}</p>
</td>
<td class="px-6 py-4 text-center text-sm border-r border-gray-300">
<p class="text-secondosiblue">{{story.confirmed}}</p>
</td>
<td class="px-6 py-4 text-center text-sm border-r border-gray-300">
<p class="text-secondosiblue">{{story.completed}}</p>
</td>
<td class="px-6 py-4 text-center text-sm border-r border-gray-300">
{% if story.task_set.all %}
{% for task in story.task_set.all %}
<div class="w-full flex flex-col justify-center items-center gap-2">
<a class="text-gray-500 underline flex justify-center items-center gap-1 hover:text-secondosiblue duration-300 cursor-pointer"
href="{% url 'detailed-task' task.task_id %}">{{task.name}}
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5" />
</svg>
</a>
<a href="{% url 'adduserstorytask' milestone.project.project_id story.id %}">
<div
class="w-[30px] h-[30px] rounded-full shadow-md bg-gray-50 text-secondosiblue border border-gray-100 flex justify-center items-center hover:bg-secondosiblue hover:text-gray-50 duration-300">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 4.5v15m7.5-7.5h-15" />
</svg>
</div>
</a>
</div>
{% endfor %}
{% else %}
<div class="w-full flex flex-col justify-center items-center gap-2">
<p class="text-secondosiblue">Add Task</p>
<a href="{% url 'adduserstorytask' milestone.project.project_id story.id %}">
<div
class="w-[30px] h-[30px] rounded-full shadow-md bg-gray-50 text-secondosiblue border border-gray-100 flex justify-center items-center hover:bg-secondosiblue hover:text-gray-50 duration-300">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 4.5v15m7.5-7.5h-15" />
</svg>
</div>
</a>
</div>
{% endif %}
</td>
<td class="px-6 py-4 text-center text-sm">
<div class="w-full flex justify-center items-center gap-3">
<a data-modal-url="{% url 'edituserstorymodal' story.id %}" class="addUserStoryButton">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor"
class="w-[18px] text-fifthosiblue hover:scale-110 duration-500 transition-transform">
<path stroke-linecap="round" stroke-linejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
</a>
<div class="cursor-pointer" data-modal-url="">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor"
class="w-[18px] text-red-500 hover:scale-110 duration-500 transition-transform">
<path stroke-linecap="round" stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</div>
</div>
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="3" class="px-6 py-4 text-center text-sm text-secondosiblue">
No Stories at the moment
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock content %}

@ -72,23 +72,27 @@
<div class="w-full md:w-fit flex flex-col md:flex-row flex-wrap justify-start items-center gap-3"> <div class="w-full md:w-fit flex flex-col md:flex-row flex-wrap justify-start items-center gap-3">
<button data-modal-url="{% url 'editprojectstatusmodal' project.id %}" <button data-modal-url="{% url 'editprojectstatusmodal' project.id %}"
class="w-full md:w-fit text-base px-3 py-2 bg-transparent text-osiblue outline-none border border-osiblue duration-300 rounded-md cursor-pointer hover:bg-osiblue hover:text-white editProjectStatusButton"> class="w-full md:w-fit text-base px-3 py-2 bg-transparent text-osiblue outline-none border border-osiblue duration-300 rounded-md cursor-pointer hover:bg-osiblue hover:text-white editProjectStatusButton">
Update Status Add Status
</button> </button>
<a href="{% url 'addmilestone' project.project_id %}" class="w-full md:w-fit">
<button
class="w-full md:w-fit text-base px-3 py-2 bg-transparent text-osiblue outline-none border border-osiblue duration-300 rounded-md cursor-pointer hover:bg-osiblue hover:text-white">Add
Milestone</button>
</a>
<button
class="w-full md:w-fit text-base px-3 py-2 bg-transparent text-osiblue outline-none border border-osiblue duration-300 rounded-md cursor-pointer hover:bg-osiblue hover:text-white">Add
Story</button>
<a href="{% url 'addepic' project.project_id %}" class="w-full md:w-fit"> <a href="{% url 'addepic' project.project_id %}" class="w-full md:w-fit">
<button <button
class="w-full md:w-fit text-base px-3 py-2 bg-transparent text-osiblue outline-none border border-osiblue duration-300 rounded-md cursor-pointer hover:bg-osiblue hover:text-white">Create class="w-full md:w-fit text-base px-3 py-2 bg-transparent text-osiblue outline-none border border-osiblue duration-300 rounded-md cursor-pointer hover:bg-osiblue hover:text-white">Add
Epic</button> Epic</button>
</a> </a>
<a href="{% url 'addprojecttask' project.project_id %}" class="w-full md:w-fit"> <a href="{% url 'addprojecttask' project.project_id %}" class="w-full md:w-fit">
<button <button
class="w-full md:w-fit text-base px-3 py-2 bg-transparent text-osiblue outline-none border border-osiblue duration-300 rounded-md cursor-pointer hover:bg-osiblue hover:text-white">Create class="w-full md:w-fit text-base px-3 py-2 bg-transparent text-osiblue outline-none border border-osiblue duration-300 rounded-md cursor-pointer hover:bg-osiblue hover:text-white">Add
Task</button> Task</button>
</a> </a>
<button
class="w-full md:w-fit text-base px-3 py-2 bg-transparent text-osiblue outline-none border border-osiblue duration-300 rounded-md cursor-pointer hover:bg-osiblue hover:text-white">Create
Story</button>
<a href="" class="w-full md:w-fit"> <a href="" class="w-full md:w-fit">
<button <button
class="w-full md:w-fit text-base px-3 py-2 bg-transparent text-osiblue outline-none border border-osiblue duration-300 rounded-md cursor-pointer hover:bg-osiblue hover:text-white">Add class="w-full md:w-fit text-base px-3 py-2 bg-transparent text-osiblue outline-none border border-osiblue duration-300 rounded-md cursor-pointer hover:bg-osiblue hover:text-white">Add
@ -388,7 +392,7 @@
<button <button
class="h-full rounded-tr-md px-4 bg-secondosiblue text-gray-200 text-[18px] outline-none border-none cursor-pointer flex justify-center items-center addUserStoryButton" class="h-full rounded-tr-md px-4 bg-secondosiblue text-gray-200 text-[18px] outline-none border-none cursor-pointer flex justify-center items-center addUserStoryButton"
data-modal-url="{% url 'adduserstorymodal' project.project_id %}"> data-modal-url="{% url 'adduserstorymodal' project_id=project.project_id %}">
<i class="fa fa-plus"></i> <i class="fa fa-plus"></i>
</button> </button>
</div> </div>
@ -396,10 +400,22 @@
<!-- TABLE HEADER --> <!-- TABLE HEADER -->
<thead class="bg-gray-50"> <thead class="bg-gray-50">
<tr> <tr>
<th scope="col"
class="px-6 py-3 text-sm font-medium text-gray-500 uppercase border-r border-gray-300 whitespace-nowrap">
Milestone
</th>
<th scope="col" <th scope="col"
class="px-6 py-3 text-sm font-medium text-gray-500 uppercase border-r border-gray-300 whitespace-nowrap"> class="px-6 py-3 text-sm font-medium text-gray-500 uppercase border-r border-gray-300 whitespace-nowrap">
Story Story
</th> </th>
<th scope="col"
class="px-6 py-3 text-sm font-medium text-gray-500 uppercase border-r border-gray-300 whitespace-nowrap">
Confirmed
</th>
<th scope="col"
class="px-6 py-3 text-sm font-medium text-gray-500 uppercase border-r border-gray-300 whitespace-nowrap">
Completed
</th>
<th scope="col" <th scope="col"
class="px-6 py-3 text-sm font-medium text-gray-500 uppercase border-r border-gray-300 whitespace-nowrap"> class="px-6 py-3 text-sm font-medium text-gray-500 uppercase border-r border-gray-300 whitespace-nowrap">
Related Task Related Task
@ -417,10 +433,33 @@
{% if stories %} {% if stories %}
{% for story in stories %} {% for story in stories %}
<tr> <tr>
<td class="px-6 py-4 text-center text-sm border-r border-gray-300">
{% if story.milestone %}
<a class="text-gray-500 underline flex justify-center items-center gap-1 hover:text-secondosiblue duration-300 cursor-pointer"
href="{% url 'detailed-milestone' story.milestone.id %}">{{story.milestone.name}}
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5" />
</svg>
</a>
{% else %}
<p class="text-secondosiblue">{{story.milestone.name}}</p>
{% endif %}
</td>
<td class="px-6 py-4 text-center text-sm border-r border-gray-300"> <td class="px-6 py-4 text-center text-sm border-r border-gray-300">
<p class="text-secondosiblue">{{story.content}}</p> <p class="text-secondosiblue">{{story.content}}</p>
</td> </td>
<td class="px-6 py-4 text-center text-sm border-r border-gray-300">
<p class="text-secondosiblue">{{story.confirmed}}</p>
</td>
<td class="px-6 py-4 text-center text-sm border-r border-gray-300">
<p class="text-secondosiblue">{{story.completed}}</p>
</td>
<td class="px-6 py-4 text-center text-sm border-r border-gray-300"> <td class="px-6 py-4 text-center text-sm border-r border-gray-300">
{% if story.task_set.all %} {% if story.task_set.all %}
{% for task in story.task_set.all %} {% for task in story.task_set.all %}
@ -466,7 +505,7 @@
<td class="px-6 py-4 text-center text-sm"> <td class="px-6 py-4 text-center text-sm">
<div class="w-full flex justify-center items-center gap-3"> <div class="w-full flex justify-center items-center gap-3">
<a href=""> <a data-modal-url="{% url 'edituserstorymodal' story.id %}" class="addUserStoryButton">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" stroke-width="1.5" stroke="currentColor"
class="w-[18px] text-fifthosiblue hover:scale-110 duration-500 transition-transform"> class="w-[18px] text-fifthosiblue hover:scale-110 duration-500 transition-transform">

@ -0,0 +1,57 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Osina</title>
<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">
</head>
<body class="font-poppinsLight">
<form method="POST" action="{% url 'edituserstorymodal' story.id %}">
{% csrf_token %}
<h1 class="text-secondosiblue text-2xl font-semibold text-center">Edit User Story</h1>
{% if not milestone %}
<div class="w-full">
<label class="text-gray-500">Milestone:</label>
<select required name="milestone" id=""
class="w-full h-[50px] py-1 px-3 border border-gray-300 outline-none rounded-md text-gray-500 mt-1">
{% for milestone in milestones %}
<option {% if story.milestone.id == milestone.id %} selected {% endif %} value="{{milestone.id}}">{{milestone.name}}</option>
{% endfor %}
</select>
</div>
{% endif %}
<div class="w-full flex justify-center items-center">
<input name="content" type="text" placeholder="User Story"
class="w-full p-3 border border-gray-300 rounded-md bg-transparent outline-none mt-4" value="{{story.content}}"
required>
</div>
<div class="w-full flex justify-start items-center gap-2 mt-4">
<input name="completed" type="checkbox" {% if story.completed %}checked{% endif %}>
<p class="text-gray-500">Completed</p>
</div>
<div class="w-full flex justify-start items-center gap-2 mt-4">
<input name="confirmed" type="checkbox" {% if story.confirmed %}checked{% endif %}>
<p class="text-gray-500">Confirmed</p>
</div>
<div class="w-full flex justify-center items-center mt-4">
<button type="submit"
class="w-fit bg-osiblue border border-osiblue rounded-md text-white text-xl px-5 py-1 hover:bg-white hover:text-osiblue duration-300">Save</button>
</div>
</form>
</body>
</html>

@ -63,7 +63,8 @@ urlpatterns = [
path('customers/<str:customer_id>/', views.customerdetails, name='customerdetails'), path('customers/<str:customer_id>/', views.customerdetails, name='customerdetails'),
path('businesses/<str:business_id>/', views.businessdetails, name='businessdetails'), path('businesses/<str:business_id>/', views.businessdetails, name='businessdetails'),
path('staffs/<str:staff_id>/', views.staffdetails, name='userdetails'), path('staffs/<str:staff_id>/', views.staffdetails, name='userdetails'),
path('projectdetails/<str:project_id>/', views.projectdetails, name='detailed-project'), path('projects/<str:project_id>/', views.projectdetails, name='detailed-project'),
path('milestones/<str:milestone_id>/', views.milestonedetails, name='detailed-milestone'),
path('tasks/<str:task_id>/', views.taskdetails, name='detailed-task'), path('tasks/<str:task_id>/', views.taskdetails, name='detailed-task'),
path('show-points/<str:task_id>/', views.show_points_modal, name='showpoints'), path('show-points/<str:task_id>/', views.show_points_modal, name='showpoints'),
path('timeline/<str:task_id>/', views.timeline_modal, name='timeline'), path('timeline/<str:task_id>/', views.timeline_modal, name='timeline'),

@ -606,7 +606,7 @@ def projectdetails(request, project_id):
project_notes = Note.objects.filter(project=project).order_by('-id') project_notes = Note.objects.filter(project=project).order_by('-id')
statuses = ProjectStatus.objects.filter(project=project).order_by('-id') statuses = ProjectStatus.objects.filter(project=project).order_by('-id')
stories = ProjectRequirement.objects.filter(project=project).order_by('-id') stories = UserStory.objects.filter(project=project).order_by('-id')
credentials = ProjectCredential.objects.filter(project=project).order_by('-id') credentials = ProjectCredential.objects.filter(project=project).order_by('-id')
albums = ProjectFileAlbum.objects.filter(project=project).order_by('-id') albums = ProjectFileAlbum.objects.filter(project=project).order_by('-id')
@ -653,6 +653,18 @@ def projectdetails(request, project_id):
return render(request, 'details_templates/project-details.html', context) return render(request, 'details_templates/project-details.html', context)
@staff_login_required
def milestonedetails(request, milestone_id):
milestone = get_object_or_404(Milestone, id=milestone_id)
stories = UserStory.objects.filter(milestone=milestone).order_by('-id')
context = {
'milestone': milestone,
'stories' : stories,
}
return render(request, 'details_templates/milestone-details.html', context)
def chat_room(request, chat_id): def chat_room(request, chat_id):

@ -70,7 +70,7 @@ function initializeModalButtons() {
addButtonClickListener("addStaffPositionButton", "fit-content", "260px"); addButtonClickListener("addStaffPositionButton", "fit-content", "260px");
addButtonClickListener("addStatusButtonMobile", "500px", "80px"); addButtonClickListener("addStatusButtonMobile", "500px", "80px");
addButtonClickListener("userRecentActivitiesButton", "400px", "600px"); addButtonClickListener("userRecentActivitiesButton", "400px", "600px");
addButtonClickListener("addUserStoryButton", "400px", "160px"); addButtonClickListener("addUserStoryButton", "400px", "330px");
addButtonClickListener("reactionDetailsButton", "400px", "300px"); addButtonClickListener("reactionDetailsButton", "400px", "300px");
addButtonClickListener("addPaymentCommentButton", "500px", "250px"); addButtonClickListener("addPaymentCommentButton", "500px", "250px");

@ -1,18 +0,0 @@
const addReqButton = document.getElementById("addReqButton");
const addReqContainerTemplate = document.getElementById("addReqContainerTemplate");
const addReqContainer = document.getElementById("addReqContainer");
addReqButton.addEventListener("click", function () {
// Clone the template and remove the "hidden" class
const newContainer = addReqContainerTemplate.cloneNode(true);
newContainer.classList.remove("hidden");
// Add an event listener to the new container's remove button
const removeReqButton = newContainer.querySelector("#removeReqButton");
removeReqButton.addEventListener("click", function () {
// Remove the clicked container when the remove button is clicked
newContainer.remove();
});
addReqContainer.appendChild(newContainer);
});
Loading…
Cancel
Save