[UPDT] PROJECT: Missing files updated
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
from django.contrib import admin
|
||||
from .models import *
|
||||
# Register your models here.
|
||||
admin.site.register(Project)
|
||||
admin.site.register(ProjectStage)
|
||||
admin.site.register(Task)
|
||||
admin.site.register(TimeSheet)
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import *
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(Project)
|
||||
admin.site.register(ProjectStage)
|
||||
admin.site.register(Task)
|
||||
admin.site.register(TimeSheet)
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ProjectConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "project"
|
||||
|
||||
def ready(self):
|
||||
from horilla.horilla_settings import APPS
|
||||
|
||||
APPS.append("project")
|
||||
super().ready()
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ProjectConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "project"
|
||||
|
||||
def ready(self):
|
||||
from django.urls import include, path
|
||||
|
||||
from horilla.urls import urlpatterns
|
||||
|
||||
urlpatterns.append(
|
||||
path("project/", include("project.urls")),
|
||||
)
|
||||
super().ready()
|
||||
try:
|
||||
from django.urls import include, path
|
||||
|
||||
from horilla.urls import urlpatterns
|
||||
|
||||
urlpatterns.append(
|
||||
path("project/", include("project.urls")),
|
||||
)
|
||||
except:
|
||||
"""
|
||||
Models not ready yet
|
||||
"""
|
||||
|
||||
0
project/cbv/__init__.py
Normal file
0
project/cbv/__init__.py
Normal file
31
project/cbv/accessibility.py
Normal file
31
project/cbv/accessibility.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
Accessibility
|
||||
"""
|
||||
|
||||
|
||||
from django.contrib.auth.context_processors import PermWrapper
|
||||
|
||||
from employee.models import Employee
|
||||
from project.models import Project, Task
|
||||
|
||||
|
||||
def task_crud_accessibility(request, instance: object = None, user_perms: PermWrapper = [], *args, **kwargs
|
||||
) -> bool:
|
||||
"""
|
||||
to access crud operations
|
||||
"""
|
||||
employee = request.user.employee_get
|
||||
is_task_manager = employee in instance.task_managers.all()
|
||||
is_project_manager = employee in instance.project.managers.all()
|
||||
if (request.user.has_perm("project.view_task") or is_project_manager or is_task_manager):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def project_manager_accessibility(request, instance: object = None, user_perms: PermWrapper = [], *args, **kwargs
|
||||
) -> bool:
|
||||
"""
|
||||
to access edit Project
|
||||
"""
|
||||
return (request.user.employee_get in instance.managers.all() or
|
||||
request.user.is_superuser)
|
||||
33
project/cbv/cbv_decorators.py
Normal file
33
project/cbv/cbv_decorators.py
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
|
||||
from project.methods import any_project_manager, any_project_member, any_task_manager, any_task_member, has_subordinates
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponseRedirect
|
||||
from horilla.horilla_middlewares import _thread_locals
|
||||
# from project.sidebar import has_subordinates
|
||||
|
||||
|
||||
decorator_with_arguments = lambda decorator: lambda *args, **kwargs: lambda func: decorator(func, *args, **kwargs)
|
||||
|
||||
|
||||
@decorator_with_arguments
|
||||
def is_projectmanager_or_member_or_perms(function,perm):
|
||||
def _function(self, *args, **kwargs):
|
||||
"""
|
||||
This method is used to check the employee is project manager or not
|
||||
"""
|
||||
request = getattr(_thread_locals,"request")
|
||||
if not getattr(self,"request",None):
|
||||
self.request = request
|
||||
user = request.user
|
||||
if (
|
||||
user.has_perm(perm) or
|
||||
any_project_manager(user) or
|
||||
any_project_member(user) or
|
||||
any_task_manager(user) or
|
||||
any_task_member(user)
|
||||
):
|
||||
return function(self, *args, **kwargs)
|
||||
messages.info(request, "You don't have permission.")
|
||||
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
|
||||
return _function
|
||||
107
project/cbv/dashboard.py
Normal file
107
project/cbv/dashboard.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""
|
||||
Dashbord of project
|
||||
"""
|
||||
|
||||
import calendar
|
||||
import datetime
|
||||
from typing import Any
|
||||
from django.db.models import Count
|
||||
from django.db.models.query import QuerySet
|
||||
from django.urls import resolve, reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.db.models import Q
|
||||
from base.methods import get_subordinates
|
||||
from horilla_views.generic.cbv.views import HorillaListView, HorillaDetailedView
|
||||
from horilla_views.cbv_methods import login_required
|
||||
from project.filters import ProjectFilter
|
||||
from project.models import Project
|
||||
from project.cbv.cbv_decorators import is_projectmanager_or_member_or_perms
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
is_projectmanager_or_member_or_perms("project.view_project"), name="dispatch"
|
||||
)
|
||||
class ProjectsDueInMonth(HorillaListView):
|
||||
|
||||
model = Project
|
||||
filter_class = ProjectFilter
|
||||
bulk_select_option = False
|
||||
columns = [(_("Project"), "title", "get_avatar")]
|
||||
show_filter_tags = False
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
if not self.request.user.has_perm("project.view_project"):
|
||||
employee = self.request.user.employee_get
|
||||
task_filter = queryset.filter(
|
||||
Q(task__task_members=employee) | Q(task__task_manager=employee)
|
||||
)
|
||||
project_filter = queryset.filter(Q(manager=employee) | Q(members=employee))
|
||||
queryset = task_filter | project_filter
|
||||
today = datetime.date.today()
|
||||
first_day = today.replace(day=1)
|
||||
last_day = calendar.monthrange(today.year, today.month)[1]
|
||||
last_day_of_month = today.replace(day=last_day)
|
||||
queryset = queryset.filter(
|
||||
Q(end_date__gte=first_day) & Q(end_date__lte=last_day_of_month)
|
||||
).exclude(status="expired")
|
||||
return queryset
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.search_url = reverse("projects-due-in-this-month")
|
||||
|
||||
row_attrs = """
|
||||
hx-get='{get_detail_url}?instance_ids={ordered_ids}'
|
||||
hx-target="#genericModalBody"
|
||||
data-target="#genericModal"
|
||||
data-toggle="oh-modal-toggle"
|
||||
"""
|
||||
|
||||
|
||||
class ProjectDetailView(HorillaDetailedView):
|
||||
"""
|
||||
detail view of the projects
|
||||
"""
|
||||
|
||||
model = Project
|
||||
title = _("Details")
|
||||
header = {"title": "title", "subtitle": "", "avatar": "get_avatar"}
|
||||
body = [
|
||||
(_("Manager"), "manager"),
|
||||
(_("Members"), "get_members"),
|
||||
(_("Status"), "status_column"),
|
||||
(_("No of Tasks"), "task_count"),
|
||||
(_("Start date"), "start_date"),
|
||||
(_("End date"), "end_date"),
|
||||
(_("Document"), "get_document_html"),
|
||||
(_("Description"), "description"),
|
||||
]
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
instnce_id = resolve(self.request.path_info).kwargs.get("pk")
|
||||
employee = self.request.user.employee_get
|
||||
project = Project.objects.get(id=instnce_id)
|
||||
if (
|
||||
employee in project.managers.all()
|
||||
or employee in project.members.all()
|
||||
or self.request.user.has_perm("project.view_project")
|
||||
):
|
||||
self.actions = [
|
||||
{
|
||||
"action": _("View Project"),
|
||||
"icon": "create-outline",
|
||||
"attrs": """
|
||||
class = "oh-btn oh-btn--info w-100"
|
||||
{redirect}
|
||||
""",
|
||||
}
|
||||
]
|
||||
|
||||
def get_queryset(self) -> QuerySet[Any]:
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset.annotate(task_count=Count("task"))
|
||||
return queryset
|
||||
135
project/cbv/project_stage.py
Normal file
135
project/cbv/project_stage.py
Normal file
@@ -0,0 +1,135 @@
|
||||
"""
|
||||
This page handles the cbv methods for project stages
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
from django.http import HttpResponse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.contrib import messages
|
||||
from horilla_views.cbv_methods import login_required
|
||||
from horilla_views.generic.cbv.views import HorillaFormView
|
||||
|
||||
# from project.decorator import project_delete_permission
|
||||
from project.forms import ProjectStageForm
|
||||
from project.methods import you_dont_have_permission
|
||||
from project.models import Project, ProjectStage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
# @method_decorator(project_delete_permission, name="dispatch")
|
||||
class ProjectStageCreateForm(HorillaFormView):
|
||||
"""
|
||||
form view fro create and edit stages
|
||||
"""
|
||||
|
||||
form_class = ProjectStageForm
|
||||
model = ProjectStage
|
||||
new_display_title = _("Create Project Stage")
|
||||
|
||||
def get(self, request, *args, pk=None, **kwargs):
|
||||
if request.GET.get("project_id"):
|
||||
project_id = request.GET.get("project_id")
|
||||
else:
|
||||
project_id = self.kwargs.get("project_id")
|
||||
stage_id = self.kwargs.get("pk")
|
||||
if project_id:
|
||||
try:
|
||||
if project_id:
|
||||
project = Project.objects.filter(id=project_id).first()
|
||||
elif stage_id:
|
||||
project = ProjectStage.objects.filter(id=stage_id).first().project
|
||||
if (
|
||||
request.user.employee_get in project.managers.all()
|
||||
or request.user.is_superuser
|
||||
):
|
||||
return super().get(request, *args, pk=pk, **kwargs)
|
||||
else:
|
||||
return you_dont_have_permission(request)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
messages.error(request, _("Something went wrong!"))
|
||||
return HttpResponse("<script>window.location.reload()</script>")
|
||||
else:
|
||||
return super().get(request, *args, pk=pk, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
project_id = self.kwargs.get("project_id")
|
||||
if project_id:
|
||||
project = Project.objects.get(id=project_id)
|
||||
self.form.fields["project"].initial = project
|
||||
if self.form.instance.pk:
|
||||
self.form_class.verbose_name = _("Update Project Stage")
|
||||
return context
|
||||
|
||||
def form_valid(self, form: ProjectStageForm) -> HttpResponse:
|
||||
if form.is_valid():
|
||||
if form.instance.pk:
|
||||
project_id = self.form.cleaned_data["project"].id
|
||||
message = _(f"{self.form.instance} Updated")
|
||||
else:
|
||||
project_id = self.kwargs.get("project_id")
|
||||
message = _("New project stage created")
|
||||
form.save()
|
||||
messages.success(self.request, message)
|
||||
return self.HttpResponse(
|
||||
f"<span hx-get='/project/task-filter/{project_id}' hx-trigger='load' hx-target='#viewContainer'></span>"
|
||||
)
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
from django import forms
|
||||
|
||||
|
||||
class StageDynamicCreateForm(ProjectStageCreateForm):
|
||||
"""
|
||||
dynamic create form for stage
|
||||
"""
|
||||
|
||||
is_dynamic_create_view = True
|
||||
template_name = HorillaFormView.template_name
|
||||
|
||||
def get_initial(self):
|
||||
initial = super().get_initial()
|
||||
project = self.request.GET.get("project")
|
||||
initial["project"] = project
|
||||
return initial
|
||||
|
||||
def init_form(self, *args, data, files, instance=None, **kwargs):
|
||||
initial = self.get_initial()
|
||||
form = super().init_form(
|
||||
*args, data=data, files=files, instance=instance, initial=initial, **kwargs
|
||||
)
|
||||
if not initial.get("project"):
|
||||
form.fields["project"].widget = forms.Select(
|
||||
attrs={"class": "oh-select oh-select-2 w-100 oh-select2"}
|
||||
)
|
||||
form.fields["project"].queryset = Project.objects.all()
|
||||
|
||||
return form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
project_id = self.request.GET.get("project_id")
|
||||
if not project_id:
|
||||
project_id = self.request.GET.get("project")
|
||||
if project_id:
|
||||
project = Project.objects.get(id=project_id)
|
||||
self.form.fields["project"].initial = project
|
||||
self.form.fields["project"].choices = [(project.id, project.title)]
|
||||
return context
|
||||
|
||||
def form_valid(self, form: ProjectStageForm) -> HttpResponse:
|
||||
if form.is_valid():
|
||||
if form.instance.pk:
|
||||
message = _(f"{self.form.instance} Updated")
|
||||
else:
|
||||
message = _("New project stage created")
|
||||
form.save()
|
||||
messages.success(self.request, _(message))
|
||||
return self.HttpResponse()
|
||||
return super().form_valid(form)
|
||||
518
project/cbv/projects.py
Normal file
518
project/cbv/projects.py
Normal file
@@ -0,0 +1,518 @@
|
||||
"""
|
||||
CBV of projects page
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
from django.http import HttpResponse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.urls import reverse
|
||||
from django.contrib import messages
|
||||
from django.views.generic import ListView
|
||||
from employee.models import Employee
|
||||
from horilla_views.cbv_methods import login_required, permission_required
|
||||
from horilla_views.generic.cbv.views import (
|
||||
HorillaCardView,
|
||||
HorillaFormView,
|
||||
HorillaListView,
|
||||
HorillaNavView,
|
||||
TemplateView,
|
||||
)
|
||||
from project.cbv.cbv_decorators import is_projectmanager_or_member_or_perms
|
||||
from project.filters import ProjectFilter
|
||||
from project.forms import ProjectForm
|
||||
from project.methods import any_project_manager, any_project_member, is_project_manager_or_super_user, you_dont_have_permission
|
||||
from project.models import Project
|
||||
from django.db.models import Q
|
||||
from horilla.horilla_middlewares import _thread_locals
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
is_projectmanager_or_member_or_perms("project.view_project"), name="dispatch"
|
||||
)
|
||||
class ProjectsView(TemplateView):
|
||||
"""
|
||||
for projects page
|
||||
"""
|
||||
|
||||
template_name = "cbv/projects/projects.html"
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
is_projectmanager_or_member_or_perms("project.view_project"), name="dispatch"
|
||||
)
|
||||
class ProjectsNavView(HorillaNavView):
|
||||
"""
|
||||
Nav bar
|
||||
"""
|
||||
|
||||
template_name = "cbv/projects/project_nav.html"
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.search_url = reverse("project-list-view")
|
||||
# self.search_in = [
|
||||
# ("status", _("Managers")),
|
||||
# ("members", _("Members")),
|
||||
# ]
|
||||
if self.request.user.has_perm("project.view_project"):
|
||||
self.actions = [
|
||||
{
|
||||
"action": _("Import"),
|
||||
"attrs": """
|
||||
id="importProject"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#projectImport"
|
||||
style="cursor: pointer;"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": _("Export"),
|
||||
"attrs": """
|
||||
id="exportProject"
|
||||
style="cursor: pointer;"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": _("Archive"),
|
||||
"attrs": """
|
||||
id="archiveProject"
|
||||
style="cursor: pointer;"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": _("Un-archive"),
|
||||
"attrs": """
|
||||
id="unArchiveProject"
|
||||
style="cursor: pointer;"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": _("Delete"),
|
||||
"attrs": """
|
||||
class="oh-dropdown__link--danger"
|
||||
data-action ="delete"
|
||||
id="deleteProject"
|
||||
style="cursor: pointer; color:red !important"
|
||||
""",
|
||||
},
|
||||
]
|
||||
self.view_types = [
|
||||
{
|
||||
"type": "list",
|
||||
"icon": "list-outline",
|
||||
"url": reverse("project-list-view"),
|
||||
"attrs": """
|
||||
title ='List'
|
||||
""",
|
||||
},
|
||||
{
|
||||
"type": "card",
|
||||
"icon": "grid-outline",
|
||||
"url": reverse("project-card-view"),
|
||||
"attrs": """
|
||||
title ='Card'
|
||||
""",
|
||||
},
|
||||
]
|
||||
if self.request.user.has_perm("project.add_project"):
|
||||
self.create_attrs = f"""
|
||||
onclick = "event.stopPropagation();"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal"
|
||||
hx-target="#genericModalBody"
|
||||
hx-get="{reverse('create-project')}"
|
||||
"""
|
||||
group_by_fields = [
|
||||
("status", _("Status")),
|
||||
("is_active", _("Is active")),
|
||||
]
|
||||
nav_title = _("Projects")
|
||||
filter_instance = ProjectFilter()
|
||||
filter_form_context_name = "form"
|
||||
filter_body_template = "cbv/projects/filter.html"
|
||||
search_swap_target = "#listContainer"
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
is_projectmanager_or_member_or_perms("project.view_project"), name="dispatch"
|
||||
)
|
||||
class ProjectsList(HorillaListView):
|
||||
"""
|
||||
Projects list view
|
||||
"""
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
if not self.request.user.has_perm("project.view_project"):
|
||||
employee = self.request.user.employee_get
|
||||
task_filter = queryset.filter(
|
||||
Q(task__task_members=employee)
|
||||
| Q(task__task_managers=employee)
|
||||
)
|
||||
project_filter = queryset.filter(
|
||||
Q(managers=employee)
|
||||
| Q(members=employee)
|
||||
)
|
||||
queryset = task_filter | project_filter
|
||||
return queryset.distinct()
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.search_url = reverse("project-list-view")
|
||||
if (self.request.user.is_superuser ):
|
||||
self.action_method = "actions"
|
||||
|
||||
model = Project
|
||||
filter_class = ProjectFilter
|
||||
|
||||
columns = [
|
||||
(_("Project"), "title"),
|
||||
(_("Project Managers"), "get_managers"),
|
||||
(_("Project Members"), "get_members"),
|
||||
(_("Status"), "status_column"),
|
||||
(_("Start Date"), "start_date"),
|
||||
(_("End Date"), "end_date"),
|
||||
(_("File"), "get_document_html"),
|
||||
(_("Description"), "get_description"),
|
||||
]
|
||||
|
||||
sortby_mapping = [
|
||||
("Project", "title"),
|
||||
("Start Date", "start_date"),
|
||||
("End Date", "end_date"),
|
||||
]
|
||||
|
||||
row_status_indications = [
|
||||
(
|
||||
"new--dot",
|
||||
_("New"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('new');
|
||||
$('#applyFilter').click();
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"in-progress--dot",
|
||||
_("In progress"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('in_progress');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"completed--dot",
|
||||
_("Completed"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('completed');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"on-hold--dot",
|
||||
_("On Hold"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('on_hold');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"cancelled--dot",
|
||||
_("Completed"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('cancelled');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"expired--dot",
|
||||
_("Expired"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('expired');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
]
|
||||
|
||||
row_status_class = "status-{status}"
|
||||
|
||||
row_attrs = """
|
||||
{redirect}
|
||||
"""
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
# @method_decorator(permission_required("project.add_project"), name="dispatch")
|
||||
class ProjectFormView(HorillaFormView):
|
||||
"""
|
||||
form view for create project
|
||||
"""
|
||||
|
||||
form_class = ProjectForm
|
||||
model = Project
|
||||
new_display_title = _("Create Project")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
user = self.request.user
|
||||
|
||||
if (not user.is_superuser and
|
||||
not user.has_perm("project.add_project")
|
||||
|
||||
):
|
||||
self.template_name = "decorator_404.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
if self.form.instance.pk:
|
||||
self.form_class.verbose_name = _("Update project")
|
||||
return context
|
||||
|
||||
def form_valid(self, form: ProjectForm) -> HttpResponse:
|
||||
if form.is_valid():
|
||||
if form.instance.pk:
|
||||
message = _(f"{self.form.instance} Updated")
|
||||
HTTP_REFERER = self.request.META.get("HTTP_REFERER", None)
|
||||
if HTTP_REFERER and "task-view/" in HTTP_REFERER:
|
||||
form.save()
|
||||
messages.success(self.request, message)
|
||||
return self.HttpResponse("<script>window.location.reload()</script>")
|
||||
else:
|
||||
message = _("New project created")
|
||||
form.save()
|
||||
messages.success(self.request, _(message))
|
||||
return self.HttpResponse()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class DynamicProjectCreationFormView(ProjectFormView):
|
||||
|
||||
is_dynamic_create_view = True
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
is_projectmanager_or_member_or_perms("project.view_project"), name="dispatch"
|
||||
)
|
||||
class ProjectCardView(HorillaCardView):
|
||||
"""
|
||||
For card view
|
||||
"""
|
||||
|
||||
model = Project
|
||||
filter_class = ProjectFilter
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
if not self.request.user.has_perm("project.view_project"):
|
||||
employee = self.request.user.employee_get
|
||||
task_filter = queryset.filter(
|
||||
Q(task__task_members=employee)
|
||||
| Q(task__task_managers=employee)
|
||||
)
|
||||
project_filter = queryset.filter(
|
||||
Q(managers=employee)
|
||||
| Q(members=employee)
|
||||
)
|
||||
queryset = task_filter | project_filter
|
||||
return queryset.distinct()
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.search_url = reverse("project-card-view")
|
||||
if (self.request.user.has_perm("project.change_project")
|
||||
or self.request.user.has_perm("project.delete_project")
|
||||
or any_project_manager(self.request.user)
|
||||
or any_project_member(self.request.user)
|
||||
):
|
||||
self.actions = [
|
||||
{
|
||||
"action": "Edit",
|
||||
"accessibility": "project.cbv.accessibility.project_manager_accessibility",
|
||||
"attrs": """
|
||||
hx-get='{get_update_url}'
|
||||
hx-target='#genericModalBody'
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal"
|
||||
class="oh-dropdown__link"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": "archive_status",
|
||||
"accessibility": "project.cbv.accessibility.project_manager_accessibility",
|
||||
"attrs": """
|
||||
href="{get_archive_url}"
|
||||
onclick="return confirm('Do you want to {archive_status} this project?')"
|
||||
class="oh-dropdown__link"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": "Delete",
|
||||
"accessibility": "project.cbv.accessibility.project_manager_accessibility",
|
||||
"attrs": """
|
||||
onclick="
|
||||
event.stopPropagation()
|
||||
deleteItem({get_delete_url});
|
||||
"
|
||||
class="oh-dropdown__link oh-dropdown__link--danger"
|
||||
""",
|
||||
},
|
||||
]
|
||||
|
||||
details = {
|
||||
"image_src": "get_avatar",
|
||||
"title": "{get_task_badge_html}",
|
||||
"subtitle": "Status : {status_column} <br> Start date : {start_date} <br>End date : {end_date}",
|
||||
}
|
||||
card_status_class = "status-{status}"
|
||||
|
||||
card_status_indications = [
|
||||
(
|
||||
"new--dot",
|
||||
_("New"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('new');
|
||||
$('#applyFilter').click();
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"in-progress--dot",
|
||||
_("In progress"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('in_progress');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"completed--dot",
|
||||
_("Completed"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('completed');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"on-hold--dot",
|
||||
_("On Hold"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('on_hold');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"cancelled--dot",
|
||||
_("Completed"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('cancelled');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"expired--dot",
|
||||
_("Expired"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('expired');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
]
|
||||
card_attrs = """
|
||||
{redirect}
|
||||
"""
|
||||
|
||||
|
||||
# def projects_tab(request, pk=None):
|
||||
# """
|
||||
# This method is used to projects tab
|
||||
# """
|
||||
|
||||
# projects = Project.objects.filter(
|
||||
# Q(manager=pk)
|
||||
# | Q(members=pk)
|
||||
# | Q(task__task_members=pk)
|
||||
# | Q(task__task_manager=pk)
|
||||
# )
|
||||
# context = {"projects": projects}
|
||||
# if pk:
|
||||
# template = "cbv/projects/project_tab.html"
|
||||
# employees = Employee.objects.filter(id=pk).distinct()
|
||||
# emoloyee = employees.first()
|
||||
# context["employee"] = emoloyee
|
||||
# return render(
|
||||
# request,
|
||||
# template,
|
||||
# context,
|
||||
# )
|
||||
|
||||
class ProjectsTabView(ListView):
|
||||
model = Project
|
||||
template_name = "cbv/projects/project_tab.html"
|
||||
context_object_name = "projects"
|
||||
|
||||
def get_queryset(self):
|
||||
pk = self.kwargs.get('pk')
|
||||
queryset = Project.objects.filter(
|
||||
Q(manager=pk)
|
||||
| Q(members=pk)
|
||||
| Q(task__task_members=pk)
|
||||
| Q(task__task_manager=pk)
|
||||
)
|
||||
return queryset.distinct()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
pk = self.kwargs.get('pk')
|
||||
if pk:
|
||||
employees = Employee.objects.filter(id=pk).distinct()
|
||||
employee = employees.first()
|
||||
context["employee"] = employee
|
||||
return context
|
||||
|
||||
# Remove the command lines after horilla converted into CBV
|
||||
# from employee.cbv.employee_profile import EmployeeProfileView
|
||||
# EmployeeProfileView.add_tab(
|
||||
# tabs=[
|
||||
# {
|
||||
# "title": "Projects",
|
||||
# # "view": projects_tab,
|
||||
# "view": ProjectsTabView.as_view(),
|
||||
# "accessibility": "employee.cbv.accessibility.workshift_accessibility",
|
||||
# },
|
||||
# ]
|
||||
# )
|
||||
583
project/cbv/tasks.py
Normal file
583
project/cbv/tasks.py
Normal file
@@ -0,0 +1,583 @@
|
||||
"""
|
||||
This page handles the cbv methods for task page
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponse
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from base.methods import get_subordinates
|
||||
from horilla_views.cbv_methods import login_required
|
||||
from horilla_views.generic.cbv.views import (
|
||||
HorillaCardView,
|
||||
HorillaDetailedView,
|
||||
HorillaFormView,
|
||||
HorillaListView,
|
||||
TemplateView,
|
||||
HorillaNavView,
|
||||
)
|
||||
from project.cbv.project_stage import StageDynamicCreateForm
|
||||
from project.cbv.projects import DynamicProjectCreationFormView
|
||||
from project.filters import TaskAllFilter
|
||||
from project.forms import TaskAllForm
|
||||
from project.methods import you_dont_have_permission
|
||||
from project.models import Project, ProjectStage, Task
|
||||
from project.templatetags.taskfilters import task_crud_perm
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class TasksTemplateView(TemplateView):
|
||||
"""
|
||||
view page of the task page
|
||||
"""
|
||||
|
||||
template_name = "cbv/tasks/task_template_view.html"
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class TaskListView(HorillaListView):
|
||||
"""
|
||||
list view of the page
|
||||
"""
|
||||
|
||||
model = Task
|
||||
filter_class = TaskAllFilter
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.view_id = "task-list-container"
|
||||
self.search_url = reverse("tasks-list-view")
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
if not self.request.user.has_perm("project.view_task"):
|
||||
employee_id = self.request.user.employee_get
|
||||
subordinates = get_subordinates(self.request)
|
||||
subordinate_ids = [subordinate.id for subordinate in subordinates]
|
||||
project = queryset.filter(
|
||||
Q(project__managers=employee_id)
|
||||
| Q(project__members=employee_id)
|
||||
| Q(project__managers__in=subordinate_ids)
|
||||
| Q(project__members__in=subordinate_ids)
|
||||
)
|
||||
queryset = (
|
||||
queryset.filter(
|
||||
Q(task_members=employee_id)
|
||||
| Q(task_managers=employee_id)
|
||||
| Q(task_members__in=subordinate_ids)
|
||||
| Q(task_managers__in=subordinate_ids)
|
||||
)
|
||||
| project
|
||||
)
|
||||
return queryset.distinct()
|
||||
|
||||
columns = [
|
||||
(_("Task"), "title"),
|
||||
(_("Project"), "project"),
|
||||
(_("Stage"), "stage"),
|
||||
(_("Mangers"), "get_managers"),
|
||||
(_("Members"), "get_members"),
|
||||
(_("End Date"), "end_date"),
|
||||
(_("Status"), "status_column"),
|
||||
(_("Description"), "description"),
|
||||
]
|
||||
|
||||
sortby_mapping = [
|
||||
("Task", "title"),
|
||||
("Project", "project__title"),
|
||||
("Stage", "stage"),
|
||||
("End Date", "end_date"),
|
||||
("Status", "status"),
|
||||
]
|
||||
|
||||
action_method = "actions"
|
||||
|
||||
row_status_indications = [
|
||||
(
|
||||
"todo--dot",
|
||||
_("To Do"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('to_do');
|
||||
$('#applyFilter').click();
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"in-progress--dot",
|
||||
_("In progress"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('in_progress');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"completed--dot",
|
||||
_("Completed"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('completed');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"expired--dot",
|
||||
_("Expired"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('expired');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
]
|
||||
|
||||
row_status_class = "status-{status}"
|
||||
|
||||
row_attrs = """
|
||||
hx-get='{task_detail_view}?instance_ids={ordered_ids}'
|
||||
hx-target="#genericModalBody"
|
||||
data-target="#genericModal"
|
||||
data-toggle="oh-modal-toggle"
|
||||
"""
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class TasksNavBar(HorillaNavView):
|
||||
"""
|
||||
navbar of teh page
|
||||
"""
|
||||
|
||||
nav_title = _("Tasks")
|
||||
filter_instance = TaskAllFilter()
|
||||
filter_form_context_name = "form"
|
||||
filter_body_template = "cbv/tasks/task_filter.html"
|
||||
search_swap_target = "#listContainer"
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
employee = self.request.user.employee_get
|
||||
projects = Project.objects.all()
|
||||
managers = [
|
||||
manager for project in projects for manager in project.managers.all()
|
||||
]
|
||||
members = [member for project in projects for member in project.members.all()]
|
||||
self.search_url = reverse("tasks-list-view")
|
||||
if employee in managers + members or self.request.user.has_perm(
|
||||
"project.add_task"
|
||||
):
|
||||
self.create_attrs = f"""
|
||||
onclick = "event.stopPropagation();"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal"
|
||||
hx-target="#genericModalBody"
|
||||
hx-get="{reverse('create-task-all')}"
|
||||
"""
|
||||
|
||||
self.view_types = [
|
||||
{
|
||||
"type": "list",
|
||||
"icon": "list-outline",
|
||||
"url": reverse("tasks-list-view"),
|
||||
"attrs": """
|
||||
title ='List'
|
||||
""",
|
||||
},
|
||||
{
|
||||
"type": "card",
|
||||
"icon": "grid-outline",
|
||||
"url": reverse("tasks-card-view"),
|
||||
"attrs": """
|
||||
title ='Card'
|
||||
""",
|
||||
},
|
||||
]
|
||||
|
||||
if self.request.user.has_perm("project.view_task"):
|
||||
self.actions = [
|
||||
{
|
||||
"action": _("Archive"),
|
||||
"attrs": """
|
||||
id="archiveTask",
|
||||
style="cursor: pointer;"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": _("Un-Archive"),
|
||||
"attrs": """
|
||||
id="unArchiveTask",
|
||||
style="cursor: pointer;"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": _("Delete"),
|
||||
"attrs": """
|
||||
class="oh-dropdown__link--danger"
|
||||
data-action = "delete"
|
||||
id="deleteTask"
|
||||
style="cursor: pointer; color:red !important"
|
||||
|
||||
""",
|
||||
},
|
||||
]
|
||||
|
||||
group_by_fields = [
|
||||
("project", _("Project")),
|
||||
("stage", _("Stage")),
|
||||
("status", _("Status")),
|
||||
]
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class TaskCreateForm(HorillaFormView):
|
||||
"""
|
||||
Form view for create and update tasks
|
||||
"""
|
||||
|
||||
form_class = TaskAllForm
|
||||
model = Task
|
||||
template_name = "cbv/tasks/task_form.html"
|
||||
new_display_title = _("Create Task")
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
if self.request.user.has_perm("project.view_task"):
|
||||
self.dynamic_create_fields = [
|
||||
("project", DynamicProjectCreationFormView),
|
||||
("stage", StageDynamicCreateForm, ["project"]),
|
||||
]
|
||||
|
||||
def get(self, request, *args, pk=None, **kwargs):
|
||||
project_id = self.kwargs.get("project_id")
|
||||
stage_id = self.kwargs.get("stage_id")
|
||||
task_id = self.kwargs.get("pk")
|
||||
try:
|
||||
if project_id:
|
||||
project = Project.objects.filter(id=project_id).first()
|
||||
elif stage_id:
|
||||
project = ProjectStage.objects.filter(id=stage_id).first().project
|
||||
elif task_id:
|
||||
task = Task.objects.filter(id=task_id).first()
|
||||
project = task.project
|
||||
elif not task_id:
|
||||
return super().get(request, *args, pk=pk, **kwargs)
|
||||
if (
|
||||
request.user.employee_get in project.managers.all()
|
||||
or request.user.is_superuser
|
||||
):
|
||||
self.dynamic_create_fields = [
|
||||
("project", DynamicProjectCreationFormView),
|
||||
("stage", StageDynamicCreateForm),
|
||||
]
|
||||
return super().get(request, *args, pk=pk, **kwargs)
|
||||
elif task_id:
|
||||
if request.user.employee_get in task.task_managers.all():
|
||||
return super().get(request, *args, pk=pk, **kwargs)
|
||||
|
||||
else:
|
||||
return you_dont_have_permission(request)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
messages.error(request, _("Something went wrong!"))
|
||||
return HttpResponse("<script>window.location.reload()</script>")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
project_id = self.kwargs.get("project_id")
|
||||
stage_id = self.kwargs.get("stage_id")
|
||||
task_id = self.kwargs.get("pk")
|
||||
|
||||
dynamic_project_id = self.request.GET.get("dynamic_project")
|
||||
|
||||
if dynamic_project_id and dynamic_project_id != "dynamic_create":
|
||||
stages = ProjectStage.objects.filter(project=dynamic_project_id)
|
||||
attrs = self.form.fields["stage"].widget.attrs
|
||||
attrs["style"] = "width:100% !important;"
|
||||
self.form.fields["stage"].choices = (
|
||||
[("", _("Select Stage"))]
|
||||
+ [(stage.pk, stage) for stage in stages]
|
||||
+ [("dynamic_create", _("Dynamic Create"))]
|
||||
)
|
||||
|
||||
if task_id and not dynamic_project_id:
|
||||
task = self.form.instance
|
||||
stages = task.project.project_stages.all()
|
||||
self.form.fields["stage"].choices = (
|
||||
[("", _("Select Stage"))]
|
||||
+ [(stage.pk, stage) for stage in stages]
|
||||
+ [("dynamic_create", _("Dynamic Create"))]
|
||||
)
|
||||
|
||||
if stage_id:
|
||||
stage = ProjectStage.objects.filter(id=stage_id).first()
|
||||
project = stage.project
|
||||
self.form.fields["stage"].initial = stage
|
||||
self.form.fields["stage"].choices = [(stage.id, stage.title)]
|
||||
self.form.fields["project"].initial = project
|
||||
self.form.fields["project"].choices = [(project.id, project.title)]
|
||||
elif project_id:
|
||||
project = Project.objects.get(id=project_id)
|
||||
self.form.fields["project"].initial = project
|
||||
self.form.fields["project"].choices = [(project.id, project.title)]
|
||||
stages = ProjectStage.objects.filter(project=project)
|
||||
self.form.fields["stage"].choices = [
|
||||
(stage.id, stage.title) for stage in stages
|
||||
]
|
||||
elif self.form.instance.pk:
|
||||
self.form_class.verbose_name = _("Update Task")
|
||||
if self.request.GET.get("project_task"):
|
||||
self.form.fields["project"].widget = forms.HiddenInput()
|
||||
self.form.fields["stage"].widget = forms.HiddenInput()
|
||||
else:
|
||||
if self.request.user.is_superuser:
|
||||
self.dynamic_create_fields = [
|
||||
("project", DynamicProjectCreationFormView),
|
||||
("stage", StageDynamicCreateForm, ["project"]),
|
||||
]
|
||||
|
||||
if project_id or stage_id:
|
||||
if (
|
||||
self.request.user.employee_get in project.managers.all()
|
||||
or self.request.user.is_superuser
|
||||
):
|
||||
|
||||
self.form.fields["project"].choices.append(
|
||||
("dynamic_create", "Dynamic create")
|
||||
)
|
||||
self.form.fields["stage"].choices.append(
|
||||
("dynamic_create", "Dynamic create")
|
||||
)
|
||||
|
||||
return context
|
||||
|
||||
def form_valid(self, form: TaskAllForm) -> HttpResponse:
|
||||
stage_id = self.kwargs.get("stage_id")
|
||||
if form.is_valid():
|
||||
if form.instance.pk:
|
||||
message = _(f"{self.form.instance} Updated")
|
||||
else:
|
||||
message = _("New Task created")
|
||||
form.save()
|
||||
messages.success(self.request, _(message))
|
||||
if stage_id or self.request.GET.get("project_task"):
|
||||
return HttpResponse("<script>location.reload();</script>")
|
||||
return self.HttpResponse("<script>$('#taskFilterButton').click();</script>")
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class DynamicTaskCreateFormView(TaskCreateForm):
|
||||
|
||||
is_dynamic_create_view = True
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
if self.request.GET:
|
||||
project_id = self.request.GET.get("project_id")
|
||||
if project_id:
|
||||
project = Project.objects.get(id=project_id)
|
||||
stages = ProjectStage.objects.filter(project__id=project_id)
|
||||
self.form.fields["project"].initial = project
|
||||
self.form.fields["project"].choices = [(project.id, project.title)]
|
||||
self.form.fields["stage"].queryset = stages
|
||||
# self.form.fields["project"].widget = forms.HiddenInput()
|
||||
return context
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class TaskDetailView(HorillaDetailedView):
|
||||
"""
|
||||
detail view of the task page
|
||||
"""
|
||||
|
||||
model = Task
|
||||
title = _("Task Details")
|
||||
|
||||
header = {"title": "title", "subtitle": "project", "avatar": "get_avatar"}
|
||||
|
||||
body = [
|
||||
(_("Task"), "title"),
|
||||
(_("Project"), "project"),
|
||||
(_("Stage"), "stage"),
|
||||
(_("Task Mangers"), "get_managers"),
|
||||
(_("Task Members"), "get_members"),
|
||||
(_("Status"), "status_column"),
|
||||
(_("End Date"), "end_date"),
|
||||
(_("Description"), "description"),
|
||||
(_("Document"), "document_col", True),
|
||||
]
|
||||
|
||||
action_method = "detail_view_actions"
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class TaskCardView(HorillaCardView):
|
||||
"""
|
||||
card view of the page
|
||||
"""
|
||||
|
||||
model = Task
|
||||
filter_class = TaskAllFilter
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.view_id = "task-card"
|
||||
self.search_url = reverse("tasks-card-view")
|
||||
self.actions = [
|
||||
{
|
||||
"action": _("Edit"),
|
||||
"accessibility": "project.cbv.accessibility.task_crud_accessibility",
|
||||
"attrs": """
|
||||
data-toggle = "oh-modal-toggle"
|
||||
data-target = "#genericModal"
|
||||
hx-target="#genericModalBody"
|
||||
hx-get ='{get_update_url}'
|
||||
class="oh-dropdown__link"
|
||||
style="cursor: pointer;"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": _("archive_status"),
|
||||
"accessibility": "project.cbv.accessibility.task_crud_accessibility",
|
||||
"attrs": """
|
||||
href="{get_archive_url}"
|
||||
onclick="return confirm('Do you want to {archive_status} this task?')"
|
||||
class="oh-dropdown__link"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": _("Delete"),
|
||||
"accessibility": "project.cbv.accessibility.task_crud_accessibility",
|
||||
"attrs": """
|
||||
onclick="
|
||||
event.stopPropagation()
|
||||
deleteItem({get_delete_url});
|
||||
"
|
||||
class="oh-dropdown__link oh-dropdown__link--danger"
|
||||
""",
|
||||
},
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
if not self.request.user.has_perm("project.view_task"):
|
||||
employee_id = self.request.user.employee_get
|
||||
subordinates = get_subordinates(self.request)
|
||||
subordinate_ids = [subordinate.id for subordinate in subordinates]
|
||||
project = queryset.filter(
|
||||
Q(project__managers=employee_id)
|
||||
| Q(project__members=employee_id)
|
||||
| Q(project__managers__in=subordinate_ids)
|
||||
| Q(project__members__in=subordinate_ids)
|
||||
)
|
||||
queryset = (
|
||||
queryset.filter(
|
||||
Q(task_members=employee_id)
|
||||
| Q(task_managers=employee_id)
|
||||
| Q(task_members__in=subordinate_ids)
|
||||
| Q(task_managers__in=subordinate_ids)
|
||||
)
|
||||
| project
|
||||
)
|
||||
return queryset.distinct()
|
||||
|
||||
details = {
|
||||
"image_src": "get_avatar",
|
||||
"title": "{title}",
|
||||
"subtitle": "Project Name : {if_project} <br> Stage Name : {stage}<br> End Date : {end_date}",
|
||||
}
|
||||
|
||||
card_attrs = """
|
||||
hx-get='{task_detail_view}?instance_ids={ordered_ids}'
|
||||
hx-target="#genericModalBody"
|
||||
data-target="#genericModal"
|
||||
data-toggle="oh-modal-toggle"
|
||||
"""
|
||||
|
||||
card_status_indications = [
|
||||
(
|
||||
"todo--dot",
|
||||
_("To Do"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('to_do');
|
||||
$('#applyFilter').click();
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"in-progress--dot",
|
||||
_("In progress"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('in_progress');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"completed--dot",
|
||||
_("Completed"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('completed');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"expired--dot",
|
||||
_("Expired"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('expired');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
]
|
||||
|
||||
card_status_class = "status-{status}"
|
||||
|
||||
|
||||
class TasksInIndividualView(TaskListView):
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
employee_id = self.request.GET.get("employee_id")
|
||||
self.row_attrs = f"""
|
||||
hx-get='{{task_detail_view}}?instance_ids={{ordered_ids}}&employee_id={employee_id}'
|
||||
hx-target="#genericModalBody"
|
||||
data-target="#genericModal"
|
||||
data-toggle="oh-modal-toggle"
|
||||
"""
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = HorillaListView.get_queryset(self)
|
||||
employee_id = self.request.GET.get("employee_id")
|
||||
project_id = self.request.GET.get("project_id")
|
||||
queryset = queryset.filter(
|
||||
Q(task_members=employee_id) | Q(task_manager=employee_id)
|
||||
)
|
||||
queryset = queryset.filter(project=project_id)
|
||||
return queryset
|
||||
|
||||
row_status_indications = None
|
||||
bulk_select_option = None
|
||||
action_method = None
|
||||
482
project/cbv/timesheet.py
Normal file
482
project/cbv/timesheet.py
Normal file
@@ -0,0 +1,482 @@
|
||||
"""
|
||||
CBV of time sheet page
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
from django import forms
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.urls import resolve, reverse
|
||||
from django.contrib import messages
|
||||
from employee.models import Employee
|
||||
from horilla_views.cbv_methods import login_required
|
||||
from horilla_views.generic.cbv.views import (
|
||||
HorillaCardView,
|
||||
HorillaDetailedView,
|
||||
HorillaFormView,
|
||||
HorillaListView,
|
||||
HorillaNavView,
|
||||
TemplateView,
|
||||
)
|
||||
from project.cbv.cbv_decorators import is_projectmanager_or_member_or_perms
|
||||
from project.cbv.projects import DynamicProjectCreationFormView
|
||||
from project.cbv.tasks import DynamicTaskCreateFormView
|
||||
from project.filters import TimeSheetFilter
|
||||
from project.forms import TimeSheetForm
|
||||
from project.models import Project, Task, TimeSheet
|
||||
from django.db.models import Q
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
is_projectmanager_or_member_or_perms("project.view_timesheet"), name="dispatch"
|
||||
)
|
||||
class TimeSheetView(TemplateView):
|
||||
"""
|
||||
for time sheet page
|
||||
"""
|
||||
|
||||
template_name = "cbv/timesheet/timesheet.html"
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
is_projectmanager_or_member_or_perms("project.view_timesheet"), name="dispatch"
|
||||
)
|
||||
class TimeSheetNavView(HorillaNavView):
|
||||
"""
|
||||
Nav bar
|
||||
"""
|
||||
template_name = "cbv/timesheet/timesheet_nav.html"
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.search_url = reverse("time-sheet-list")
|
||||
url = f"{reverse('personal-time-sheet-view',kwargs={'emp_id': self.request.user.employee_get.id})}"
|
||||
self.actions = [
|
||||
{
|
||||
"action": _("Delete"),
|
||||
"attrs": """
|
||||
class="oh-dropdown__link--danger"
|
||||
data-action ="delete"
|
||||
onclick="deleteTimeSheet();"
|
||||
style="cursor: pointer; color:red !important"
|
||||
""",
|
||||
},
|
||||
]
|
||||
self.view_types = [
|
||||
{
|
||||
"type": "list",
|
||||
"icon": "list-outline",
|
||||
"url": reverse("time-sheet-list"),
|
||||
"attrs": """
|
||||
title ='List'
|
||||
""",
|
||||
},
|
||||
{
|
||||
"type": "card",
|
||||
"icon": "grid-outline",
|
||||
"url": reverse("time-sheet-card"),
|
||||
"attrs": """
|
||||
title ='Card'
|
||||
""",
|
||||
},
|
||||
{
|
||||
"type": "graph",
|
||||
"icon": "bar-chart",
|
||||
"attrs": f"""
|
||||
onclick="event.stopPropagation();
|
||||
window.location.href='{url}'"
|
||||
title ='Graph'
|
||||
""",
|
||||
},
|
||||
]
|
||||
|
||||
self.create_attrs = f"""
|
||||
onclick = "event.stopPropagation();"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal"
|
||||
hx-target="#genericModalBody"
|
||||
hx-get="{reverse('create-time-sheet')}"
|
||||
"""
|
||||
group_by_fields = [
|
||||
("employee_id", _("Employee")),
|
||||
("project_id", _("Project")),
|
||||
("date", _("Date")),
|
||||
("status", _("Status")),
|
||||
("employee_id__employee_work_info__reporting_manager_id", _("Reporting Manager")),
|
||||
("employee_id__employee_work_info__department_id", _("Department")),
|
||||
("employee_id__employee_work_info__job_position_id", _("Job Position")),
|
||||
("employee_id__employee_work_info__employee_type_id", _("Employement Type")),
|
||||
("employee_id__employee_work_info__company_id", _("Company")),
|
||||
]
|
||||
|
||||
nav_title = _("Time Sheet")
|
||||
filter_instance = TimeSheetFilter()
|
||||
filter_form_context_name = "form"
|
||||
filter_body_template = "cbv/timesheet/filter.html"
|
||||
search_swap_target = "#listContainer"
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
is_projectmanager_or_member_or_perms("project.view_timesheet"), name="dispatch"
|
||||
)
|
||||
class TimeSheetList(HorillaListView):
|
||||
"""
|
||||
Time sheet list view
|
||||
"""
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
if not self.request.user.has_perm("project.view_timesheet"):
|
||||
employee = self.request.user.employee_get
|
||||
queryset = queryset.filter(
|
||||
Q(task_id__task_managers=employee)
|
||||
| Q(project_id__managers=employee)
|
||||
| Q(employee_id=employee)
|
||||
).distinct()
|
||||
return queryset
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.search_url = reverse("time-sheet-list")
|
||||
self.action_method = "actions"
|
||||
|
||||
model = TimeSheet
|
||||
filter_class = TimeSheetFilter
|
||||
|
||||
columns = [
|
||||
(_("Employee"), "employee_id", "employee_id__get_avatar"),
|
||||
(_("Project"), "project_id"),
|
||||
(_("Task"), "task_id"),
|
||||
(_("Date"), "date"),
|
||||
(_("Time Spent"), "time_spent"),
|
||||
(_("Status"), "status_column"),
|
||||
(_("Description"), "description"),
|
||||
]
|
||||
|
||||
sortby_mapping = [
|
||||
("Employee", "employee_id__employee_first_name", "employee_id__get_avatar"),
|
||||
("Project", "project_id__title"),
|
||||
("Task", "task_id__title"),
|
||||
("Time Spent", "time_spent"),
|
||||
("Date", "date"),
|
||||
]
|
||||
|
||||
row_status_indications = [
|
||||
(
|
||||
"in-progress--dot",
|
||||
_("In progress"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('in_Progress');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"completed--dot",
|
||||
_("Completed"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('completed');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
]
|
||||
row_attrs = """
|
||||
hx-get='{detail_view}?instance_ids={ordered_ids}'
|
||||
hx-target="#genericModalBody"
|
||||
data-target="#genericModal"
|
||||
data-toggle="oh-modal-toggle"
|
||||
"""
|
||||
|
||||
row_status_class = "status-{status}"
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
is_projectmanager_or_member_or_perms("project.view_timesheet"), name="dispatch"
|
||||
)
|
||||
class TaskTimeSheet(TimeSheetList):
|
||||
|
||||
row_attrs = ""
|
||||
row_status_indications = False
|
||||
bulk_select_option = False
|
||||
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.view_id = "task-timesheet-container"
|
||||
task_id = resolve(self.request.path_info).kwargs.get("task_id")
|
||||
self.request.task_id = task_id
|
||||
employee_id = self.request.GET.get("employee_id")
|
||||
if employee_id:
|
||||
self.action_method = "actions"
|
||||
|
||||
def get_context_data(self, **kwargs: Any):
|
||||
context = super().get_context_data(**kwargs)
|
||||
task_id = self.kwargs.get("task_id")
|
||||
task = Task.objects.get(id=task_id)
|
||||
project = task.project
|
||||
context["task_id"] = task_id
|
||||
context["project"] = project
|
||||
context["task"] = task
|
||||
return context
|
||||
|
||||
template_name = "cbv/timesheet/task_timesheet.html"
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = HorillaListView.get_queryset(self)
|
||||
task_id = self.kwargs.get("task_id")
|
||||
task = Task.objects.filter( id = task_id).first()
|
||||
queryset = TimeSheet.objects.filter(task_id=task_id)
|
||||
queryset = queryset.filter(task_id=task_id)
|
||||
employee_id = self.request.GET.get("employee_id")
|
||||
if employee_id:
|
||||
employee = Employee.objects.filter(id = employee_id ).first()
|
||||
if (not employee in task.task_managers.all()
|
||||
and not employee in task.project.managers.all()
|
||||
and not employee.employee_user_id.is_superuser
|
||||
):
|
||||
queryset = queryset.filter(employee_id=employee_id)
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
is_projectmanager_or_member_or_perms("project.view_timesheet"), name="dispatch"
|
||||
)
|
||||
class TimeSheetFormView(HorillaFormView):
|
||||
"""
|
||||
form view for create project
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.dynamic_create_fields = [
|
||||
("task_id", DynamicTaskCreateFormView),
|
||||
]
|
||||
if self.request.user.has_perm("project.add_project"):
|
||||
self.dynamic_create_fields.append(
|
||||
("project_id", DynamicProjectCreationFormView)
|
||||
)
|
||||
|
||||
form_class = TimeSheetForm
|
||||
model = TimeSheet
|
||||
new_display_title = _("Create Time Sheet")
|
||||
# template_name = "cbv/timesheet/form.html"
|
||||
|
||||
def get_initial(self) -> dict:
|
||||
initial = super().get_initial()
|
||||
task_id = self.kwargs.get("task_id")
|
||||
if task_id:
|
||||
task = Task.objects.get(id=task_id)
|
||||
project_id = task.project
|
||||
initial["project_id"] = project_id.id
|
||||
initial["task_id"] = task.id
|
||||
return initial
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
task_id = self.kwargs.get("task_id")
|
||||
user_employee_id = self.request.user.employee_get.id
|
||||
project = None
|
||||
if task_id:
|
||||
task = Task.objects.get(id=task_id)
|
||||
project = task.project
|
||||
employee = Employee.objects.filter(id=user_employee_id)
|
||||
|
||||
if self.form.instance.pk:
|
||||
task_id = self.form.instance.task_id.id
|
||||
project = self.form.instance.project_id
|
||||
tasks = Task.objects.filter(project=project)
|
||||
employee = Employee.objects.filter(id=user_employee_id)
|
||||
task = Task.objects.get(id=task_id)
|
||||
self.form.fields["task_id"].queryset = tasks
|
||||
self.form.fields["task_id"].choices = [
|
||||
(item.id, item.title) for item in tasks
|
||||
]
|
||||
self.form.fields["task_id"].choices.append(
|
||||
("dynamic_create", "Dynamic create")
|
||||
)
|
||||
task_id = self.request.GET.get("task_id")
|
||||
self.form_class.verbose_name = _("Update Time Sheet")
|
||||
# If the timesheet create from task or project
|
||||
if project:
|
||||
if (self.request.user.is_superuser or
|
||||
self.request.user.has_perm("project.add_project")
|
||||
):
|
||||
members = ( project.managers.all() |
|
||||
project.members.all() |
|
||||
task.task_members.all() |task.task_managers.all()
|
||||
).distinct()
|
||||
elif ( employee.first() in project.managers.all()):
|
||||
members = (
|
||||
employee | project.members.all() |
|
||||
task.task_members.all() | task.task_managers.all()
|
||||
).distinct()
|
||||
elif( employee.first() in task.task_managers.all() ):
|
||||
members = ( employee | task.task_members.all()).distinct()
|
||||
else :
|
||||
members = employee
|
||||
if task_id:
|
||||
self.form.fields["project_id"].widget = forms.HiddenInput()
|
||||
self.form.fields["task_id"].widget = forms.HiddenInput()
|
||||
self.form.fields["employee_id"].queryset = members
|
||||
|
||||
# If the timesheet create directly
|
||||
else :
|
||||
employee = self.request.user.employee_get
|
||||
if self.request.user.has_perm('project.add_timesheet'):
|
||||
projects = Project.objects.all()
|
||||
else:
|
||||
projects = (
|
||||
Project.objects.filter(managers = employee) |
|
||||
Project.objects.filter(members = employee) |
|
||||
Project.objects.filter(
|
||||
id__in=Task.objects.filter(task_managers=employee).values_list('project', flat=True)
|
||||
) |
|
||||
Project.objects.filter(
|
||||
id__in=Task.objects.filter(task_members=employee).values_list('project', flat=True)
|
||||
)
|
||||
).distinct()
|
||||
self.form.fields['project_id'].queryset = projects
|
||||
return context
|
||||
|
||||
def form_valid(self, form: TimeSheetForm) -> HttpResponse:
|
||||
if form.is_valid():
|
||||
if form.instance.pk:
|
||||
message = _(f"{self.form.instance} Updated")
|
||||
else:
|
||||
message = _("New time sheet created")
|
||||
form.save()
|
||||
messages.success(self.request, _(message))
|
||||
return self.HttpResponse()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
is_projectmanager_or_member_or_perms("project.view_timesheet"), name="dispatch"
|
||||
)
|
||||
class TimeSheetCardView(HorillaCardView):
|
||||
"""
|
||||
For card view
|
||||
"""
|
||||
|
||||
model = TimeSheet
|
||||
filter_class = TimeSheetFilter
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
if not self.request.user.has_perm("project.view_timesheet"):
|
||||
employee = self.request.user.employee_get
|
||||
queryset = queryset.filter(
|
||||
Q(task_id__task_managers=employee)
|
||||
| Q(project_id__managers=employee)
|
||||
| Q(employee_id=employee)
|
||||
).distinct()
|
||||
return queryset
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.search_url = reverse("time-sheet-card")
|
||||
self.actions = [
|
||||
{
|
||||
"action": "Edit",
|
||||
"attrs": """
|
||||
hx-get='{get_update_url}'
|
||||
hx-target='#genericModalBody'
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal"
|
||||
class="oh-dropdown__link"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": "Delete",
|
||||
"attrs": """
|
||||
onclick="
|
||||
event.stopPropagation()
|
||||
deleteItem({get_delete_url});
|
||||
"
|
||||
class="oh-dropdown__link oh-dropdown__link--danger"
|
||||
""",
|
||||
},
|
||||
]
|
||||
|
||||
details = {
|
||||
"image_src": "employee_id__get_avatar",
|
||||
"title": "{employee_id}",
|
||||
"subtitle": " <b>{date}</b> <br>Project : <b>{project_id}</b> <br><b>{task_id}</b> | Time Spent : <b>{time_spent}</b>",
|
||||
}
|
||||
|
||||
card_status_class = "status-{status}"
|
||||
|
||||
card_status_indications = [
|
||||
(
|
||||
"in-progress--dot",
|
||||
_("In progress"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('in_Progress');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
(
|
||||
"completed--dot",
|
||||
_("Completed"),
|
||||
"""
|
||||
onclick="
|
||||
$('#applyFilter').closest('form').find('[name=status]').val('completed');
|
||||
$('#applyFilter').click();
|
||||
|
||||
"
|
||||
""",
|
||||
),
|
||||
]
|
||||
|
||||
card_attrs = """
|
||||
hx-get='{detail_view}?instance_ids={ordered_ids}'
|
||||
hx-target="#genericModalBody"
|
||||
data-target="#genericModal"
|
||||
data-toggle="oh-modal-toggle"
|
||||
"""
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
is_projectmanager_or_member_or_perms("project.view_timesheet"), name="dispatch"
|
||||
)
|
||||
class TimeSheetDetailView(HorillaDetailedView):
|
||||
"""
|
||||
detail view of the page
|
||||
"""
|
||||
|
||||
model = TimeSheet
|
||||
title = _("Details")
|
||||
header = {
|
||||
"title": "employee_id",
|
||||
"subtitle": "project_id",
|
||||
"avatar": "employee_id__get_avatar",
|
||||
}
|
||||
|
||||
body = [
|
||||
(_("Task"), "task_id"),
|
||||
(_("Date"), "date"),
|
||||
(_("Time Spent"), "time_spent"),
|
||||
(_("Status"), "status_column"),
|
||||
(_("Description"), "description"),
|
||||
]
|
||||
|
||||
action_method = "detail_actions"
|
||||
@@ -1,138 +1,189 @@
|
||||
from project.methods import any_project_manager, any_project_member, any_task_manager, any_task_member
|
||||
from .models import Project,Task,ProjectStage
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
|
||||
decorator_with_arguments = lambda decorator: lambda *args, **kwargs: lambda func: decorator(func, *args, **kwargs)
|
||||
|
||||
@decorator_with_arguments
|
||||
def is_projectmanager_or_member_or_perms(function,perm):
|
||||
def _function(request, *args, **kwargs):
|
||||
"""
|
||||
This method is used to check the employee is project manager or not
|
||||
"""
|
||||
user = request.user
|
||||
if (
|
||||
user.has_perm(perm) or
|
||||
any_project_manager(user) or
|
||||
any_project_member(user) or
|
||||
any_task_manager(user) or
|
||||
any_task_member(user)
|
||||
):
|
||||
return function(request, *args, **kwargs)
|
||||
messages.info(request, "You don't have permission.")
|
||||
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
|
||||
return _function
|
||||
|
||||
@decorator_with_arguments
|
||||
def project_update_permission(function=None, *args, **kwargs):
|
||||
def check_project_member(request,project_id=None,*args, **kwargs,):
|
||||
"""
|
||||
This method is used to check the employee is project member or not
|
||||
"""
|
||||
project = Project.objects.get(id = project_id)
|
||||
if (request.user.has_perm('project.change_project') or
|
||||
request.user.employee_get == project.manager or
|
||||
request.user.employee_get in project.members.all()
|
||||
):
|
||||
return function(request, *args,project_id=project_id, **kwargs)
|
||||
messages.info(request,'You dont have permission.')
|
||||
return HttpResponseRedirect(request. META. get('HTTP_REFERER', '/'))
|
||||
# return function(request, *args, **kwargs)
|
||||
return check_project_member
|
||||
|
||||
|
||||
@decorator_with_arguments
|
||||
def project_delete_permission(function=None,*args,**kwargs):
|
||||
def is_project_manager(request,project_id=None,*args, **kwargs,):
|
||||
"""
|
||||
This method is used to check the employee is project manager or not
|
||||
"""
|
||||
project = Project.objects.get(id = project_id)
|
||||
if (
|
||||
request.user.employee_get == project.manager or
|
||||
request.user.has_perm('project.delete_project')
|
||||
):
|
||||
return function(request, *args,project_id=project_id, **kwargs)
|
||||
messages.info(request,'You dont have permission.')
|
||||
return HttpResponseRedirect(request. META. get('HTTP_REFERER', '/'))
|
||||
return is_project_manager
|
||||
|
||||
|
||||
@decorator_with_arguments
|
||||
def project_stage_update_permission(function=None, *args, **kwargs):
|
||||
def check_project_member(request,stage_id=None,*args, **kwargs,):
|
||||
"""
|
||||
This method is used to check the employee is project stage member or not
|
||||
"""
|
||||
project = ProjectStage.objects.get(id = stage_id).project
|
||||
if (request.user.has_perm('project.change_project') or
|
||||
request.user.has_perm('project.change_task') or
|
||||
request.user.employee_get == project.manager or
|
||||
request.user.employee_get in project.members.all()
|
||||
):
|
||||
return function(request, *args,stage_id=stage_id, **kwargs)
|
||||
messages.info(request,'You dont have permission.')
|
||||
return HttpResponseRedirect(request. META. get('HTTP_REFERER', '/'))
|
||||
# return function(request, *args, **kwargs)
|
||||
return check_project_member
|
||||
|
||||
@decorator_with_arguments
|
||||
def project_stage_delete_permission(function=None,*args,**kwargs):
|
||||
def is_project_manager(request,stage_id=None,*args, **kwargs,):
|
||||
"""
|
||||
This method is used to check the employee is project stage manager or not
|
||||
"""
|
||||
project = ProjectStage.objects.get(id = stage_id).project
|
||||
if (
|
||||
request.user.employee_get == project.manager or
|
||||
request.user.has_perm('project.delete_project')
|
||||
):
|
||||
return function(request, *args,stage_id=stage_id, **kwargs)
|
||||
messages.info(request,'You dont have permission.')
|
||||
return HttpResponseRedirect(request. META. get('HTTP_REFERER', '/'))
|
||||
return is_project_manager
|
||||
|
||||
@decorator_with_arguments
|
||||
def task_update_permission(function=None,*args,**kwargs):
|
||||
def is_task_member(request,task_id):
|
||||
"""
|
||||
This method is used to check the employee is task member or not
|
||||
"""
|
||||
task = Task.objects.get(id = task_id)
|
||||
project = task.project
|
||||
|
||||
if (request.user.has_perm('project.change_task') or
|
||||
request.user.has_perm('project.change_project') or
|
||||
request.user.employee_get == task.task_manager or
|
||||
request.user.employee_get in task.task_members.all() or
|
||||
request.user.employee_get == project.manager or
|
||||
request.user.employee_get in project.members.all()
|
||||
):
|
||||
return function(request, *args,task_id=task_id, **kwargs)
|
||||
|
||||
messages.info(request,'You dont have permission.')
|
||||
return HttpResponseRedirect(request. META. get('HTTP_REFERER', '/'))
|
||||
return is_task_member
|
||||
|
||||
|
||||
@decorator_with_arguments
|
||||
def task_delete_permission(function=None,*args,**kwargs):
|
||||
def is_task_manager(request,task_id):
|
||||
"""
|
||||
This method is used to check the employee is task manager or not
|
||||
"""
|
||||
task = Task.objects.get(id = task_id)
|
||||
project = task.project
|
||||
|
||||
if (request.user.has_perm('project.delete_task') or
|
||||
request.user.has_perm('project.delete_project') or
|
||||
request.user.employee_get == task.task_manager or
|
||||
request.user.employee_get == project.manager
|
||||
):
|
||||
return function(request,task_id=task_id)
|
||||
|
||||
messages.info(request,'You dont have permission.')
|
||||
return HttpResponseRedirect(request. META. get('HTTP_REFERER', '/'))
|
||||
return is_task_manager
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponse
|
||||
|
||||
from project.methods import (
|
||||
any_project_manager,
|
||||
any_project_member,
|
||||
any_task_manager,
|
||||
any_task_member,
|
||||
)
|
||||
|
||||
from .models import Project, ProjectStage, Task
|
||||
|
||||
decorator_with_arguments = (
|
||||
lambda decorator: lambda *args, **kwargs: lambda func: decorator(
|
||||
func, *args, **kwargs
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@decorator_with_arguments
|
||||
def is_projectmanager_or_member_or_perms(function, perm):
|
||||
def _function(request, *args, **kwargs):
|
||||
"""
|
||||
This method is used to check the employee is project manager or not
|
||||
"""
|
||||
user = request.user
|
||||
if (
|
||||
user.has_perm(perm)
|
||||
or any_project_manager(user)
|
||||
or any_project_member(user)
|
||||
or any_task_manager(user)
|
||||
or any_task_member(user)
|
||||
):
|
||||
return function(request, *args, **kwargs)
|
||||
messages.info(request, "You don't have permission.")
|
||||
return HttpResponse("<script>window.location.reload()</script>")
|
||||
|
||||
return _function
|
||||
|
||||
|
||||
@decorator_with_arguments
|
||||
def project_update_permission(function=None, *args, **kwargs):
|
||||
def check_project_member(
|
||||
request,
|
||||
project_id=None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
This method is used to check the employee is project member or not
|
||||
"""
|
||||
project = Project.objects.get(id=project_id)
|
||||
employee = request.user.employee_get
|
||||
if (
|
||||
request.user.has_perm("project.change_project")
|
||||
or employee in project.managers.all()
|
||||
or employee in project.members.all()
|
||||
or any(
|
||||
employee in task.task_managers.all() for task in project.task_set.all()
|
||||
)
|
||||
or any(
|
||||
employee in task.task_members.all() for task in project.task_set.all()
|
||||
)
|
||||
):
|
||||
return function(request, *args, project_id=project_id, **kwargs)
|
||||
messages.info(request, "You dont have permission.")
|
||||
return HttpResponse("<script>window.location.reload()</script>")
|
||||
# return function(request, *args, **kwargs)
|
||||
|
||||
return check_project_member
|
||||
|
||||
|
||||
@decorator_with_arguments
|
||||
def project_delete_permission(function=None, *args, **kwargs):
|
||||
def is_project_manager(
|
||||
request,
|
||||
project_id=None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
This method is used to check the employee is project manager or not
|
||||
"""
|
||||
project = Project.objects.get(id=project_id)
|
||||
if (
|
||||
request.user.employee_get in project.managers.all()
|
||||
or request.user.is_superuser
|
||||
):
|
||||
return function(request, *args, project_id=project_id, **kwargs)
|
||||
messages.info(request, "You dont have permission.")
|
||||
return HttpResponse("<script>window.location.reload()</script>")
|
||||
|
||||
return is_project_manager
|
||||
|
||||
|
||||
@decorator_with_arguments
|
||||
def project_stage_update_permission(function=None, *args, **kwargs):
|
||||
def check_project_member(
|
||||
request,
|
||||
stage_id=None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
This method is used to check the employee is project stage member or not
|
||||
"""
|
||||
project = ProjectStage.objects.get(id=stage_id).project
|
||||
if (
|
||||
request.user.has_perm("project.change_project")
|
||||
or request.user.has_perm("project.change_task")
|
||||
or request.user.employee_get in project.managers.all()
|
||||
or request.user.employee_get in project.members.all()
|
||||
):
|
||||
return function(request, *args, stage_id=stage_id, **kwargs)
|
||||
messages.info(request, "You dont have permission.")
|
||||
return HttpResponse("<script>window.location.reload()</script>")
|
||||
# return function(request, *args, **kwargs)
|
||||
|
||||
return check_project_member
|
||||
|
||||
|
||||
@decorator_with_arguments
|
||||
def project_stage_delete_permission(function=None, *args, **kwargs):
|
||||
def is_project_manager(
|
||||
request,
|
||||
stage_id=None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
This method is used to check the employee is project stage manager or not
|
||||
"""
|
||||
project = ProjectStage.objects.get(id=stage_id).project
|
||||
if (
|
||||
request.user.employee_get in project.managers.all()
|
||||
or request.user.is_superuser
|
||||
):
|
||||
return function(request, *args, stage_id=stage_id, **kwargs)
|
||||
messages.info(request, "You dont have permission.")
|
||||
return HttpResponse("<script>window.location.reload()</script>")
|
||||
|
||||
return is_project_manager
|
||||
|
||||
|
||||
@decorator_with_arguments
|
||||
def task_update_permission(function=None, *args, **kwargs):
|
||||
def is_task_member(request, task_id):
|
||||
"""
|
||||
This method is used to check the employee is task member or not
|
||||
"""
|
||||
task = Task.objects.get(id=task_id)
|
||||
project = task.project
|
||||
|
||||
if (
|
||||
request.user.has_perm("project.change_task")
|
||||
or request.user.has_perm("project.change_project")
|
||||
or request.user.employee_get in task.task_managers.all()
|
||||
or request.user.employee_get in task.task_members.all()
|
||||
or request.user.employee_get in project.managers.all()
|
||||
or request.user.employee_get in project.members.all()
|
||||
):
|
||||
return function(request, *args, task_id=task_id, **kwargs)
|
||||
|
||||
messages.info(request, "You dont have permission.")
|
||||
return HttpResponse("<script>window.location.reload()</script>")
|
||||
|
||||
return is_task_member
|
||||
|
||||
|
||||
@decorator_with_arguments
|
||||
def task_delete_permission(function=None, *args, **kwargs):
|
||||
def is_task_manager(request, task_id):
|
||||
"""
|
||||
This method is used to check the employee is task manager or not
|
||||
"""
|
||||
task = Task.objects.get(id=task_id)
|
||||
project = task.project
|
||||
|
||||
if (
|
||||
request.user.is_superuser
|
||||
or request.user.employee_get in task.task_managers.all()
|
||||
or request.user.employee_get in project.managers.all()
|
||||
):
|
||||
return function(request, task_id=task_id)
|
||||
|
||||
messages.info(request, "You dont have permission.")
|
||||
return HttpResponse("<script>window.location.reload()</script>")
|
||||
|
||||
return is_task_manager
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
import django_filters
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
import django_filters
|
||||
from attendance.filters import FilterSet
|
||||
from horilla.filters import filter_by_name
|
||||
from .models import TimeSheet, Project, Task,Employee
|
||||
|
||||
from horilla.filters import FilterSet, HorillaFilterSet, filter_by_name
|
||||
|
||||
from .models import Employee, Project, Task, TimeSheet
|
||||
|
||||
|
||||
class ProjectFilter(FilterSet):
|
||||
class ProjectFilter(HorillaFilterSet):
|
||||
search = django_filters.CharFilter(method="filter_by_project")
|
||||
search_field = django_filters.CharFilter(method="search_in")
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = ["title",
|
||||
"manager",
|
||||
"status",
|
||||
"end_date",
|
||||
"start_date",
|
||||
]
|
||||
|
||||
manager = django_filters.ModelChoiceFilter(
|
||||
field_name = 'manager',queryset=Employee.objects.all()
|
||||
)
|
||||
fields = [
|
||||
"title",
|
||||
"managers",
|
||||
"members",
|
||||
"status",
|
||||
"end_date",
|
||||
"start_date",
|
||||
"is_active",
|
||||
]
|
||||
|
||||
start_from = django_filters.DateFilter(
|
||||
field_name="start_date",
|
||||
lookup_expr="gte",
|
||||
@@ -32,16 +33,18 @@ class ProjectFilter(FilterSet):
|
||||
lookup_expr="lte",
|
||||
widget=forms.DateInput(attrs={"type": "date"}),
|
||||
)
|
||||
|
||||
|
||||
def filter_by_project(self, queryset, _, value):
|
||||
if self.data.get("search_field"):
|
||||
return queryset
|
||||
queryset = queryset.filter(title__icontains=value)
|
||||
return queryset
|
||||
|
||||
|
||||
class TaskFilter(FilterSet):
|
||||
search = django_filters.CharFilter(method="filter_by_task")
|
||||
task_manager = django_filters.ModelChoiceFilter(
|
||||
field_name = 'task_manager',queryset=Employee.objects.all()
|
||||
task_managers = django_filters.ModelChoiceFilter(
|
||||
field_name="task_managers", queryset=Employee.objects.all()
|
||||
)
|
||||
end_till = django_filters.DateFilter(
|
||||
field_name="end_date",
|
||||
@@ -51,23 +54,22 @@ class TaskFilter(FilterSet):
|
||||
|
||||
class Meta:
|
||||
model = Task
|
||||
fields = ["title",
|
||||
"stage",
|
||||
"task_manager",
|
||||
"end_date",
|
||||
"status",
|
||||
"project",
|
||||
]
|
||||
fields = [
|
||||
"title",
|
||||
"stage",
|
||||
"task_managers",
|
||||
"end_date",
|
||||
"status",
|
||||
"project",
|
||||
]
|
||||
|
||||
def filter_by_task(self, queryset, _, value):
|
||||
queryset = queryset.filter(title__icontains=value)
|
||||
return queryset
|
||||
|
||||
class TaskAllFilter(FilterSet):
|
||||
|
||||
|
||||
class TaskAllFilter(HorillaFilterSet):
|
||||
search = django_filters.CharFilter(method="filter_by_task")
|
||||
manager = django_filters.ModelChoiceFilter(
|
||||
field_name = 'task_manager',queryset=Employee.objects.all()
|
||||
)
|
||||
end_till = django_filters.DateFilter(
|
||||
field_name="end_date",
|
||||
lookup_expr="lte",
|
||||
@@ -76,21 +78,22 @@ class TaskAllFilter(FilterSet):
|
||||
|
||||
class Meta:
|
||||
model = Task
|
||||
fields = ["title",
|
||||
'project',
|
||||
"stage",
|
||||
"task_manager",
|
||||
"end_date",
|
||||
"status",
|
||||
]
|
||||
fields = [
|
||||
"title",
|
||||
"project",
|
||||
"stage",
|
||||
"task_managers",
|
||||
"task_members",
|
||||
"end_date",
|
||||
"status",
|
||||
]
|
||||
|
||||
def filter_by_task(self, queryset, _, value):
|
||||
queryset = queryset.filter(title__icontains=value)
|
||||
return queryset
|
||||
|
||||
|
||||
|
||||
class TimeSheetFilter(FilterSet):
|
||||
class TimeSheetFilter(HorillaFilterSet):
|
||||
"""
|
||||
Filter set class for Timesheet model
|
||||
"""
|
||||
@@ -109,9 +112,11 @@ class TimeSheetFilter(FilterSet):
|
||||
widget=forms.DateInput(attrs={"type": "date"}),
|
||||
)
|
||||
|
||||
project = django_filters.ModelChoiceFilter(
|
||||
field_name="project_id", queryset=Project.objects.all()
|
||||
),
|
||||
project = (
|
||||
django_filters.ModelChoiceFilter(
|
||||
field_name="project_id", queryset=Project.objects.all()
|
||||
),
|
||||
)
|
||||
|
||||
task = django_filters.ModelChoiceFilter(
|
||||
field_name="task_id", queryset=Task.objects.all()
|
||||
@@ -131,7 +136,7 @@ class TimeSheetFilter(FilterSet):
|
||||
"date",
|
||||
"status",
|
||||
]
|
||||
|
||||
|
||||
def filter_by_employee(self, queryset, _, value):
|
||||
"""
|
||||
Filter queryset by first name or last name.
|
||||
@@ -159,4 +164,3 @@ class TimeSheetFilter(FilterSet):
|
||||
employee_id__employee_last_name__icontains=last_name
|
||||
)
|
||||
return queryset
|
||||
|
||||
|
||||
203
project/forms.py
203
project/forms.py
@@ -1,14 +1,23 @@
|
||||
from typing import Any
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from .models import *
|
||||
from payroll.forms.forms import ModelForm
|
||||
from django.db.models import Q
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from base.forms import ModelForm
|
||||
from horilla.horilla_middlewares import _thread_locals
|
||||
|
||||
from .models import *
|
||||
|
||||
|
||||
class ProjectForm(ModelForm):
|
||||
"""
|
||||
Form for Project model
|
||||
"""
|
||||
|
||||
cols = {"description": 12}
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Meta class to add the additional info
|
||||
@@ -26,6 +35,7 @@ class ProjectTimeSheetForm(ModelForm):
|
||||
"""
|
||||
Form for Project model in Time sheet form
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ProjectTimeSheetForm, self).__init__(*args, **kwargs)
|
||||
self.fields["status"].widget.attrs.update(
|
||||
@@ -34,12 +44,13 @@ class ProjectTimeSheetForm(ModelForm):
|
||||
"class": "oh-select",
|
||||
}
|
||||
)
|
||||
|
||||
def __init__(self, *args, request=None, **kwargs):
|
||||
super(ProjectTimeSheetForm, self).__init__(*args, **kwargs)
|
||||
self.fields["manager"].widget.attrs.update({"id":"manager_id"})
|
||||
self.fields["status"].widget.attrs.update({"id":"status_id"})
|
||||
self.fields["members"].widget.attrs.update({"id":"members_id"})
|
||||
self.fields['title'].widget.attrs.update({'id':'id_project'})
|
||||
self.fields["managers"].widget.attrs.update({"id": "managers_id"})
|
||||
self.fields["status"].widget.attrs.update({"id": "status_id"})
|
||||
self.fields["members"].widget.attrs.update({"id": "members_id"})
|
||||
self.fields["title"].widget.attrs.update({"id": "id_project"})
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
@@ -54,7 +65,6 @@ class ProjectTimeSheetForm(ModelForm):
|
||||
}
|
||||
|
||||
|
||||
|
||||
class TaskForm(ModelForm):
|
||||
"""
|
||||
Form for Task model
|
||||
@@ -71,16 +81,36 @@ class TaskForm(ModelForm):
|
||||
|
||||
widgets = {
|
||||
"end_date": forms.DateInput(attrs={"type": "date"}),
|
||||
"project":forms.HiddenInput(),
|
||||
"stage":forms.HiddenInput(),
|
||||
"sequence":forms.HiddenInput()
|
||||
}
|
||||
|
||||
"project": forms.HiddenInput(),
|
||||
"stage": forms.HiddenInput(),
|
||||
"sequence": forms.HiddenInput(),
|
||||
}
|
||||
|
||||
|
||||
class QuickTaskForm(ModelForm):
|
||||
class Meta:
|
||||
model = Task
|
||||
fields = ["title", "task_managers", "project", "stage", "end_date"]
|
||||
widgets = {
|
||||
"project": forms.HiddenInput(),
|
||||
"stage": forms.HiddenInput(),
|
||||
"end_date": forms.HiddenInput(),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(QuickTaskForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields["title"].widget.attrs.update(
|
||||
{"class": "oh-input w-100 mb-2", "placeholder": _("Task Title")}
|
||||
)
|
||||
self.fields["task_managers"].required = True
|
||||
|
||||
|
||||
class TaskFormCreate(ModelForm):
|
||||
|
||||
"""
|
||||
Form for Task model in create button inside task view
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Meta class to add the additional info
|
||||
@@ -92,19 +122,19 @@ class TaskFormCreate(ModelForm):
|
||||
|
||||
widgets = {
|
||||
"end_date": forms.DateInput(attrs={"type": "date"}),
|
||||
"project":forms.HiddenInput(),
|
||||
"sequence":forms.HiddenInput(),
|
||||
"project": forms.HiddenInput(),
|
||||
"sequence": forms.HiddenInput(),
|
||||
"stage": forms.SelectMultiple(
|
||||
attrs={
|
||||
"class": "oh-select oh-select-2 select2-hidden-accessible",
|
||||
"onchange": "keyResultChange($(this))",
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def __init__(self, *args, request=None, **kwargs):
|
||||
super(TaskFormCreate, self).__init__(*args, **kwargs)
|
||||
self.fields["stage"].widget.attrs.update({"id":"project_stage"})
|
||||
self.fields["stage"].widget.attrs.update({"id": "project_stage"})
|
||||
|
||||
def as_p(self):
|
||||
"""
|
||||
@@ -113,33 +143,71 @@ class TaskFormCreate(ModelForm):
|
||||
context = {"form": self}
|
||||
table_html = render_to_string("common_form.html", context)
|
||||
return table_html
|
||||
|
||||
|
||||
|
||||
class TaskAllForm(ModelForm):
|
||||
"""
|
||||
Form for Task model in task all view
|
||||
"""
|
||||
|
||||
cols = {
|
||||
"description": 12,
|
||||
}
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Meta class to add the additional info
|
||||
"""
|
||||
|
||||
model = Task
|
||||
fields = "__all__"
|
||||
|
||||
widgets= {
|
||||
widgets = {
|
||||
"start_date": forms.DateInput(attrs={"type": "date"}),
|
||||
"end_date": forms.DateInput(attrs={"type": "date"}),
|
||||
"sequence":forms.HiddenInput()
|
||||
"sequence": forms.HiddenInput(),
|
||||
}
|
||||
|
||||
def __init__(self, *args, request=None, **kwargs):
|
||||
super(TaskAllForm, self).__init__(*args, **kwargs)
|
||||
self.fields["project"].choices = list(self.fields["project"].choices)
|
||||
self.fields["project"].choices.append(
|
||||
("create_new_project", "Create a new project")
|
||||
)
|
||||
# Remove the select2 class from the "project" field
|
||||
# self.fields["project"].widget.attrs.pop("class", None)
|
||||
# self.fields["stage"].widget.attrs.pop("class", None)
|
||||
self.fields["stage"].widget.attrs.update({"id":"project_stage"})
|
||||
request = getattr(_thread_locals, "request")
|
||||
|
||||
self.fields["stage"].widget.attrs.update({"id": "project_stage"})
|
||||
self.fields["project"].widget.attrs.update(
|
||||
{
|
||||
"onchange": """
|
||||
$('[name=dynamic_project]').val(this.value);
|
||||
setTimeout(() => {
|
||||
$('#getStageButton').click();
|
||||
}, 100);
|
||||
"""
|
||||
}
|
||||
)
|
||||
|
||||
request = getattr(_thread_locals, "request", None)
|
||||
employee = request.user.employee_get
|
||||
if not self.instance.pk:
|
||||
if request.user.is_superuser:
|
||||
projects = Project.objects.all()
|
||||
elif Project.objects.filter(managers=employee).exists():
|
||||
projects = Project.objects.filter(managers=employee)
|
||||
self.fields["project"].queryset = projects
|
||||
|
||||
else:
|
||||
task = self.instance
|
||||
if request.user.is_superuser:
|
||||
projects = Project.objects.all()
|
||||
elif employee in task.project.managers.all():
|
||||
projects = Project.objects.filter(managers=employee)
|
||||
elif employee in task.task_managers.all():
|
||||
# Limit fields accessible to task managers
|
||||
projects = Project.objects.filter(id=self.instance.project.id)
|
||||
self.fields["project"].disabled = True
|
||||
self.fields["stage"].disabled = True
|
||||
self.fields["task_managers"].disabled = True
|
||||
else:
|
||||
projects = Project.objects.filter(id=self.instance.project.id)
|
||||
self.fields["project"].queryset = projects
|
||||
|
||||
|
||||
class TimeSheetForm(ModelForm):
|
||||
@@ -147,6 +215,8 @@ class TimeSheetForm(ModelForm):
|
||||
Form for Time Sheet model
|
||||
"""
|
||||
|
||||
cols = {"description": 12}
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Meta class to add the additional info
|
||||
@@ -160,37 +230,63 @@ class TimeSheetForm(ModelForm):
|
||||
|
||||
def __init__(self, *args, request=None, **kwargs):
|
||||
super(TimeSheetForm, self).__init__(*args, **kwargs)
|
||||
if request:
|
||||
if not request.user.has_perm("project.add_timesheet"):
|
||||
employee = Employee.objects.filter(employee_user_id=request.user)
|
||||
employee_list = Employee.objects.filter(employee_work_info__reporting_manager_id=employee.first())
|
||||
self.fields["employee_id"].queryset = employee_list | employee
|
||||
if len(employee_list) == 0:
|
||||
self.fields["employee_id"].widget = forms.HiddenInput()
|
||||
self.fields['project_id'].widget.attrs.update({'id':'id_project'})
|
||||
self.fields["project_id"].choices = list(self.fields["project_id"].choices)
|
||||
self.fields["project_id"].choices.append(
|
||||
("create_new_project", "Create a new project")
|
||||
request = getattr(_thread_locals, "request", None)
|
||||
employee = request.user.employee_get
|
||||
hx_trigger_value = "change" if self.instance.id else "load,change"
|
||||
if not self.initial.get("project_id") == "dynamic_create":
|
||||
self.fields["project_id"].widget.attrs.update(
|
||||
{
|
||||
"hx-target": "#id_task_id_parent_div",
|
||||
"hx-trigger": hx_trigger_value,
|
||||
"hx-include": "#id_task_id",
|
||||
"hx-swap": "innerHTML",
|
||||
"hx-get": "/project/get-tasks-of-project/",
|
||||
}
|
||||
)
|
||||
self.fields["task_id"].widget.attrs.update(
|
||||
{
|
||||
"hx-target": "#id_employee_id_parent_div",
|
||||
"hx-include": "#id_project_id",
|
||||
"hx-trigger": hx_trigger_value,
|
||||
"hx-swap": "innerHTML",
|
||||
"hx-get": "/project/get-members-of-project/",
|
||||
}
|
||||
)
|
||||
|
||||
if not request.user.has_perm("project.add_timesheet"):
|
||||
projects = Project.objects.filter(
|
||||
Q(managers=employee)
|
||||
| Q(members=employee)
|
||||
| Q(task__task_members=employee)
|
||||
| Q(task__task_managers=employee)
|
||||
).distinct()
|
||||
self.fields["project_id"].queryset = projects
|
||||
|
||||
|
||||
class TimesheetInTaskForm(ModelForm):
|
||||
class Meta:
|
||||
"""
|
||||
Meta class to add the additional info
|
||||
"""
|
||||
|
||||
model = TimeSheet
|
||||
fields = "__all__"
|
||||
widgets = {
|
||||
"date": forms.DateInput(attrs={"type": "date"}),
|
||||
"project_id":forms.HiddenInput(),
|
||||
"task_id":forms.HiddenInput(),
|
||||
"project_id": forms.HiddenInput(),
|
||||
"task_id": forms.HiddenInput(),
|
||||
}
|
||||
|
||||
|
||||
class ProjectStageForm(ModelForm):
|
||||
"""
|
||||
Form for Project stage model
|
||||
"""
|
||||
|
||||
cols = {
|
||||
"title": 12,
|
||||
}
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Meta class to add the additional info
|
||||
@@ -200,15 +296,14 @@ class ProjectStageForm(ModelForm):
|
||||
fields = "__all__"
|
||||
# exclude = ("project",)
|
||||
|
||||
widgets = {
|
||||
"project":forms.HiddenInput(),
|
||||
'sequence':forms.HiddenInput()
|
||||
}
|
||||
|
||||
widgets = {"project": forms.HiddenInput()}
|
||||
|
||||
|
||||
class TaskTimeSheetForm(ModelForm):
|
||||
"""
|
||||
Form for Task model in timesheet form
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Meta class to add the additional info
|
||||
@@ -231,7 +326,6 @@ class TaskTimeSheetForm(ModelForm):
|
||||
{
|
||||
"style": "width: 100%; height: 47px;",
|
||||
"class": "oh-select",
|
||||
|
||||
}
|
||||
)
|
||||
self.fields["description"].widget.attrs.update(
|
||||
@@ -246,14 +340,5 @@ class TaskTimeSheetForm(ModelForm):
|
||||
"class": "oh-select",
|
||||
}
|
||||
)
|
||||
|
||||
self.fields["stage"].widget.attrs.update(
|
||||
{
|
||||
"id":'project_stage'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
self.fields["stage"].widget.attrs.update({"id": "project_stage"})
|
||||
|
||||
@@ -1,196 +1,239 @@
|
||||
import random
|
||||
from django.core.paginator import Paginator
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponseRedirect
|
||||
from base.methods import get_pagination
|
||||
from recruitment.decorators import decorator_with_arguments
|
||||
from employee.models import Employee
|
||||
from project.models import TimeSheet,Project,Task
|
||||
|
||||
|
||||
def strtime_seconds(time):
|
||||
"""
|
||||
this method is used to reconvert time in H:M formate string back to seconds and return it
|
||||
args:
|
||||
time : time in H:M format
|
||||
"""
|
||||
ftr = [3600, 60, 1]
|
||||
return sum(a * b for a, b in zip(ftr, map(int, time.split(":"))))
|
||||
|
||||
|
||||
def paginator_qry(qryset, page_number):
|
||||
"""
|
||||
This method is used to generate common paginator limit.
|
||||
"""
|
||||
paginator = Paginator(qryset, get_pagination())
|
||||
qryset = paginator.get_page(page_number)
|
||||
return qryset
|
||||
|
||||
|
||||
def random_color_generator():
|
||||
r = random.randint(0, 255)
|
||||
g = random.randint(0, 255)
|
||||
b = random.randint(0, 255)
|
||||
if r==g or g==b or b==r:
|
||||
random_color_generator()
|
||||
return f"rgba({r}, {g}, {b} , 0.7)"
|
||||
|
||||
|
||||
# color_palette=[]
|
||||
# Function to generate distinct colors for each project
|
||||
def generate_colors(num_colors):
|
||||
# Define a color palette with distinct colors
|
||||
color_palette = [
|
||||
"rgba(255, 99, 132, 1)", # Red
|
||||
"rgba(54, 162, 235, 1)", # Blue
|
||||
"rgba(255, 206, 86, 1)", # Yellow
|
||||
"rgba(75, 192, 192, 1)", # Green
|
||||
"rgba(153, 102, 255, 1)", # Purple
|
||||
"rgba(255, 159, 64, 1)", # Orange
|
||||
]
|
||||
|
||||
if num_colors > len(color_palette):
|
||||
for i in range(num_colors-len(color_palette)):
|
||||
color_palette.append(random_color_generator())
|
||||
|
||||
colors = []
|
||||
for i in range(num_colors):
|
||||
# color=random_color_generator()
|
||||
colors.append(color_palette[i % len(color_palette)])
|
||||
|
||||
return colors
|
||||
|
||||
def any_project_manager(user):
|
||||
employee = user.employee_get
|
||||
if employee.project_manager.all().exists():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def any_project_member(user):
|
||||
employee = user.employee_get
|
||||
if employee.project_members.all().exists():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def any_task_manager(user):
|
||||
employee = user.employee_get
|
||||
if employee.task_set.all().exists():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def any_task_member(user):
|
||||
employee = user.employee_get
|
||||
if employee.tasks.all().exists():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@decorator_with_arguments
|
||||
def is_projectmanager_or_member_or_perms(function,perm):
|
||||
def _function(request, *args, **kwargs):
|
||||
|
||||
"""
|
||||
This method is used to check the employee is project manager or not
|
||||
"""
|
||||
user = request.user
|
||||
if (
|
||||
user.has_perm(perm) or
|
||||
any_project_manager(user) or
|
||||
any_project_member(user) or
|
||||
any_task_manager(user) or
|
||||
any_task_member(user)
|
||||
):
|
||||
return function(request, *args, **kwargs)
|
||||
messages.info(request, "You don't have permission.")
|
||||
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
|
||||
return _function
|
||||
|
||||
|
||||
# def is_project_member(request,project_id):
|
||||
# """
|
||||
# This method is used to check the employee is project member or not
|
||||
# """
|
||||
# print(Project.objects.get(id = project_id))
|
||||
# print(Project.objects.get(id = project_id).members.all() )
|
||||
# if (request.user.has_perm('project.change_project') or
|
||||
# request.user.employee_get == Project.objects.get(id = project_id).manager or
|
||||
# request.user.employee_get in Project.objects.get(id = project_id).members.all()
|
||||
# ):
|
||||
# return True
|
||||
# return False
|
||||
|
||||
# def is_project_manager(request,project_id):
|
||||
# """
|
||||
# This method is used to check the employee is project manager or not
|
||||
# """
|
||||
# print(Project.objects.get(id = project_id))
|
||||
# print(Project.objects.get(id = project_id).manager )
|
||||
# if (
|
||||
# request.user.employee_get == Project.objects.get(id = project_id).manager or
|
||||
# request.user.has_perm('project.delete_project')
|
||||
|
||||
# ):
|
||||
# return True
|
||||
# return False
|
||||
|
||||
|
||||
# def is_project_manager(request, project_id):
|
||||
# """
|
||||
# This function checks if the user is a project manager or has permission to delete a project.
|
||||
# """
|
||||
# user = request.user
|
||||
# try:
|
||||
# project = Project.objects.get(id=project_id)
|
||||
# except Project.DoesNotExist:
|
||||
# return False # Project with the specified ID does not exist
|
||||
|
||||
# if user.employee_get == project.manager or user.has_perm('project.delete_project'):
|
||||
# return True # User is a project manager or has delete_project permission
|
||||
|
||||
# return False # User does not have the required permission
|
||||
|
||||
|
||||
def is_task_member(request,task_id):
|
||||
"""
|
||||
This method is used to check the employee is task member or not
|
||||
"""
|
||||
if (request.user.has_perm('project.change_task') or
|
||||
request.user.employee_get == Task.objects.get(id = task_id).task_manager or
|
||||
request.user.employee_get in Task.objects.get(id = task_id).task_members.all()
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_task_manager(request,task_id):
|
||||
"""
|
||||
This method is used to check the employee is task member or not
|
||||
"""
|
||||
if (request.user.has_perm('project.delete_task') or
|
||||
request.user.employee_get == Task.objects.get(id = task_id).task_manager
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def time_sheet_update_permissions(request,time_sheet_id):
|
||||
if (
|
||||
request.user.has_perm("project.change_timesheet")
|
||||
or request.user.employee_get == TimeSheet.objects.get(id=time_sheet_id).employee_id
|
||||
or TimeSheet.objects.get(id=time_sheet_id).employee_id in Employee.objects.filter(employee_work_info__reporting_manager_id=request.user.employee_get)
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def time_sheet_delete_permissions(request,time_sheet_id):
|
||||
if (
|
||||
request.user.has_perm("project.delete_timesheet")
|
||||
or request.user.employee_get == TimeSheet.objects.get(id=time_sheet_id).employee_id
|
||||
or TimeSheet.objects.get(id=time_sheet_id).employee_id in Employee.objects.filter(employee_work_info__reporting_manager_id=request.user.employee_get)
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
import random
|
||||
|
||||
from django.contrib import messages
|
||||
from django.core.paginator import Paginator
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
|
||||
from base.methods import get_pagination, get_subordinates
|
||||
from employee.models import Employee
|
||||
from project.models import Project, Task, TimeSheet
|
||||
|
||||
decorator_with_arguments = (
|
||||
lambda decorator: lambda *args, **kwargs: lambda func: decorator(
|
||||
func, *args, **kwargs
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def strtime_seconds(time):
|
||||
"""
|
||||
this method is used to reconvert time in H:M formate string back to seconds and return it
|
||||
args:
|
||||
time : time in H:M format
|
||||
"""
|
||||
ftr = [3600, 60, 1]
|
||||
return sum(a * b for a, b in zip(ftr, map(int, time.split(":"))))
|
||||
|
||||
|
||||
def paginator_qry(qryset, page_number):
|
||||
"""
|
||||
This method is used to generate common paginator limit.
|
||||
"""
|
||||
paginator = Paginator(qryset, get_pagination())
|
||||
qryset = paginator.get_page(page_number)
|
||||
return qryset
|
||||
|
||||
|
||||
def random_color_generator():
|
||||
r = random.randint(0, 255)
|
||||
g = random.randint(0, 255)
|
||||
b = random.randint(0, 255)
|
||||
if r == g or g == b or b == r:
|
||||
random_color_generator()
|
||||
return f"rgba({r}, {g}, {b} , 0.7)"
|
||||
|
||||
|
||||
# color_palette=[]
|
||||
# Function to generate distinct colors for each project
|
||||
def generate_colors(num_colors):
|
||||
# Define a color palette with distinct colors
|
||||
color_palette = [
|
||||
"rgba(255, 99, 132, 1)", # Red
|
||||
"rgba(54, 162, 235, 1)", # Blue
|
||||
"rgba(255, 206, 86, 1)", # Yellow
|
||||
"rgba(75, 192, 192, 1)", # Green
|
||||
"rgba(153, 102, 255, 1)", # Purple
|
||||
"rgba(255, 159, 64, 1)", # Orange
|
||||
]
|
||||
|
||||
if num_colors > len(color_palette):
|
||||
for i in range(num_colors - len(color_palette)):
|
||||
color_palette.append(random_color_generator())
|
||||
|
||||
colors = []
|
||||
for i in range(num_colors):
|
||||
# color=random_color_generator()
|
||||
colors.append(color_palette[i % len(color_palette)])
|
||||
|
||||
return colors
|
||||
|
||||
|
||||
def any_project_manager(user):
|
||||
employee = user.employee_get
|
||||
if employee.project_managers.all().exists():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def any_project_member(user):
|
||||
employee = user.employee_get
|
||||
if employee.project_members.all().exists():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def any_task_manager(user):
|
||||
employee = user.employee_get
|
||||
if employee.task_set.all().exists():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def any_task_member(user):
|
||||
employee = user.employee_get
|
||||
if employee.tasks.all().exists():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
@decorator_with_arguments
|
||||
def is_projectmanager_or_member_or_perms(function, perm):
|
||||
def _function(request, *args, **kwargs):
|
||||
"""
|
||||
This method is used to check the employee is project manager or not
|
||||
"""
|
||||
user = request.user
|
||||
if (
|
||||
user.has_perm(perm)
|
||||
or any_project_manager(user)
|
||||
or any_project_member(user)
|
||||
or any_task_manager(user)
|
||||
or any_task_member(user)
|
||||
):
|
||||
return function(request, *args, **kwargs)
|
||||
messages.info(request, "You don't have permission.")
|
||||
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
|
||||
|
||||
return _function
|
||||
|
||||
|
||||
def is_task_member(request, task_id):
|
||||
"""
|
||||
This method is used to check the employee is task member or not
|
||||
"""
|
||||
if (
|
||||
request.user.has_perm("project.change_task")
|
||||
or request.user.employee_get in Task.objects.get(id=task_id).task_managers.all()
|
||||
or request.user.employee_get in Task.objects.get(id=task_id).task_members.all()
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_task_manager(request, task_id):
|
||||
"""
|
||||
This method is used to check the employee is task member or not
|
||||
"""
|
||||
if (
|
||||
request.user.has_perm("project.delete_task")
|
||||
or request.user.employee_get in Task.objects.get(id=task_id).task_managers.all()
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def time_sheet_update_permissions(request, time_sheet_id):
|
||||
if (
|
||||
request.user.has_perm("project.change_timesheet")
|
||||
or request.user.employee_get
|
||||
== TimeSheet.objects.get(id=time_sheet_id).employee_id
|
||||
or TimeSheet.objects.get(id=time_sheet_id).employee_id
|
||||
in Employee.objects.filter(
|
||||
employee_work_info__reporting_manager_id=request.user.employee_get
|
||||
)
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def time_sheet_delete_permissions(request, time_sheet_id):
|
||||
employee = request.user.employee_get
|
||||
timesheet = TimeSheet.objects.filter(id=time_sheet_id).first()
|
||||
if (
|
||||
request.user.has_perm("project.delete_timesheet")
|
||||
or timesheet.employee_id == employee
|
||||
or employee in timesheet.task_id.task_managers.all()
|
||||
or employee in timesheet.task_id.project.managers.all()
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def get_all_project_members_and_managers():
|
||||
all_projects = Project.objects.all()
|
||||
all_tasks = Task.objects.all()
|
||||
|
||||
all_ids = set()
|
||||
|
||||
for project in all_projects:
|
||||
all_ids.update(
|
||||
manager.id for manager in project.managers.all()
|
||||
) # Add manager ID
|
||||
all_ids.update(member.id for member in project.members.all()) # Add member IDs
|
||||
|
||||
for task in all_tasks:
|
||||
all_ids.update(
|
||||
task_manager.id for task_manager in task.task_managers.all()
|
||||
) # Add task manager ID
|
||||
all_ids.update(
|
||||
task_member.id for task_member in task.task_members.all()
|
||||
) # Add task member IDs
|
||||
|
||||
# Return a single queryset for all employees
|
||||
return Employee.objects.filter(id__in=all_ids)
|
||||
|
||||
|
||||
def has_subordinates(request):
|
||||
"""
|
||||
used to check whether the project contain users subordinates or not
|
||||
"""
|
||||
all_members_info = get_all_project_members_and_managers()
|
||||
subordinates = get_subordinates(request)
|
||||
|
||||
member = {member for member in all_members_info}
|
||||
|
||||
for subordinate in subordinates:
|
||||
if subordinate in member:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def is_project_manager_or_super_user(request, project):
|
||||
"""
|
||||
Method to check whether user is a manager of project or
|
||||
user is a super user.
|
||||
"""
|
||||
return (
|
||||
request.user.employee_get in project.managers.all() or request.user.is_superuser
|
||||
)
|
||||
|
||||
|
||||
def you_dont_have_permission(request):
|
||||
"""
|
||||
Method to return you dont have permission
|
||||
"""
|
||||
messages.info(request, "You dont have permission.")
|
||||
previous_url = request.META.get("HTTP_REFERER", "/")
|
||||
key = "HTTP_HX_REQUEST"
|
||||
if key in request.META.keys():
|
||||
return render(request, "decorator_404.html")
|
||||
script = f'<script>window.location.href = "{previous_url}"</script>'
|
||||
return HttpResponse(script)
|
||||
|
||||
@@ -1,249 +1,617 @@
|
||||
"""
|
||||
models.py
|
||||
|
||||
This module is used to register models for project app
|
||||
|
||||
"""
|
||||
from django.db import models
|
||||
from django.apps import apps
|
||||
from employee.models import Employee
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from employee.models import Employee
|
||||
from datetime import date
|
||||
|
||||
|
||||
|
||||
# Create your models here.
|
||||
|
||||
def validate_time_format(value):
|
||||
"""
|
||||
this method is used to validate the format of duration like fields.
|
||||
"""
|
||||
if len(value) > 5:
|
||||
raise ValidationError(_("Invalid format, it should be HH:MM format"))
|
||||
try:
|
||||
hour, minute = value.split(":")
|
||||
|
||||
if len(hour) < 2 or len(minute) < 2:
|
||||
raise ValidationError(_( "Invalid format, it should be HH:MM format"))
|
||||
|
||||
minute = int(minute)
|
||||
if len(hour) > 2 or minute not in range(60):
|
||||
raise ValidationError(_("Invalid time"))
|
||||
except ValueError as error:
|
||||
raise ValidationError(_("Invalid format")) from error
|
||||
|
||||
|
||||
|
||||
class Project(models.Model):
|
||||
PROJECT_STATUS = [
|
||||
("new", "New"),
|
||||
("in_progress", "In Progress"),
|
||||
("completed", "Completed"),
|
||||
("on_hold", "On Hold"),
|
||||
("cancelled", "Cancelled"),
|
||||
("expired", "Expired"),
|
||||
]
|
||||
title = models.CharField(max_length=200, unique=True, verbose_name="Name")
|
||||
manager = models.ForeignKey(
|
||||
Employee,
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="project_manager",
|
||||
verbose_name="Project Manager",
|
||||
)
|
||||
members = models.ManyToManyField(
|
||||
Employee,
|
||||
blank=True,
|
||||
related_name="project_members",
|
||||
verbose_name="Project Members",
|
||||
)
|
||||
status = models.CharField(choices=PROJECT_STATUS, max_length=250, default="new")
|
||||
start_date = models.DateField()
|
||||
end_date = models.DateField(null=True, blank=True)
|
||||
document = models.FileField(
|
||||
upload_to="project/files", blank=True, null=True, verbose_name="Project File"
|
||||
)
|
||||
is_active = models.BooleanField(default=True)
|
||||
description = models.TextField()
|
||||
objects = models.Manager()
|
||||
|
||||
|
||||
|
||||
def clean(self) -> None:
|
||||
# validating end date
|
||||
if self.end_date is not None:
|
||||
if self.end_date < self.start_date:
|
||||
raise ValidationError({"document": "End date is less than start date"})
|
||||
if self.end_date < date.today():
|
||||
self.status = "expired"
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
|
||||
class ProjectStage(models.Model):
|
||||
"""
|
||||
ProjectStage model
|
||||
"""
|
||||
title = models.CharField(max_length=200)
|
||||
project = models.ForeignKey(
|
||||
Project,
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="project_stages",
|
||||
)
|
||||
sequence = models.IntegerField(null=True,blank=True)
|
||||
is_end_stage = models.BooleanField(default=False)
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.title}"
|
||||
|
||||
def clean(self) -> None:
|
||||
if self.is_end_stage:
|
||||
project = self.project
|
||||
existing_end_stage = project.project_stages.filter(is_end_stage = True).exclude(id = self.id)
|
||||
|
||||
if existing_end_stage:
|
||||
end_stage = project.project_stages.filter(is_end_stage = True).first()
|
||||
raise ValidationError(
|
||||
_(f"Already exist an end stage - {end_stage.title}.")
|
||||
)
|
||||
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Meta class to add the additional info
|
||||
"""
|
||||
|
||||
unique_together = ["project", "title"]
|
||||
|
||||
|
||||
class Task(models.Model):
|
||||
"""
|
||||
Task model
|
||||
"""
|
||||
TASK_STATUS = [
|
||||
("to_do", "To Do"),
|
||||
("in_progress", "In Progress"),
|
||||
("completed", "Completed"),
|
||||
("expired", "Expired"),
|
||||
]
|
||||
title = models.CharField(max_length=200)
|
||||
project = models.ForeignKey(Project, on_delete=models.CASCADE, null = True, blank= True)
|
||||
stage = models.ForeignKey(
|
||||
ProjectStage,
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="tasks",
|
||||
verbose_name="Project Stage",
|
||||
)
|
||||
task_manager = models.ForeignKey(
|
||||
Employee,
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Task Manager",
|
||||
)
|
||||
task_members = models.ManyToManyField(
|
||||
Employee, blank=True, related_name="tasks", verbose_name="Task Members"
|
||||
)
|
||||
start_date = models.DateField(null=True, blank=True)
|
||||
end_date = models.DateField(null=True, blank=True)
|
||||
status = models.CharField(choices=TASK_STATUS, max_length=250, default="to_do")
|
||||
document = models.FileField(
|
||||
upload_to="task/files", blank=True, null=True, verbose_name="Task File"
|
||||
)
|
||||
is_active = models.BooleanField(default=True)
|
||||
description = models.TextField()
|
||||
sequence = models.IntegerField(default=0)
|
||||
objects = models.Manager()
|
||||
|
||||
|
||||
def clean(self) -> None:
|
||||
if self.end_date is not None and self.project.end_date is not None:
|
||||
if (
|
||||
self.project.end_date < self.end_date
|
||||
or self.project.start_date > self.end_date
|
||||
):
|
||||
raise ValidationError(
|
||||
{"status": "End date must be between project start and end dates."}
|
||||
)
|
||||
if self.end_date < date.today():
|
||||
self.status = "expired"
|
||||
class Meta:
|
||||
"""
|
||||
Meta class to add the additional info
|
||||
"""
|
||||
unique_together = ["project", "title"]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.title}"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class TimeSheet(models.Model):
|
||||
"""
|
||||
TimeSheet model
|
||||
"""
|
||||
TIME_SHEET_STATUS = [
|
||||
("in_Progress", "In Progress"),
|
||||
("completed", "Completed"),
|
||||
]
|
||||
project_id = models.ForeignKey(
|
||||
Project,
|
||||
on_delete=models.CASCADE,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="project_timesheet",
|
||||
verbose_name="Project",
|
||||
)
|
||||
task_id = models.ForeignKey(
|
||||
Task,
|
||||
on_delete=models.CASCADE,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="task_timesheet",
|
||||
verbose_name="Task",
|
||||
)
|
||||
employee_id = models.ForeignKey(
|
||||
Employee,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Employee",
|
||||
)
|
||||
date = models.DateField(default=timezone.now)
|
||||
time_spent = models.CharField(
|
||||
null=True,
|
||||
default="00:00",
|
||||
max_length=10,
|
||||
validators=[validate_time_format],
|
||||
verbose_name="Hours Spent",
|
||||
)
|
||||
status = models.CharField(
|
||||
choices=TIME_SHEET_STATUS, max_length=250, default="in_Progress"
|
||||
)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
objects = models.Manager()
|
||||
|
||||
class Meta:
|
||||
ordering = ("-id",)
|
||||
|
||||
def clean(self):
|
||||
if self.project_id is None:
|
||||
raise ValidationError({"project_id": "Project name is Required."})
|
||||
if self.description is None or self.description == "":
|
||||
raise ValidationError(
|
||||
{"description": "Please provide a description to your Time sheet"}
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.project_id} {self.date} {self.time_spent}"
|
||||
"""
|
||||
models.py
|
||||
|
||||
This module is used to register models for project app
|
||||
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from datetime import date
|
||||
|
||||
from django.apps import apps
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.templatetags.static import static
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from employee.models import Employee
|
||||
from horilla.horilla_middlewares import _thread_locals
|
||||
from horilla_views.cbv_methods import render_template
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
def validate_time_format(value):
|
||||
"""
|
||||
this method is used to validate the format of duration like fields.
|
||||
"""
|
||||
if len(value) > 5:
|
||||
raise ValidationError(_("Invalid format, it should be HH:MM format"))
|
||||
try:
|
||||
hour, minute = value.split(":")
|
||||
|
||||
if len(hour) < 2 or len(minute) < 2:
|
||||
raise ValidationError(_("Invalid format, it should be HH:MM format"))
|
||||
|
||||
minute = int(minute)
|
||||
if len(hour) > 2 or minute not in range(60):
|
||||
raise ValidationError(_("Invalid time"))
|
||||
except ValueError as error:
|
||||
raise ValidationError(_("Invalid format")) from error
|
||||
|
||||
|
||||
class Project(models.Model):
|
||||
PROJECT_STATUS = [
|
||||
("new", "New"),
|
||||
("in_progress", "In Progress"),
|
||||
("completed", "Completed"),
|
||||
("on_hold", "On Hold"),
|
||||
("cancelled", "Cancelled"),
|
||||
("expired", "Expired"),
|
||||
]
|
||||
title = models.CharField(max_length=200, unique=True, verbose_name="Name")
|
||||
managers = models.ManyToManyField(
|
||||
Employee,
|
||||
blank=True,
|
||||
related_name="project_managers",
|
||||
verbose_name="Project Managers",
|
||||
)
|
||||
members = models.ManyToManyField(
|
||||
Employee,
|
||||
blank=True,
|
||||
related_name="project_members",
|
||||
verbose_name="Project Members",
|
||||
)
|
||||
status = models.CharField(choices=PROJECT_STATUS, max_length=250, default="new")
|
||||
start_date = models.DateField()
|
||||
end_date = models.DateField(null=True, blank=True)
|
||||
document = models.FileField(
|
||||
upload_to="project/files", blank=True, null=True, verbose_name="Project File"
|
||||
)
|
||||
is_active = models.BooleanField(default=True)
|
||||
description = models.TextField()
|
||||
objects = models.Manager()
|
||||
|
||||
def get_description(self, length=50):
|
||||
"""
|
||||
Returns a truncated version of the description attribute.
|
||||
|
||||
Parameters:
|
||||
length (int): The maximum length of the returned description.
|
||||
"""
|
||||
return (
|
||||
self.description
|
||||
if len(self.description) <= length
|
||||
else self.description[:length] + "..."
|
||||
)
|
||||
|
||||
def get_managers(self):
|
||||
"""
|
||||
managers column
|
||||
"""
|
||||
employees = self.managers.all()
|
||||
if employees:
|
||||
employee_names_string = "<br>".join(
|
||||
[str(employee) for employee in employees]
|
||||
)
|
||||
return employee_names_string
|
||||
|
||||
def get_members(self):
|
||||
"""
|
||||
members column
|
||||
"""
|
||||
employees = self.members.all()
|
||||
if employees:
|
||||
employee_names_string = "<br>".join(
|
||||
[str(employee) for employee in employees]
|
||||
)
|
||||
return employee_names_string
|
||||
|
||||
def get_avatar(self):
|
||||
"""
|
||||
Method will retun the api to the avatar or path to the profile image
|
||||
"""
|
||||
url = f"https://ui-avatars.com/api/?name={self.title}&background=random"
|
||||
return url
|
||||
|
||||
def get_document_html(self):
|
||||
if self.document:
|
||||
document_url = self.document.url
|
||||
image_url = static("images/ui/project/document.png")
|
||||
return format_html(
|
||||
'<a href="{0}" style="text-decoration: none" rel="noopener noreferrer" class="oh-btn oh-btn--light" target="_blank" onclick="event.stopPropagation();">'
|
||||
'<span class="oh-file-icon oh-file-icon--pdf"></span>'
|
||||
"  View"
|
||||
"</a>",
|
||||
document_url,
|
||||
image_url,
|
||||
)
|
||||
|
||||
def redirect(self):
|
||||
"""
|
||||
This method generates an onclick URL for task viewing.
|
||||
"""
|
||||
request = getattr(_thread_locals, "request", None)
|
||||
employee = request.user.employee_get
|
||||
url = reverse_lazy("task-view", kwargs={"project_id": self.pk})
|
||||
|
||||
if (
|
||||
employee in self.managers.all()
|
||||
or employee in self.members.all()
|
||||
or any(employee in task.task_managers.all() for task in self.task_set.all())
|
||||
or any(employee in task.task_members.all() for task in self.task_set.all())
|
||||
or request.user.has_perm("project.view_project")
|
||||
):
|
||||
return f"onclick=\"window.location.href='{url}?view=list'\""
|
||||
return ""
|
||||
|
||||
def get_detail_url(self):
|
||||
"""
|
||||
This method to get detail url
|
||||
"""
|
||||
url = reverse_lazy("project-detailed-view", kwargs={"pk": self.pk})
|
||||
return url
|
||||
|
||||
def get_update_url(self):
|
||||
"""
|
||||
This method to get update url
|
||||
"""
|
||||
url = reverse_lazy("update-project", kwargs={"pk": self.pk})
|
||||
return url
|
||||
|
||||
def get_archive_url(self):
|
||||
"""
|
||||
This method to get archive url
|
||||
"""
|
||||
url = reverse_lazy("project-archive", kwargs={"project_id": self.pk})
|
||||
return url
|
||||
|
||||
def get_task_badge_html(self):
|
||||
task_count = self.task_set.count()
|
||||
title = self.title
|
||||
return format_html(
|
||||
'<div style="display: flex; align-items: center;">'
|
||||
' <div class="oh-tabs__input-badge-container">'
|
||||
' <span class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round mr-1" title="{1} Tasks">'
|
||||
" {1}"
|
||||
" </span>"
|
||||
" </div>"
|
||||
" <div>{0}</div>"
|
||||
"</div>",
|
||||
title,
|
||||
task_count,
|
||||
)
|
||||
|
||||
def get_delete_url(self):
|
||||
"""
|
||||
This method to get delete url
|
||||
"""
|
||||
url = reverse_lazy("delete-project", kwargs={"project_id": self.pk})
|
||||
message = "Are you sure you want to delete this project?"
|
||||
return f"'{url}'" + "," + f"'{message}'"
|
||||
|
||||
def actions(self):
|
||||
"""
|
||||
This method for get custom column for action.
|
||||
"""
|
||||
|
||||
return render_template(
|
||||
path="cbv/projects/actions.html",
|
||||
context={"instance": self},
|
||||
)
|
||||
|
||||
def archive_status(self):
|
||||
"""
|
||||
archive status
|
||||
"""
|
||||
if self.is_active:
|
||||
return "Archive"
|
||||
else:
|
||||
return "Un-Archive"
|
||||
|
||||
def clean(self) -> None:
|
||||
# validating end date
|
||||
if self.end_date is not None:
|
||||
if self.end_date < self.start_date:
|
||||
raise ValidationError({"document": "End date is less than start date"})
|
||||
if self.end_date < date.today():
|
||||
self.status = "expired"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
is_new = self.pk is None
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
if is_new:
|
||||
ProjectStage.objects.create(
|
||||
title="Todo",
|
||||
project=self,
|
||||
sequence=1,
|
||||
is_end_stage=False,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def status_column(self):
|
||||
return dict(self.PROJECT_STATUS).get(self.status)
|
||||
|
||||
|
||||
class ProjectStage(models.Model):
|
||||
"""
|
||||
ProjectStage model
|
||||
"""
|
||||
|
||||
title = models.CharField(max_length=200)
|
||||
project = models.ForeignKey(
|
||||
Project,
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="project_stages",
|
||||
)
|
||||
sequence = models.IntegerField(null=True, blank=True, editable=False)
|
||||
is_end_stage = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.title}"
|
||||
|
||||
def clean(self) -> None:
|
||||
if self.is_end_stage:
|
||||
project = self.project
|
||||
existing_end_stage = project.project_stages.filter(
|
||||
is_end_stage=True
|
||||
).exclude(id=self.id)
|
||||
|
||||
if existing_end_stage:
|
||||
end_stage = project.project_stages.filter(is_end_stage=True).first()
|
||||
raise ValidationError(
|
||||
_(f"Already exist an end stage - {end_stage.title}.")
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.sequence is None:
|
||||
last_stage = (
|
||||
ProjectStage.objects.filter(project=self.project)
|
||||
.order_by("sequence")
|
||||
.last()
|
||||
)
|
||||
if last_stage:
|
||||
self.sequence = last_stage.sequence + 1
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
project_stages_after = ProjectStage.objects.filter(
|
||||
project=self.project, sequence__gt=self.sequence
|
||||
)
|
||||
|
||||
# Decrement the sequence of the following stages
|
||||
for stage in project_stages_after:
|
||||
stage.sequence -= 1
|
||||
stage.save()
|
||||
|
||||
super().delete(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Meta class to add the additional info
|
||||
"""
|
||||
|
||||
unique_together = ["project", "title"]
|
||||
|
||||
|
||||
class Task(models.Model):
|
||||
"""
|
||||
Task model
|
||||
"""
|
||||
|
||||
TASK_STATUS = [
|
||||
("to_do", "To Do"),
|
||||
("in_progress", "In Progress"),
|
||||
("completed", "Completed"),
|
||||
("expired", "Expired"),
|
||||
]
|
||||
title = models.CharField(max_length=200)
|
||||
project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True)
|
||||
stage = models.ForeignKey(
|
||||
ProjectStage,
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
related_name="tasks",
|
||||
verbose_name="Project Stage",
|
||||
)
|
||||
task_managers = models.ManyToManyField(
|
||||
Employee,
|
||||
blank=True,
|
||||
verbose_name="Task Managers",
|
||||
)
|
||||
task_members = models.ManyToManyField(
|
||||
Employee, blank=True, related_name="tasks", verbose_name="Task Members"
|
||||
)
|
||||
status = models.CharField(choices=TASK_STATUS, max_length=250, default="to_do")
|
||||
start_date = models.DateField(null=True, blank=True)
|
||||
end_date = models.DateField(null=True, blank=True)
|
||||
document = models.FileField(
|
||||
upload_to="task/files", blank=True, null=True, verbose_name="Task File"
|
||||
)
|
||||
is_active = models.BooleanField(default=True)
|
||||
description = models.TextField()
|
||||
sequence = models.IntegerField(default=0)
|
||||
objects = models.Manager()
|
||||
|
||||
def clean(self) -> None:
|
||||
if self.end_date is not None and self.project.end_date is not None:
|
||||
if (
|
||||
self.project.end_date < self.end_date
|
||||
or self.project.start_date > self.end_date
|
||||
):
|
||||
raise ValidationError(
|
||||
{
|
||||
"end_date": _(
|
||||
"The task end date must be between the project's start and end dates."
|
||||
)
|
||||
}
|
||||
)
|
||||
if self.end_date < date.today():
|
||||
self.status = "expired"
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Meta class to add the additional info
|
||||
"""
|
||||
|
||||
unique_together = ["project", "title"]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.title}"
|
||||
|
||||
def if_project(self):
|
||||
"""
|
||||
Return project if have,otherwise return none
|
||||
"""
|
||||
|
||||
return self.project if self.project else "None"
|
||||
|
||||
def task_detail_view(self):
|
||||
"""
|
||||
detail view of task
|
||||
"""
|
||||
|
||||
url = reverse("task-detail-view", kwargs={"pk": self.pk})
|
||||
return url
|
||||
|
||||
def status_column(self):
|
||||
"""
|
||||
to get status
|
||||
"""
|
||||
return dict(self.TASK_STATUS).get(self.status)
|
||||
|
||||
def get_managers(self):
|
||||
"""
|
||||
return task managers
|
||||
"""
|
||||
managers = self.task_managers.all()
|
||||
if managers:
|
||||
managers_name_string = "<br>".join([str(manager) for manager in managers])
|
||||
return managers_name_string
|
||||
else:
|
||||
return ""
|
||||
|
||||
def get_members(self):
|
||||
"""
|
||||
return task members
|
||||
"""
|
||||
members = self.task_members.all()
|
||||
if members:
|
||||
members_name_string = "<br>".join([str(member) for member in members])
|
||||
return members_name_string
|
||||
else:
|
||||
return ""
|
||||
|
||||
def actions(self):
|
||||
"""
|
||||
This method for get custom column for action.
|
||||
"""
|
||||
# request = getattr(_thread_locals, "request", None)
|
||||
# is_task_manager = self.task_manager == request.user
|
||||
# print(self.title)
|
||||
# is_project_manager = self.project.manager == request.user if self.project else False
|
||||
# print(self.project)
|
||||
# has_permission = request.user.has_perm('project.view_task') # Replace 'your_app' with your app name
|
||||
|
||||
# if is_task_manager or is_project_manager or has_permission:
|
||||
# return render_template(
|
||||
# "cbv/tasks/task_actions.html",
|
||||
# {"instance": self}
|
||||
# )
|
||||
# else:
|
||||
# return ""
|
||||
|
||||
return render_template(
|
||||
path="cbv/tasks/task_actions.html",
|
||||
context={"instance": self},
|
||||
)
|
||||
|
||||
def get_avatar(self):
|
||||
"""
|
||||
Method will retun the api to the avatar or path to the profile image
|
||||
"""
|
||||
url = f"https://ui-avatars.com/api/?name={self.title}&background=random"
|
||||
return url
|
||||
|
||||
def document_col(self):
|
||||
"""
|
||||
This method for get custom document coloumn .
|
||||
"""
|
||||
|
||||
return render_template(
|
||||
path="cbv/tasks/task_document.html",
|
||||
context={"instance": self},
|
||||
)
|
||||
|
||||
def detail_view_actions(self):
|
||||
"""
|
||||
This method for get detail view actions.
|
||||
"""
|
||||
|
||||
return render_template(
|
||||
path="cbv/tasks/task_detail_actions.html",
|
||||
context={"instance": self},
|
||||
)
|
||||
|
||||
def get_update_url(self):
|
||||
"""
|
||||
to get the update url
|
||||
"""
|
||||
url = reverse("update-task-all", kwargs={"pk": self.pk})
|
||||
return url
|
||||
|
||||
def archive_status(self):
|
||||
"""
|
||||
archive status
|
||||
"""
|
||||
if self.is_active:
|
||||
return "Archive"
|
||||
else:
|
||||
return "Un-Archive"
|
||||
|
||||
def get_archive_url(self):
|
||||
"""
|
||||
to get archive url
|
||||
"""
|
||||
|
||||
url = reverse("task-all-archive", kwargs={"task_id": self.pk})
|
||||
return url
|
||||
|
||||
def get_delete_url(self):
|
||||
"""
|
||||
to get delete url
|
||||
"""
|
||||
|
||||
url = reverse("delete-task", kwargs={"task_id": self.pk})
|
||||
url_with_params = f"{url}?task_all=true"
|
||||
message = "Are you sure you want to delete this task?"
|
||||
return f"'{url_with_params}'" + "," + f"'{message}'"
|
||||
|
||||
|
||||
class TimeSheet(models.Model):
|
||||
"""
|
||||
TimeSheet model
|
||||
"""
|
||||
|
||||
TIME_SHEET_STATUS = [
|
||||
("in_Progress", "In Progress"),
|
||||
("completed", "Completed"),
|
||||
]
|
||||
project_id = models.ForeignKey(
|
||||
Project,
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
related_name="project_timesheet",
|
||||
verbose_name="Project",
|
||||
)
|
||||
task_id = models.ForeignKey(
|
||||
Task,
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
related_name="task_timesheet",
|
||||
verbose_name="Task",
|
||||
)
|
||||
employee_id = models.ForeignKey(
|
||||
Employee,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Employee",
|
||||
)
|
||||
date = models.DateField(default=timezone.now)
|
||||
time_spent = models.CharField(
|
||||
null=True,
|
||||
default="00:00",
|
||||
max_length=10,
|
||||
validators=[validate_time_format],
|
||||
verbose_name="Hours Spent",
|
||||
)
|
||||
status = models.CharField(
|
||||
choices=TIME_SHEET_STATUS, max_length=250, default="in_Progress"
|
||||
)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
objects = models.Manager()
|
||||
|
||||
class Meta:
|
||||
ordering = ("-id",)
|
||||
|
||||
def clean(self):
|
||||
if self.project_id is None:
|
||||
raise ValidationError({"project_id": "Project name is Required."})
|
||||
if self.description is None or self.description == "":
|
||||
raise ValidationError(
|
||||
{"description": "Please provide a description to your Time sheet"}
|
||||
)
|
||||
if self.employee_id:
|
||||
employee = self.employee_id
|
||||
if self.task_id:
|
||||
task = self.task_id
|
||||
if (
|
||||
not employee in task.task_managers.all()
|
||||
and not employee in task.task_members.all()
|
||||
and not employee in task.project.managers.all()
|
||||
and not employee in task.project.members.all()
|
||||
):
|
||||
raise ValidationError(_("Employee not included in this task"))
|
||||
elif self.project_id:
|
||||
if (
|
||||
not employee in self.project_id.managers.all()
|
||||
and not employee in self.project_id.members.all()
|
||||
):
|
||||
raise ValidationError(_("Employee not included in this project"))
|
||||
if self.date > datetime.datetime.today().date():
|
||||
raise ValidationError({"date": _("You cannot choose a future date.")})
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.employee_id} {self.project_id} {self.task_id} {self.date} {self.time_spent}"
|
||||
|
||||
def status_column(self):
|
||||
return dict(self.TIME_SHEET_STATUS).get(self.status)
|
||||
|
||||
def actions(self):
|
||||
"""
|
||||
This method for get custom column for action.
|
||||
"""
|
||||
|
||||
return render_template(
|
||||
path="cbv/timesheet/actions.html",
|
||||
context={"instance": self},
|
||||
)
|
||||
|
||||
def detail_actions(self):
|
||||
"""
|
||||
This method for get custom column for action.
|
||||
"""
|
||||
|
||||
return render_template(
|
||||
path="cbv/timesheet/detail_actions.html",
|
||||
context={"instance": self},
|
||||
)
|
||||
|
||||
def get_update_url(self):
|
||||
"""
|
||||
This method to get update url
|
||||
"""
|
||||
url = reverse_lazy("update-time-sheet", kwargs={"pk": self.pk})
|
||||
return url
|
||||
|
||||
def get_delete_url(self):
|
||||
"""
|
||||
This method to get delete url
|
||||
"""
|
||||
url = reverse_lazy("delete-time-sheet", kwargs={"time_sheet_id": self.pk})
|
||||
message = "Are you sure you want to delete this time sheet?"
|
||||
return f"'{url}'" + "," + f"'{message}'"
|
||||
|
||||
def detail_view(self):
|
||||
"""
|
||||
for detail view of page
|
||||
"""
|
||||
url = reverse("time-sheet-detail-view", kwargs={"pk": self.pk})
|
||||
return url
|
||||
|
||||
@@ -1,105 +1,117 @@
|
||||
"""
|
||||
project/sidebar.py
|
||||
"""
|
||||
|
||||
from django.urls import reverse
|
||||
from base.templatetags.basefilters import is_reportingmanager
|
||||
from django.utils.translation import gettext_lazy as trans
|
||||
from django.contrib.auth.context_processors import PermWrapper
|
||||
|
||||
|
||||
from project.methods import any_project_manager, any_project_member, any_task_manager, any_task_member
|
||||
|
||||
MENU = trans("Project")
|
||||
IMG_SRC = "images/ui/project.png"
|
||||
ACCESSIBILITY = "project.sidebar.menu_accessibilty"
|
||||
|
||||
SUBMENUS = [
|
||||
{
|
||||
"menu": trans("Dashboard"),
|
||||
"redirect": reverse("project-dashboard-view"),
|
||||
"accessibility": "project.sidebar.dashboard_accessibility"
|
||||
},
|
||||
{
|
||||
"menu": trans("Projects"),
|
||||
"redirect": reverse("project-view"),
|
||||
"accessibility": "project.sidebar.project_accessibility",
|
||||
},
|
||||
{
|
||||
"menu": trans("Tasks"),
|
||||
"redirect": reverse("task-all"),
|
||||
"accessibility": "project.sidebar.task_accessibility",
|
||||
},
|
||||
{
|
||||
"menu": trans("Timesheet"),
|
||||
"redirect": reverse("view-time-sheet"),
|
||||
"accessibility": "project.sidebar.timesheet_accessibility",
|
||||
},
|
||||
]
|
||||
|
||||
def menu_accessibilty(
|
||||
request, _menu: str = "", user_perms: PermWrapper = [], *args, **kwargs
|
||||
) -> bool:
|
||||
user = request.user
|
||||
return (
|
||||
"project" in user_perms or
|
||||
is_reportingmanager(user) or
|
||||
any_project_manager(user) or
|
||||
any_project_member(user) or
|
||||
any_task_manager(user) or
|
||||
any_task_member(user)
|
||||
)
|
||||
|
||||
def dashboard_accessibility(request, submenu, user_perms, *args, **kwargs):
|
||||
user = request.user
|
||||
if (
|
||||
user.has_perm("project.view_project") or
|
||||
is_reportingmanager(user) or
|
||||
any_project_manager(user) or
|
||||
any_task_manager(user)
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def project_accessibility(request, submenu, user_perms, *args, **kwargs):
|
||||
user = request.user
|
||||
if (
|
||||
user.has_perm("project.view_project") or
|
||||
is_reportingmanager(user) or
|
||||
any_project_manager(user) or
|
||||
any_project_member(user) or
|
||||
any_task_manager(user) or
|
||||
any_task_member(user)
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def task_accessibility(request, submenu, user_perms, *args, **kwargs):
|
||||
user = request.user
|
||||
if (
|
||||
user.has_perm("project.view_task") or
|
||||
is_reportingmanager(user) or
|
||||
any_project_manager(user) or
|
||||
any_project_member(user) or
|
||||
any_task_manager(user) or
|
||||
any_task_member(user)
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def timesheet_accessibility(request, submenu, user_perms, *args, **kwargs):
|
||||
user = request.user
|
||||
if (
|
||||
user.has_perm("project.view_timesheet") or
|
||||
is_reportingmanager(user) or
|
||||
any_project_manager(user) or
|
||||
any_project_member(user) or
|
||||
any_task_manager(user) or
|
||||
any_task_member(user)
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
"""
|
||||
project/sidebar.py
|
||||
"""
|
||||
|
||||
from django.contrib.auth.context_processors import PermWrapper
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as trans
|
||||
|
||||
from base.templatetags.basefilters import is_reportingmanager
|
||||
from project.methods import (
|
||||
any_project_manager,
|
||||
any_project_member,
|
||||
any_task_manager,
|
||||
any_task_member,
|
||||
get_all_project_members_and_managers,
|
||||
has_subordinates,
|
||||
)
|
||||
|
||||
MENU = trans("Project")
|
||||
IMG_SRC = "images/ui/project.png"
|
||||
ACCESSIBILITY = "project.sidebar.menu_accessibilty"
|
||||
|
||||
SUBMENUS = [
|
||||
{
|
||||
"menu": trans("Dashboard"),
|
||||
"redirect": reverse("project-dashboard-view"),
|
||||
"accessibility": "project.sidebar.dashboard_accessibility",
|
||||
},
|
||||
{
|
||||
"menu": trans("Projects"),
|
||||
"redirect": reverse("project-view"),
|
||||
"accessibility": "project.sidebar.project_accessibility",
|
||||
},
|
||||
{
|
||||
"menu": trans("Tasks"),
|
||||
"redirect": reverse("task-all"),
|
||||
"accessibility": "project.sidebar.task_accessibility",
|
||||
},
|
||||
{
|
||||
"menu": trans("Timesheet"),
|
||||
"redirect": reverse("view-time-sheet"),
|
||||
"accessibility": "project.sidebar.timesheet_accessibility",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def menu_accessibilty(
|
||||
request, _menu: str = "", user_perms: PermWrapper = [], *args, **kwargs
|
||||
) -> bool:
|
||||
user = request.user
|
||||
return (
|
||||
"project" in user_perms
|
||||
# or has_subordinates(request)
|
||||
or any_project_manager(user)
|
||||
or any_project_member(user)
|
||||
or any_task_manager(user)
|
||||
or any_task_member(user)
|
||||
)
|
||||
|
||||
|
||||
def dashboard_accessibility(request, submenu, user_perms, *args, **kwargs):
|
||||
user = request.user
|
||||
if (
|
||||
user.has_perm("project.view_project")
|
||||
# or has_subordinates(request)
|
||||
or is_reportingmanager(user)
|
||||
or any_project_manager(user)
|
||||
or any_task_manager(user)
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def project_accessibility(request, submenu, user_perms, *args, **kwargs):
|
||||
user = request.user
|
||||
if (
|
||||
user.has_perm("project.view_project")
|
||||
# or has_subordinates(request)
|
||||
or any_project_manager(user)
|
||||
or any_project_member(user)
|
||||
or any_task_manager(user)
|
||||
or any_task_member(user)
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def task_accessibility(request, submenu, user_perms, *args, **kwargs):
|
||||
user = request.user
|
||||
if (
|
||||
user.has_perm("project.view_task")
|
||||
# or has_subordinates(request)
|
||||
or any_project_manager(user)
|
||||
or any_project_member(user)
|
||||
or any_task_manager(user)
|
||||
or any_task_member(user)
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def timesheet_accessibility(request, submenu, user_perms, *args, **kwargs):
|
||||
user = request.user
|
||||
if (
|
||||
user.has_perm("project.view_timesheet")
|
||||
# or has_subordinates(request)
|
||||
or any_project_manager(user)
|
||||
or any_project_member(user)
|
||||
or any_task_manager(user)
|
||||
or any_task_member(user)
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -1,89 +1,89 @@
|
||||
$(document).ready(function(){
|
||||
function projectStatusChart(dataSet, labels) {
|
||||
const data = {
|
||||
labels: labels,
|
||||
datasets: dataSet,
|
||||
};
|
||||
// Create chart using the Chart.js library
|
||||
window['projectChart'] = {}
|
||||
const ctx = document.getElementById("projectStatusCanvas").getContext("2d");
|
||||
|
||||
projectChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: data,
|
||||
options: {
|
||||
},
|
||||
});
|
||||
}
|
||||
$.ajax({
|
||||
url: "/project/project-status-chart",
|
||||
type: "GET",
|
||||
success: function (response) {
|
||||
// Code to handle the response
|
||||
// response = {'dataSet': [{'label': 'Odoo developer 2023-03-30', 'data': [3, 0, 5, 3]}, {'label': 'React developer 2023-03-31', 'data': [0, 1, 1, 0]}, {'label': 'Content Writer 2023-04-01', 'data': [1, 0, 0, 0]}], 'labels': ['Initial', 'Test', 'Interview', 'Hired']}
|
||||
dataSet = response.dataSet;
|
||||
labels = response.labels;
|
||||
projectStatusChart(dataSet, labels);
|
||||
|
||||
},
|
||||
});
|
||||
$('#projectStatusForward').click(function (e) {
|
||||
var chartType = projectChart.config.type
|
||||
if (chartType === 'line') {
|
||||
chartType = 'bar';
|
||||
} else if(chartType==='bar') {
|
||||
chartType = 'doughnut';
|
||||
} else if(chartType==='doughnut'){
|
||||
chartType = 'pie'
|
||||
}else if(chartType==='pie'){
|
||||
chartType = 'line'
|
||||
}
|
||||
projectChart.config.type = chartType;
|
||||
projectChart.update();
|
||||
});
|
||||
|
||||
// for creating task status chart
|
||||
function taskStatusChart(dataSet, labels) {
|
||||
const data = {
|
||||
labels: labels,
|
||||
datasets: dataSet,
|
||||
};
|
||||
// Create chart using the Chart.js library
|
||||
window['taskChart'] = {}
|
||||
const ctx = document.getElementById("taskStatusCanvas").getContext("2d");
|
||||
|
||||
taskChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: data,
|
||||
options: {
|
||||
},
|
||||
});
|
||||
}
|
||||
$.ajax({
|
||||
url: "/project/task-status-chart",
|
||||
type: "GET",
|
||||
success: function (response) {
|
||||
// Code to handle the response
|
||||
// response = {'dataSet': [{'label': 'Odoo developer 2023-03-30', 'data': [3, 0, 5, 3]}, {'label': 'React developer 2023-03-31', 'data': [0, 1, 1, 0]}, {'label': 'Content Writer 2023-04-01', 'data': [1, 0, 0, 0]}], 'labels': ['Initial', 'Test', 'Interview', 'Hired']}
|
||||
dataSet = response.dataSet;
|
||||
labels = response.labels;
|
||||
taskStatusChart(dataSet, labels);
|
||||
|
||||
},
|
||||
});
|
||||
$('#taskStatusForward').click(function (e) {
|
||||
var chartType = taskChart.config.type
|
||||
if (chartType === 'line') {
|
||||
chartType = 'bar';
|
||||
} else if(chartType==='bar') {
|
||||
chartType = 'doughnut';
|
||||
} else if(chartType==='doughnut'){
|
||||
chartType = 'pie'
|
||||
}else if(chartType==='pie'){
|
||||
chartType = 'line'
|
||||
}
|
||||
taskChart.config.type = chartType;
|
||||
taskChart.update();
|
||||
});
|
||||
|
||||
});
|
||||
$(document).ready(function(){
|
||||
function projectStatusChart(dataSet, labels) {
|
||||
const data = {
|
||||
labels: labels,
|
||||
datasets: dataSet,
|
||||
};
|
||||
// Create chart using the Chart.js library
|
||||
window['projectChart'] = {}
|
||||
const ctx = document.getElementById("projectStatusCanvas").getContext("2d");
|
||||
|
||||
projectChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: data,
|
||||
options: {
|
||||
},
|
||||
});
|
||||
}
|
||||
$.ajax({
|
||||
url: "/project/project-status-chart",
|
||||
type: "GET",
|
||||
success: function (response) {
|
||||
// Code to handle the response
|
||||
// response = {'dataSet': [{'label': 'Odoo developer 2023-03-30', 'data': [3, 0, 5, 3]}, {'label': 'React developer 2023-03-31', 'data': [0, 1, 1, 0]}, {'label': 'Content Writer 2023-04-01', 'data': [1, 0, 0, 0]}], 'labels': ['Initial', 'Test', 'Interview', 'Hired']}
|
||||
dataSet = response.dataSet;
|
||||
labels = response.labels;
|
||||
projectStatusChart(dataSet, labels);
|
||||
|
||||
},
|
||||
});
|
||||
$('#projectStatusForward').click(function (e) {
|
||||
var chartType = projectChart.config.type
|
||||
if (chartType === 'line') {
|
||||
chartType = 'bar';
|
||||
} else if(chartType==='bar') {
|
||||
chartType = 'doughnut';
|
||||
} else if(chartType==='doughnut'){
|
||||
chartType = 'pie'
|
||||
}else if(chartType==='pie'){
|
||||
chartType = 'line'
|
||||
}
|
||||
projectChart.config.type = chartType;
|
||||
projectChart.update();
|
||||
});
|
||||
|
||||
// for creating task status chart
|
||||
function taskStatusChart(dataSet, labels) {
|
||||
const data = {
|
||||
labels: labels,
|
||||
datasets: dataSet,
|
||||
};
|
||||
// Create chart using the Chart.js library
|
||||
window['taskChart'] = {}
|
||||
const ctx = document.getElementById("taskStatusCanvas").getContext("2d");
|
||||
|
||||
taskChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: data,
|
||||
options: {
|
||||
},
|
||||
});
|
||||
}
|
||||
$.ajax({
|
||||
url: "/project/task-status-chart",
|
||||
type: "GET",
|
||||
success: function (response) {
|
||||
// Code to handle the response
|
||||
// response = {'dataSet': [{'label': 'Odoo developer 2023-03-30', 'data': [3, 0, 5, 3]}, {'label': 'React developer 2023-03-31', 'data': [0, 1, 1, 0]}, {'label': 'Content Writer 2023-04-01', 'data': [1, 0, 0, 0]}], 'labels': ['Initial', 'Test', 'Interview', 'Hired']}
|
||||
dataSet = response.dataSet;
|
||||
labels = response.labels;
|
||||
taskStatusChart(dataSet, labels);
|
||||
|
||||
},
|
||||
});
|
||||
$('#taskStatusForward').click(function (e) {
|
||||
var chartType = taskChart.config.type
|
||||
if (chartType === 'line') {
|
||||
chartType = 'bar';
|
||||
} else if(chartType==='bar') {
|
||||
chartType = 'doughnut';
|
||||
} else if(chartType==='doughnut'){
|
||||
chartType = 'pie'
|
||||
}else if(chartType==='pie'){
|
||||
chartType = 'line'
|
||||
}
|
||||
taskChart.config.type = chartType;
|
||||
taskChart.update();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,252 +1,309 @@
|
||||
var downloadMessages = {
|
||||
ar: "هل ترغب في تنزيل القالب؟",
|
||||
de: "Möchten Sie die Vorlage herunterladen?",
|
||||
es: "¿Quieres descargar la plantilla?",
|
||||
en: "Do you want to download the template?",
|
||||
fr: "Voulez-vous télécharger le modèle ?",
|
||||
};
|
||||
|
||||
var importsuccess = {
|
||||
ar: "نجح الاستيراد", // Arabic
|
||||
de: "Import erfolgreich", // German
|
||||
es: "Importado con éxito", // Spanish
|
||||
en: "Imported Successfully!", // English
|
||||
fr: "Importation réussie" // French
|
||||
};
|
||||
|
||||
var uploadsuccess = {
|
||||
ar: "تحميل كامل", // Arabic
|
||||
de: "Upload abgeschlossen", // German
|
||||
es: "Carga completa", // Spanish
|
||||
en: "Upload Complete!", // English
|
||||
fr: "Téléchargement terminé" // French
|
||||
};
|
||||
|
||||
var uploadingmessage = {
|
||||
ar: "جارٍ الرفع",
|
||||
de: "Hochladen...",
|
||||
es: "Subiendo...",
|
||||
en: "Uploading...",
|
||||
fr: "Téléchargement en cours...",
|
||||
};
|
||||
|
||||
var validationmessage = {
|
||||
ar: "يرجى تحميل ملف بامتداد .xlsx فقط.",
|
||||
de: "Bitte laden Sie nur eine Datei mit der Erweiterung .xlsx hoch.",
|
||||
es: "Por favor, suba un archivo con la extensión .xlsx solamente.",
|
||||
en: "Please upload a file with the .xlsx extension only.",
|
||||
fr: "Veuillez télécharger uniquement un fichier avec l'extension .xlsx.",
|
||||
};
|
||||
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== '') {
|
||||
const cookies = document.cookie.split(';');
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
function getCurrentLanguageCode(callback) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/employee/get-language-code/",
|
||||
success: function (response) {
|
||||
var languageCode = response.language_code;
|
||||
callback(languageCode); // Pass the language code to the callback
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Get the form element
|
||||
var form = document.getElementById("projectImportForm");
|
||||
|
||||
// Add an event listener to the form submission
|
||||
form.addEventListener("submit", function (event) {
|
||||
// Prevent the default form submission
|
||||
event.preventDefault();
|
||||
|
||||
// Create a new form data object
|
||||
var formData = new FormData();
|
||||
|
||||
// Append the file to the form data object
|
||||
var fileInput = document.querySelector("#projectImportFile");
|
||||
formData.append("file", fileInput.files[0]);
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/project/project-import",
|
||||
dataType: "binary",
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: {
|
||||
"X-CSRFToken": getCookie('csrftoken'), // Replace with your csrf token value
|
||||
},
|
||||
xhrFields: {
|
||||
responseType: "blob",
|
||||
},
|
||||
success: function (response) {
|
||||
const file = new Blob([response], {
|
||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
});
|
||||
const url = URL.createObjectURL(file);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = "ImportError.xlsx";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
},
|
||||
error: function (xhr, textStatus, errorThrown) {
|
||||
console.error("Error downloading file:", errorThrown);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$("#importProject").click(function (e) {
|
||||
e.preventDefault();
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var confirmMessage = downloadMessages[languageCode];
|
||||
// Use SweetAlert for the confirmation dialog
|
||||
Swal.fire({
|
||||
|
||||
text: confirmMessage,
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#008000',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Confirm'
|
||||
}).then(function(result) {
|
||||
if (result.isConfirmed) {
|
||||
$("#loading").show();
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', "/project/project-import", true);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
|
||||
xhr.upload.onprogress = function (e) {
|
||||
if (e.lengthComputable) {
|
||||
var percent = (e.loaded / e.total) * 100;
|
||||
$(".progress-bar").width(percent + "%").attr("aria-valuenow", percent);
|
||||
$("#progress-text").text("Uploading... " + percent.toFixed(2) + "%");
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = function (e) {
|
||||
if (this.status == 200) {
|
||||
const file = new Blob([this.response], {
|
||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
});
|
||||
const url = URL.createObjectURL(file);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = "project_template.xlsx";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function (e) {
|
||||
console.error("Error downloading file:", e);
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ajaxStart(function () {
|
||||
$("#loading").show();
|
||||
});
|
||||
|
||||
$(document).ajaxStop(function () {
|
||||
$("#loading").hide();
|
||||
});
|
||||
|
||||
function simulateProgress() {
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function(code){
|
||||
languageCode = code;
|
||||
var importMessage = importsuccess[languageCode];
|
||||
var uploadMessage = uploadsuccess[languageCode];
|
||||
var uploadingMessage = uploadingmessage[languageCode];
|
||||
let progressBar = document.querySelector('.progress-bar');
|
||||
let progressText = document.getElementById('progress-text');
|
||||
|
||||
let width = 0;
|
||||
let interval = setInterval(function() {
|
||||
if (width >= 100) {
|
||||
clearInterval(interval);
|
||||
progressText.innerText = uploadMessage;
|
||||
setTimeout(function() {
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
}, 3000);
|
||||
Swal.fire({
|
||||
text: importMessage,
|
||||
icon: "success",
|
||||
showConfirmButton: false,
|
||||
timer: 2000,
|
||||
timerProgressBar: true,
|
||||
});
|
||||
setTimeout(function() {
|
||||
$('#projectImport').removeClass('oh-modal--show');
|
||||
location.reload(true);
|
||||
}, 2000);
|
||||
} else {
|
||||
width++;
|
||||
progressBar.style.width = width + '%';
|
||||
progressBar.setAttribute('aria-valuenow', width);
|
||||
progressText.innerText = uploadingMessage + width + '%';
|
||||
}
|
||||
}, 20);
|
||||
}
|
||||
)}
|
||||
|
||||
document.getElementById('projectImportForm').addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function(code){
|
||||
languageCode = code;
|
||||
var erroMessage = validationmessage[languageCode];
|
||||
|
||||
var fileInput = $('#projectImportFile').val();
|
||||
var allowedExtensions = /(\.xlsx)$/i;
|
||||
|
||||
if (!allowedExtensions.exec(fileInput)) {
|
||||
|
||||
var errorMessage = document.createElement('div');
|
||||
errorMessage.classList.add('error-message');
|
||||
|
||||
errorMessage.textContent = erroMessage;
|
||||
|
||||
document.getElementById('error-container').appendChild(errorMessage);
|
||||
|
||||
fileInput.value = '';
|
||||
|
||||
setTimeout(function() {
|
||||
errorMessage.remove();
|
||||
}, 2000);
|
||||
|
||||
return false;
|
||||
}
|
||||
else{
|
||||
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
|
||||
|
||||
simulateProgress();
|
||||
}
|
||||
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
var downloadMessages = {
|
||||
ar: "هل ترغب في تنزيل القالب؟",
|
||||
de: "Möchten Sie die Vorlage herunterladen?",
|
||||
es: "¿Quieres descargar la plantilla?",
|
||||
en: "Do you want to download the template?",
|
||||
fr: "Voulez-vous télécharger le modèle ?",
|
||||
};
|
||||
|
||||
var importsuccess = {
|
||||
ar: "نجح الاستيراد", // Arabic
|
||||
de: "Import erfolgreich", // German
|
||||
es: "Importado con éxito", // Spanish
|
||||
en: "Imported Successfully!", // English
|
||||
fr: "Importation réussie" // French
|
||||
};
|
||||
|
||||
var uploadsuccess = {
|
||||
ar: "تحميل كامل", // Arabic
|
||||
de: "Upload abgeschlossen", // German
|
||||
es: "Carga completa", // Spanish
|
||||
en: "Upload Complete!", // English
|
||||
fr: "Téléchargement terminé" // French
|
||||
};
|
||||
|
||||
var uploadingmessage = {
|
||||
ar: "جارٍ الرفع",
|
||||
de: "Hochladen...",
|
||||
es: "Subiendo...",
|
||||
en: "Uploading...",
|
||||
fr: "Téléchargement en cours...",
|
||||
};
|
||||
|
||||
var validationmessage = {
|
||||
ar: "يرجى تحميل ملف بامتداد .xlsx فقط.",
|
||||
de: "Bitte laden Sie nur eine Datei mit der Erweiterung .xlsx hoch.",
|
||||
es: "Por favor, suba un archivo con la extensión .xlsx solamente.",
|
||||
en: "Please upload a file with the .xlsx extension only.",
|
||||
fr: "Veuillez télécharger uniquement un fichier avec l'extension .xlsx.",
|
||||
};
|
||||
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== '') {
|
||||
const cookies = document.cookie.split(';');
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
function getCurrentLanguageCode(callback) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/employee/get-language-code/",
|
||||
success: function (response) {
|
||||
var languageCode = response.language_code;
|
||||
callback(languageCode); // Pass the language code to the callback
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Get the form element
|
||||
var form = document.getElementById("projectImportForm");
|
||||
|
||||
// Add an event listener to the form submission
|
||||
form.addEventListener("submit", function (event) {
|
||||
// Prevent the default form submission
|
||||
event.preventDefault();
|
||||
|
||||
// Create a new form data object
|
||||
var formData = new FormData();
|
||||
|
||||
// Append the file to the form data object
|
||||
var fileInput = document.querySelector("#projectImportFile");
|
||||
formData.append("file", fileInput.files[0]);
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/project/project-import",
|
||||
dataType: "binary",
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: {
|
||||
"X-CSRFToken": getCookie('csrftoken'), // Replace with your csrf token value
|
||||
},
|
||||
xhrFields: {
|
||||
responseType: "blob",
|
||||
},
|
||||
success: function (response) {
|
||||
const file = new Blob([response], {
|
||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
});
|
||||
const url = URL.createObjectURL(file);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = "ImportError.xlsx";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
},
|
||||
error: function (xhr, textStatus, errorThrown) {
|
||||
console.error("Error downloading file:", errorThrown);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$("#importProject").click(function (e) {
|
||||
e.preventDefault();
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var confirmMessage = downloadMessages[languageCode];
|
||||
// Use SweetAlert for the confirmation dialog
|
||||
Swal.fire({
|
||||
|
||||
text: confirmMessage,
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#008000',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Confirm'
|
||||
}).then(function(result) {
|
||||
if (result.isConfirmed) {
|
||||
$("#loading").show();
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', "/project/project-import", true);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
|
||||
xhr.upload.onprogress = function (e) {
|
||||
if (e.lengthComputable) {
|
||||
var percent = (e.loaded / e.total) * 100;
|
||||
$(".progress-bar").width(percent + "%").attr("aria-valuenow", percent);
|
||||
$("#progress-text").text("Uploading... " + percent.toFixed(2) + "%");
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = function (e) {
|
||||
if (this.status == 200) {
|
||||
const file = new Blob([this.response], {
|
||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
});
|
||||
const url = URL.createObjectURL(file);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = "project_template.xlsx";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function (e) {
|
||||
console.error("Error downloading file:", e);
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#importProject', function (e) {
|
||||
e.preventDefault();
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var confirmMessage = downloadMessages[languageCode];
|
||||
// Use SweetAlert for the confirmation dialog
|
||||
Swal.fire({
|
||||
text: confirmMessage,
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#008000',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Confirm'
|
||||
}).then(function(result) {
|
||||
if (result.isConfirmed) {
|
||||
$("#loading").show();
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', "/project/project-import", true);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
|
||||
xhr.upload.onprogress = function (e) {
|
||||
if (e.lengthComputable) {
|
||||
var percent = (e.loaded / e.total) * 100;
|
||||
$(".progress-bar").width(percent + "%").attr("aria-valuenow", percent);
|
||||
$("#progress-text").text("Uploading... " + percent.toFixed(2) + "%");
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = function (e) {
|
||||
if (this.status == 200) {
|
||||
const file = new Blob([this.response], {
|
||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
});
|
||||
const url = URL.createObjectURL(file);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = "project_template.xlsx";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
// Clean up by removing the link element
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function (e) {
|
||||
console.error("Error downloading file:", e);
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$(document).ajaxStart(function () {
|
||||
$("#loading").show();
|
||||
});
|
||||
|
||||
$(document).ajaxStop(function () {
|
||||
$("#loading").hide();
|
||||
});
|
||||
|
||||
function simulateProgress() {
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function(code){
|
||||
languageCode = code;
|
||||
var importMessage = importsuccess[languageCode];
|
||||
var uploadMessage = uploadsuccess[languageCode];
|
||||
var uploadingMessage = uploadingmessage[languageCode];
|
||||
let progressBar = document.querySelector('.progress-bar');
|
||||
let progressText = document.getElementById('progress-text');
|
||||
|
||||
let width = 0;
|
||||
let interval = setInterval(function() {
|
||||
if (width >= 100) {
|
||||
clearInterval(interval);
|
||||
progressText.innerText = uploadMessage;
|
||||
setTimeout(function() {
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
}, 3000);
|
||||
Swal.fire({
|
||||
text: importMessage,
|
||||
icon: "success",
|
||||
showConfirmButton: false,
|
||||
timer: 2000,
|
||||
timerProgressBar: true,
|
||||
});
|
||||
setTimeout(function() {
|
||||
$('#projectImport').removeClass('oh-modal--show');
|
||||
location.reload(true);
|
||||
}, 2000);
|
||||
} else {
|
||||
width++;
|
||||
progressBar.style.width = width + '%';
|
||||
progressBar.setAttribute('aria-valuenow', width);
|
||||
progressText.innerText = uploadingMessage + width + '%';
|
||||
}
|
||||
}, 20);
|
||||
}
|
||||
)}
|
||||
|
||||
document.getElementById('projectImportForm').addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function(code){
|
||||
languageCode = code;
|
||||
var erroMessage = validationmessage[languageCode];
|
||||
|
||||
var fileInput = $('#projectImportFile').val();
|
||||
var allowedExtensions = /(\.xlsx)$/i;
|
||||
|
||||
if (!allowedExtensions.exec(fileInput)) {
|
||||
|
||||
var errorMessage = document.createElement('div');
|
||||
errorMessage.classList.add('error-message');
|
||||
|
||||
errorMessage.textContent = erroMessage;
|
||||
|
||||
document.getElementById('error-container').appendChild(errorMessage);
|
||||
|
||||
fileInput.value = '';
|
||||
|
||||
setTimeout(function() {
|
||||
errorMessage.remove();
|
||||
}, 2000);
|
||||
|
||||
return false;
|
||||
}
|
||||
else{
|
||||
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
|
||||
|
||||
simulateProgress();
|
||||
}
|
||||
|
||||
});
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,25 +1,25 @@
|
||||
$(document).ready(function(){
|
||||
$("#filter-project").keyup(function (e) {
|
||||
$(".project-view-type").attr("hx-vals", `{"search":"${$(this).val()}"}`);
|
||||
});
|
||||
$(".project-view-type").click(function (e) {
|
||||
let view = $(this).data("view");
|
||||
var currentURL = window.location.href;
|
||||
if (view != undefined){
|
||||
// Check if the query string already exists in the URL
|
||||
if (/\?view=[^&]+/.test(currentURL)) {
|
||||
// If the query parameter ?view exists, replace it with the new value
|
||||
newURL = currentURL.replace(/\?view=[^&]+/, "?view="+view);
|
||||
}
|
||||
else {
|
||||
// If the query parameter ?view does not exist, add it to the URL
|
||||
var separator = currentURL.includes('?') ? '&' : '?';
|
||||
newURL = currentURL + separator + "view="+view;
|
||||
}
|
||||
|
||||
history.pushState({}, "", newURL);
|
||||
$("#filter-project").attr("hx-vals", `{"view":"${view}"}`);
|
||||
$('#timesheetForm').attr("hx-vals", `{"view":"${view}"}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
$(document).ready(function(){
|
||||
$("#filter-project").keyup(function (e) {
|
||||
$(".project-view-type").attr("hx-vals", `{"search":"${$(this).val()}"}`);
|
||||
});
|
||||
$(".project-view-type").click(function (e) {
|
||||
let view = $(this).data("view");
|
||||
var currentURL = window.location.href;
|
||||
if (view != undefined){
|
||||
// Check if the query string already exists in the URL
|
||||
if (/\?view=[^&]+/.test(currentURL)) {
|
||||
// If the query parameter ?view exists, replace it with the new value
|
||||
newURL = currentURL.replace(/\?view=[^&]+/, "?view="+view);
|
||||
}
|
||||
else {
|
||||
// If the query parameter ?view does not exist, add it to the URL
|
||||
var separator = currentURL.includes('?') ? '&' : '?';
|
||||
newURL = currentURL + separator + "view="+view;
|
||||
}
|
||||
|
||||
history.pushState({}, "", newURL);
|
||||
$("#filter-project").attr("hx-vals", `{"view":"${view}"}`);
|
||||
$('#timesheetForm').attr("hx-vals", `{"view":"${view}"}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,138 +1,177 @@
|
||||
$(document).ready(function(){
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== "") {
|
||||
const cookies = document.cookie.split(";");
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
// for the search function
|
||||
$("#filter-task").keyup(function (e) {
|
||||
var search = $(this).val().toLowerCase();
|
||||
var view = $(this).data('view')
|
||||
if (view == 'list') {
|
||||
$('.task_row').each(function () {
|
||||
var task = $(this).data('task')
|
||||
if (task.includes(search)) {
|
||||
$(this).show();
|
||||
} else {
|
||||
$(this).hide();
|
||||
}
|
||||
})
|
||||
|
||||
} else {
|
||||
$('.task').each(function () {
|
||||
var task = $(this).data('task')
|
||||
if (task.includes(search)) {
|
||||
$(this).show();
|
||||
} else {
|
||||
$(this).hide();
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$('.task').mousedown(function(){
|
||||
window ['previous_task_id'] = $(this).attr('data-task-id')
|
||||
window ['previous_stage_id'] = $(this).parent().attr('data-stage-id')
|
||||
});
|
||||
|
||||
$(".tasks-container").on("DOMNodeInserted", function (e) {
|
||||
var updated_task_id = $(e.target).attr('data-task-id');
|
||||
var updated_stage_id = $(this).attr("data-stage-id");
|
||||
if (updated_task_id != null) {
|
||||
var new_seq = {}
|
||||
var task_container = $(this).children(".task")
|
||||
task_container.each(function(i, obj) {
|
||||
new_seq[$(obj).data('task-id')] = i
|
||||
});
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: '/project/drag-and-drop-task',
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
updated_task_id: updated_task_id,
|
||||
updated_stage_id : updated_stage_id,
|
||||
previous_task_id : previous_task_id,
|
||||
previous_stage_id : previous_stage_id,
|
||||
sequence:JSON.stringify(new_seq),
|
||||
},
|
||||
success: function(response){
|
||||
if (response.change) { // Check if the 'change' attribute in the response is True
|
||||
$("#ohMessages").append(`
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated oh-alert--${response.type}">
|
||||
${response.message}
|
||||
</div>
|
||||
</div>`);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
|
||||
$('.stage').mouseup(function(){
|
||||
window['previous_stage_id'] = $(this).attr('data-stage-id')
|
||||
window['previous_sequence'] = $(this).attr('data-sequence')
|
||||
setTimeout(function() {
|
||||
var new_seq = {}
|
||||
$('.stage').each(function(i, obj) {
|
||||
new_seq[$(obj).attr('data-stage-id')] = i
|
||||
});
|
||||
$.ajax({
|
||||
type: 'post',
|
||||
url: '/project/drag-and-drop-stage',
|
||||
data:{
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
sequence:JSON.stringify(new_seq),
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.change) { // Check if the 'change' attribute in the response is True
|
||||
$("#ohMessages").append(`
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated oh-alert--${response.type}">
|
||||
${response.message}
|
||||
</div>
|
||||
</div>`);
|
||||
}
|
||||
},
|
||||
})
|
||||
}, 100);
|
||||
})
|
||||
|
||||
$("#filter-task").keyup(function (e) {
|
||||
$(".task-view-type").attr("hx-vals", `{"search":"${$(this).val()}"}`);
|
||||
});
|
||||
$(".task-view-type").click(function (e) {
|
||||
let view = $(this).data("view");
|
||||
var currentURL = window.location.href;
|
||||
if (view != undefined){
|
||||
// Check if the query string already exists in the URL
|
||||
if (/\?view=[^&]+/.test(currentURL)) {
|
||||
// If the query parameter ?view exists, replace it with the new value
|
||||
newURL = currentURL.replace(/\?view=[^&]+/, "?view="+view);
|
||||
}
|
||||
else {
|
||||
// If the query parameter ?view does not exist, add it to the URL
|
||||
var separator = currentURL.includes('?') ? '&' : '?';
|
||||
newURL = currentURL + separator + "view=card";
|
||||
}
|
||||
|
||||
history.pushState({}, "", newURL);
|
||||
$("#filter-task").attr("hx-vals", `{"view":"${view}"}`);
|
||||
$('#timesheetForm').attr("hx-vals", `{"view":"${view}"}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
function toggleDropdown(id) {
|
||||
var element = document.getElementById('taskCreateForm' + id);
|
||||
element.classList.toggle('d-none');
|
||||
}
|
||||
|
||||
function updateStageClass(element) {
|
||||
const parent = $(element).parent();
|
||||
const isCollapsed = parent.hasClass('oh-kanban__section');
|
||||
const stageId = parent.data('stage-id'); // Get the data-stage-id attribute value
|
||||
const collapsedProjectStages = JSON.parse(localStorage.getItem('collapsedProjectStages')) || []; // Get the collapsed stages from localStorage or initialize an empty array
|
||||
|
||||
|
||||
// Toggle between class states
|
||||
parent.toggleClass('oh-kanban__section oh-kanban-group stage');
|
||||
parent.toggleClass('ml-2 stage ui-sortable-handle');
|
||||
|
||||
if (isCollapsed) {
|
||||
setTimeout(() => {
|
||||
parent.addClass('oh-kanban-card--collapsed stage');
|
||||
}, 100);
|
||||
if (!collapsedProjectStages.includes(stageId)) {
|
||||
collapsedProjectStages.push(stageId);
|
||||
localStorage.setItem('collapsedProjectStages', JSON.stringify(collapsedProjectStages));
|
||||
}
|
||||
} else {
|
||||
parent.removeClass('oh-kanban-card--collapsed');
|
||||
const index = collapsedProjectStages.indexOf(stageId);
|
||||
if (index > -1) {
|
||||
collapsedProjectStages.splice(index, 1); // Remove the stageId from the array
|
||||
localStorage.setItem('collapsedProjectStages', JSON.stringify(collapsedProjectStages)); // Update localStorage
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle task container visibility
|
||||
parent.find('.task-container').toggleClass('d-none');
|
||||
parent.find('.oh-kanban__head-actions').first().toggleClass('d-none');
|
||||
|
||||
}
|
||||
|
||||
function loadCollapsedProjectStages() {
|
||||
// Retrieve collapsed project stages from local storage
|
||||
let collapsedProjectStages = [];
|
||||
const collapsedProjectStagesData = localStorage.getItem('collapsedProjectStages');
|
||||
if (collapsedProjectStagesData) {
|
||||
try {
|
||||
// Parse the JSON only if it's a valid string
|
||||
collapsedProjectStages = JSON.parse(collapsedProjectStagesData);
|
||||
} catch (error) {
|
||||
console.error('Error parsing JSON from local storage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over collapsed project stages
|
||||
$.each(collapsedProjectStages, function (index, stageId) {
|
||||
const stageElement = $(`[data-stage-id='${stageId}']`);
|
||||
if (stageElement.length) {
|
||||
const groupHead = stageElement.find('.oh-kanban-group__head');
|
||||
if (groupHead.length) {
|
||||
updateStageClass(groupHead[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
let old_stage_seq = {}; // Define the old sequence of stages globally
|
||||
|
||||
loadCollapsedProjectStages();
|
||||
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== "") {
|
||||
const cookies = document.cookie.split(";");
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
$('.task-container').sortable({
|
||||
// Enables sortable functionality for task containers,
|
||||
// tracking drag-and-drop events and updating task sequences via AJAX.
|
||||
connectWith: ".task-container",
|
||||
start: function (event, ui) {
|
||||
var $draggedCard = ui.item;
|
||||
var $taskOldStage = $draggedCard.closest('.oh-kanban__section');
|
||||
taskOldStageId = $taskOldStage.data('stage-id'); // Store the task old stage ID
|
||||
window.$oldStageTaskCount = $taskOldStage.find('.task-count')
|
||||
},
|
||||
stop: function (event, ui) {
|
||||
var $draggedCard = ui.item;
|
||||
var $taskNewStage = $draggedCard.closest('.oh-kanban__section');
|
||||
var $newStageTaskCount = $taskNewStage.find('.task-count');
|
||||
var taskNewStageId = $taskNewStage.data('stage-id'); // Get the task new stage ID
|
||||
var taskId = $draggedCard.data('task-id');
|
||||
|
||||
// Check if the task has moved to a new stage
|
||||
if (taskNewStageId !== taskOldStageId) {
|
||||
var new_stage_seq = {};
|
||||
var task_container = $(this).children(".task");
|
||||
task_container.each(function (i, obj) {
|
||||
new_stage_seq[$(obj).data('task-id')] = i;
|
||||
});
|
||||
|
||||
// Update the task counts in old stage by -1
|
||||
var oldCount = parseInt(window.$oldStageTaskCount.html());
|
||||
window.$oldStageTaskCount.html(oldCount - 1);
|
||||
|
||||
// Increment the new stage task count by 1
|
||||
var newCount = parseInt($newStageTaskCount.html());
|
||||
$newStageTaskCount.html(newCount + 1);
|
||||
|
||||
// Trigger AJAX if the task has changed stages
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: '/project/drag-and-drop-task',
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
updated_task_id: taskId,
|
||||
updated_stage_id: taskNewStageId,
|
||||
previous_task_id: taskId,
|
||||
previous_stage_id: taskOldStageId,
|
||||
sequence: JSON.stringify(new_stage_seq),
|
||||
},
|
||||
success: function (response) {
|
||||
if (response.change === true) {
|
||||
$("#reloadMessagesButton").click();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('.stage').mousedown(function (e) {
|
||||
// Capture old sequence of stages on mousedown
|
||||
old_stage_seq = {};
|
||||
$('.stage').each(function (i, obj) {
|
||||
old_stage_seq[$(obj).attr('data-stage-id')] = i;
|
||||
});
|
||||
});
|
||||
|
||||
$('.stage').mouseup(function (e) {
|
||||
//For stage position rearrange event
|
||||
setTimeout(function () {
|
||||
var new_stage_seq = {};
|
||||
$('.stage').each(function (i, obj) {
|
||||
new_stage_seq[$(obj).attr('data-stage-id')] = i;
|
||||
});
|
||||
|
||||
// Compare old_stage_seq with new_stage_seq to trigger the ajax request
|
||||
if (JSON.stringify(old_stage_seq) !== JSON.stringify(new_stage_seq)) {
|
||||
$.ajax({
|
||||
type: 'post',
|
||||
url: '/project/drag-and-drop-stage',
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
sequence: JSON.stringify(new_stage_seq),
|
||||
},
|
||||
success: function (response) {
|
||||
if (response.change) {
|
||||
if (response.change === true) {
|
||||
$("#reloadMessagesButton").click();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,220 +1,371 @@
|
||||
var archiveMessages = {
|
||||
// ar: "هل ترغب حقًا في أرشفة جميع الموظفين المحددين؟",
|
||||
// de: "Möchten Sie wirklich alle ausgewählten Mitarbeiter archivieren?",
|
||||
// es: "¿Realmente quieres archivar a todos los empleados seleccionados?",
|
||||
en: "Do you really want to archive all the selected tasks?",
|
||||
// fr: "Voulez-vous vraiment archiver tous les employés sélectionnés ?",
|
||||
};
|
||||
|
||||
var unarchiveMessages = {
|
||||
// ar: "هل ترغب حقًا في إلغاء أرشفة جميع الموظفين المحددين؟",
|
||||
// de: "Möchten Sie wirklich alle ausgewählten Mitarbeiter aus der Archivierung zurückholen?",
|
||||
// es: "¿Realmente quieres desarchivar a todos los empleados seleccionados?",
|
||||
en: "Do you really want to unarchive all the selected tasks?",
|
||||
// fr: "Voulez-vous vraiment désarchiver tous les employés sélectionnés?",
|
||||
};
|
||||
|
||||
var deleteMessages = {
|
||||
// ar: "هل ترغب حقًا في حذف جميع الموظفين المحددين؟",
|
||||
// de: "Möchten Sie wirklich alle ausgewählten Mitarbeiter löschen?",
|
||||
// es: "¿Realmente quieres eliminar a todos los empleados seleccionados?",
|
||||
en: "Do you really want to delete all the selected tasks?",
|
||||
// fr: "Voulez-vous vraiment supprimer tous les employés sélectionnés?",
|
||||
};
|
||||
|
||||
var norowMessages = {
|
||||
// ar: "لم يتم تحديد أي صفوف.",
|
||||
// de: "Es wurden keine Zeilen ausgewählt.",
|
||||
// es: "No se han seleccionado filas.",
|
||||
en: "No rows have been selected.",
|
||||
// fr: "Aucune ligne n'a été sélectionnée.",
|
||||
};
|
||||
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== "") {
|
||||
const cookies = document.cookie.split(";");
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
function getCurrentLanguageCode(callback) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/employee/get-language-code/",
|
||||
success: function (response) {
|
||||
var languageCode = response.language_code;
|
||||
callback(languageCode); // Pass the language code to the callback
|
||||
},
|
||||
});
|
||||
}
|
||||
$(".all-task-all").change(function (e) {
|
||||
var is_checked = $(this).is(":checked");
|
||||
if (is_checked) {
|
||||
$(".all-task-all-row").prop("checked", true);
|
||||
} else {
|
||||
$(".all-task-all-row").prop("checked", false);
|
||||
}
|
||||
});
|
||||
|
||||
$("#archiveTaskAll").click(function (e) {
|
||||
e.preventDefault();
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var confirmMessage = archiveMessages[languageCode];
|
||||
var textMessage = norowMessages[languageCode];
|
||||
var checkedRows = $(".all-task-all-row").filter(":checked");
|
||||
if (checkedRows.length === 0) {
|
||||
Swal.fire({
|
||||
text: textMessage,
|
||||
icon: "warning",
|
||||
confirmButtonText: "Close",
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
text: confirmMessage,
|
||||
icon: "info",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#008000",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Confirm",
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
e.preventDefault();
|
||||
ids = [];
|
||||
checkedRows.each(function () {
|
||||
ids.push($(this).attr("id"));
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/project/task-all-bulk-archive?is_active=False",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
ids: JSON.stringify(ids),
|
||||
},
|
||||
success: function (response, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 200) {
|
||||
location.reload(); // Reload the current page
|
||||
} else {
|
||||
// console.log("Unexpected HTTP status:", jqXHR.status);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$("#unArchiveTaskAll").click(function (e) {
|
||||
e.preventDefault();
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var confirmMessage = unarchiveMessages[languageCode];
|
||||
var textMessage = norowMessages[languageCode];
|
||||
var checkedRows = $(".all-task-all-row").filter(":checked");
|
||||
if (checkedRows.length === 0) {
|
||||
Swal.fire({
|
||||
text: textMessage,
|
||||
icon: "warning",
|
||||
confirmButtonText: "Close",
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
text: confirmMessage,
|
||||
icon: "info",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#008000",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Confirm",
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
var checkedRows = $(".all-task-all-row").filter(":checked");
|
||||
ids = [];
|
||||
checkedRows.each(function () {
|
||||
ids.push($(this).attr("id"));
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/project/task-all-bulk-archive?is_active=True",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
ids: JSON.stringify(ids),
|
||||
},
|
||||
success: function (response, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 200) {
|
||||
location.reload(); // Reload the current page
|
||||
} else {
|
||||
// console.log("Unexpected HTTP status:", jqXHR.status);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#deleteTaskAll").click(function (e) {
|
||||
e.preventDefault();
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var confirmMessage = deleteMessages[languageCode];
|
||||
var textMessage = norowMessages[languageCode];
|
||||
var checkedRows = $(".all-task-all-row").filter(":checked");
|
||||
if (checkedRows.length === 0) {
|
||||
Swal.fire({
|
||||
text: textMessage,
|
||||
icon: "warning",
|
||||
confirmButtonText: "Close",
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
text: confirmMessage,
|
||||
icon: "error",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#008000",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Confirm",
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
var checkedRows = $(".all-task-all-row").filter(":checked");
|
||||
ids = [];
|
||||
checkedRows.each(function () {
|
||||
ids.push($(this).attr("id"));
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/project/task-all-bulk-delete",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
ids: JSON.stringify(ids),
|
||||
},
|
||||
success: function (response, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 200) {
|
||||
location.reload(); // Reload the current page
|
||||
} else {
|
||||
// console.log("Unexpected HTTP status:", jqXHR.status);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
var archiveMessage = {
|
||||
// ar: "هل ترغب حقًا في أرشفة جميع الموظفين المحددين؟",
|
||||
// de: "Möchten Sie wirklich alle ausgewählten Mitarbeiter archivieren?",
|
||||
// es: "¿Realmente quieres archivar a todos los empleados seleccionados?",
|
||||
en: "Do you really want to archive all the selected tasks?",
|
||||
// fr: "Voulez-vous vraiment archiver tous les employés sélectionnés ?",
|
||||
};
|
||||
|
||||
var unarchiveMessage = {
|
||||
// ar: "هل ترغب حقًا في إلغاء أرشفة جميع الموظفين المحددين؟",
|
||||
// de: "Möchten Sie wirklich alle ausgewählten Mitarbeiter aus der Archivierung zurückholen?",
|
||||
// es: "¿Realmente quieres desarchivar a todos los empleados seleccionados?",
|
||||
en: "Do you really want to unarchive all the selected tasks?",
|
||||
// fr: "Voulez-vous vraiment désarchiver tous les employés sélectionnés?",
|
||||
};
|
||||
|
||||
var deleteMessage = {
|
||||
// ar: "هل ترغب حقًا في حذف جميع الموظفين المحددين؟",
|
||||
// de: "Möchten Sie wirklich alle ausgewählten Mitarbeiter löschen?",
|
||||
// es: "¿Realmente quieres eliminar a todos los empleados seleccionados?",
|
||||
en: "Do you really want to delete all the selected tasks?",
|
||||
// fr: "Voulez-vous vraiment supprimer tous les employés sélectionnés?",
|
||||
};
|
||||
|
||||
var norowMessage = {
|
||||
// ar: "لم يتم تحديد أي صفوف.",
|
||||
// de: "Es wurden keine Zeilen ausgewählt.",
|
||||
// es: "No se han seleccionado filas.",
|
||||
en: "No rows have been selected.",
|
||||
// fr: "Aucune ligne n'a été sélectionnée.",
|
||||
};
|
||||
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== "") {
|
||||
const cookies = document.cookie.split(";");
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
function getCurrentLanguageCode(callback) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/employee/get-language-code/",
|
||||
success: function (response) {
|
||||
var languageCode = response.language_code;
|
||||
callback(languageCode); // Pass the language code to the callback
|
||||
},
|
||||
});
|
||||
}
|
||||
$(".all-task-all").change(function (e) {
|
||||
var is_checked = $(this).is(":checked");
|
||||
if (is_checked) {
|
||||
$(".all-task-all-row").prop("checked", true);
|
||||
} else {
|
||||
$(".all-task-all-row").prop("checked", false);
|
||||
}
|
||||
});
|
||||
|
||||
$("#archiveTaskAll").click(function (e) {
|
||||
e.preventDefault();
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var confirmMessage = archiveMessage[languageCode];
|
||||
var textMessage = norowMessage[languageCode];
|
||||
var checkedRows = $(".all-task-all-row").filter(":checked");
|
||||
if (checkedRows.length === 0) {
|
||||
Swal.fire({
|
||||
text: textMessage,
|
||||
icon: "warning",
|
||||
confirmButtonText: "Close",
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
text: confirmMessage,
|
||||
icon: "info",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#008000",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Confirm",
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
e.preventDefault();
|
||||
ids = [];
|
||||
checkedRows.each(function () {
|
||||
ids.push($(this).attr("id"));
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/project/task-all-bulk-archive?is_active=False",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
ids: JSON.stringify(ids),
|
||||
},
|
||||
success: function (response, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 200) {
|
||||
location.reload(); // Reload the current page
|
||||
} else {
|
||||
// console.log("Unexpected HTTP status:", jqXHR.status);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//Bulk archive
|
||||
|
||||
$(document).on('click', '#archiveTask', function (e) {
|
||||
e.preventDefault();
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var confirmMessage = archiveMessage[languageCode];
|
||||
var textMessage = norowMessage[languageCode];
|
||||
ids = [];
|
||||
ids.push($("#selectedInstances").attr("data-ids"));
|
||||
ids = JSON.parse($("#selectedInstances").attr("data-ids"));
|
||||
if (ids.length === 0) {
|
||||
Swal.fire({
|
||||
text: textMessage,
|
||||
icon: "warning",
|
||||
confirmButtonText: "Close",
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
text: confirmMessage,
|
||||
icon: "info",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#008000",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Confirm",
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/project/task-all-bulk-archive?is_active=False",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
ids: JSON.stringify(ids),
|
||||
},
|
||||
success: function (response, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 200) {
|
||||
location.reload();
|
||||
} else {
|
||||
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
$("#unArchiveTaskAll").click(function (e) {
|
||||
e.preventDefault();
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var confirmMessage = unarchiveMessage[languageCode];
|
||||
var textMessage = norowMessage[languageCode];
|
||||
var checkedRows = $(".all-task-all-row").filter(":checked");
|
||||
if (checkedRows.length === 0) {
|
||||
Swal.fire({
|
||||
text: textMessage,
|
||||
icon: "warning",
|
||||
confirmButtonText: "Close",
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
text: confirmMessage,
|
||||
icon: "info",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#008000",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Confirm",
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
var checkedRows = $(".all-task-all-row").filter(":checked");
|
||||
ids = [];
|
||||
checkedRows.each(function () {
|
||||
ids.push($(this).attr("id"));
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/project/task-all-bulk-archive?is_active=True",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
ids: JSON.stringify(ids),
|
||||
},
|
||||
success: function (response, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 200) {
|
||||
location.reload(); // Reload the current page
|
||||
} else {
|
||||
// console.log("Unexpected HTTP status:", jqXHR.status);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//Bulk unarchive
|
||||
|
||||
$(document).on('click', '#unArchiveTask', function (e) {
|
||||
e.preventDefault();
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var confirmMessage = unarchiveMessage[languageCode];
|
||||
var textMessage = norowMessage[languageCode];
|
||||
ids = [];
|
||||
ids.push($("#selectedInstances").attr("data-ids"));
|
||||
ids = JSON.parse($("#selectedInstances").attr("data-ids"));
|
||||
if (ids.length === 0) {
|
||||
Swal.fire({
|
||||
text: textMessage,
|
||||
icon: "warning",
|
||||
confirmButtonText: "Close",
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
text: confirmMessage,
|
||||
icon: "info",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#008000",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Confirm",
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/project/task-all-bulk-archive?is_active=True",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
ids: JSON.stringify(ids),
|
||||
},
|
||||
success: function (response, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 200) {
|
||||
location.reload();
|
||||
} else {
|
||||
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#deleteTaskAll").click(function (e) {
|
||||
e.preventDefault();
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var confirmMessage = deleteMessage[languageCode];
|
||||
var textMessage = norowMessage[languageCode];
|
||||
var checkedRows = $(".all-task-all-row").filter(":checked");
|
||||
if (checkedRows.length === 0) {
|
||||
Swal.fire({
|
||||
text: textMessage,
|
||||
icon: "warning",
|
||||
confirmButtonText: "Close",
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
text: confirmMessage,
|
||||
icon: "error",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#008000",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Confirm",
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
var checkedRows = $(".all-task-all-row").filter(":checked");
|
||||
ids = [];
|
||||
checkedRows.each(function () {
|
||||
ids.push($(this).attr("id"));
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/project/task-all-bulk-delete",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
ids: JSON.stringify(ids),
|
||||
},
|
||||
success: function (response, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 200) {
|
||||
location.reload(); // Reload the current page
|
||||
} else {
|
||||
// console.log("Unexpected HTTP status:", jqXHR.status);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//Bulk delete
|
||||
|
||||
$(document).on('click', '#deleteTask', function (e) {
|
||||
e.preventDefault();
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var confirmMessage = deleteMessage[languageCode];
|
||||
var textMessage = norowMessage[languageCode];
|
||||
ids = [];
|
||||
ids.push($("#selectedInstances").attr("data-ids"));
|
||||
ids = JSON.parse($("#selectedInstances").attr("data-ids"));
|
||||
if (ids.length === 0) {
|
||||
Swal.fire({
|
||||
text: textMessage,
|
||||
icon: "warning",
|
||||
confirmButtonText: "Close",
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
text: confirmMessage,
|
||||
icon: "info",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#008000",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Confirm",
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/project/task-all-bulk-delete",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
ids: JSON.stringify(ids),
|
||||
},
|
||||
success: function (response, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 200) {
|
||||
location.reload();
|
||||
} else {
|
||||
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,118 +1,172 @@
|
||||
var archiveMessages = {
|
||||
// ar: "هل ترغب حقًا في أرشفة جميع الموظفين المحددين؟",
|
||||
// de: "Möchten Sie wirklich alle ausgewählten Mitarbeiter archivieren?",
|
||||
// es: "¿Realmente quieres archivar a todos los empleados seleccionados?",
|
||||
en: "Do you really want to archive all the selected timesheet?",
|
||||
// fr: "Voulez-vous vraiment archiver tous les employés sélectionnés ?",
|
||||
};
|
||||
|
||||
var unarchiveMessages = {
|
||||
// ar: "هل ترغب حقًا في إلغاء أرشفة جميع الموظفين المحددين؟",
|
||||
// de: "Möchten Sie wirklich alle ausgewählten Mitarbeiter aus der Archivierung zurückholen?",
|
||||
// es: "¿Realmente quieres desarchivar a todos los empleados seleccionados?",
|
||||
en: "Do you really want to unarchive all the selected timesheet?",
|
||||
// fr: "Voulez-vous vraiment désarchiver tous les employés sélectionnés?",
|
||||
};
|
||||
|
||||
var deleteMessages = {
|
||||
// ar: "هل ترغب حقًا في حذف جميع الموظفين المحددين؟",
|
||||
// de: "Möchten Sie wirklich alle ausgewählten Mitarbeiter löschen?",
|
||||
// es: "¿Realmente quieres eliminar a todos los empleados seleccionados?",
|
||||
en: "Do you really want to delete all the selected timesheet?",
|
||||
// fr: "Voulez-vous vraiment supprimer tous les employés sélectionnés?",
|
||||
};
|
||||
|
||||
var norowMessages = {
|
||||
// ar: "لم يتم تحديد أي صفوف.",
|
||||
// de: "Es wurden keine Zeilen ausgewählt.",
|
||||
// es: "No se han seleccionado filas.",
|
||||
en: "No rows have been selected.",
|
||||
// fr: "Aucune ligne n'a été sélectionnée.",
|
||||
};
|
||||
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== "") {
|
||||
const cookies = document.cookie.split(";");
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
function getCurrentLanguageCode(callback) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/employee/get-language-code/",
|
||||
success: function (response) {
|
||||
var languageCode = response.language_code;
|
||||
callback(languageCode); // Pass the language code to the callback
|
||||
},
|
||||
});
|
||||
}
|
||||
$(".all-time-sheet").change(function (e) {
|
||||
var is_checked = $(this).is(":checked");
|
||||
if (is_checked) {
|
||||
$(".all-time-sheet-row").prop("checked", true);
|
||||
} else {
|
||||
$(".all-time-sheet-row").prop("checked", false);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$("#deleteTimeSheet").click(function (e) {
|
||||
e.preventDefault();
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var confirmMessage = deleteMessages[languageCode];
|
||||
var textMessage = norowMessages[languageCode];
|
||||
var checkedRows = $(".all-time-sheet-row").filter(":checked");
|
||||
if (checkedRows.length === 0) {
|
||||
Swal.fire({
|
||||
text: textMessage,
|
||||
icon: "warning",
|
||||
confirmButtonText: "Close",
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
text: confirmMessage,
|
||||
icon: "error",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#008000",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Confirm",
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
var checkedRows = $(".all-time-sheet-row").filter(":checked");
|
||||
ids = [];
|
||||
checkedRows.each(function () {
|
||||
ids.push($(this).attr("id"));
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/project/time-sheet-bulk-delete",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
ids: JSON.stringify(ids),
|
||||
},
|
||||
success: function (response, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 200) {
|
||||
location.reload(); // Reload the current page
|
||||
} else {
|
||||
// console.log("Unexpected HTTP status:", jqXHR.status);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var archiveMessages = {
|
||||
// ar: "هل ترغب حقًا في أرشفة جميع الموظفين المحددين؟",
|
||||
// de: "Möchten Sie wirklich alle ausgewählten Mitarbeiter archivieren?",
|
||||
// es: "¿Realmente quieres archivar a todos los empleados seleccionados?",
|
||||
en: "Do you really want to archive all the selected timesheet?",
|
||||
// fr: "Voulez-vous vraiment archiver tous les employés sélectionnés ?",
|
||||
};
|
||||
|
||||
var unarchiveMessages = {
|
||||
// ar: "هل ترغب حقًا في إلغاء أرشفة جميع الموظفين المحددين؟",
|
||||
// de: "Möchten Sie wirklich alle ausgewählten Mitarbeiter aus der Archivierung zurückholen?",
|
||||
// es: "¿Realmente quieres desarchivar a todos los empleados seleccionados?",
|
||||
en: "Do you really want to unarchive all the selected timesheet?",
|
||||
// fr: "Voulez-vous vraiment désarchiver tous les employés sélectionnés?",
|
||||
};
|
||||
|
||||
var deleteMessagesBulk = {
|
||||
// ar: "هل ترغب حقًا في حذف جميع الموظفين المحددين؟",
|
||||
// de: "Möchten Sie wirklich alle ausgewählten Mitarbeiter löschen?",
|
||||
// es: "¿Realmente quieres eliminar a todos los empleados seleccionados?",
|
||||
en: "Do you really want to delete all the selected timesheet?",
|
||||
// fr: "Voulez-vous vraiment supprimer tous les employés sélectionnés?",
|
||||
};
|
||||
|
||||
var norowMessages = {
|
||||
// ar: "لم يتم تحديد أي صفوف.",
|
||||
// de: "Es wurden keine Zeilen ausgewählt.",
|
||||
// es: "No se han seleccionado filas.",
|
||||
en: "No rows have been selected.",
|
||||
// fr: "Aucune ligne n'a été sélectionnée.",
|
||||
};
|
||||
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== "") {
|
||||
const cookies = document.cookie.split(";");
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
function getCurrentLanguageCode(callback) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/employee/get-language-code/",
|
||||
success: function (response) {
|
||||
var languageCode = response.language_code;
|
||||
callback(languageCode); // Pass the language code to the callback
|
||||
},
|
||||
});
|
||||
}
|
||||
$(".all-time-sheet").change(function (e) {
|
||||
var is_checked = $(this).is(":checked");
|
||||
if (is_checked) {
|
||||
$(".all-time-sheet-row").prop("checked", true);
|
||||
} else {
|
||||
$(".all-time-sheet-row").prop("checked", false);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$("#deleteTimeSheet").click(function (e) {
|
||||
e.preventDefault();
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var confirmMessage = deleteMessagesBulk[languageCode];
|
||||
var textMessage = norowMessages[languageCode];
|
||||
var checkedRows = $(".all-time-sheet-row").filter(":checked");
|
||||
if (checkedRows.length === 0) {
|
||||
Swal.fire({
|
||||
text: textMessage,
|
||||
icon: "warning",
|
||||
confirmButtonText: "Close",
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
text: confirmMessage,
|
||||
icon: "error",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#008000",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Confirm",
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
var checkedRows = $(".all-time-sheet-row").filter(":checked");
|
||||
ids = [];
|
||||
checkedRows.each(function () {
|
||||
ids.push($(this).attr("id"));
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/project/time-sheet-bulk-delete",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
ids: JSON.stringify(ids),
|
||||
},
|
||||
success: function (response, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 200) {
|
||||
location.reload(); // Reload the current page
|
||||
} else {
|
||||
// console.log("Unexpected HTTP status:", jqXHR.status);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function deleteTimeSheet(){
|
||||
var languageCode = null;
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var confirmMessage = deleteMessagesBulk[languageCode];
|
||||
var textMessage = norowMessages[languageCode];
|
||||
ids = [];
|
||||
ids.push($("#selectedInstances").attr("data-ids"));
|
||||
ids = JSON.parse($("#selectedInstances").attr("data-ids"));
|
||||
if (ids.length === 0) {
|
||||
Swal.fire({
|
||||
text: textMessage,
|
||||
icon: "warning",
|
||||
confirmButtonText: "Close",
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
text: confirmMessage,
|
||||
icon: "error",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#008000",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Confirm",
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
// var checkedRows = $(".all-time-sheet-row").filter(":checked");
|
||||
// ids = [];
|
||||
// checkedRows.each(function () {
|
||||
// ids.push($(this).attr("id"));
|
||||
// });
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/project/time-sheet-bulk-delete",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
ids: JSON.stringify(ids),
|
||||
},
|
||||
success: function (response, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 200) {
|
||||
location.reload(); // Reload the current page
|
||||
} else {
|
||||
// console.log("Unexpected HTTP status:", jqXHR.status);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
70
project/templates/cbv/projects/actions.html
Normal file
70
project/templates/cbv/projects/actions.html
Normal file
@@ -0,0 +1,70 @@
|
||||
{% load i18n %}
|
||||
{% load taskfilters %}
|
||||
<div class="oh-btn-group">
|
||||
{% if perms.project.change_project or request.user|is_project_manager:instance %}
|
||||
<a
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal"
|
||||
hx-get="{% url 'update-project' instance.id %}"
|
||||
hx-target="#genericModalBody"
|
||||
onclick="event.stopPropagation();"
|
||||
>
|
||||
<ion-icon
|
||||
name="create-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
style="color: blue"
|
||||
aria-label="create outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
{% else %}
|
||||
<button
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
disabled
|
||||
>
|
||||
<ion-icon
|
||||
name="create-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
style="color: blue"
|
||||
aria-label="create outline"
|
||||
></ion-icon>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if perms.project.delete_project or request.user|is_project_manager:instance %}
|
||||
{% if instance.is_active %}
|
||||
<a
|
||||
class="oh-btn oh-btn--light-bkg w-100"
|
||||
href="{{instance.get_archive_url}}"
|
||||
onclick="return confirm('Do you want to {{instance.archive_status}} this project?')"
|
||||
title="{% trans 'Archive' %}"
|
||||
>
|
||||
<ion-icon name="archive"></ion-icon>
|
||||
</a>
|
||||
{% else %}
|
||||
<a
|
||||
class="oh-btn oh-btn--light-bkg w-100"
|
||||
href="{{instance.get_archive_url}}"
|
||||
onclick="return confirm('Do you want to {{instance.archive_status}} this project?')"
|
||||
title="{% trans 'Un-Archive' %}"
|
||||
>
|
||||
<ion-icon name="archive"></ion-icon>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if perms.project.delete_project %}
|
||||
<a
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
href="{% url 'delete-project' instance.id %}?view=list"
|
||||
onclick="event.preventDefault();event.stopPropagation(); confirm(`{% trans 'Do you want to delete this project?' %}`)"
|
||||
>
|
||||
<ion-icon
|
||||
name="trash-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="trash outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
40
project/templates/cbv/projects/filter.html
Normal file
40
project/templates/cbv/projects/filter.html
Normal file
@@ -0,0 +1,40 @@
|
||||
{% load i18n%}
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header"
|
||||
onclick="event.stopImmediatePropagation();$(this).parent().toggleClass('oh-accordion--show');"
|
||||
>{% trans "Project" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Manager" %}</label>
|
||||
{{form.managers}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Status" %}</label>
|
||||
{{form.status}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Start from" %}</label>
|
||||
{{form.start_from}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Members" %}</label>
|
||||
{{form.members}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Active" %}</label>
|
||||
{{form.is_active}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "End till" %}</label>
|
||||
{{form.end_till}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
125
project/templates/cbv/projects/project_details.html
Normal file
125
project/templates/cbv/projects/project_details.html
Normal file
@@ -0,0 +1,125 @@
|
||||
{% load mathfilters %}
|
||||
{% load static i18n %}
|
||||
{% load taskfilters %}
|
||||
<div class="oh-card">
|
||||
<div class="oh-helpdesk__header">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<ul class="helpdesk__card-items">
|
||||
<li class="helpdesk__card-item">
|
||||
<span class="helpdesk__card-label">{% trans "Project:" %}</span>
|
||||
<span class="helpdesk__card-value b" name="" id="">{{project}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<span class="helpdesk__card-value me-3" style="display:flex">
|
||||
{% if perms.pms.change_project or request.user|is_project_manager_or_member:project %}
|
||||
<select id="status" class="oh-select oh-select--sm ms-3 mr-1 " name="status" title="Status"
|
||||
hx-post="{%url 'change-project-status' project.id %}"
|
||||
hx-trigger="change" hx-target="#reloadMessages">
|
||||
<option value="{{project.status}}" selected>
|
||||
{% trans project.get_status_display %}
|
||||
</option>
|
||||
{%for status in project.PROJECT_STATUS %}
|
||||
{% if project.status != status.0 %}
|
||||
<option value="{{status.0}}">{% trans status.1 %}</option>
|
||||
{% endif%}
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<label id="status" class="oh-select oh-select--sm ms-3" name="status" title="Status">
|
||||
{% trans project.get_status_display %}
|
||||
</label>
|
||||
{% endif %}
|
||||
{% if perms.project.change_project or perms.project.add_projectstage or request.user|is_project_manager_or_member:project %}
|
||||
<div class="oh-btn-group">
|
||||
{% if perms.project.add_project %}
|
||||
<a
|
||||
onclick="event.preventDefault();event.stopPropagation()"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#objectCreateModal"
|
||||
hx-get="{% url 'update-project' project.id %}"
|
||||
hx-target="#objectCreateModalTarget"
|
||||
class="oh-btn oh-btn--light-bkg w-100"
|
||||
title="{% trans 'Edit' %}"
|
||||
><ion-icon name="create-outline"></ion-icon
|
||||
></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ul class="helpdesk__card-items">
|
||||
<li class="helpdesk__card-item">
|
||||
<span class="helpdesk__card-label">{% trans "Managers:" %}</span>
|
||||
<span class="helpdesk__card-value" name="" id="">
|
||||
<div class="d-flex justify-content-between custom-scroll">
|
||||
<div class="avatars" id="avatarsContainer">
|
||||
{% for manager in project.managers.all %}
|
||||
<a
|
||||
href="{% url 'employee-view-individual' manager.id %}"
|
||||
class="avatars__item"
|
||||
title="{{manager}}"
|
||||
><img class="avatar" src="{{manager.get_avatar}}" alt=""
|
||||
/></a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</li>
|
||||
<li class="helpdesk__card-item">
|
||||
<span class="helpdesk__card-label">{% trans "Members:" %}</span>
|
||||
<span class="helpdesk__card-value" name="" id="">
|
||||
<div class="d-flex justify-content-between custom-scroll">
|
||||
<div class="avatars" id="avatarsContainer">
|
||||
{% for menber in project.members.all %}
|
||||
<a
|
||||
href="{% url 'employee-view-individual' menber.id %}"
|
||||
class="avatars__item"
|
||||
title="{{menber}}"
|
||||
><img class="avatar" src="{{menber.get_avatar}}" alt=""
|
||||
/></a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</li>
|
||||
<li class="helpdesk__card-item">
|
||||
<span class="helpdesk__card-label">{% trans "Start date:" %}</span>
|
||||
<span class="helpdesk__card-value" name="" id=""
|
||||
>{{project.start_date}}
|
||||
</span
|
||||
>
|
||||
</li>
|
||||
<li class="helpdesk__card-item">
|
||||
<span class="helpdesk__card-label">{% trans "End date:" %}</span>
|
||||
<span class="helpdesk__card-value" name="" id=""
|
||||
>{{project.end_date}}
|
||||
</span
|
||||
>
|
||||
<span title = 'due {% if project.end_date == today %} today {% else %}in {{project.end_date|sub:today}}{% endif %}'>
|
||||
<ion-icon
|
||||
class="text-{% if project.end_date < today %}danger {% elif project.end_date == today %}warning {% else %}success{% endif %}"
|
||||
name="time-sharp"
|
||||
>
|
||||
</ion-icon>
|
||||
</span>
|
||||
</li>
|
||||
<li class="helpdesk__card-item">
|
||||
<div class="d-flex">
|
||||
<div>
|
||||
<span class="helpdesk__card-label">{% trans "Description:" %}</span>
|
||||
</div>
|
||||
<details>
|
||||
<summary>
|
||||
</summary>
|
||||
<p>
|
||||
{{project.description}}
|
||||
</p>
|
||||
</details>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
38
project/templates/cbv/projects/project_list.html
Normal file
38
project/templates/cbv/projects/project_list.html
Normal file
@@ -0,0 +1,38 @@
|
||||
{% load basefilters i18n %}
|
||||
{% for project in projects %}
|
||||
<div class="oh-user_permission-list_item perm-accordion exclude-accordion-style" onclick="$(this).next().toggle();$(this).toggleClass('perm-accordion-active');$(this).next().find('.oh-general__tab-target').removeClass('d-none');">
|
||||
<div class="oh-user_permission-list_profile">
|
||||
<div class="oh-navbar__user-photo oh-user_permission--profile">
|
||||
<img src="{{ project.get_avatar }}" class="oh-navbar__user-image" loading="lazy" />
|
||||
</div>
|
||||
<div class="oh-feedback-card__name-container ms-1" hx-target="#genericModalBody" data-target="#genericModal" data-toggle="oh-modal-toggle" hx-get="{% url 'project-detailed-view' project.id %}">
|
||||
<span class="oh-card__title oh-card__title--sm fw-bold me-1">{{ project.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex d-flex-reverse">
|
||||
<button
|
||||
onclick = "event.stopPropagation();"
|
||||
class="oh-btn "
|
||||
title="{% trans 'View Project' %}"
|
||||
hx-target="#genericModalBody"
|
||||
data-target="#genericModal"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-get="{% url 'project-detailed-view' project.id %}"
|
||||
|
||||
><ion-icon
|
||||
name="eye-outline"
|
||||
role="img"
|
||||
></ion-icon
|
||||
></button>
|
||||
|
||||
<button class="oh-accordion-meta__btn oh-user_permssion-dropdownbtn"><ion-icon class="ms-2 oh-accordion-meta__btn-icon md hydrated" name="caret-down-outline" role="img" aria-label="caret down outline"></ion-icon></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel view-employees" id="panel{{ employee.id }}" data-user-id="{{ employee.id }}">
|
||||
<div class="oh-general__tab-target oh-profile-section">
|
||||
<div hx-get="{% url 'tasks-list-individual-view' %}?employee_id={{ employee.id }}&project_id={{ project.id }}" hx-trigger="load">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
9
project/templates/cbv/projects/project_nav.html
Normal file
9
project/templates/cbv/projects/project_nav.html
Normal file
@@ -0,0 +1,9 @@
|
||||
{% load i18n %}
|
||||
<div id="stage-container">
|
||||
{% include "generic/horilla_nav.html" %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('#applyFilter').closest('form').find('[name=is_active]').val('true');
|
||||
$('#applyFilter').click();
|
||||
</script>
|
||||
19
project/templates/cbv/projects/project_tab.html
Normal file
19
project/templates/cbv/projects/project_tab.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{% load i18n %} {% load static %}
|
||||
<div id="messages" class="oh-alert-container"></div>
|
||||
{% if projects %}
|
||||
<div
|
||||
class="oh-inner-sidebar-content__header d-flex justify-content-between align-items-center"
|
||||
>
|
||||
<h2 class="oh-inner-sidebar-content__title">{% trans "Projects" %}</h2>
|
||||
</div>
|
||||
<div id="projectContainer">
|
||||
{% include "cbv/projects/project_list.html" %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 100px;margin: 10px auto ;" src="{% static 'images/ui/project/project.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">{% trans "No projects assigned to this employee." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
149
project/templates/cbv/projects/projects.html
Normal file
149
project/templates/cbv/projects/projects.html
Normal file
@@ -0,0 +1,149 @@
|
||||
{% extends "index.html" %}
|
||||
{% load i18n %}{% load static %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
.expired--dot{
|
||||
background-color:gray;
|
||||
}
|
||||
.cancelled--dot{
|
||||
background-color:red;
|
||||
}
|
||||
.on-hold--dot{
|
||||
background-color:orange;
|
||||
}
|
||||
.completed--dot{
|
||||
background-color:yellowgreen;
|
||||
}
|
||||
.in-progress--dot{
|
||||
background-color: rgb(103, 171, 238);
|
||||
}
|
||||
.new--dot{
|
||||
background-color: cyan;
|
||||
}
|
||||
.status-completed{
|
||||
border-left: 3.4px solid yellowgreen !important;
|
||||
border-radius: 4px 0 0 4px;
|
||||
|
||||
}
|
||||
.status-cancelled {
|
||||
|
||||
border-left: 3.4px solid red !important;
|
||||
border-radius: 4px 0 0 4px;
|
||||
|
||||
}
|
||||
.status-expired {
|
||||
|
||||
border-left: 3.4px solid gray !important;
|
||||
border-radius: 4px 0 0 4px;
|
||||
|
||||
}
|
||||
.status-on_hold {
|
||||
|
||||
border-left: 3.4px solid orange !important;
|
||||
border-radius: 4px 0 0 4px;
|
||||
|
||||
}
|
||||
.status-in_progress {
|
||||
|
||||
border-left: 3.4px solid rgb(103, 171, 238); !important;
|
||||
border-radius: 4px 0 0 4px;
|
||||
|
||||
}
|
||||
.status-new {
|
||||
|
||||
border-left: 3.4px solid cyan !important;
|
||||
border-radius: 4px 0 0 4px;
|
||||
|
||||
}
|
||||
</style>
|
||||
<meta name="csrf-token" content="{{ csrf_token }}">
|
||||
|
||||
|
||||
|
||||
<div hx-get="{% url 'project-nav-view' %}?{{request.GET.urlencode}}" hx-trigger="load">
|
||||
</div>
|
||||
|
||||
|
||||
{% include "generic/components.html" %}
|
||||
|
||||
|
||||
<div
|
||||
class="oh-checkpoint-badge mb-2"
|
||||
id="selectedInstances"
|
||||
data-ids="[]"
|
||||
data-clicked=""
|
||||
style="display: none"
|
||||
></div>
|
||||
|
||||
|
||||
<div
|
||||
class="oh-wrapper"
|
||||
id="listContainer">
|
||||
<div class="animated-background">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="projectImport"
|
||||
role="dialog"
|
||||
aria-labelledby="projectImport"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="oh-modal__dialog">
|
||||
<div class="oh-modal__dialog-header">
|
||||
<h2 class="oh-modal__dialog-title" id="projectImportLavel">
|
||||
{% trans "Import Project" %}
|
||||
</h2>
|
||||
<button class="oh-modal__close" aria-label="Close">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-modal__dialog-body p-0 pt-2" id="projectImportModalBody">
|
||||
<form
|
||||
|
||||
id="projectImportForm"
|
||||
enctype="multipart/form-data"
|
||||
>
|
||||
<div id="loading" style="display: none;">
|
||||
<div id="progress">
|
||||
<div class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
<p id="progress-text">{% trans "Uploading..." %}</p>
|
||||
</div>
|
||||
|
||||
<div id="error-container" style='color:red;'></div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="oh-dropdown__import-form">
|
||||
<label class="oh-dropdown__import-label" for="uploadFile">
|
||||
<ion-icon name="cloud-upload" class="oh-dropdown__import-form-icon md hydrated" role="img" aria-label="cloud upload"></ion-icon>
|
||||
<span class="oh-dropdown__import-form-title">{% trans "Upload a File" %}</span>
|
||||
<span class="oh-dropdown__import-form-text">{% trans "Drag and drop files here" %}</span>
|
||||
</label>
|
||||
<input type="file" name="file" id="projectImportFile" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer d-flex flex-row-reverse">
|
||||
<input
|
||||
type="submit"
|
||||
class="oh-btn oh-btn--secondary"
|
||||
value='{% trans "Upload" %}'
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="{% static 'cbv/deleteFunc.js' %}"></script>
|
||||
<script src="{% static '/project/import.js' %}"></script>
|
||||
<script src="{% static '/project/project_action.js' %}"></script>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
51
project/templates/cbv/tasks/task_actions.html
Normal file
51
project/templates/cbv/tasks/task_actions.html
Normal file
@@ -0,0 +1,51 @@
|
||||
{% load i18n %}
|
||||
{% load taskfilters %}
|
||||
<div onclick="event.stopPropagation();">
|
||||
{% if request.user|task_crud_perm:instance or perms.project.view_task %}
|
||||
<div class="oh-btn-group">
|
||||
<a hx-get="{% url 'update-task-all' instance.id %}?instance_ids={{instance.ordered_ids}}"
|
||||
hx-target='#genericModalBody' data-toggle='oh-modal-toggle' data-target="#genericModal" hx-swap='innerHTML'
|
||||
class="oh-btn oh-btn--light-bkg w-100" title="Edit">
|
||||
<ion-icon name="create-outline"></ion-icon>
|
||||
</a>
|
||||
{% if instance.is_active %}
|
||||
<a href="{% url 'task-all-archive' instance.id %}"
|
||||
onclick="return confirm('{% trans 'Do you want to archive this task?' %}')"
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg small-button" title="{% trans 'Archive' %}">
|
||||
<ion-icon name="archive"></ion-icon>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'task-all-archive' instance.id %}"
|
||||
onclick="return confirm('{% trans 'Do you want to un archive this task?' %}')"
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg small-button" title="{% trans 'Un-Archive' %}">
|
||||
<ion-icon name="archive"></ion-icon>
|
||||
</a>
|
||||
{% endif %}
|
||||
<form action="{% url 'delete-task' instance.id %}?task_all=true" method="post"
|
||||
onsubmit="return confirm('{% trans "Do you want Delete this Task ?" %}')" style="display: contents">
|
||||
{% csrf_token %}
|
||||
<button class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100" title="{% trans 'Delete' %}">
|
||||
<ion-icon name="trash-outline" role="img" class="md hydrated" aria-label="trash outline"></ion-icon>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="oh-btn-group">
|
||||
<a href="#" class="oh-btn oh-btn--light-bkg w-100 oh-btn--disabled" title="Edit" aria-disabled="true">
|
||||
<ion-icon name="create-outline"></ion-icon>
|
||||
</a>
|
||||
|
||||
<button type="button" class="oh-btn oh-btn--danger-outline w-100 oh-btn--disabled" title="{% trans 'Delete' %}"
|
||||
aria-disabled="true" disabled>
|
||||
<ion-icon name="trash-outline" role="img" class="md hydrated" aria-label="trash outline"></ion-icon>
|
||||
</button>
|
||||
|
||||
<a href="#" class="oh-btn oh-btn--danger-outline oh-btn--light-bkg small-button oh-btn--disabled"
|
||||
title="{% trans 'Archive' %}" aria-disabled="true">
|
||||
<ion-icon name="archive"></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
27
project/templates/cbv/tasks/task_detail_actions.html
Normal file
27
project/templates/cbv/tasks/task_detail_actions.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% load i18n %}
|
||||
{% if request.user|task_crud_perm:instance or perms.project.view_task %}
|
||||
<a hx-get="{% url 'update-task' instance.id %}?project_task=true" hx-target='#genericModalBody'
|
||||
data-toggle="oh-modal-toggle" data-target="#genericModal" data-key=".oh-kanban__section"
|
||||
class="oh-btn oh-btn-group oh-btn--info" style="width: 50%;">
|
||||
<ion-icon name="create-outline" role="img" class="md hydrated" aria-label="create outline">
|
||||
</ion-icon>
|
||||
{% trans "Edit" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<a hx-get="{% url 'task-timesheet' instance.id %}?employee_id={{request.user.employee_get.id}}"
|
||||
hx-target='#TaskTimesheetTarget' data-target="#TaskTimesheetModal" data-toggle="oh-modal-toggle"
|
||||
data-key=".oh-kanban__section"
|
||||
class="oh-btn oh-btn--info {% if not request.user|task_crud_perm:instance and not perms.project.view_task %} w-100 {% endif %}"
|
||||
style="width: 56%; background-color:green">
|
||||
<ion-icon name="bar-chart" role="img" class="md hydrated" aria-label="create outline">
|
||||
</ion-icon>
|
||||
{% trans "Time sheet" %}
|
||||
</a>
|
||||
{% if request.user|task_crud_perm:instance or perms.project.view_task %}
|
||||
<a href="{% url 'delete-task' instance.id %}?view={{request.GET.view }}" class="oh-btn oh-btn-group oh-btn--danger"
|
||||
style="width: 50%;"
|
||||
onclick="event.preventDefault();event.stopPropagation(); confirm(`{% trans 'Do you want to delete this task?' %}`)">
|
||||
<ion-icon name="trash-outline" role="img" class="md hydrated" aria-label="create outline"></ion-icon>
|
||||
{% trans "Delete" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
10
project/templates/cbv/tasks/task_document.html
Normal file
10
project/templates/cbv/tasks/task_document.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{% load i18n %}
|
||||
{% if instance.document %}
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Document" %}</span>
|
||||
<div class="d-flex ">
|
||||
<a href="{{instance.document.url}}" rel="noopener noreferrer" target="_blank"><span
|
||||
class="oh-file-icon oh-file-icon--pdf" onmouseover="enlargeattachment('{{instance.document.url}}')"
|
||||
style="width:40px;height:40px"></span>
|
||||
</a>
|
||||
</div>
|
||||
{% endif%}
|
||||
41
project/templates/cbv/tasks/task_filter.html
Normal file
41
project/templates/cbv/tasks/task_filter.html
Normal file
@@ -0,0 +1,41 @@
|
||||
{% load i18n %} {% load basefilters %}
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header"
|
||||
onclick="event.stopImmediatePropagation();$(this).parent().toggleClass('oh-accordion--show');">
|
||||
{% trans "Task" %}
|
||||
</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Manager" %}</label>
|
||||
{{form.task_manager}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Stage" %}</label>
|
||||
{{form.stage}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Project" %}</label>
|
||||
{{form.project}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Status" %}</label>
|
||||
{{form.status}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "End Date" %}</label>
|
||||
{{form.end_till}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
9
project/templates/cbv/tasks/task_form.html
Normal file
9
project/templates/cbv/tasks/task_form.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<div>
|
||||
<form hidden hx-get="{{request.path}}" hx-target="#id_stage_parent_div" hx-swap="outerHTML"
|
||||
hx-select="#id_stage_parent_div, .dynamic_stage_scripts">
|
||||
<input type="text" name="dynamic_project">
|
||||
<button id="getStageButton" type="submit">
|
||||
</button>
|
||||
</form>
|
||||
{% include 'generic/horilla_form.html' %}
|
||||
</div>
|
||||
125
project/templates/cbv/tasks/task_template_view.html
Normal file
125
project/templates/cbv/tasks/task_template_view.html
Normal file
@@ -0,0 +1,125 @@
|
||||
{% extends "index.html" %}
|
||||
{% load i18n %}{% load static %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
.expired--dot {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.todo--dot {
|
||||
background-color: yellowgreen;
|
||||
}
|
||||
|
||||
.completed--dot {
|
||||
background-color: rgb(103, 171, 238);
|
||||
}
|
||||
|
||||
.in-progress--dot {
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
border-left: 3.4px solid rgb(103, 171, 238) !important;
|
||||
border-radius: 4px 0 0 4px;
|
||||
|
||||
}
|
||||
|
||||
.status-to_do {
|
||||
|
||||
border-left: 3.4px solid yellowgreen !important;
|
||||
border-radius: 4px 0 0 4px;
|
||||
|
||||
}
|
||||
|
||||
.status-expired {
|
||||
|
||||
border-left: 3.4px solid red !important;
|
||||
border-radius: 4px 0 0 4px;
|
||||
|
||||
}
|
||||
|
||||
.status-in_progress {
|
||||
|
||||
border-left: 3.4px solid orange; !important;
|
||||
border-radius: 4px 0 0 4px;
|
||||
|
||||
}
|
||||
</style>
|
||||
<meta name="csrf-token" content="{{ csrf_token }}">
|
||||
|
||||
|
||||
|
||||
<div hx-get="{% url 'tasks-navbar' %}" hx-trigger="load">
|
||||
</div>
|
||||
|
||||
|
||||
{% include "generic/components.html" %}
|
||||
|
||||
|
||||
<div class="oh-checkpoint-badge mb-2" id="selectedInstances" data-ids="[]" data-clicked="" style="display: none"></div>
|
||||
|
||||
|
||||
<div class="oh-wrapper" id="listContainer">
|
||||
<div class="animated-background">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="oh-modal" id="TaskTimesheetModal" role="dialog" aria-labelledby="TaskTimesheetModal" aria-hidden="true">
|
||||
<div class="oh-modal__dialog" style="max-width:1350px" id="TaskTimesheetTarget"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="oh-modal" id="addTimesheetModal" role="dialog" aria-labelledby="addTimesheetModal" aria-hidden="true"
|
||||
style="z-index: 1022;">
|
||||
<div class="oh-modal__dialog" id="addTimesheetModalBody">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="{% static '/task_all/task_all_action.js' %}"></script>
|
||||
<script src="{% static 'cbv/deleteFunc.js' %}"></script>
|
||||
|
||||
<script>
|
||||
|
||||
function enlargeattachment(src, $element) {
|
||||
$("#enlargeattachmentContainer").empty()
|
||||
var enlargeattachmentContainer = $("#enlargeattachmentContainer")
|
||||
enlargeattachmentContainer.empty()
|
||||
style = 'width:100%; height:90%; box-shadow: 0 10px 10px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2); background:white'
|
||||
var enlargedImage = $('<iframe>').attr({ src: src, style: style })
|
||||
var name = $('<span>').text(src.split('/').pop().replace(/_/g, ' '))
|
||||
enlargeattachmentContainer.append(enlargedImage)
|
||||
enlargeattachmentContainer.append(name)
|
||||
setTimeout(function () {
|
||||
enlargeattachmentContainer.show()
|
||||
|
||||
const iframe = document.querySelector('iframe').contentWindow
|
||||
var iframe_document = iframe.document
|
||||
iframe_image = iframe_document.getElementsByTagName('img')[0]
|
||||
$(iframe_image).attr('style', 'width:100%; height:100%;')
|
||||
}, 100)
|
||||
}
|
||||
|
||||
function hideEnlargeattachment() {
|
||||
var enlargeattachmentContainer = $('#enlargeattachmentContainer')
|
||||
enlargeattachmentContainer.empty()
|
||||
}
|
||||
$(document).on('click', function (event) {
|
||||
if (!$(event.target).closest('#enlargeattachmentContainer').length) {
|
||||
hideEnlargeattachment()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
58
project/templates/cbv/timesheet/actions.html
Normal file
58
project/templates/cbv/timesheet/actions.html
Normal file
@@ -0,0 +1,58 @@
|
||||
{% load i18n %} {% load taskfilters %}
|
||||
{% if request.user|time_sheet_crud_perm:instance or perms.project.change_timesheet or perms.project.delete_timesheet %}
|
||||
<div class="oh-btn-group" onclick="event.stopPropagation();event.preventDefault()">
|
||||
<a
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="{% if request.task_id %}#addTimesheetModal{% else %}#genericModal{% endif %}"
|
||||
hx-get="{% if request.task_id %}{% url 'update-time-sheet' instance.id %}?task_id={{ request.task_id }}{% else %}{% url 'update-time-sheet' instance.id %}{% endif %}"
|
||||
hx-target="{% if request.task_id %}#addTimesheetModalBody{% else %}#genericModalBody{% endif %}"
|
||||
>
|
||||
<ion-icon
|
||||
name="create-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
style="color: blue"
|
||||
aria-label="create outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
{% if request.task_id %}
|
||||
<a
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
hx-delete="{% url 'delete-time-sheet' instance.id %}?view=list&task_id={{ request.task_id }}&employee_id=request.user.employee_get.id"
|
||||
hx-confirm="{% trans 'Do you want to delete this time sheet?' %}"
|
||||
hx-target="#TaskTimesheetTarget"
|
||||
>
|
||||
<ion-icon
|
||||
name="trash-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="trash outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
{% else %}
|
||||
<a
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
href="{% url 'delete-time-sheet' instance.id %}?view=list"
|
||||
onclick="confirm(`{% trans 'Do you want to delete this time sheet?' %}`)"
|
||||
>
|
||||
<ion-icon
|
||||
name="trash-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="trash outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="oh-btn-group" onclick="event.stopPropagation();event.preventDefault()">
|
||||
<a class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100 oh-btn--disabled" >
|
||||
<ion-icon name="create-outline" role="img" class="md hydrated" style="color: blue" aria-label="create outline" ></ion-icon>
|
||||
</a>
|
||||
|
||||
<a class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100 oh-btn--disabled">
|
||||
<ion-icon name="trash-outline" role="img" class="md hydrated" aria-label="trash outline" ></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
23
project/templates/cbv/timesheet/detail_actions.html
Normal file
23
project/templates/cbv/timesheet/detail_actions.html
Normal file
@@ -0,0 +1,23 @@
|
||||
{% load i18n %}
|
||||
{% load basefilters %}
|
||||
<div class="oh-btn-group w-100">
|
||||
<a hx-get="{% url 'update-time-sheet' instance.id %}" hx-target="#genericModalBody" data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal" class="oh-btn oh-btn--warning"
|
||||
style="{% if perms.project.view_timesheet or request.user|is_reportingmanager %} width: 25%;{% else %}width: 50%;{% endif %}">
|
||||
<ion-icon name="create-outline" role="img" class="md hydrated" aria-label="create outline"></ion-icon>
|
||||
{% trans "Edit" %}
|
||||
</a>
|
||||
{% if perms.project.view_timesheet or request.user|is_reportingmanager %}
|
||||
<a href="{% url 'personal-time-sheet-view' instance.employee_id.id %}" class="oh-btn oh-btn--info me-1 ms-1"
|
||||
style="width: 48%;">
|
||||
<ion-icon name="bar-chart" role="img" class="md hydrated" aria-label="create outline"></ion-icon>
|
||||
{% trans "View Timesheet Chart" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'delete-time-sheet' instance.id %}" class="oh-btn oh-btn--danger"
|
||||
style="{% if perms.project.view_timesheet or request.user|is_reportingmanager %} width: 25%;{% else %}width: 50%;{% endif %}"
|
||||
onclick="event.preventDefault();event.stopPropagation(); confirm(`{% trans 'Do you want to delete this project?' %}`)">
|
||||
<ion-icon name="trash-outline" role="img" class="md hydrated" aria-label="create outline"></ion-icon>
|
||||
{% trans "Delete" %}
|
||||
</a>
|
||||
</div>
|
||||
29
project/templates/cbv/timesheet/employee_field.html
Normal file
29
project/templates/cbv/timesheet/employee_field.html
Normal file
@@ -0,0 +1,29 @@
|
||||
{% load i18n %}
|
||||
<label class="oh-label required-star" for="id_{{ form.employee_id.name }}"
|
||||
title="{{ form.employee_id.help_text|safe }}">{% trans form.employee_id.label %}</label>
|
||||
{{form.employee_id}}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
||||
$("select").on("select2:select", function (e) {
|
||||
$(this).closest("select")[0].dispatchEvent(new Event("change"));
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
var task_id = "{{ task_id }}"
|
||||
if (task_id === "dynamic_create") {
|
||||
var project_id = "{{ project_id }}"
|
||||
var buttonId = "#modalButtontask_id"
|
||||
var formId = "#modalButtontask_idForm"
|
||||
var hxVals = {
|
||||
project_id: project_id
|
||||
};
|
||||
// $(buttonId).attr('hx-vals', JSON.stringify(hxVals));
|
||||
$(formId).attr('hx-vals', JSON.stringify(hxVals));
|
||||
$(formId).attr('hx-target', "#dynamicModaltask_idBody");
|
||||
|
||||
$(buttonId).click();
|
||||
}
|
||||
|
||||
</script>
|
||||
59
project/templates/cbv/timesheet/filter.html
Normal file
59
project/templates/cbv/timesheet/filter.html
Normal file
@@ -0,0 +1,59 @@
|
||||
{% load i18n %}{% load basefilters%}
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Time Sheet" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Project" %}</label>
|
||||
{{form.project_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Status" %}</label>
|
||||
{{form.status}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Task" %}</label>
|
||||
{{form.task}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Date" %}</label>
|
||||
{{form.date}}
|
||||
</div>
|
||||
</div>
|
||||
{% if perms.project.view_timesheet or request.user|is_reportingmanager %}
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Employee" %}</label>
|
||||
{{form.employee_id}}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Advanced" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Start Date From" %}</label>
|
||||
{{form.start_from}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Till End Date" %}</label>
|
||||
{{form.end_till}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
3
project/templates/cbv/timesheet/form.html
Normal file
3
project/templates/cbv/timesheet/form.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<div >
|
||||
{% include "generic/horilla_form.html" %}
|
||||
</div>
|
||||
15
project/templates/cbv/timesheet/task_field.html
Normal file
15
project/templates/cbv/timesheet/task_field.html
Normal file
@@ -0,0 +1,15 @@
|
||||
{% load i18n %}
|
||||
<label class="oh-label required-star" for="id_{{ form.task_id.title }}" title="{{ form.task_id.help_text|safe }}">
|
||||
{% trans form.task_id.label %}
|
||||
</label>
|
||||
<div id="dynamic_field_task_id">
|
||||
{{form.task_id}}
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
||||
$("select").on("select2:select", function (e) {
|
||||
$(this).closest("select")[0].dispatchEvent(new Event("change"));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
37
project/templates/cbv/timesheet/task_timesheet.html
Normal file
37
project/templates/cbv/timesheet/task_timesheet.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{% load i18n %}
|
||||
<div id="task-timesheet-container">
|
||||
{% include "generic/components.html" %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5 style="margin-bottom: 20px;">{% trans "Time sheet" %}</h5>
|
||||
</span>
|
||||
{% comment %} for add timesheet {% endcomment %}
|
||||
<div class="d-flex flex-row-reverse oh-wrapper oh-main__titlebar-button-container">
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div>
|
||||
<a class="oh-btn oh-btn--secondary oh-btn--shadow" data-toggle="oh-modal-toggle"
|
||||
data-target="#addTimesheetModal" hx-get="{% url 'create-timesheet-task' task_id %}"
|
||||
hx-target="#addTimesheetModalBody">
|
||||
<ion-icon class="me-2" name="add-outline"></ion-icon>{% trans " Add" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" style="
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 1.5rem;
|
||||
opacity: 0.7;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 15px;
|
||||
cursor: pointer;
|
||||
-webkit-transition: all 0.3s ease-in-out;
|
||||
transition: all 0.3s ease-in-out;"
|
||||
onclick="event.stopPropagation(); $(this).parents().find('.oh-modal--show:last').toggleClass('oh-modal--show')"
|
||||
id="close">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
{% include "generic/horilla_list_table.html" %}
|
||||
</div>
|
||||
</div>
|
||||
51
project/templates/cbv/timesheet/timesheet.html
Normal file
51
project/templates/cbv/timesheet/timesheet.html
Normal file
@@ -0,0 +1,51 @@
|
||||
{% extends "index.html" %}
|
||||
{% load i18n %}{% load static %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
.completed--dot {
|
||||
background-color: yellowgreen;
|
||||
}
|
||||
|
||||
.in-progress--dot {
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
.new--dot {
|
||||
background-color: cyan;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
border-left: 3.4px solid yellowgreen !important;
|
||||
border-radius: 4px 0 0 4px;
|
||||
|
||||
}
|
||||
|
||||
.status-in_Progress {
|
||||
|
||||
border-left: 3.4px solid orange !important;
|
||||
border-radius: 4px 0 0 4px;
|
||||
|
||||
}
|
||||
</style>
|
||||
<meta name="csrf-token" content="{{ csrf_token }}">
|
||||
|
||||
|
||||
|
||||
<div hx-get="{% url " time-sheet-nav" %}" hx-trigger="load">
|
||||
</div>
|
||||
|
||||
|
||||
{% include "generic/components.html" %}
|
||||
|
||||
|
||||
<div class="oh-checkpoint-badge mb-2" id="selectedInstances" data-ids="[]" data-clicked="" style="display: none"></div>
|
||||
|
||||
|
||||
<div class="oh-wrapper" id="listContainer">
|
||||
<div class="animated-background"></div>
|
||||
</div>
|
||||
<script src="{% static 'cbv/deleteFunc.js' %}"></script>
|
||||
<script src="{% static 'time_sheet/time_sheet_action.js' %}"></script>
|
||||
{% endblock %}
|
||||
8
project/templates/cbv/timesheet/timesheet_nav.html
Normal file
8
project/templates/cbv/timesheet/timesheet_nav.html
Normal file
@@ -0,0 +1,8 @@
|
||||
{% load i18n %}
|
||||
<div id="stage-container">
|
||||
{% include "generic/horilla_nav.html" %}
|
||||
</div>
|
||||
<script>
|
||||
$('#applyFilter').closest('form').find('[name=field]').val('employee_id');
|
||||
$('#applyFilter').click();
|
||||
</script>
|
||||
@@ -1,152 +1,143 @@
|
||||
{% extends 'index.html' %}
|
||||
{% block content %}
|
||||
{% load static i18n %}
|
||||
{% load i18n %}
|
||||
|
||||
<main :class="sidebarOpen ? 'oh-main__sidebar-visible' : ''">
|
||||
<div class="oh-wrapper">
|
||||
<div class="oh-dashboard row">
|
||||
<div class="oh-dashboard__left col-12 col-sm-12 col-md-12 col-lg-9">
|
||||
<div class="oh-dashboard__cards row">
|
||||
<!-- column -->
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-4">
|
||||
<div class="oh-card-dashboard oh-card-dashboard oh-card-dashboard--success">
|
||||
<div class="oh-card-dashboard__header">
|
||||
<span class="oh-card-dashboard__title">{% trans "Total Projects" %}</span>
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body">
|
||||
<a href="{% url 'objective-list-view' %}" style="text-decoration: none;" class="oh-card-dashboard__counts">
|
||||
<span class="oh-card-dashboard__count">{{total_projects}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- column -->
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-4">
|
||||
<div class="oh-card-dashboard oh-card-dashboard oh-card-dashboard--neutral">
|
||||
<div class="oh-card-dashboard__header">
|
||||
<span class="oh-card-dashboard__title">{% trans "New Projects" %}</span>
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body">
|
||||
<a href="{% url 'objective-list-view' %}" style="text-decoration: none;" class="oh-card-dashboard__counts">
|
||||
<span class="oh-card-dashboard__count">{{new_projects}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- column -->
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-4">
|
||||
<div class="oh-card-dashboard oh-card-dashboard oh-card-dashboard--warning">
|
||||
<div class="oh-card-dashboard__header">
|
||||
<span class="oh-card-dashboard__title">{% trans "Projects in progress" %}</span>
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body">
|
||||
<a href="{% url 'objective-list-view' %}" style="text-decoration: none;" class="oh-card-dashboard__counts">
|
||||
<span class="oh-card-dashboard__count">{{projects_in_progress}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- movable cards for charts -->
|
||||
<div class="oh-dashboard__movable-cards row mt-4">
|
||||
<!-- chart -->
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6 oh-card-dashboard--moveable">
|
||||
<div class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent">
|
||||
<div class="oh-card-dashboard__header oh-card-dashboard__header--divider">
|
||||
<span class="oh-card-dashboard__title">{% trans "Project Status" %}</span>
|
||||
<span class="oh-card-dashboard__title float-end" id="projectStatusForward"><ion-icon name="caret-forward"></ion-icon></span>
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body">
|
||||
<canvas id="projectStatusCanvas"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- chart -->
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6 oh-card-dashboard--moveable">
|
||||
<div class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent">
|
||||
<div class="oh-card-dashboard__header oh-card-dashboard__header--divider">
|
||||
<span class="oh-card-dashboard__title">{% trans "Task Status" %}</span>
|
||||
<span class="oh-card-dashboard__title float-end" id="taskStatusForward"><ion-icon name="caret-forward"></ion-icon></span>
|
||||
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body">
|
||||
<canvas id="taskStatusCanvas"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right end column -->
|
||||
<div class="oh-dashboard__right col-12 col-sm-12 col-md-12 col-lg-3">
|
||||
<!-- Dashboard events -->
|
||||
<div class="oh-dashboard__events">
|
||||
<ul class="oh-dashboard__events-nav">
|
||||
<li class="oh-dashboard__events-nav-item oh-dashboard__events-nav-item--active" data-target="0">
|
||||
</li>
|
||||
<li class="oh-dashboard__events-nav-item" data-target="1"></li>
|
||||
<li class="oh-dashboard__events-nav-item" data-target="2"></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent mt-3">
|
||||
<div class="oh-card-dashboard__header oh-card-dashboard__header--divider">
|
||||
<span class="oh-card-dashboard__title"> {% trans "Projects due in this month" %}</span>
|
||||
<span class="oh-card-dashboard__title float-end"><a href="{% url 'project-view' %}" style="text-decoration:none; color:orange;">{% trans "View all" %}</a></span>
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body">
|
||||
<ul class="oh-card-dashboard__user-list">
|
||||
{% if unexpired_project %}
|
||||
{% for project in unexpired_project %}
|
||||
<li class="oh-card-dashboard__user-item">
|
||||
<a
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#ProjectModal"
|
||||
hx-get="{%url 'project-detailed-view' project.id %}"
|
||||
hx-target="#ProjectFormTarget"
|
||||
>
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img src="https://ui-avatars.com/api/?name=Beth+Gibbons&background=random"
|
||||
class="oh-profile__image" alt="Beth Gibbons" />
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark">{{project.title}}</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 70px;margin: 10px auto ;" src="{% static 'images/ui/project/project.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:16px" class="oh-404__subtitle">{% trans "No projects due in this month." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectModal"
|
||||
role="dialog"
|
||||
aria-labelledby="ProjectModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="ProjectFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
</main>
|
||||
<!-- Java script -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="{% static 'dashboard/projectChart.js' %}"></script>
|
||||
|
||||
{% endblock content %}
|
||||
{% extends 'index.html' %}
|
||||
{% block content %}
|
||||
{% load static i18n %}
|
||||
{% load i18n %}
|
||||
|
||||
<main :class="sidebarOpen ? 'oh-main__sidebar-visible' : ''">
|
||||
<div class="oh-wrapper">
|
||||
<div class="oh-dashboard row">
|
||||
<div class="oh-dashboard__left col-12 col-sm-12 col-md-12 col-lg-9">
|
||||
<div class="oh-dashboard__cards row">
|
||||
<!-- column -->
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-4">
|
||||
<div class="oh-card-dashboard oh-card-dashboard oh-card-dashboard--success">
|
||||
<div class="oh-card-dashboard__header">
|
||||
<span class="oh-card-dashboard__title">{% trans "Total Projects" %}</span>
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body">
|
||||
<a href="{% url 'project-view' %}" style="text-decoration: none;" class="oh-card-dashboard__counts">
|
||||
<span class="oh-card-dashboard__count">{{total_projects}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- column -->
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-4">
|
||||
<div class="oh-card-dashboard oh-card-dashboard oh-card-dashboard--neutral">
|
||||
<div class="oh-card-dashboard__header">
|
||||
<span class="oh-card-dashboard__title">{% trans "New Projects" %}</span>
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body">
|
||||
<a href="{% url 'project-view' %}?{{request.GET.urlencode}}&status=new" style="text-decoration: none;" class="oh-card-dashboard__counts">
|
||||
<span class="oh-card-dashboard__count">{{new_projects}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- column -->
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-4">
|
||||
<div class="oh-card-dashboard oh-card-dashboard oh-card-dashboard--warning">
|
||||
<div class="oh-card-dashboard__header">
|
||||
<span class="oh-card-dashboard__title">{% trans "Projects in progress" %}</span>
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body">
|
||||
<a href="{% url 'project-view' %}?{{request.GET.urlencode}}&status=in_progress" style="text-decoration: none;" class="oh-card-dashboard__counts">
|
||||
<span class="oh-card-dashboard__count">{{projects_in_progress}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- movable cards for charts -->
|
||||
<div class="oh-dashboard__movable-cards row mt-4">
|
||||
<!-- chart -->
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6 oh-card-dashboard--moveable">
|
||||
<div class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent">
|
||||
<div class="oh-card-dashboard__header oh-card-dashboard__header--divider">
|
||||
<span class="oh-card-dashboard__title">{% trans "Project Status" %}</span>
|
||||
<span class="oh-card-dashboard__title float-end" id="projectStatusForward"><ion-icon name="caret-forward"></ion-icon></span>
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body">
|
||||
<canvas id="projectStatusCanvas"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- chart -->
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6 oh-card-dashboard--moveable">
|
||||
<div class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent">
|
||||
<div class="oh-card-dashboard__header oh-card-dashboard__header--divider">
|
||||
<span class="oh-card-dashboard__title">{% trans "Task Status" %}</span>
|
||||
<span class="oh-card-dashboard__title float-end" id="taskStatusForward"><ion-icon name="caret-forward"></ion-icon></span>
|
||||
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body">
|
||||
<canvas id="taskStatusCanvas"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right end column -->
|
||||
<div class="oh-dashboard__right col-12 col-sm-12 col-md-12 col-lg-3">
|
||||
<!-- Dashboard events -->
|
||||
<div class="oh-dashboard__events">
|
||||
<ul class="oh-dashboard__events-nav">
|
||||
<li class="oh-dashboard__events-nav-item oh-dabdshboard__events-nav-item--active" data-target="0">
|
||||
</li>
|
||||
<li class="oh-dashboard__events-nav-item" data-target="1"></li>
|
||||
<li class="oh-dashboard__events-nav-item" data-target="2"></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent mt-3">
|
||||
<div class="oh-card-dashboard__header oh-card-dashboard__header--divider">
|
||||
<span class="oh-card-dashboard__title"> {% trans "Projects due in this month" %}</span>
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body">
|
||||
<div class="oh-sticky-table h-100" hx-get="{% url 'projects-due-in-this-month' %}" hx-trigger="load" style="border: none;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectModal"
|
||||
role="dialog"
|
||||
aria-labelledby="ProjectModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
id="ProjectFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectModalShow"
|
||||
role="dialog"
|
||||
aria-labelledby="ProjectModalShow"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
|
||||
>
|
||||
<div class="oh-modal__dialog-header">
|
||||
<h2 class="oh-modal__dialog-title">{% trans 'Projects' %}</h2>
|
||||
<button type="button" class="oh-modal__close--custom" onclick="$(this).closest('.oh-modal--show').removeClass('oh-modal--show');">
|
||||
<ion-icon name="close-outline" role="img" aria-label="close outline"></ion-icon>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div class="oh-modal__dialog-body" id="ProjectModalShowTarget">
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<!-- Java script -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="{% static 'dashboard/projectChart.js' %}"></script>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
@@ -1,71 +1,71 @@
|
||||
{% load i18n %} {% load yes_no %}
|
||||
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="projectDetails">
|
||||
<h5 style="margin-bottom: 20px;">{{project.title}}</h5>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-timeoff-modal__stats-container mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Manager" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{project.manager}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Members" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{% for member in project.members.all %}{{member}}, {% endfor %}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Status" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{project.get_status_display}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "No of Tasks" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task_count}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Start Date" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{project.start_date}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "End date" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{project.end_date}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Document" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{project.document}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Description" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{project.description}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-modal__button-container text-center" style="width: 50%; margin:0 auto">
|
||||
{% comment %} <div class="oh-btn-group"> {% endcomment %}
|
||||
<a href="{%url 'task-view' project.id %}"
|
||||
class="oh-btn oh-btn--info" >
|
||||
<ion-icon
|
||||
name="create-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="create outline"
|
||||
>
|
||||
</ion-icon>{% trans "View" %}
|
||||
</a>
|
||||
{% comment %} </div> {% endcomment %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% load i18n %} {% load horillafilters %}
|
||||
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="projectDetails">
|
||||
<h5 style="margin-bottom: 20px;">{{project.title}}</h5>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-timeoff-modal__stats-container mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Manager" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{project.manager}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Members" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{% for member in project.members.all %}{{member}}, {% endfor %}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Status" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{project.get_status_display}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "No of Tasks" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task_count}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Start Date" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{project.start_date}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "End date" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{project.end_date}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Document" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{project.document}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Description" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{project.description}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-modal__button-container text-center" style="width: 50%; margin:0 auto">
|
||||
{% comment %} <div class="oh-btn-group"> {% endcomment %}
|
||||
<a href="{%url 'task-view' project.id %}"
|
||||
class="oh-btn oh-btn--info" >
|
||||
<ion-icon
|
||||
name="create-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="create outline"
|
||||
>
|
||||
</ion-icon>{% trans "View" %}
|
||||
</a>
|
||||
{% comment %} </div> {% endcomment %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
{% load i18n %} {% load basefilters %}
|
||||
{% comment %} <form
|
||||
hx-get='{% url "project-filter" %}?view={{request.GET.view }}'
|
||||
hx-target="#view-container"
|
||||
hx-swap="innerHTML"
|
||||
id="projectFilterForm"> {% endcomment %}
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header"
|
||||
onclick="event.stopImmediatePropagation();$(this).parent().toggleClass('oh-accordion--show');"
|
||||
>{% trans "Project" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Manager" %}</label>
|
||||
{{f.form.manager}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Status" %}</label>
|
||||
{{f.form.status}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Start from" %}</label>
|
||||
{{f.form.start_from}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "End till" %}</label>
|
||||
{{f.form.end_till}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button class="oh-btn oh-btn--secondary oh-btn--small w-100 filterButton" type="submit" >
|
||||
{% trans "Filter" %}
|
||||
</button>
|
||||
</div>
|
||||
{% comment %} </form> {% endcomment %}
|
||||
{% load i18n %} {% load basefilters %}
|
||||
{% comment %} <form
|
||||
hx-get='{% url "project-filter" %}?view={{request.GET.view }}'
|
||||
hx-target="#view-container"
|
||||
hx-swap="innerHTML"
|
||||
id="projectFilterForm"> {% endcomment %}
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header"
|
||||
onclick="event.stopImmediatePropagation();$(this).parent().toggleClass('oh-accordion--show');"
|
||||
>{% trans "Project" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Manager" %}</label>
|
||||
{{f.form.manager}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Status" %}</label>
|
||||
{{f.form.status}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Start from" %}</label>
|
||||
{{f.form.start_from}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "End till" %}</label>
|
||||
{{f.form.end_till}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button class="oh-btn oh-btn--secondary oh-btn--small w-100 filterButton" type="submit" >
|
||||
{% trans "Filter" %}
|
||||
</button>
|
||||
</div>
|
||||
{% comment %} </form> {% endcomment %}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Project" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#TaskStageTarget"
|
||||
onclick="location.reload()"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'create-project' %}"
|
||||
hx-target="#ProjectFormTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Project" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#TaskStageTarget"
|
||||
onclick="location.reload()"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'create-project' %}"
|
||||
hx-target="#ProjectFormTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Project" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#ProjectFormTarget"
|
||||
onclick="location.reload()"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'update-project' project_id %}"
|
||||
hx-target="#ProjectFormTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Project" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#ProjectFormTarget"
|
||||
onclick="location.reload()"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'update-project' project_id %}"
|
||||
hx-target="#ProjectFormTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,235 +1,234 @@
|
||||
{% load i18n %}
|
||||
<!-- start of import form -->
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="projectImport"
|
||||
role="dialog"
|
||||
aria-labelledby="projectImport"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="oh-modal__dialog">
|
||||
<div class="oh-modal__dialog-header">
|
||||
<h2 class="oh-modal__dialog-title" id="projectImportLavel">
|
||||
{% trans "Import Project" %}
|
||||
</h2>
|
||||
<button class="oh-modal__close" aria-label="Close">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-modal__dialog-body p-0 pt-2" id="projectImportModalBody">
|
||||
<form
|
||||
|
||||
id="projectImportForm"
|
||||
enctype="multipart/form-data"
|
||||
>
|
||||
<div id="loading" style="display: none;">
|
||||
<div id="progress">
|
||||
<div class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
<p id="progress-text">{% trans "Uploading..." %}</p>
|
||||
</div>
|
||||
|
||||
<div id="error-container" style='color:red;'></div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
{% csrf_token %}
|
||||
{% comment %} {% endcomment %}
|
||||
<div class="oh-dropdown__import-form">
|
||||
<label class="oh-dropdown__import-label" for="uploadFile">
|
||||
<ion-icon name="cloud-upload" class="oh-dropdown__import-form-icon md hydrated" role="img" aria-label="cloud upload"></ion-icon>
|
||||
<span class="oh-dropdown__import-form-title">{% trans "Upload a File" %}</span>
|
||||
<span class="oh-dropdown__import-form-text">{% trans "Drag and drop files here" %}</span>
|
||||
</label>
|
||||
<input type="file" name="file" id="projectImportFile" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer d-flex flex-row-reverse">
|
||||
<input
|
||||
type="submit"
|
||||
class="oh-btn oh-btn--secondary"
|
||||
value='{% trans "Upload" %}'
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end of import form -->
|
||||
|
||||
<!-- start of navbar contents -->
|
||||
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">{% trans "Projects" %}</h1>
|
||||
<a
|
||||
class="oh-main__titlebar-search-toggle"
|
||||
role="button"
|
||||
aria-label="Toggle Search"
|
||||
@click="searchShow = !searchShow"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-main__titlebar-serach-icon md hydrated"
|
||||
role="img"
|
||||
aria-label="search outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right">
|
||||
{% if projects %}
|
||||
{% comment %} for search{% endcomment %}
|
||||
<form
|
||||
hx-get='{% url "project-filter" %}?view={{request.GET.view }}'
|
||||
hx-target="#view-container"
|
||||
hx-swap="innerHTML"
|
||||
id="projectFilterForm"
|
||||
class="d-flex"
|
||||
onsubmit="event.preventDefault()"
|
||||
>
|
||||
<div
|
||||
class="oh-input-group oh-input__search-group"
|
||||
:class="searchShow ? 'oh-input__search-group--show' : ''"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-input-group__icon oh-input-group__icon--left"
|
||||
></ion-icon>
|
||||
<input
|
||||
type="text"
|
||||
class="oh-input oh-input__icon"
|
||||
aria-label="Search Input"
|
||||
id="filter-project"
|
||||
name="search"
|
||||
placeholder="{% trans 'Search' %}"
|
||||
onkeyup="$('.filterButton')[0].click()"
|
||||
/>
|
||||
</div>
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<ul class="oh-view-types ml-2">
|
||||
{% comment %} for list view {% endcomment %}
|
||||
<li class="oh-view-type project-view-type" data-view="list">
|
||||
<a
|
||||
href="{% url 'project-view' %}?view=list"
|
||||
{% comment %} hx-target="#view-container" {% endcomment %}
|
||||
class="oh-btn oh-btn--view"
|
||||
><ion-icon name="list-outline"></ion-icon
|
||||
></a>
|
||||
</li>
|
||||
{% comment %} for card view {% endcomment %}
|
||||
<li class="oh-view-type project-view-type" data-view="card">
|
||||
<a
|
||||
href="{% url 'project-view' %}?view=card"
|
||||
{% comment %} hx-target="#view-container" {% endcomment %}
|
||||
class="oh-btn oh-btn--view"
|
||||
><ion-icon name="grid-outline"></ion-icon
|
||||
></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% comment %} for filtering {% endcomment %}
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button
|
||||
class="oh-btn ml-2"
|
||||
@click="open = !open"
|
||||
onclick="event.preventDefault()"
|
||||
>
|
||||
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
||||
<div id="filterCount"></div>
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
|
||||
x-show="open"
|
||||
@click.outside="open = false"
|
||||
style="display: none;"
|
||||
>
|
||||
{% include 'project/new/filter_project.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% comment %} for actions {% endcomment %}
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button
|
||||
class="oh-btn oh-btn--dropdown oh-btn oh-btn--shadow"
|
||||
@click="open = !open"
|
||||
@click.outside="open = false"
|
||||
>
|
||||
{% trans "Actions" %}
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open" style="display: none;">
|
||||
<ul class="oh-dropdown__items">
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#"
|
||||
class="oh-dropdown__link "
|
||||
id="importProject"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#projectImport"
|
||||
>{% trans "Import" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#"
|
||||
class="oh-dropdown__link "
|
||||
id="exportProject"
|
||||
>{% trans "Export" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link " id="duplicateProject"
|
||||
>{% trans "Duplicate" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link " id="archiveProject"
|
||||
>{% trans "Archive" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link " id="unArchiveProject"
|
||||
>{% trans "Un-Archive" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a
|
||||
href="#"
|
||||
class="oh-dropdown__link oh-dropdown__link--danger"
|
||||
id="deleteProject"
|
||||
>{% trans "Delete" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% comment %} for create project {% endcomment %}
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div>
|
||||
<a
|
||||
class="oh-btn oh-btn--secondary oh-btn--shadow"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#ProjectModal"
|
||||
hx-get="{% url 'create-project' %}"
|
||||
hx-target="#ProjectFormTarget"
|
||||
>
|
||||
<ion-icon class="me-2" name="add-outline"></ion-icon>{% trans "Create" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% load i18n %}
|
||||
<!-- start of import form -->
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="projectImport"
|
||||
role="dialog"
|
||||
aria-labelledby="projectImport"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="oh-modal__dialog">
|
||||
<div class="oh-modal__dialog-header">
|
||||
<h2 class="oh-modal__dialog-title" id="projectImportLavel">
|
||||
{% trans "Import Project" %}
|
||||
</h2>
|
||||
<button class="oh-modal__close" aria-label="Close">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-modal__dialog-body p-0 pt-2" id="projectImportModalBody">
|
||||
<form
|
||||
|
||||
id="projectImportForm"
|
||||
enctype="multipart/form-data"
|
||||
>
|
||||
<div id="loading" style="display: none;">
|
||||
<div id="progress">
|
||||
<div class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
<p id="progress-text">{% trans "Uploading..." %}</p>
|
||||
</div>
|
||||
|
||||
<div id="error-container" style='color:red;'></div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
{% csrf_token %}
|
||||
{% comment %} {% endcomment %}
|
||||
<div class="oh-dropdown__import-form">
|
||||
<label class="oh-dropdown__import-label" for="uploadFile">
|
||||
<ion-icon name="cloud-upload" class="oh-dropdown__import-form-icon md hydrated" role="img" aria-label="cloud upload"></ion-icon>
|
||||
<span class="oh-dropdown__import-form-title">{% trans "Upload a File" %}</span>
|
||||
<span class="oh-dropdown__import-form-text">{% trans "Drag and drop files here" %}</span>
|
||||
</label>
|
||||
<input type="file" name="file" id="projectImportFile" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer d-flex flex-row-reverse">
|
||||
<input
|
||||
type="submit"
|
||||
class="oh-btn oh-btn--secondary"
|
||||
value='{% trans "Upload" %}'
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end of import form -->
|
||||
|
||||
<!-- start of navbar contents -->
|
||||
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">{% trans "Projects" %}</h1>
|
||||
<a
|
||||
class="oh-main__titlebar-search-toggle"
|
||||
role="button"
|
||||
aria-label="Toggle Search"
|
||||
@click="searchShow = !searchShow"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-main__titlebar-serach-icon md hydrated"
|
||||
role="img"
|
||||
aria-label="search outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right">
|
||||
{% if projects %}
|
||||
{% comment %} for search{% endcomment %}
|
||||
<form
|
||||
hx-get='{% url "project-filter" %}?view={{request.GET.view }}'
|
||||
hx-target="#view-container"
|
||||
hx-swap="innerHTML"
|
||||
id="projectFilterForm"
|
||||
class="d-flex"
|
||||
onsubmit="event.preventDefault()"
|
||||
>
|
||||
<div
|
||||
class="oh-input-group oh-input__search-group"
|
||||
:class="searchShow ? 'oh-input__search-group--show' : ''"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-input-group__icon oh-input-group__icon--left"
|
||||
></ion-icon>
|
||||
<input
|
||||
type="text"
|
||||
class="oh-input oh-input__icon"
|
||||
aria-label="Search Input"
|
||||
id="filter-project"
|
||||
name="search"
|
||||
placeholder="{% trans 'Search' %}"
|
||||
onkeyup="$('.filterButton')[0].click()"
|
||||
/>
|
||||
</div>
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<ul class="oh-view-types ml-2">
|
||||
{% comment %} for list view {% endcomment %}
|
||||
<li class="oh-view-type project-view-type" data-view="list">
|
||||
<a
|
||||
href="{% url 'project-view' %}?view=list"
|
||||
{% comment %} hx-target="#view-container" {% endcomment %}
|
||||
class="oh-btn oh-btn--view"
|
||||
><ion-icon name="list-outline"></ion-icon
|
||||
></a>
|
||||
</li>
|
||||
{% comment %} for card view {% endcomment %}
|
||||
<li class="oh-view-type project-view-type" data-view="card">
|
||||
<a
|
||||
href="{% url 'project-view' %}?view=card"
|
||||
{% comment %} hx-target="#view-container" {% endcomment %}
|
||||
class="oh-btn oh-btn--view"
|
||||
><ion-icon name="grid-outline"></ion-icon
|
||||
></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% comment %} for filtering {% endcomment %}
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button
|
||||
class="oh-btn ml-2"
|
||||
@click="open = !open"
|
||||
onclick="event.preventDefault()"
|
||||
>
|
||||
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
||||
<div id="filterCount"></div>
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
|
||||
x-show="open"
|
||||
@click.outside="open = false"
|
||||
style="display: none;"
|
||||
>
|
||||
{% include 'project/new/filter_project.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% comment %} for actions {% endcomment %}
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button
|
||||
class="oh-btn oh-btn--dropdown oh-btn oh-btn--shadow"
|
||||
@click="open = !open"
|
||||
@click.outside="open = false"
|
||||
>
|
||||
{% trans "Actions" %}
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open" style="display: none;">
|
||||
<ul class="oh-dropdown__items">
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#"
|
||||
class="oh-dropdown__link "
|
||||
id="importProject"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#projectImport"
|
||||
>{% trans "Import" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#"
|
||||
class="oh-dropdown__link "
|
||||
id="exportProject"
|
||||
>{% trans "Export" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link " id="duplicateProject"
|
||||
>{% trans "Duplicate" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link " id="archiveProject"
|
||||
>{% trans "Archive" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link " id="unArchiveProject"
|
||||
>{% trans "Un-Archive" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a
|
||||
href="#"
|
||||
class="oh-dropdown__link oh-dropdown__link--danger"
|
||||
id="deleteProject"
|
||||
>{% trans "Delete" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% comment %} for create project {% endcomment %}
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div>
|
||||
<a
|
||||
class="oh-btn oh-btn--secondary oh-btn--shadow"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#ProjectModal"
|
||||
hx-get="{% url 'create-project' %}"
|
||||
hx-target="#ProjectFormTarget"
|
||||
>
|
||||
<ion-icon class="me-2" name="add-outline"></ion-icon>{% trans "Create" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
{% extends 'index.html' %}
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
|
||||
<main :class="sidebarOpen ? 'oh-main__sidebar-visible' : ''">
|
||||
{% include 'project/new/navbar.html' %}
|
||||
</main>
|
||||
|
||||
<div id="view-container" class="oh-wrapper">
|
||||
|
||||
{% if view_type == 'list' %}
|
||||
{% include 'project/new/project_list_view.html' %}
|
||||
|
||||
{% else %}
|
||||
{% include 'project/new/project_kanban_view.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectModal"
|
||||
role="dialog"
|
||||
aria-labelledby="ProjectModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="ProjectFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% extends 'index.html' %}
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
|
||||
<main :class="sidebarOpen ? 'oh-main__sidebar-visible' : ''">
|
||||
{% include 'project/new/navbar.html' %}
|
||||
</main>
|
||||
|
||||
<div id="view-container" class="oh-wrapper">
|
||||
|
||||
{% if view_type == 'list' %}
|
||||
{% include 'project/new/project_list_view.html' %}
|
||||
|
||||
{% else %}
|
||||
{% include 'project/new/project_kanban_view.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectModal"
|
||||
role="dialog"
|
||||
aria-labelledby="ProjectModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="ProjectFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
@@ -1,201 +1,201 @@
|
||||
|
||||
{% load static %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load basefilters %}
|
||||
|
||||
|
||||
{% comment %} for showing messages {% endcomment %}
|
||||
{% if messages %}
|
||||
<div class="oh-alert-container">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "filter_tags.html" %}
|
||||
{% comment %} easy filters {% endcomment %}
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=new&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:cyan"></span>
|
||||
{% trans "New" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=in_progress&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:rgb(103, 171, 238)"></span>
|
||||
{% trans "In progress" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=completed&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:yellowgreen"></span>
|
||||
{% trans "Completed" %}
|
||||
</span><span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=on_hold&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:orange"></span>
|
||||
{% trans "On Hold" %}
|
||||
</span>
|
||||
</span><span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=cancelled&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:Red"></span>
|
||||
{% trans "Cancelled" %}
|
||||
</span>
|
||||
</span><span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=expired&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:gray"></span>
|
||||
{% trans "Expired" %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
{% comment %} kanban view {% endcomment %}
|
||||
{% if projects %}
|
||||
<div class="oh-layout--grid-3">
|
||||
{% for project in projects %}
|
||||
<div class="oh-kanban-card {% if project.status == 'new'%} row-status--cyan
|
||||
{% elif project.status == 'in_progress' %} row-status--blue
|
||||
{% elif project.status == 'completed' %} row-status--yellow
|
||||
{% elif project.status == 'on_hold' %} row-status--orange
|
||||
{% elif project.status == 'cancelled' %} row-status--red
|
||||
{% else %} row-status--gray
|
||||
{% endif %}"
|
||||
style="color: inherit;text-decoration: none;">
|
||||
|
||||
{% comment %} url link {% endcomment %}
|
||||
<a href="{% url 'task-view' project.id %}?view=card" style="color: inherit;text-decoration: none; display: flex;width:600px;">
|
||||
|
||||
{% comment %} placing image {% endcomment %}
|
||||
<div class="oh-kanban-card__avatar">
|
||||
<div class="oh-kanban-card__profile-container">
|
||||
{% if project.image %}
|
||||
<img
|
||||
src="{{emp.employee_profile.url}}"
|
||||
class="oh-kanban-card__profile-image"
|
||||
alt="Username"
|
||||
/>
|
||||
{% else %}
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{project.title}}&background=random"
|
||||
class="oh-kanban-card__profile-image"
|
||||
alt="Username"
|
||||
/>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-kanban-card__details">
|
||||
<span class="oh-kanban-card__title">{{project.title}}</span>
|
||||
<span class="oh-kanban-card__subtitle">{% trans "Project manager" %} : {{project.manager}}</span><br>
|
||||
<span class="oh-kanban-card__subtitle"> {% trans "Status" %}: {{project.get_status_display}}</span><br>
|
||||
<span class="oh-kanban-card__subtitle">{% trans "End date" %} : {{project.end_date}}</span>
|
||||
</div>
|
||||
</a>
|
||||
<div class="oh-tabs__input-badge-container">
|
||||
<span class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round mr-1" title="{{ project.task_set.all|length}} Tasks">
|
||||
{{ project.task_set.all|length}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="oh-kanban-card__dots">
|
||||
|
||||
{% comment %} dropdown {% endcomment %}
|
||||
<div class="oh-dropdown" x-data="{show: false}">
|
||||
<button class="oh-btn oh-btn--transparent text-muted p-3" @click="show = !show">
|
||||
<ion-icon name="ellipsis-vertical-sharp" role="img" class="md hydrated" aria-label="ellipsis vertical sharp"></ion-icon>
|
||||
</button>
|
||||
{% comment %} dropdown menu {% endcomment %}
|
||||
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--dark-border oh-dropdown__menu--right" x-show="show" @click.outside="show = false" style="display: none;">
|
||||
<ul class="oh-dropdown__items">
|
||||
{% comment %} {% if perms.project.change_project %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a
|
||||
hx-get='{% url "update-project" project.id %}'
|
||||
hx-target='#ProjectFormTarget' class="oh-dropdown__link"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#ProjectModal"
|
||||
data-key=".oh-kanban__section"
|
||||
>{% trans "Edit" %}
|
||||
</a>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.employee.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
{% if project.is_active %}
|
||||
<a href="{% url 'project-archive' project.id %}" onclick="return confirm('{% trans 'Do you want to archive this project?' %}')" class="oh-dropdown__link">
|
||||
{% trans "Archive" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'project-archive' project.id %}" onclick="return confirm('{% trans 'Do you want to un archive this project?' %}')" class="oh-dropdown__link">
|
||||
{% trans "Un-Archive" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.employee.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<form action="{% url 'delete-project' project.id %}?view=card" method="post" onsubmit="return confirm('{% trans 'Do you want to delete this employee?' %}')">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="oh-dropdown__link oh-dropdown__link--danger">{% trans "Delete" %}</button>
|
||||
</form>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% comment %} pagination {% endcomment %}
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
>
|
||||
{% trans "Page" %} {{ projects.number }} {% trans "of" %} {{ projects.paginator.num_pages }}.
|
||||
</span>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{projects.number}}"
|
||||
hx-get="{% url 'project-filter' %}?view=card?page="
|
||||
hx-target="#view-container"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{projects.paginator.num_pages}}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if projects.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#view-container' hx-get="{% url 'project-filter' %}?view=card&?{{pd}}&page=1" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#view-container' hx-get="{% url 'project-filter' %}?view=card&?{{pd}}&page={{ projects.previous_page_number }}" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if projects.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#view-container' hx-get="{% url 'project-filter' %}?view=card&?{{pd}}&page={{ projects.next_page_number }}" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#view-container' hx-get="{% url 'project-filter' %}?view=card&?{{pd}}&page={{ projects.paginator.num_pages }}" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% comment %} </div> {% endcomment %}
|
||||
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 200px;margin: 10px auto ;" src="{% static 'images/ui/project/project.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">{% trans "There are currently no available projects; please create a new one." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% comment %} js scripts {% endcomment %}
|
||||
<script src="{% static '/project/project_view.js' %}"></script>
|
||||
<script src="{% static '/project/import.js' %}"></script>
|
||||
|
||||
{% load static %}
|
||||
|
||||
{% load i18n %}{% load horillafilters %}
|
||||
{% load basefilters %}
|
||||
|
||||
|
||||
{% comment %} for showing messages {% endcomment %}
|
||||
{% if messages %}
|
||||
<div class="oh-alert-container">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "filter_tags.html" %}
|
||||
{% comment %} easy filters {% endcomment %}
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=new&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:cyan"></span>
|
||||
{% trans "New" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=in_progress&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:rgb(103, 171, 238)"></span>
|
||||
{% trans "In progress" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=completed&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:yellowgreen"></span>
|
||||
{% trans "Completed" %}
|
||||
</span><span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=on_hold&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:orange"></span>
|
||||
{% trans "On Hold" %}
|
||||
</span>
|
||||
</span><span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=cancelled&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:Red"></span>
|
||||
{% trans "Cancelled" %}
|
||||
</span>
|
||||
</span><span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=expired&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:gray"></span>
|
||||
{% trans "Expired" %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
{% comment %} kanban view {% endcomment %}
|
||||
{% if projects %}
|
||||
<div class="oh-layout--grid-3">
|
||||
{% for project in projects %}
|
||||
<div class="oh-kanban-card {% if project.status == 'new'%} row-status--cyan
|
||||
{% elif project.status == 'in_progress' %} row-status--blue
|
||||
{% elif project.status == 'completed' %} row-status--yellow
|
||||
{% elif project.status == 'on_hold' %} row-status--orange
|
||||
{% elif project.status == 'cancelled' %} row-status--red
|
||||
{% else %} row-status--gray
|
||||
{% endif %}"
|
||||
style="color: inherit;text-decoration: none;">
|
||||
|
||||
{% comment %} url link {% endcomment %}
|
||||
<a href="{% url 'task-view' project.id %}?view=card" style="color: inherit;text-decoration: none; display: flex;width:600px;">
|
||||
|
||||
{% comment %} placing image {% endcomment %}
|
||||
<div class="oh-kanban-card__avatar">
|
||||
<div class="oh-kanban-card__profile-container">
|
||||
{% if project.image %}
|
||||
<img
|
||||
src="{{emp.employee_profile.url}}"
|
||||
class="oh-kanban-card__profile-image"
|
||||
alt="Username"
|
||||
/>
|
||||
{% else %}
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{project.title}}&background=random"
|
||||
class="oh-kanban-card__profile-image"
|
||||
alt="Username"
|
||||
/>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-kanban-card__details">
|
||||
<span class="oh-kanban-card__title">{{project.title}}</span>
|
||||
<span class="oh-kanban-card__subtitle">{% trans "Project manager" %} : {{project.manager}}</span><br>
|
||||
<span class="oh-kanban-card__subtitle"> {% trans "Status" %}: {{project.get_status_display}}</span><br>
|
||||
<span class="oh-kanban-card__subtitle">{% trans "End date" %} : {{project.end_date}}</span>
|
||||
</div>
|
||||
</a>
|
||||
<div class="oh-tabs__input-badge-container">
|
||||
<span class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round mr-1" title="{{ project.task_set.all|length}} Tasks">
|
||||
{{ project.task_set.all|length}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="oh-kanban-card__dots">
|
||||
|
||||
{% comment %} dropdown {% endcomment %}
|
||||
<div class="oh-dropdown" x-data="{show: false}">
|
||||
<button class="oh-btn oh-btn--transparent text-muted p-3" @click="show = !show">
|
||||
<ion-icon name="ellipsis-vertical-sharp" role="img" class="md hydrated" aria-label="ellipsis vertical sharp"></ion-icon>
|
||||
</button>
|
||||
{% comment %} dropdown menu {% endcomment %}
|
||||
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--dark-border oh-dropdown__menu--right" x-show="show" @click.outside="show = false" style="display: none;">
|
||||
<ul class="oh-dropdown__items">
|
||||
{% comment %} {% if perms.project.change_project %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a
|
||||
hx-get='{% url "update-project" project.id %}'
|
||||
hx-target='#ProjectFormTarget' class="oh-dropdown__link"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#ProjectModal"
|
||||
data-key=".oh-kanban__section"
|
||||
>{% trans "Edit" %}
|
||||
</a>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.employee.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
{% if project.is_active %}
|
||||
<a href="{% url 'project-archive' project.id %}" onclick="return confirm('{% trans 'Do you want to archive this project?' %}')" class="oh-dropdown__link">
|
||||
{% trans "Archive" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'project-archive' project.id %}" onclick="return confirm('{% trans 'Do you want to un archive this project?' %}')" class="oh-dropdown__link">
|
||||
{% trans "Un-Archive" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.employee.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<form action="{% url 'delete-project' project.id %}?view=card" method="post" onsubmit="return confirm('{% trans 'Do you want to delete this employee?' %}')">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="oh-dropdown__link oh-dropdown__link--danger">{% trans "Delete" %}</button>
|
||||
</form>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% comment %} pagination {% endcomment %}
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
>
|
||||
{% trans "Page" %} {{ projects.number }} {% trans "of" %} {{ projects.paginator.num_pages }}.
|
||||
</span>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{projects.number}}"
|
||||
hx-get="{% url 'project-filter' %}?view=card?page="
|
||||
hx-target="#view-container"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{projects.paginator.num_pages}}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if projects.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#view-container' hx-get="{% url 'project-filter' %}?view=card&?{{pd}}&page=1" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#view-container' hx-get="{% url 'project-filter' %}?view=card&?{{pd}}&page={{ projects.previous_page_number }}" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if projects.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#view-container' hx-get="{% url 'project-filter' %}?view=card&?{{pd}}&page={{ projects.next_page_number }}" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#view-container' hx-get="{% url 'project-filter' %}?view=card&?{{pd}}&page={{ projects.paginator.num_pages }}" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% comment %} </div> {% endcomment %}
|
||||
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 200px;margin: 10px auto ;" src="{% static 'images/ui/project/project.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">{% trans "There are currently no available projects; please create a new one." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% comment %} js scripts {% endcomment %}
|
||||
<script src="{% static '/project/project_view.js' %}"></script>
|
||||
<script src="{% static '/project/import.js' %}"></script>
|
||||
|
||||
@@ -1,236 +1,236 @@
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
|
||||
{% comment %} for showing messages {% endcomment %}
|
||||
{% if messages %}
|
||||
<div class="oh-alert-container">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "filter_tags.html" %}
|
||||
|
||||
<div id="view-container">
|
||||
{% if projects %}
|
||||
<div class="oh-tabs__content oh-tabs__content--active" style="padding-top: 10px;">
|
||||
{% comment %} easy filters {% endcomment %}
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=new&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:cyan"></span>
|
||||
{% trans "New" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=in_progress&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:rgb(103, 171, 238)"></span>
|
||||
{% trans "In progress" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=completed&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:yellowgreen"></span>
|
||||
{% trans "Completed" %}
|
||||
</span><span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=on_hold&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:orange"></span>
|
||||
{% trans "On Hold" %}
|
||||
</span>
|
||||
</span><span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=cancelled&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:Red"></span>
|
||||
{% trans "Cancelled" %}
|
||||
</span>
|
||||
</span><span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=expired&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:gray"></span>
|
||||
{% trans "Expired" %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">
|
||||
<div class="d-flex">
|
||||
<div class="">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="oh-input oh-input__checkbox mt-1 mr-2 all-projects"
|
||||
title='{% trans "Select All" %}'
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
hx-target="#section"
|
||||
hx-get="{% url 'search-candidate' %}?{{pd}}&orderby=name&view=list"
|
||||
>
|
||||
{% trans "Project" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Project Manager" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Project Members" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Status" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Start Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "End Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "File" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Description" %}</div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% for project in projects %}
|
||||
<div class="oh-sticky-table__tr ui-sortable-handle" data-url="{% url 'task-view' project.id %}?view=list">
|
||||
<div class="oh-sticky-table__sd {% if project.status == 'new'%} row-status--cyan
|
||||
{% elif project.status == 'in_progress' %} row-status--blue
|
||||
{% elif project.status == 'completed' %} row-status--yellow
|
||||
{% elif project.status == 'on_hold' %} row-status--orange
|
||||
{% elif project.status == 'cancelled' %} row-status--red
|
||||
{% else %} row-status--gray
|
||||
{% endif %}" >
|
||||
<div onclick="event.stopPropagation();">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="{{project.id}}"
|
||||
class="oh-input oh-input__checkbox mt-1 mr-2 all-project-row"
|
||||
/>
|
||||
{{project.title}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-sticky-table__td">{{project.manager}}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{% for employee in project.members.all %} {{employee}}<br />
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{project.get_status_display}}</div>
|
||||
<div class="oh-sticky-table__td">{{project.start_date}}</div>
|
||||
<div class="oh-sticky-table__td">{% if project.end_date %}{{project.end_date}}{% endif %}</div>
|
||||
<div class="oh-sticky-table__td">{% if project.document %}<a href="{{ project.document.url }}" target="_blank" onclick="event.stopPropagation();"><img style="width: 25px;margin: 5px auto ;" src="{% static 'images/ui/project/document.png' %}" class="" alt="document"/>{% endif %}</a></div>
|
||||
<div class="oh-sticky-table__td">{{project.description}}</div>
|
||||
|
||||
<div class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group">
|
||||
<a
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#ProjectModal"
|
||||
hx-get="{% url 'update-project' project.id %}"
|
||||
hx-target="#ProjectFormTarget"
|
||||
onclick="event.stopPropagation();"
|
||||
>
|
||||
<ion-icon
|
||||
name="create-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
style="color: blue"
|
||||
aria-label="create outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
<a
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
href="{% url 'delete-project' project.id %}?view=list"
|
||||
onclick="event.preventDefault();event.stopPropagation(); confirm(`{% trans 'Do you want to delete this project?' %}`)"
|
||||
>
|
||||
<ion-icon
|
||||
name="trash-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="trash outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 200px;margin: 10px auto ;" src="{% static 'images/ui/project/project.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">{% trans "There are currently no available projects; please create a new one." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% comment %} <div class="oh-pagination">
|
||||
<span class="oh-pagination__page">
|
||||
{% trans "Page" %} {{ allowances.number }} {% trans "of" %} {{
|
||||
allowances.paginator.num_pages }}.
|
||||
</span>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{allowances.number}}"
|
||||
hx-get="{% url 'filter-allowance' %}?{{pd}}"
|
||||
hx-target="#payroll-allowance-container"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label"
|
||||
>{% trans "of" %} {{allowances.paginator.num_pages}}</span
|
||||
>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if allowances.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#payroll-allowance-container"
|
||||
hx-get="{% url 'filter-allowance' %}?{{pd}}&page=1"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "First" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#payroll-allowance-container"
|
||||
hx-get="{% url 'filter-allowance' %}?{{pd}}&page={{ allowances.previous_page_number }}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Previous" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %} {% if allowances.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#payroll-allowance-container"
|
||||
hx-get="{% url 'filter-allowance' %}?{{pd}}&page={{ allowances.next_page_number }}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Next" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#payroll-allowance-container"
|
||||
hx-get="{% url 'filter-allowance' %}?{{pd}}&page={{ allowances.paginator.num_pages }}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Last" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div> {% endcomment %}
|
||||
</div>
|
||||
|
||||
|
||||
{% comment %} js scripts {% endcomment %}
|
||||
<script src="{% static '/project/project_view.js' %}"></script>
|
||||
<script>
|
||||
const tableRows = document.querySelectorAll('.oh-sticky-table__tr');
|
||||
tableRows.forEach(row => {
|
||||
row.addEventListener('click', event => {
|
||||
if (event.target.closest('.oh-sticky-table__thead')) {
|
||||
return;
|
||||
}
|
||||
const url = row.getAttribute('data-url');
|
||||
window.location.href = url;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="{% static '/project/project_action.js' %}"></script>
|
||||
<script src="{% static '/project/import.js' %}"></script>
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %} {% load horillafilters %}
|
||||
|
||||
|
||||
{% comment %} for showing messages {% endcomment %}
|
||||
{% if messages %}
|
||||
<div class="oh-alert-container">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "filter_tags.html" %}
|
||||
|
||||
<div id="view-container">
|
||||
{% if projects %}
|
||||
<div class="oh-tabs__content oh-tabs__content--active" style="padding-top: 10px;">
|
||||
{% comment %} easy filters {% endcomment %}
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=new&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:cyan"></span>
|
||||
{% trans "New" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=in_progress&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:rgb(103, 171, 238)"></span>
|
||||
{% trans "In progress" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=completed&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:yellowgreen"></span>
|
||||
{% trans "Completed" %}
|
||||
</span><span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=on_hold&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:orange"></span>
|
||||
{% trans "On Hold" %}
|
||||
</span>
|
||||
</span><span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=cancelled&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:Red"></span>
|
||||
{% trans "Cancelled" %}
|
||||
</span>
|
||||
</span><span class="m-3 draft" hx-get="{% url 'project-filter' %}?{{pd}}&status=expired&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:gray"></span>
|
||||
{% trans "Expired" %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">
|
||||
<div class="d-flex">
|
||||
<div class="">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="oh-input oh-input__checkbox mt-1 mr-2 all-projects"
|
||||
title='{% trans "Select All" %}'
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
hx-target="#section"
|
||||
hx-get="{% url 'search-candidate' %}?{{pd}}&orderby=name&view=list"
|
||||
>
|
||||
{% trans "Project" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Project Manager" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Project Members" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Status" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Start Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "End Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "File" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Description" %}</div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% for project in projects %}
|
||||
<div class="oh-sticky-table__tr ui-sortable-handle" data-url="{% url 'task-view' project.id %}?view=list">
|
||||
<div class="oh-sticky-table__sd {% if project.status == 'new'%} row-status--cyan
|
||||
{% elif project.status == 'in_progress' %} row-status--blue
|
||||
{% elif project.status == 'completed' %} row-status--yellow
|
||||
{% elif project.status == 'on_hold' %} row-status--orange
|
||||
{% elif project.status == 'cancelled' %} row-status--red
|
||||
{% else %} row-status--gray
|
||||
{% endif %}" >
|
||||
<div onclick="event.stopPropagation();">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="{{project.id}}"
|
||||
class="oh-input oh-input__checkbox mt-1 mr-2 all-project-row"
|
||||
/>
|
||||
{{project.title}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-sticky-table__td">{{project.manager}}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{% for employee in project.members.all %} {{employee}}<br />
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{project.get_status_display}}</div>
|
||||
<div class="oh-sticky-table__td">{{project.start_date}}</div>
|
||||
<div class="oh-sticky-table__td">{% if project.end_date %}{{project.end_date}}{% endif %}</div>
|
||||
<div class="oh-sticky-table__td">{% if project.document %}<a href="{{ project.document.url }}" target="_blank" onclick="event.stopPropagation();"><img style="width: 25px;margin: 5px auto ;" src="{% static 'images/ui/project/document.png' %}" class="" alt="document"/>{% endif %}</a></div>
|
||||
<div class="oh-sticky-table__td">{{project.description}}</div>
|
||||
|
||||
<div class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group">
|
||||
<a
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#ProjectModal"
|
||||
hx-get="{% url 'update-project' project.id %}"
|
||||
hx-target="#ProjectFormTarget"
|
||||
onclick="event.stopPropagation();"
|
||||
>
|
||||
<ion-icon
|
||||
name="create-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
style="color: blue"
|
||||
aria-label="create outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
<a
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
href="{% url 'delete-project' project.id %}?view=list"
|
||||
onclick="event.preventDefault();event.stopPropagation(); confirm(`{% trans 'Do you want to delete this project?' %}`)"
|
||||
>
|
||||
<ion-icon
|
||||
name="trash-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="trash outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 200px;margin: 10px auto ;" src="{% static 'images/ui/project/project.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">{% trans "There are currently no available projects; please create a new one." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% comment %} <div class="oh-pagination">
|
||||
<span class="oh-pagination__page">
|
||||
{% trans "Page" %} {{ allowances.number }} {% trans "of" %} {{
|
||||
allowances.paginator.num_pages }}.
|
||||
</span>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{allowances.number}}"
|
||||
hx-get="{% url 'filter-allowance' %}?{{pd}}"
|
||||
hx-target="#payroll-allowance-container"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label"
|
||||
>{% trans "of" %} {{allowances.paginator.num_pages}}</span
|
||||
>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if allowances.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#payroll-allowance-container"
|
||||
hx-get="{% url 'filter-allowance' %}?{{pd}}&page=1"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "First" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#payroll-allowance-container"
|
||||
hx-get="{% url 'filter-allowance' %}?{{pd}}&page={{ allowances.previous_page_number }}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Previous" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %} {% if allowances.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#payroll-allowance-container"
|
||||
hx-get="{% url 'filter-allowance' %}?{{pd}}&page={{ allowances.next_page_number }}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Next" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#payroll-allowance-container"
|
||||
hx-get="{% url 'filter-allowance' %}?{{pd}}&page={{ allowances.paginator.num_pages }}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Last" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div> {% endcomment %}
|
||||
</div>
|
||||
|
||||
|
||||
{% comment %} js scripts {% endcomment %}
|
||||
<script src="{% static '/project/project_view.js' %}"></script>
|
||||
<script>
|
||||
const tableRows = document.querySelectorAll('.oh-sticky-table__tr');
|
||||
tableRows.forEach(row => {
|
||||
row.addEventListener('click', event => {
|
||||
if (event.target.closest('.oh-sticky-table__thead')) {
|
||||
return;
|
||||
}
|
||||
const url = row.getAttribute('data-url');
|
||||
window.location.href = url;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="{% static '/project/project_action.js' %}"></script>
|
||||
<script src="{% static '/project/import.js' %}"></script>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Project Stage" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#TaskFormTarget"
|
||||
onclick="location.reload()"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'create-project-stage' project_id %}"
|
||||
hx-target="#TaskFormTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Project Stage" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#TaskFormTarget"
|
||||
onclick="location.reload()"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'create-project-stage' project_id %}"
|
||||
hx-target="#TaskFormTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Project Stage" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#TaskFormTarget"
|
||||
onclick="location.reload()"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'update-project-stage' stage_id %}"
|
||||
hx-target="#TaskFormTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}<span>{{form.errorList}}</span>
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Project Stage" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#TaskFormTarget"
|
||||
onclick="location.reload()"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'update-project-stage' stage_id %}"
|
||||
hx-target="#TaskFormTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}<span>{{form.errorList}}</span>
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,43 +1,44 @@
|
||||
{% load i18n %} {% load basefilters %}
|
||||
<form
|
||||
hx-get='{% url "task-filter" project_id %}?view={{request.GET.view}}'
|
||||
hx-target="#view-container"
|
||||
hx-swap="innerHTML"
|
||||
id="taskFilterForm">
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Task" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<input type="text" value="{{project_id}}" name="project" style="display:none;">
|
||||
<div class="col-sm-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Manager" %}</label>
|
||||
{{f.form.task_manager}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Stage" %}</label>
|
||||
{{f.form.stage}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Status" %}</label>
|
||||
{{f.form.status}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "End till" %}</label>
|
||||
{{f.form.end_till}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button class="oh-btn oh-btn--secondary oh-btn--small w-100 filterButton">
|
||||
{% trans "Filter" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% load i18n %} {% load basefilters %}
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Task" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<input type="text" value="{{project_id}}" name="project" style="display:none;">
|
||||
<div class="col-sm-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Managers" %}</label>
|
||||
{{f.form.task_managers}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Stage" %}</label>
|
||||
{{f.form.stage}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Members" %}</label>
|
||||
{{f.form.task_members}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Status" %}</label>
|
||||
{{f.form.status}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "End till" %}</label>
|
||||
{{f.form.end_till}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button class="oh-btn oh-btn--secondary oh-btn--small w-100 filterButton"
|
||||
id="taskFilterButton">
|
||||
{% trans "Filter" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Task" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#TaskFormTarget"
|
||||
onclick="location.reload()"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'create-task' stage_id %}"
|
||||
hx-target="#TaskFormTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Task" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#TaskFormTarget"
|
||||
onclick="location.reload()"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'create-task' stage_id %}"
|
||||
hx-target="#TaskFormTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Task" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#TaskFormTarget"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{% url 'create-task-in-project' project_id %}"
|
||||
hx-target="#TaskFormTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
var data = {{ stages|safe }};
|
||||
$('#project_stage').html('<options>--------</options>')
|
||||
|
||||
// Iterate through the data array and create options
|
||||
$.each(data, function(index, stage) {
|
||||
$("#project_stage").append("<option value=" + stage.pk + ">" + stage.fields.title + "</option>");
|
||||
});
|
||||
$("#project_stage").append( "<option value='create_new_stage'>Create a new Stage</option>");
|
||||
$("#project_stage").append( "<option value='old_stage'>Old Stage</option>");
|
||||
|
||||
});
|
||||
$("#project_stage").change(function(e){
|
||||
stage_id = $(this).val()
|
||||
|
||||
if (stage_id === "create_new_stage") {
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/project/create-stage-taskall',
|
||||
data: { project_id: {{project_id}} },
|
||||
success: function (response) {
|
||||
$("#ProjectStageModal").addClass("oh-modal--show");
|
||||
$("#ProjectStageFormTarget").html(response);
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Task" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#TaskFormTarget"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{% url 'create-task-in-project' project_id %}"
|
||||
hx-target="#TaskFormTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
var data = {{ stages|safe }};
|
||||
$('#project_stage').html('<options>--------</options>')
|
||||
|
||||
// Iterate through the data array and create options
|
||||
$.each(data, function(index, stage) {
|
||||
$("#project_stage").append("<option value=" + stage.pk + ">" + stage.fields.title + "</option>");
|
||||
});
|
||||
$("#project_stage").append( "<option value='create_new_stage'>Create a new Stage</option>");
|
||||
$("#project_stage").append( "<option value='old_stage'>Old Stage</option>");
|
||||
|
||||
});
|
||||
$("#project_stage").change(function(e){
|
||||
stage_id = $(this).val()
|
||||
|
||||
if (stage_id === "create_new_stage") {
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/project/create-stage-taskall',
|
||||
data: { project_id: {{project_id}} },
|
||||
success: function (response) {
|
||||
$("#ProjectStageModal").addClass("oh-modal--show");
|
||||
$("#ProjectStageFormTarget").html(response);
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Timesheet" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
style="
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 1.5rem;
|
||||
opacity: 0.7;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 15px;
|
||||
cursor: pointer;
|
||||
-webkit-transition: all 0.3s ease-in-out;
|
||||
transition: all 0.3s ease-in-out;
|
||||
"
|
||||
onclick="event.stopPropagation(); $(this).parents().find('.oh-modal--show:last').toggleClass('oh-modal--show')"
|
||||
id="close1"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{% url 'create-timesheet-task' task_id %}"
|
||||
hx-target="#TimesheetUpdateTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Timesheet" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
style="
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 1.5rem;
|
||||
opacity: 0.7;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 15px;
|
||||
cursor: pointer;
|
||||
-webkit-transition: all 0.3s ease-in-out;
|
||||
transition: all 0.3s ease-in-out;
|
||||
"
|
||||
onclick="event.stopPropagation(); $(this).parents().find('.oh-modal--show:last').toggleClass('oh-modal--show')"
|
||||
id="close1"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{% url 'create-timesheet-task' task_id %}"
|
||||
hx-target="#TimesheetUpdateTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
26
project/templates/task/new/forms/quick_create_task_form.html
Normal file
26
project/templates/task/new/forms/quick_create_task_form.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<form class="oh-kanban__add-card-form" hx-post="{% url 'quick-create-task' stage_id %}" hx-target="#{{hx_target}}">
|
||||
{% if form.errors %}
|
||||
<ul class="errorlist nonfield">
|
||||
{% for error in form.non_field_errors %}
|
||||
<li>
|
||||
{{ error }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{{form.as_p}}
|
||||
<div class="d-flex align-items-center justify-content-end">
|
||||
<a role="button"
|
||||
class="oh-btn oh-btn--light-dark-text oh-btn--x-small oh-kanban__add-card-cancel-btn me-2 mt-2">Cancel</a>
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--x-small mt-2">Add</button>
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
$(document).ready(function (e) {
|
||||
$('.oh-kanban__add-card-cancel-btn').click(function (e) {
|
||||
e.stopPropagation();
|
||||
$(this).closest('.oh-kanban__add-card-container').addClass('d-none');
|
||||
$(this).closest('.list-quick-add').toggleClass('d-none');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -1,55 +1,55 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header ">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Task" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
style="
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 1.5rem;
|
||||
opacity: 0.7;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 15px;
|
||||
cursor: pointer;
|
||||
-webkit-transition: all 0.3s ease-in-out;
|
||||
transition: all 0.3s ease-in-out;"
|
||||
onclick="event.stopPropagation(); $(this).parents().find('.oh-modal--show:last').toggleClass('oh-modal--show')"
|
||||
id="close1"
|
||||
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'update-task' task_id %}"
|
||||
hx-target="#TaskUpdateTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- <script>
|
||||
$(document).ready(function(){
|
||||
console.log('ready modal')
|
||||
$('#close1').click(function(e){
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
var closestModal = $(this).closest('.oh-modal--show');
|
||||
console.log(closestModal);
|
||||
closestModal.removeClass('oh-modal--show')
|
||||
// e.preventDefault()
|
||||
// var parentDiv = $(this).parents().closest('oh-modal--show')
|
||||
// console.log(parentDiv)
|
||||
// parentDiv.classList.remove('oh-modal--show')
|
||||
})
|
||||
});
|
||||
</script> -->
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header ">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Task" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
style="
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 1.5rem;
|
||||
opacity: 0.7;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 15px;
|
||||
cursor: pointer;
|
||||
-webkit-transition: all 0.3s ease-in-out;
|
||||
transition: all 0.3s ease-in-out;"
|
||||
onclick="event.stopPropagation(); $(this).parents().find('.oh-modal--show:last').toggleClass('oh-modal--show')"
|
||||
id="close1"
|
||||
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'update-task' task_id %}"
|
||||
hx-target="#TaskUpdateTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- <script>
|
||||
$(document).ready(function(){
|
||||
console.log('ready modal')
|
||||
$('#close1').click(function(e){
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
var closestModal = $(this).closest('.oh-modal--show');
|
||||
console.log(closestModal);
|
||||
closestModal.removeClass('oh-modal--show')
|
||||
// e.preventDefault()
|
||||
// var parentDiv = $(this).parents().closest('oh-modal--show')
|
||||
// console.log(parentDiv)
|
||||
// parentDiv.classList.remove('oh-modal--show')
|
||||
})
|
||||
});
|
||||
</script> -->
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Timesheet" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
style="
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 1.5rem;
|
||||
opacity: 0.7;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 15px;
|
||||
cursor: pointer;
|
||||
-webkit-transition: all 0.3s ease-in-out;
|
||||
transition: all 0.3s ease-in-out;"
|
||||
onclick="event.stopPropagation(); $(this).parents().find('.oh-modal--show:last').toggleClass('oh-modal--show')"
|
||||
id="close1"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{% url 'update-timesheet-task' timesheet_id %}"
|
||||
hx-target="#TimesheetUpdateTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Timesheet" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
style="
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 1.5rem;
|
||||
opacity: 0.7;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 15px;
|
||||
cursor: pointer;
|
||||
-webkit-transition: all 0.3s ease-in-out;
|
||||
transition: all 0.3s ease-in-out;"
|
||||
onclick="event.stopPropagation(); $(this).parents().find('.oh-modal--show:last').toggleClass('oh-modal--show')"
|
||||
id="close1"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{% url 'update-timesheet-task' timesheet_id %}"
|
||||
hx-target="#TimesheetUpdateTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,144 +1,141 @@
|
||||
{% extends 'index.html' %}
|
||||
|
||||
{% block content %}
|
||||
{% load i18n %} {% load yes_no %}{% load static %}
|
||||
|
||||
<main :class="sidebarOpen ? 'oh-main__sidebar-visible' : ''">
|
||||
{% include 'task/new/task_navbar.html' %}
|
||||
</main>
|
||||
<div id="ohMessages"></div>
|
||||
<div id="view-container" class="oh-wrapper">
|
||||
{% if view_type == 'list' %}
|
||||
{% include 'task/new/task_list_view.html' %}
|
||||
{% else %}
|
||||
{% include 'task/new/task_kanban_view.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="TaskModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TaskModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="TaskFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="TaskUpdateModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TaskUpdateModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="TaskUpdateTarget"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectStageModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TaskTimesheetModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width:550px"
|
||||
id="ProjectStageFormTarget"
|
||||
></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="TaskTimesheetModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TaskTimesheetModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width:1350px"
|
||||
id="TaskTimesheetTarget"
|
||||
></div>
|
||||
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal {{request.GET.active}}"
|
||||
id="TimesheetUpdateModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TaskUpdateModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="TimesheetUpdateTarget"
|
||||
></div>
|
||||
</div>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script>
|
||||
{% comment %} function cDelete(timeSheetId) {
|
||||
console.log("Confirm Delete called"); // Add this line for debugging
|
||||
if (chonfirm("{% trans 'Do you want to delete this time sheet?' %}")) {
|
||||
// User confirmed, perform the delete operation here
|
||||
deleteTimeSheet(timeSheetId);
|
||||
}
|
||||
} {% endcomment %}
|
||||
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== "") {
|
||||
const cookies = document.cookie.split(";");
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
function deleteTimeSheet(element,timeSheetId) {
|
||||
console.log($(element).parent().closest('ui-sortable-handle')); // Add this line for debugging
|
||||
$.ajax({
|
||||
url: "/project/delete-time-sheet-ajax/" + timeSheetId + "/", // Include the time_sheet.id in the URL
|
||||
type: "post",
|
||||
data:{
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
},
|
||||
success: function(response) {
|
||||
// Time sheet deleted successfully, you can handle this case as needed
|
||||
// For example, you can redirect the user or update the UI.
|
||||
$("#ohMessages").append(`
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated oh-alert--${response.type}">
|
||||
${response.message}
|
||||
</div>
|
||||
</div>`)
|
||||
$(element).closest('.oh-sticky-table__tr.ui-sortable-handle').hide()
|
||||
},
|
||||
error: function() {
|
||||
// Handle errors if the delete operation fails
|
||||
alert("Failed to delete time sheet");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
{% extends 'index.html' %}
|
||||
|
||||
{% block content %}
|
||||
{% load i18n %} {% load horillafilters %}{% load static %}
|
||||
<style>
|
||||
.avatars {
|
||||
display: flex;
|
||||
padding: 8px 10px 8px 10px;
|
||||
}
|
||||
|
||||
.avatars__item {
|
||||
background-color: #596376;
|
||||
border: 2px solid white;
|
||||
border-radius: 100%;
|
||||
color: #ffffff;
|
||||
display: block;
|
||||
font-family: sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: 100;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
line-height: 17px;
|
||||
text-align: center;
|
||||
transition: margin 0.1s ease-in-out;
|
||||
overflow: hidden;
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.avatars__item:first-child {
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.avatars__item:nth-child(2) {
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.avatars__item:nth-child(3) {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.avatars__item:nth-child(4) {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.avatars__item:nth-child(5) {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.avatars__item:last-child {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.avatars__item img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.avatars:hover .avatars__item {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.select2-container .select2-selection.select2-selection--multiple {
|
||||
padding: 5px !important;
|
||||
max-height: 70px !important;
|
||||
overflow: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.search-highlight {
|
||||
background-color: rgba(255, 68, 0, 0.076);
|
||||
}
|
||||
#enlargeImageContainer {
|
||||
position: absolute;
|
||||
left: -300px;
|
||||
top: 100px;
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
</style>
|
||||
<main :class="sidebarOpen ? 'oh-main__sidebar-visible' : ''">
|
||||
{% include 'task/new/task_navbar.html' %}
|
||||
</main>
|
||||
{% include "generic/components.html" %}
|
||||
<div class="oh-wrapper">
|
||||
{% include "cbv/projects/project_details.html" %}
|
||||
|
||||
<div id="viewContainer" >
|
||||
{% if view_type == 'list' %}
|
||||
{% include 'task/new/task_list_view.html' %}
|
||||
{% else %}
|
||||
{% include 'task/new/task_kanban_view.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-modal" id="TaskModal" role="dialog" aria-labelledby="TaskModal" aria-hidden="true">
|
||||
<div class="oh-modal__dialog" style="max-width: 550px" id="TaskFormTarget"></div>
|
||||
</div>
|
||||
|
||||
<div class="oh-modal" id="TaskUpdateModal" role="dialog" aria-labelledby="TaskUpdateModal" aria-hidden="true">
|
||||
<div class="oh-modal__dialog" style="max-width: 550px" id="TaskUpdateTarget"></div>
|
||||
</div>
|
||||
|
||||
<div class="oh-modal" id="ProjectStageModal" role="dialog" aria-labelledby="TaskTimesheetModal" aria-hidden="true">
|
||||
<div class="oh-modal__dialog" style="max-width:550px" id="ProjectStageFormTarget"></div>
|
||||
</div>
|
||||
|
||||
<div class="oh-modal" id="TaskTimesheetModal" role="dialog" aria-labelledby="TaskTimesheetModal" aria-hidden="true">
|
||||
<div class="oh-modal__dialog" style="max-width:1350px" id="TaskTimesheetTarget"></div>
|
||||
</div>
|
||||
|
||||
<div class="oh-modal {{request.GET.active}}" id="TimesheetUpdateModal" role="dialog" aria-labelledby="TaskUpdateModal"
|
||||
aria-hidden="true">
|
||||
<div class="oh-modal__dialog" style="max-width: 550px" id="TimesheetUpdateTarget"></div>
|
||||
</div>
|
||||
|
||||
<div class="oh-modal" id="addTimesheetModal" role="dialog" aria-labelledby="addTimesheetModal" aria-hidden="true"
|
||||
style="z-index: 1022;">
|
||||
<div class="oh-modal__dialog" id="addTimesheetModalBody">
|
||||
<!-- Modal hx content goes here -->
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('#taskFilterButton').click()
|
||||
});
|
||||
|
||||
$("#viewContainer").on("htmx:afterSwap", function (e) {
|
||||
// Check if the event originated from the quick add button or editable input field
|
||||
var triggerElement = e.target ? e.target : null;
|
||||
if (triggerElement && (triggerElement.classList.contains('oh-table__editable-input') || triggerElement.classList.contains('oh-kanban__add-card-container'))) {
|
||||
// If the event is triggered by the 'Quick add' button or Editable input field , return and do nothing
|
||||
return;
|
||||
}
|
||||
// Load new scripts
|
||||
$.getScript("{% static 'build/js/web.frontend.min.js' %}", function () { });
|
||||
$.getScript("{% static '/project/task_pipeline.js' %}", function () { });
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
|
||||
<script src="{% static '/project/task_pipeline.js' %}"></script>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -1,105 +1,105 @@
|
||||
{% load i18n %} {% load yes_no %}
|
||||
<div id="ohMessages"></div>
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
{% comment %}
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">
|
||||
<div class="d-flex">
|
||||
<div class="">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="oh-input oh-input__checkbox mt-1 mr-2 all-candidate"
|
||||
/>
|
||||
</div>
|
||||
<div>{% trans "Tasks" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Task Assigner" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Task Members" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "End Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Stage" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Document" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Description" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Actions" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endcomment %}
|
||||
|
||||
<div class="oh-sticky-table__tr ui-sortable-handle task_row">
|
||||
<div class="oh-sticky-table__sd">{{task.title}}</div>
|
||||
<div class="oh-sticky-table__td">{{task.task_assigner}}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{% for employee in task.task_members.all %} {{employee}}<br />
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{task.end_date}}</div>
|
||||
{% comment %}
|
||||
<div class="oh-sticky-table__td">{{task.stage}}</div>
|
||||
{% endcomment %}
|
||||
<div class="oh-sticky-table__td oh-table-config__td">
|
||||
<select
|
||||
id="stageChange{{task.id}}"
|
||||
class="oh-select w-100 stage-change"
|
||||
data-task="{{task.id}}"
|
||||
>
|
||||
{% for stage in stages %} {% if stage.id == task.stage.id %}
|
||||
<option value="{{stage.id}}" selected>{{stage}}</option>
|
||||
{% else %}
|
||||
<option value="{{stage.id}}">{{stage}}</option>
|
||||
{% endif %} {% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="oh-sticky-table__td">{{task.document}}</div>
|
||||
<div class="oh-sticky-table__td">{{task.description}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== "") {
|
||||
const cookies = document.cookie.split(";");
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||
cookieValue = decodeURIComponent(
|
||||
cookie.substring(name.length + 1)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
$(".stage-change").change(function () {
|
||||
var stage = $(this).val();
|
||||
var task = $(this).data("task");
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: "/project/task-stage-change",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
stage: stage,
|
||||
task: task,
|
||||
},
|
||||
success: function (response) {
|
||||
$("#ohMessages").append(`
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated oh-alert--${response.type}">
|
||||
${response.message}
|
||||
</div>
|
||||
</div>`);
|
||||
},
|
||||
});
|
||||
$(this).parents(".task_row").remove();
|
||||
$();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
{% load i18n %} {% load horillafilters %}
|
||||
<div id="ohMessages"></div>
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
{% comment %}
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">
|
||||
<div class="d-flex">
|
||||
<div class="">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="oh-input oh-input__checkbox mt-1 mr-2 all-candidate"
|
||||
/>
|
||||
</div>
|
||||
<div>{% trans "Tasks" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Task Assigner" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Task Members" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "End Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Stage" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Document" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Description" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Actions" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endcomment %}
|
||||
|
||||
<div class="oh-sticky-table__tr ui-sortable-handle task_row">
|
||||
<div class="oh-sticky-table__sd">{{task.title}}</div>
|
||||
<div class="oh-sticky-table__td">{{task.task_assigner}}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{% for employee in task.task_members.all %} {{employee}}<br />
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{task.end_date}}</div>
|
||||
{% comment %}
|
||||
<div class="oh-sticky-table__td">{{task.stage}}</div>
|
||||
{% endcomment %}
|
||||
<div class="oh-sticky-table__td oh-table-config__td">
|
||||
<select
|
||||
id="stageChange{{task.id}}"
|
||||
class="oh-select w-100 stage-change"
|
||||
data-task="{{task.id}}"
|
||||
>
|
||||
{% for stage in stages %} {% if stage.id == task.stage.id %}
|
||||
<option value="{{stage.id}}" selected>{{stage}}</option>
|
||||
{% else %}
|
||||
<option value="{{stage.id}}">{{stage}}</option>
|
||||
{% endif %} {% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="oh-sticky-table__td">{{task.document}}</div>
|
||||
<div class="oh-sticky-table__td">{{task.description}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== "") {
|
||||
const cookies = document.cookie.split(";");
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||
cookieValue = decodeURIComponent(
|
||||
cookie.substring(name.length + 1)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
$(".stage-change").change(function () {
|
||||
var stage = $(this).val();
|
||||
var task = $(this).data("task");
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: "/project/task-stage-change",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
stage: stage,
|
||||
task: task,
|
||||
},
|
||||
success: function (response) {
|
||||
$("#ohMessages").append(`
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated oh-alert--${response.type}">
|
||||
${response.message}
|
||||
</div>
|
||||
</div>`);
|
||||
},
|
||||
});
|
||||
$(this).parents(".task_row").remove();
|
||||
$();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
209
project/templates/task/new/task_card_view.html
Normal file
209
project/templates/task/new/task_card_view.html
Normal file
@@ -0,0 +1,209 @@
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
|
||||
{% comment %} for showing messages {% endcomment %}
|
||||
{% if messages %}
|
||||
<div class="oh-alert-container" >
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div id = 'ohMessages'>
|
||||
</div>
|
||||
{% include "filter_tags.html" %}
|
||||
|
||||
{% if stages %}
|
||||
{% comment %} vertical tabs {% endcomment %}
|
||||
<div class="oh-wrapper oh-tabs">
|
||||
<div class="ui-sortable" id ="project container">
|
||||
<div class="oh-kanban ui-sortable" id='projectStages'>
|
||||
|
||||
{% comment %} stages view {% endcomment %}
|
||||
{% for stage in stages %}
|
||||
<div
|
||||
class="oh-kanban__section stages"
|
||||
data-container='stages'
|
||||
id="kanban{{stage.id}}"
|
||||
|
||||
data-project-id = "{{project.id}}"
|
||||
>
|
||||
<div class="oh-kanban__section-head stage ms-4" style="cursor: pointer;"
|
||||
data-stage-id="{{stage.id}}"
|
||||
data-sequence = "{{stage.sequence}}"
|
||||
data-project-id='{{project.id}}'>
|
||||
<div class="d-flex">
|
||||
<span class="oh-kanban__section-title" data-type="label">{{stage.title}}</span>
|
||||
</div>
|
||||
<div class="oh-tabs__input-badge-container">
|
||||
<span class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round mr-1">
|
||||
{{ stage.tasks.all|length}}
|
||||
</span>
|
||||
</div>
|
||||
{% comment %} drop down menu {% endcomment %}
|
||||
<div class="oh-kanban__head-actions oh-kanban__dropdown" >
|
||||
<div class="oh-kanban__head-actions oh-kanban__dropdown">
|
||||
<div >
|
||||
<a class="" @click="open = !open" style="padding:20px"
|
||||
hx-get='{% url "create-task" stage.id %}'
|
||||
hx-target='#TaskFormTarget' class="oh-dropdown__link"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskModal"
|
||||
data-key=".oh-kanban__section"
|
||||
onclick="handleCreateTaskClick(event)"
|
||||
>
|
||||
<ion-icon name="add-sharp" role="img" class="md hydrated mt-1" aria-label="add sharp"></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<button class="oh-btn oh-btn--small oh-btn--transparent oh-kanban__btn oh-kanban__dropdown-toggle">
|
||||
<ion-icon name="ellipsis-vertical-sharp" role="img" class="md hydrated" aria-label="ellipsis vertical sharp"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-dropdown oh-kanban__dropdown-menu d-none">
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right">
|
||||
<ul class="oh-dropdown__items">
|
||||
<li class="oh-dropdown__item">
|
||||
<a
|
||||
hx-get='{% url "update-project-stage" stage.id %}'
|
||||
hx-target='#TaskFormTarget' class="oh-dropdown__link"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskModal"
|
||||
data-key=".oh-kanban__section"
|
||||
>{% trans "Edit" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<form action="{% url 'delete-project-stage' stage.id %}" onsubmit="return confirm('{% trans "Are you sure you want to delete this stage?" %}');" method="post">
|
||||
{% csrf_token %}
|
||||
<button class="oh-dropdown__link oh-dropdown__link--danger ">{% trans "Delete" %}</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% comment %} task inside stage {% endcomment %}
|
||||
<div
|
||||
class="oh-kanban__section-body ui-sortable tasks-container"
|
||||
style="left-padding:10px;"
|
||||
data-stage-id='{{stage.id}}'
|
||||
data-project-id="{{project.id}}"
|
||||
>
|
||||
|
||||
{% for task in stage.tasks.all|dictsort:"sequence" %}
|
||||
{% if task in tasks %}
|
||||
<div
|
||||
class="oh-kanban__card task ms-3"
|
||||
data-task-id="{{task.id}}"
|
||||
data-task="{{task.title}}"
|
||||
id="task-{{task.id}}"
|
||||
style="cursor: pointer;overflow: visible;left-padding:10px"
|
||||
>
|
||||
<div class="oh-kanban__card-head">
|
||||
<a hx-get='{% url "task-details" task.id %}?view=card'
|
||||
hx-target='#TaskFormTarget' class="oh-dropdown__link"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskModal"
|
||||
data-key=".oh-kanban__section"
|
||||
>
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{task}}&background=random"
|
||||
class="oh-profile__image"
|
||||
alt="task"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="oh-kanban-card__details">
|
||||
<span class="oh-kanban-card__title">
|
||||
{{task}}
|
||||
</span>
|
||||
<span class="oh-kanban-card__subtitle">{{task.task_manager}} </span><br>
|
||||
<span class="oh-kanban-card__subtitle">{{task.end_date}}</span><br>
|
||||
<span class="oh-kanban-card__subtitle">{{task.get_status_display}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
|
||||
{% comment %} drop down inside card {% endcomment %}
|
||||
|
||||
<div class="oh-kanban__card-actions oh-kanban__dropdown">
|
||||
<button
|
||||
class="oh-btn oh-btn--small oh-btn--transparent oh-kanban__btn oh-kanban__dropdown-toggle"
|
||||
>
|
||||
<ion-icon
|
||||
name="ellipsis-vertical-sharp"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="ellipsis vertical sharp"
|
||||
></ion-icon>
|
||||
</button>
|
||||
<div class="oh-dropdown oh-kanban__dropdown-menu d-none">
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right">
|
||||
<ul class="oh-dropdown__items">
|
||||
<li class="oh-dropdown__item">
|
||||
<a
|
||||
hx-get='{% url "update-task" task.id %}?project_task=true'
|
||||
hx-target='#TaskFormTarget' class="oh-dropdown__link"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskModal"
|
||||
data-key=".oh-kanban__section"
|
||||
>{% trans "Edit" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<form action="{% url 'delete-task' task.id %}?view=card" onsubmit="return confirm('{% trans "Are you sure you want to delete this task?" %}');" method="post">
|
||||
{% csrf_token %}
|
||||
<button class="oh-dropdown__link oh-dropdown__link--danger ">{% trans "Delete" %}</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% comment %} <a
|
||||
class="oh-btn oh-btn--secondary oh-btn--shadow"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#ProjectStageModal"
|
||||
hx-get="{% url 'create-project-stage' project_id %}"
|
||||
hx-target="#ProjectStageFormTarget"
|
||||
>
|
||||
<ion-icon class="me-1" name="add-outline"></ion-icon>{% trans "Stage" %}
|
||||
</a> {% endcomment %}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 200px;margin: 10px auto ;" src="{% static 'images/ui/project/task.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">{% trans "There are currently no available tasks; please create a new one." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% comment %} js files {% endcomment %}
|
||||
<script src="{% static '/project/task_pipeline.js' %}"></script>
|
||||
|
||||
|
||||
@@ -1,134 +1,133 @@
|
||||
{% load i18n %} {% load yes_no %}
|
||||
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5 style="margin-bottom: 20px;">{{task.title}}</h5>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-timeoff-modal__stats-container mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Title" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task.title}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Project" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task.project}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Stage" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task.stage}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Task manager" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task.task_manager}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Task members" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{% for member in task.task_members.all %}{{member}}, {% endfor %}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Status" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task.get_status_display}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "End Date" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task.end_date}}</span>
|
||||
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Description" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task.description}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Document" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task.document}}
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-modal__button-container text-center">
|
||||
<div class="oh-btn-group">
|
||||
|
||||
<a hx-get="{% url 'update-task' task.id %}"
|
||||
hx-target='#TaskUpdateTarget'
|
||||
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskUpdateModal"
|
||||
data-key=".oh-kanban__section"
|
||||
class="oh-btn oh-btn--info" style="width: 50%;">
|
||||
<ion-icon
|
||||
name="create-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="create outline"
|
||||
>
|
||||
</ion-icon>{% trans "Edit" %}
|
||||
</a>
|
||||
<a hx-get="{% url 'task-timesheet' task.id %}"
|
||||
hx-target='#TaskTimesheetTarget'
|
||||
data-target="#TaskTimesheetModal"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-key=".oh-kanban__section"
|
||||
class="oh-btn oh-btn--info" style="width: 56%; background-color:green">
|
||||
<ion-icon
|
||||
name="bar-chart"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="create outline"
|
||||
>
|
||||
</ion-icon>{% trans " Time sheet" %}
|
||||
</a>
|
||||
<a href="{% url 'delete-task' task.id %}?view={{request.GET.view }}" class="oh-btn oh-btn--danger"style="width: 50%;"
|
||||
onclick="event.preventDefault();event.stopPropagation(); confirm(`{% trans 'Do you want to delete this task?' %}`)"
|
||||
>
|
||||
{% comment %} <a hx-get="{% url 'delete-task' task.id %}" class="oh-btn oh-btn--danger"style="width: 50%;"
|
||||
onclick="event.preventDefault();event.stopPropagation(); confirm(`{% trans 'Do you want to delete this task?' %}`)"
|
||||
hx-target='#TaskUpdateTarget'
|
||||
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskUpdateModal"
|
||||
data-key=".oh-kanban__section"
|
||||
class="oh-btn oh-btn--info" style="width: 50%;">
|
||||
{% endcomment %}
|
||||
<ion-icon
|
||||
name="trash-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="create outline"
|
||||
></ion-icon>{% trans "Delete" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% comment %} <script>
|
||||
$(document).ready(function(){
|
||||
console.log('ready modal')
|
||||
$('.oh-modal__close').click(function(e){
|
||||
e.preventDefault()
|
||||
var parentDiv = $(this).parents().closest('oh-modal--show')
|
||||
console.log(parentDiv)
|
||||
parentDiv.classList.remove('oh-modal--show')
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
|
||||
</script> {% endcomment %}
|
||||
|
||||
{% load i18n %} {% load horillafilters %}
|
||||
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5 style="margin-bottom: 20px;">{{task.title}}</h5>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-timeoff-modal__stats-container mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Title" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task.title}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Project" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task.project}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Stage" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task.stage}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Task manager" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task.task_manager}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Task members" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{% for member in task.task_members.all %}{{member}}, {% endfor %}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Status" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task.get_status_display}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "End Date" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task.end_date}}</span>
|
||||
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Description" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task.description}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Document" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{task.document}}
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-modal__button-container text-center">
|
||||
<div class="oh-btn-group">
|
||||
|
||||
<a hx-get="{% url 'update-task' task.id %}"
|
||||
hx-target='#TaskUpdateTarget'
|
||||
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskUpdateModal"
|
||||
data-key=".oh-kanban__section"
|
||||
class="oh-btn oh-btn--info" style="width: 50%;">
|
||||
<ion-icon
|
||||
name="create-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="create outline"
|
||||
>
|
||||
</ion-icon>{% trans "Edit" %}
|
||||
</a>
|
||||
<a hx-get="{% url 'task-timesheet' task.id %}"
|
||||
hx-target='#TaskTimesheetTarget'
|
||||
data-target="#TaskTimesheetModal"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-key=".oh-kanban__section"
|
||||
class="oh-btn oh-btn--info" style="width: 56%; background-color:green">
|
||||
<ion-icon
|
||||
name="bar-chart"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="create outline"
|
||||
>
|
||||
</ion-icon>{% trans " Time sheet" %}
|
||||
</a>
|
||||
<a href="{% url 'delete-task' task.id %}?view={{request.GET.view }}" class="oh-btn oh-btn--danger"style="width: 50%;"
|
||||
onclick="event.preventDefault();event.stopPropagation(); confirm(`{% trans 'Do you want to delete this task?' %}`)"
|
||||
>
|
||||
{% comment %} <a hx-get="{% url 'delete-task' task.id %}" class="oh-btn oh-btn--danger"style="width: 50%;"
|
||||
onclick="event.preventDefault();event.stopPropagation(); confirm(`{% trans 'Do you want to delete this task?' %}`)"
|
||||
hx-target='#TaskUpdateTarget'
|
||||
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskUpdateModal"
|
||||
data-key=".oh-kanban__section"
|
||||
class="oh-btn oh-btn--info" style="width: 50%;">
|
||||
{% endcomment %}
|
||||
<ion-icon
|
||||
name="trash-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="create outline"
|
||||
></ion-icon>{% trans "Delete" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% comment %} <script>
|
||||
$(document).ready(function(){
|
||||
console.log('ready modal')
|
||||
$('.oh-modal__close').click(function(e){
|
||||
e.preventDefault()
|
||||
var parentDiv = $(this).parents().closest('oh-modal--show')
|
||||
console.log(parentDiv)
|
||||
parentDiv.classList.remove('oh-modal--show')
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
|
||||
</script> {% endcomment %}
|
||||
|
||||
@@ -1,210 +1,168 @@
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
|
||||
{% comment %} for showing messages {% endcomment %}
|
||||
{% if messages %}
|
||||
<div class="oh-alert-container" >
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% if messages %}
|
||||
<div class="oh-alert-container">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% include "filter_tags.html" %}
|
||||
|
||||
{% if stages %}
|
||||
<div class="oh-tabs oh-card mt-3 mb-3">
|
||||
<div class="oh-kanban ui-sortable" id="projectStages" style="border: none;">
|
||||
{% for stage in stages %}
|
||||
<div class="oh-kanban__section ml-2 stage" id="projectStage{{stage.id}}" style="opacity: 1;"
|
||||
data-stage-id="{{stage.id}}" data-sequence="{{stage.sequence}}" data-project-id="{{project.id}}">
|
||||
<div class="oh-kanban-group__head" style="cursor: pointer;" onclick="updateStageClass(this)">
|
||||
<span class="oh-kanban-gorup__title" data-type="label" style="display: flex;">
|
||||
{{stage.title}}
|
||||
<div class="oh-tabs__input-badge-container ml-2">
|
||||
<span class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round mr-1 task-count">
|
||||
{{ stage.tasks.all|length}}
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
{% if request.user|is_project_manager:stage.project or request.user.is_superuser %}
|
||||
<div class="oh-kanban__head-actions oh-kanban__dropdown" onclick="event.stopPropagation();">
|
||||
<button class="oh-btn oh-btn--small oh-btn--transparent oh-kanban__btn oh-kanban__dropdown-toggle">
|
||||
<ion-icon name="ellipsis-vertical-sharp" role="img" class="md hydrated"
|
||||
aria-label="ellipsis vertical sharp"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-dropdown oh-kanban__dropdown-menu d-none">
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right">
|
||||
<ul class="oh-dropdown__items">
|
||||
<li class="oh-dropdown__item">
|
||||
<a hx-get='{% url "update-project-stage" stage.id %}' hx-target='#TaskFormTarget'
|
||||
class="oh-dropdown__link" data-toggle="oh-modal-toggle" data-target="#TaskModal">
|
||||
{% trans "Edit" %}
|
||||
</a>
|
||||
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<a hx-confirm="{% trans 'Are you sure you want to delete this stage?' %}"
|
||||
hx-post="{% url 'delete-project-stage' stage.id %}?view=card"
|
||||
hx-target="#projectStage{{stage.id}}"
|
||||
class="oh-dropdown__link oh-dropdown__link--danger">{% trans "Delete" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<button class="oh-btn oh-btn--small oh-btn--transparent oh-kanban__btn oh-kanban__add-card-btn"
|
||||
title="{% trans 'Add task' %}" hx-get="{% url 'quick-create-task' stage.id %}"
|
||||
hx-target="#taskCreateForm{{stage.id}}">
|
||||
<ion-icon name="add-sharp" role="img" class="md hydrated" aria-label="add sharp"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="oh-kanban__section-body ui-sortable task-container">
|
||||
<div class="oh-card oh-kanban__add-card-container mb-2 d-none" id="taskCreateForm{{stage.id}}">
|
||||
</div>
|
||||
{% for task in stage.tasks.all|dictsort:"sequence" %}
|
||||
{% if task in tasks %}
|
||||
<div class="oh-kanban__card task" data-task-id="{{task.id}}" style="opacity: 1;min-height:85px">
|
||||
<div class="oh-kanban__card-head" hx-get="{% url 'task-detail-view' task.id %}?view=card"
|
||||
hx-target='#genericModalBody' data-toggle="oh-modal-toggle" style="cursor: pointer;"
|
||||
data-target="#genericModal">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img src="{{task.get_avatar}}"
|
||||
class="oh-profile__image" alt="{{task}}">
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark" data-type="label"
|
||||
style="font-weight:bold">{{task}}</span>
|
||||
</div>
|
||||
{% if request.user|is_project_manager:stage.project or request.user.is_superuser or request.user|is_task_manager:task %}
|
||||
<div class="oh-kanban__card-actions oh-kanban__dropdown" onclick="event.stopPropagation();">
|
||||
<button
|
||||
class="oh-btn oh-btn--small oh-btn--transparent oh-kanban__btn oh-kanban__dropdown-toggle">
|
||||
<ion-icon name="ellipsis-vertical-sharp" role="img" class="md hydrated"
|
||||
aria-label="ellipsis vertical sharp"></ion-icon>
|
||||
</button>
|
||||
|
||||
<div class="oh-dropdown oh-kanban__dropdown-menu d-none">
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right">
|
||||
<ul class="oh-dropdown__items">
|
||||
<li class="oh-dropdown__item">
|
||||
<a hx-get='{% url "update-task" task.id %}?project_task=true'
|
||||
hx-target='#TaskFormTarget' class="oh-dropdown__link"
|
||||
data-toggle="oh-modal-toggle" data-target="#TaskModal">
|
||||
{% trans "Edit" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<a hx-confirm="{% trans 'Do you really want to delete this task?' %}"
|
||||
hx-post="{% url 'delete-task' task.id %}?view=card"
|
||||
hx-target="#taskCreateForm{{stage.id}}"
|
||||
class="oh-dropdown__link oh-dropdown__link--danger">
|
||||
{% trans "Delete" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="oh-kanban__card-body">
|
||||
<button class="oh-kanban__card-body-collapse oh-kanban__card-collapse--down"
|
||||
aria-label="Toggle Options"></button>
|
||||
<div class="oh-kanban__card-content oh-kanban__card-content--hide">
|
||||
<span class="oh-kanban-card__subtitle d-flex">{% trans "Managers: " %}
|
||||
<span class="oh-kanban-card__title ml-1">
|
||||
{% for manager in task.task_managers.all %}
|
||||
{{manager}}
|
||||
{% endfor %}
|
||||
</span>
|
||||
</span><br>
|
||||
<div style="display: flex;" class="mb-2">
|
||||
<label class="oh-kanban-card__subtitle">{% trans "Status" %} </label>
|
||||
<select class="oh-table__editable-input w-100 ml-2"
|
||||
hx-get="{% url 'update-project-task-status' task.id %}?view=card"
|
||||
hx-trigger="change" hx-swap="afterend" tabindex="-1" aria-hidden="true"
|
||||
name="status">
|
||||
{% for option in task.TASK_STATUS %}
|
||||
<option value="{{option.0}}" {% if option.0 == task.status %}selected{% endif %}>{{option.1}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-kanban__card-footer">
|
||||
<span class="oh-kanban__card-footer-text oh-text--light">{% trans "End date:" %} :
|
||||
{{task.end_date}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if request.user|is_project_manager:stages.first.project or request.user.is_superuser %}
|
||||
<div class="oh-kanban__add-container">
|
||||
<button class="oh-btn oh-btn--x-small oh-kanban__add-section mr-2" data-toggle="oh-modal-toggle"
|
||||
data-target="#objectCreateModal" hx-get="{% url 'create-project-stage' project_id %}"
|
||||
hx-target="#objectCreateModalTarget">
|
||||
<ion-icon class="me-1 md hydrated" name="add-outline" role="img" aria-label="add outline"></ion-icon>
|
||||
{% trans "Add Stage" %}
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div>
|
||||
<img style="display: block;width: 200px;margin: 10px auto ;" src="{% static 'images/ui/project/task.png' %}"
|
||||
class="" alt="Page not found. 404." />
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">
|
||||
{% trans "No search result found!" %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div id = 'ohMessages'>
|
||||
</div>
|
||||
{% include "filter_tags.html" %}
|
||||
|
||||
{% if stages %}
|
||||
{% comment %} vertical tabs {% endcomment %}
|
||||
<div class="oh-wrapper oh-tabs">
|
||||
<div class="ui-sortable" id ="project container">
|
||||
<div class="oh-kanban ui-sortable" id='project_stages'>
|
||||
|
||||
{% comment %} stages view {% endcomment %}
|
||||
{% for stage in stages %}
|
||||
<div
|
||||
class="oh-kanban__section stages"
|
||||
data-container='stages'
|
||||
id="kanban{{stage.id}}"
|
||||
|
||||
data-project-id = "{{project.id}}"
|
||||
>
|
||||
<div class="oh-kanban__section-head stage ms-4" style="cursor: pointer;"
|
||||
data-stage-id="{{stage.id}}"
|
||||
data-sequence = "{{stage.sequence}}"
|
||||
data-project-id='{{project.id}}'>
|
||||
<div class="d-flex">
|
||||
<span class="oh-kanban__section-title" data-type="label">{{stage.title}}</span>
|
||||
</div>
|
||||
<div class="oh-tabs__input-badge-container">
|
||||
<span class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round mr-1">
|
||||
{{ stage.tasks.all|length}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% comment %} drop down menu {% endcomment %}
|
||||
<div class="oh-kanban__head-actions oh-kanban__dropdown" >
|
||||
<div class="oh-kanban__head-actions oh-kanban__dropdown">
|
||||
<div >
|
||||
<a class="" @click="open = !open" style="padding:20px"
|
||||
hx-get='{% url "create-task" stage.id %}'
|
||||
hx-target='#TaskFormTarget' class="oh-dropdown__link"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskModal"
|
||||
data-key=".oh-kanban__section"
|
||||
onclick="handleCreateTaskClick(event)"
|
||||
>
|
||||
<ion-icon name="add-sharp" role="img" class="md hydrated mt-1" aria-label="add sharp"></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<button class="oh-btn oh-btn--small oh-btn--transparent oh-kanban__btn oh-kanban__dropdown-toggle">
|
||||
<ion-icon name="ellipsis-vertical-sharp" role="img" class="md hydrated" aria-label="ellipsis vertical sharp"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-dropdown oh-kanban__dropdown-menu d-none">
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right">
|
||||
<ul class="oh-dropdown__items">
|
||||
<li class="oh-dropdown__item">
|
||||
<a
|
||||
hx-get='{% url "update-project-stage" stage.id %}'
|
||||
hx-target='#TaskFormTarget' class="oh-dropdown__link"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskModal"
|
||||
data-key=".oh-kanban__section"
|
||||
>{% trans "Edit" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<form action="{% url 'delete-project-stage' stage.id %}" onsubmit="return confirm('{% trans "Are you sure you want to delete this stage?" %}');" method="post">
|
||||
{% csrf_token %}
|
||||
<button class="oh-dropdown__link oh-dropdown__link--danger ">{% trans "Delete" %}</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% comment %} task inside stage {% endcomment %}
|
||||
<div
|
||||
class="oh-kanban__section-body ui-sortable tasks-container"
|
||||
style="left-padding:10px;"
|
||||
data-stage-id='{{stage.id}}'
|
||||
data-project-id="{{project.id}}"
|
||||
>
|
||||
|
||||
{% for task in stage.tasks.all|dictsort:"sequence" %}
|
||||
{% if task in tasks %}
|
||||
<div
|
||||
class="oh-kanban__card task ms-3"
|
||||
data-task-id="{{task.id}}"
|
||||
data-task="{{task.title}}"
|
||||
id="task-{{task.id}}"
|
||||
style="cursor: pointer;overflow: visible;left-padding:10px"
|
||||
>
|
||||
<div class="oh-kanban__card-head">
|
||||
<a hx-get='{% url "task-details" task.id %}?view=card'
|
||||
hx-target='#TaskFormTarget' class="oh-dropdown__link"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskModal"
|
||||
data-key=".oh-kanban__section"
|
||||
>
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{task}}&background=random"
|
||||
class="oh-profile__image"
|
||||
alt="task"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="oh-kanban-card__details">
|
||||
<span class="oh-kanban-card__title">
|
||||
{{task}}
|
||||
</span>
|
||||
<span class="oh-kanban-card__subtitle">{{task.task_manager}}</span><br>
|
||||
<span class="oh-kanban-card__subtitle">{{task.end_date}}</span><br>
|
||||
<span class="oh-kanban-card__subtitle">{{task.get_status_display}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
|
||||
{% comment %} drop down inside card {% endcomment %}
|
||||
|
||||
<div class="oh-kanban__card-actions oh-kanban__dropdown">
|
||||
<button
|
||||
class="oh-btn oh-btn--small oh-btn--transparent oh-kanban__btn oh-kanban__dropdown-toggle"
|
||||
>
|
||||
<ion-icon
|
||||
name="ellipsis-vertical-sharp"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="ellipsis vertical sharp"
|
||||
></ion-icon>
|
||||
</button>
|
||||
<div class="oh-dropdown oh-kanban__dropdown-menu d-none">
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right">
|
||||
<ul class="oh-dropdown__items">
|
||||
<li class="oh-dropdown__item">
|
||||
<a
|
||||
hx-get='{% url "update-task" task.id %}'
|
||||
hx-target='#TaskFormTarget' class="oh-dropdown__link"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskModal"
|
||||
data-key=".oh-kanban__section"
|
||||
>{% trans "Edit" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<form action="{% url 'delete-task' task.id %}?view=card" onsubmit="return confirm('{% trans "Are you sure you want to delete this task?" %}');" method="post">
|
||||
{% csrf_token %}
|
||||
<button class="oh-dropdown__link oh-dropdown__link--danger ">{% trans "Delete" %}</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<a
|
||||
class="oh-btn oh-btn--secondary oh-btn--shadow"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#ProjectStageModal"
|
||||
hx-get="{% url 'create-project-stage' project_id %}"
|
||||
hx-target="#ProjectStageFormTarget"
|
||||
>
|
||||
<ion-icon class="me-1" name="add-outline"></ion-icon>{% trans "Stage" %}
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 200px;margin: 10px auto ;" src="{% static 'images/ui/project/task.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">{% trans "There are currently no available tasks; please create a new one." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% comment %} js files {% endcomment %}
|
||||
<script src="{% static '/project/task_pipeline.js' %}"></script>
|
||||
|
||||
|
||||
|
||||
@@ -1,427 +1,214 @@
|
||||
{% load static %}
|
||||
{% load i18n %} {% load yes_no %}
|
||||
|
||||
|
||||
{% comment %} for showing messages {% endcomment %}
|
||||
<div class="oh-alert-container" id='ajaxMessages'>
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load horillafilters taskfilters %}
|
||||
|
||||
{% if messages %}
|
||||
<div class="oh-alert-container" id='ohMessages'>
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "filter_tags.html" %}
|
||||
<div id="projectStages" class="oh-card" style="margin-top: 5px">
|
||||
{% if request.user|is_project_manager:stages.first.project or request.user.is_superuser %}
|
||||
<div class="oh-kanban__add-container mb-2">
|
||||
<button class="oh-btn oh-btn--x-small oh-kanban__add-section mr-2" data-toggle="oh-modal-toggle"
|
||||
data-target="#objectCreateModal" hx-get="{% url 'create-project-stage' project_id %}"
|
||||
hx-target="#objectCreateModalTarget">
|
||||
<ion-icon class="me-1 md hydrated" name="add-outline" role="img" aria-label="add outline"></ion-icon>
|
||||
{% trans "Add Stage" %}
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if stages %}
|
||||
<div class="oh-accordion-meta">
|
||||
<div class="">
|
||||
{% for stage in stages %}
|
||||
<!-- Start of stages comes under the project will loading here -->
|
||||
<div class="oh-accordion-meta__header oh-accordion-meta__header--custom stage_header" data-target="#taskStage{{stage.id}}"
|
||||
style ="min-height: 50px;"
|
||||
>
|
||||
<div class="d-flex">
|
||||
<span class="oh-accordion-meta__title" style="display:inherit;">
|
||||
<span class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round mr-1">
|
||||
{{ stage.tasks.all|length}}
|
||||
</span>
|
||||
{{stage.title}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex oh-tabs__input-badge-container">
|
||||
|
||||
</div>
|
||||
<div class="oh-btn-group ml-3">
|
||||
{% if request.user|is_project_manager:stage.project or request.user.is_superuser %}
|
||||
<div class="mr-2" onclick="event.stopPropagation();">
|
||||
<a class="oh-btn oh-btn--secondary oh-btn--shadow"
|
||||
data-toggle = "oh-modal-toggle" data-target="#genericModal"
|
||||
hx-get="{% url 'create-stage-task' stage.id %}" hx-target="#genericModalBody"
|
||||
>
|
||||
<ion-icon class="me-2" name="add-outline"></ion-icon>{% trans "Add Task" %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn oh-stop-prop oh-accordion-meta__btn" @click="open = !open"
|
||||
@click.outside="open = false">
|
||||
{% trans "Actions" %}
|
||||
<ion-icon class="ms-2 oh-accordion-meta__btn-icon" name="caret-down-outline"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open" style="display: none">
|
||||
<ul class="oh-dropdown__items">
|
||||
<li class="oh-dropdown__item">
|
||||
<a class="oh-dropdown__link oh-dropdown__link" data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskModal" hx-get="{% url 'update-project-stage' stage.id %}"
|
||||
onclick="event.stopPropagation()" hx-target="#TaskFormTarget">
|
||||
{% trans "Update" %}</a>
|
||||
</li>
|
||||
|
||||
<li class="oh-dropdown__item">
|
||||
<form action="{% url 'delete-project-stage' stage.id %}"
|
||||
onsubmit="return confirm('{% trans " Are you sure you want to delete this stage?" %}');" method="post">
|
||||
{% csrf_token %}
|
||||
<button class="oh-dropdown__link oh-dropdown__link--danger">{% trans "Delete" %}</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div id="taskCreateForm{{stage.id}}"
|
||||
class="oh-dropdown__menu oh-dropdown__menu--highlight oh-dropdown__menu--top-100 oh-dropdown__close-outside-click oh-dropdown__menu--right pt-2 pb-2 list-quick-add d-none"
|
||||
style="z-index: 15;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End of stages comes under the project will loading here -->
|
||||
<div class="oh-accordion-meta__body d-none" id="taskStage{{stage.id}}">
|
||||
{% if stage.tasks.all %}
|
||||
<!-- Start of tasks comes under the stage will loading here -->
|
||||
<div class="oh-sticky-table oh-sticky-table--no-overflow mb-5">
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">
|
||||
<div class="d-flex">
|
||||
<div class="">
|
||||
<input type="checkbox" id="selectAllTasks {{stage.id}}"
|
||||
class="oh-input oh-input__checkbox mt-1 mr-2 all-candidate" />
|
||||
</div>
|
||||
<div>
|
||||
{% trans "Tasks" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Task Managers" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "End Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Status" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Stage" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Document" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Description" %}</div>
|
||||
{% if request.user|is_project_manager:stage.project or request.user.is_superuser %}
|
||||
<div class="oh-sticky-table__th">{% trans "Actions" %}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% for task in stage.tasks.all %}
|
||||
{% if task in tasks %}
|
||||
<div class="oh-sticky-table__tr ui-sortable-handle task_row" data-task="{{task.title}}"
|
||||
data-toggle="oh-modal-toggle" data-target="#genericModal" hx-target="#genericModalBody"
|
||||
hx-get="{% url 'task-detail-view' task.id %}?view=list" onclick="event.stopPropagation()">
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="d-flex">
|
||||
<div class="">
|
||||
<input type="checkbox" id="selectRowTask {{task.id}}"
|
||||
class="oh-input oh-input__checkbox mt-1 mr-2 all-candidate" />
|
||||
</div>
|
||||
{{task.title}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{% for manager in task.task_managers.all %}
|
||||
{{manager}},
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{task.end_date}}</div>
|
||||
<div class="oh-sticky-table__td">{{task.get_status_display}}</div>
|
||||
<div class="oh-sticky-table__td oh-table-config__td" onclick='event.stopPropagation();'>
|
||||
<select class="oh-table__editable-input w-100 mt-2 mr-2"
|
||||
hx-get="{% url 'update-project-task-status' task.id %}?view=list"
|
||||
hx-trigger="change" hx-swap="afterend" tabindex="-1" aria-hidden="true"
|
||||
name="status">
|
||||
{% for option in task.TASK_STATUS %}
|
||||
<option value="{{option.0}}" {% if option.0 == task.status %}selected{% endif %}>
|
||||
{{option.1}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td" onclick="event.stopPropagation()">
|
||||
{% if task.document %}
|
||||
<a style="text-decoration: none" class="oh-btn oh-btn--light"
|
||||
href="/media/{{task.document}}" target="_blank" title="{{task.document}}"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span class="oh-file-icon oh-file-icon--pdf"></span>   View
|
||||
</a>
|
||||
{% else %}
|
||||
{% trans "None" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{task.description}}</div>
|
||||
{% if request.user|is_project_manager:stage.project or request.user.is_superuser %}
|
||||
<div class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group"
|
||||
onclick="event.preventDefault();event.stopPropagation()"
|
||||
>
|
||||
<button class="oh-btn oh-btn--light-bkg w-100" title="{% trans 'Edit' %}"
|
||||
data-toggle="oh-modal-toggle" data-target="#TaskModal"
|
||||
hx-get="{% url 'update-task' task.id %}?project_task=true"
|
||||
hx-target="#TaskFormTarget"><ion-icon
|
||||
name="create-outline"></ion-icon></button>
|
||||
|
||||
<a class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
hx-confirm="{% trans 'Do you really want to delete this task?' %}"
|
||||
hx-post="{% url 'delete-task' task.id %}?view=list"
|
||||
hx-target="#taskCreateForm{{stage.id}}">
|
||||
<ion-icon name="trash-outline" role="img" class="md hydrated"
|
||||
aria-label="trash outline"></ion-icon>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End of tasks comes under the stage will loading here -->
|
||||
{% else %}
|
||||
<div class="oh-empty">
|
||||
<img src="{% static 'images/ui/grid.svg' %} " class="mb-4" width="140" height="140" alt="Empty" />
|
||||
<p class="oh-empty__message">
|
||||
{% trans "No tasks found in this stage." %}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 200px;margin: 10px auto ;" src="{% static 'images/ui/project/task.png' %}"
|
||||
class="" alt="Page not found. 404." />
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">{% trans "No search result found!" %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if messages %}
|
||||
<div class="oh-alert-container" id='ohMessages'>
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "filter_tags.html" %}
|
||||
{% if stages %}
|
||||
<div class="oh-card" id="">
|
||||
<div class="oh-accordion-meta">
|
||||
<div class="">
|
||||
{% for stage in stages %}
|
||||
|
||||
<div
|
||||
class="oh-accordion-meta__header oh-accordion-meta__header--custom stage_header"
|
||||
data-target="#taskstage{{stage.id}}"
|
||||
>
|
||||
<div class="d-flex">
|
||||
<span class="oh-accordion-meta__title" style="display:inherit;">
|
||||
<span class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round mr-1">
|
||||
{{ stage.tasks.all|length}}
|
||||
</span>
|
||||
{{stage.title}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex oh-tabs__input-badge-container">
|
||||
|
||||
</div>
|
||||
<div class="oh-btn-group ml-3">
|
||||
<div>
|
||||
<a
|
||||
class="oh-btn oh-btn--secondary oh-btn--shadow"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskModal"
|
||||
hx-get="{% url 'create-task' stage.id %}"
|
||||
hx-target="#TaskFormTarget"
|
||||
>
|
||||
<ion-icon class="me-2" name="add-outline"></ion-icon>{% trans "Add Task" %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button
|
||||
class="oh-btn oh-stop-prop oh-accordion-meta__btn"
|
||||
@click="open = !open"
|
||||
@click.outside="open = false"
|
||||
>
|
||||
{% trans "Actions" %}
|
||||
<ion-icon
|
||||
class="ms-2 oh-accordion-meta__btn-icon"
|
||||
name="caret-down-outline"
|
||||
></ion-icon>
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--right"
|
||||
x-show="open"
|
||||
style="display: none"
|
||||
>
|
||||
<ul class="oh-dropdown__items">
|
||||
<li class="oh-dropdown__item">
|
||||
<a
|
||||
class="oh-dropdown__link oh-dropdown__link"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskModal"
|
||||
hx-get="{% url 'update-project-stage' stage.id %}"
|
||||
onclick="event.stopPropagation()"
|
||||
hx-target="#TaskFormTarget"
|
||||
>
|
||||
{% trans "Update" %}</a
|
||||
>
|
||||
</li>
|
||||
|
||||
<li class="oh-dropdown__item">
|
||||
<form action="{% url 'delete-project-stage' stage.id %}" onsubmit="return confirm('{% trans "Are you sure you want to delete this stage?" %}');" method="post">
|
||||
{% csrf_token %}
|
||||
<button class="oh-dropdown__link oh-dropdown__link--danger ">{% trans "Delete" %}</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="oh-accordion-meta__body d-none"
|
||||
id="taskstage{{stage.id}}"
|
||||
>
|
||||
<!-- htmx task will loading here -->
|
||||
<div
|
||||
class="oh-sticky-table oh-sticky-table--no-overflow mb-5">
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable" >
|
||||
<div class="oh-sticky-table__thead" >
|
||||
<div class="oh-sticky-table__tr" id="task_view{{stage.id}}" >
|
||||
<div class="oh-sticky-table__th" >
|
||||
<div class="d-flex" >
|
||||
<div class="">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="oh-input oh-input__checkbox mt-1 mr-2 all-candidate"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
>
|
||||
{% trans "Tasks" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Task Manager" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "End Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Status" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Stage" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Document" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Description" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Actions" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% for task in stage.tasks.all %}
|
||||
|
||||
{% comment %} {% if task in tasks %} {% endcomment %}
|
||||
<div class="oh-sticky-table__tr ui-sortable-handle task_row"
|
||||
data-task = "{{task.title}}"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskModal"
|
||||
hx-target="#TaskFormTarget"
|
||||
hx-get="{% url 'task-details' task.id %}?view=list"
|
||||
onclick="event.stopPropagation()"
|
||||
>
|
||||
<div class="oh-sticky-table__sd" >{{task.title}}</div>
|
||||
<div class="oh-sticky-table__td">{{task.task_manager}}</div>
|
||||
<div class="oh-sticky-table__td">{{task.end_date}}</div>
|
||||
<div class="oh-sticky-table__td">{{task.get_status_display}}</div>
|
||||
<div class="oh-sticky-table__td oh-table-config__td">
|
||||
<select
|
||||
id="stageChange{{task.id}}"
|
||||
class="oh-select w-100 stage-change"
|
||||
data-task='{{task.id}}'
|
||||
onchange="stageChange(this)"
|
||||
>
|
||||
|
||||
{% for stage in stages %}
|
||||
{% if stage.id == task.stage.id %}
|
||||
<option value="{{stage.id}}" selected>{{stage}}</option>
|
||||
{% else %}
|
||||
<option value="{{stage.id}}">{{stage}}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="oh-sticky-table__td">{{task.document}}</div>
|
||||
<div class="oh-sticky-table__td">{{task.description}}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group">
|
||||
<button class="oh-btn oh-btn--light-bkg w-100" title="{% trans 'Edit' %}" data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskModal" hx-get="{% url 'update-task' task.id %}"
|
||||
hx-target="#TaskFormTarget"><ion-icon name="create-outline"></ion-icon></button>
|
||||
|
||||
{% comment %} <a class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100" id="delete-link"
|
||||
href="{% url 'delete-task' task.id %}?view=list" title="{% trans 'Delete' %}">
|
||||
<ion-icon name="trash-outline"></ion-icon>
|
||||
</a> {% endcomment %}
|
||||
<a
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
href="{% url 'delete-task' task.id %}?view=list"
|
||||
onclick="event.preventDefault();event.stopPropagation(); confirm(`{% trans 'Do you want to delete this task?' %}`)"
|
||||
>
|
||||
<ion-icon
|
||||
name="trash-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="trash outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$(".oh-accordion-meta__header").click(function () {
|
||||
var target = $(this).data("target");
|
||||
$(target).toggleClass("d-none");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 200px;margin: 10px auto ;" src="{% static 'images/ui/project/task.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">{% trans "There are currently no available tasks; please create a new one." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<script src="{% static '/project/task_pipeline.js' %}"></script>
|
||||
|
||||
|
||||
<script>
|
||||
function stageChange(selectedElement){
|
||||
var stage = selectedElement.value;
|
||||
var task = selectedElement.getAttribute('data-task');
|
||||
var task_row = $(selectedElement).parents('.task_row')
|
||||
task_row.remove();
|
||||
$(`#taskstage${stage} .oh-sticky-table__thead`).after(task_row)
|
||||
$.ajax({
|
||||
type:'post',
|
||||
url:'/project/task-stage-change',
|
||||
data:{
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
stage:stage,
|
||||
task:task,
|
||||
},
|
||||
success: function(response){
|
||||
$("#ajaxMessages").append(`
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated oh-alert--${response.type}">
|
||||
${response.message}
|
||||
</div>
|
||||
</div>`);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== "") {
|
||||
const cookies = document.cookie.split(";");
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
{% comment %}
|
||||
<div class="oh-card">
|
||||
<div class="oh-accordion-meta">
|
||||
<div class="oh-accordion-meta__item">
|
||||
{% for project_stage in project_stages %} {{project_stage.stage}} <br/>
|
||||
{% endfor %}
|
||||
{% for task in tasks %}
|
||||
<div class="oh-accordion-meta__header">
|
||||
<span class="oh-accordion-meta__title"
|
||||
> {{ task.task_title }}</span
|
||||
>
|
||||
<div class="oh-accordion-meta__actions">
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button
|
||||
class="oh-btn oh-stop-prop oh-accordion-meta__btn"
|
||||
@click="open = !open"
|
||||
@click.outside="open = false"
|
||||
>
|
||||
Actions
|
||||
<ion-icon
|
||||
class="ms-2 oh-accordion-meta__btn-icon"
|
||||
name="caret-down-outline"
|
||||
></ion-icon>
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--right"
|
||||
x-show="open"
|
||||
>
|
||||
<ul class="oh-dropdown__items">
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link">Arrange</a>
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link">Forward</a>
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link">Archive</a>
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<a
|
||||
href="#"
|
||||
class="oh-dropdown__link oh-dropdown__link--danger"
|
||||
>Delete</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-accordion-meta__body d-none">
|
||||
<div
|
||||
class="oh-sticky-table oh-sticky-table--no-overflow mb-5"
|
||||
>
|
||||
<div class="oh-sticky-table__table">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">Task Assigner</div>
|
||||
<div class="oh-sticky-table__th">Task Members</div>
|
||||
<div class="oh-sticky-table__th">End Date</div>
|
||||
<div class="oh-sticky-table__th">Status</div>
|
||||
<div class="oh-sticky-table__th">Documents</div>
|
||||
<div class="oh-sticky-table__th">Description</div>
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button
|
||||
class="oh-btn oh-stop-prop oh-btn--transparent oh-accordion-meta__btn"
|
||||
@click="open = !open"
|
||||
@click.outside="open = false"
|
||||
>
|
||||
<ion-icon
|
||||
name="ellipsis-vertical"
|
||||
></ion-icon>
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--right"
|
||||
x-show="open"
|
||||
>
|
||||
<ul class="oh-dropdown__items">
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link"
|
||||
>Arrange</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link"
|
||||
>Forward</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link"
|
||||
>Archive</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<a
|
||||
href="#"
|
||||
class="oh-dropdown__link oh-dropdown__link--danger"
|
||||
>Delete</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__tbody">
|
||||
<div
|
||||
class="oh-sticky-table__tr oh-multiple-table-sort__movable"
|
||||
>
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name=Magdalene&background=random"
|
||||
class="oh-profile__image"
|
||||
alt="Mary Magdalene"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{task.task_assigner_id}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{% for employee in task.task_members_id.all %} {{employee}}<br />
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{task.end_date}}</div>
|
||||
<div class="oh-sticky-table__td">{{task.status}}</div>
|
||||
<div class="oh-sticky-table__td">{{task.document}}</div>
|
||||
<div class="oh-sticky-table__td">{{task.description}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="./build/js/web.frontend.min.js"></script>
|
||||
|
||||
<script
|
||||
type="module"
|
||||
src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"
|
||||
></script>
|
||||
<script
|
||||
nomodule
|
||||
src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"
|
||||
></script>
|
||||
<script
|
||||
type="module"
|
||||
type="text/javascript"
|
||||
src="./build/vendor/ionicons/ionicons/dist/ionicons/ionicons.esm.js"
|
||||
></script> {% endcomment %}
|
||||
|
||||
{% comment %} js files {% endcomment %}
|
||||
|
||||
@@ -1,104 +1,72 @@
|
||||
{% load i18n %}
|
||||
|
||||
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
||||
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">{{project}} {% trans ":Tasks" %} </h1>
|
||||
<a
|
||||
class="oh-main__titlebar-search-toggle"
|
||||
role="button"
|
||||
aria-label="Toggle Search"
|
||||
@click="searchShow = !searchShow"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-main__titlebar-serach-icon md hydrated"
|
||||
role="img"
|
||||
aria-label="search outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right">
|
||||
{% comment %} for search{% endcomment %}
|
||||
<div
|
||||
class="oh-input-group oh-input__search-group"
|
||||
:class="searchShow ? 'oh-input__search-group--show' : ''"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-input-group__icon oh-input-group__icon--left"
|
||||
></ion-icon>
|
||||
<input
|
||||
type="text"
|
||||
class="oh-input oh-input__icon"
|
||||
aria-label="Search Input"
|
||||
id="filter-task"
|
||||
data-view = "{{request.GET.view}}"
|
||||
name="search"
|
||||
placeholder="{% trans 'Search' %}"
|
||||
{% comment %} hx-get="{% url 'task-filter' project_id %}?view={{request.GET.view}}"
|
||||
hx-trigger="keyup changed delay:500ms, search"
|
||||
hx-target="#view-container" {% endcomment %}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<ul class="oh-view-types ml-2">
|
||||
{% comment %} for list view {% endcomment %}
|
||||
<li class="oh-view-type task-view-type" data-view="list">
|
||||
<a href= "{% url 'task-view' project_id %}?view=list"
|
||||
class="oh-btn oh-btn--view"
|
||||
>
|
||||
<ion-icon name="list-outline"></ion-icon>
|
||||
</a>
|
||||
</li>
|
||||
{% comment %} for card view {% endcomment %}
|
||||
<li class="oh-view-type task-view-type" data-view="card">
|
||||
<a
|
||||
|
||||
href= "{% url 'task-view' project_id %}"
|
||||
class="oh-btn oh-btn--view"
|
||||
>
|
||||
<ion-icon name="grid-outline"></ion-icon
|
||||
|
||||
></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% comment %} for filtering {% endcomment %}
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn ml-2" @click="open = !open">
|
||||
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
|
||||
x-show="open"
|
||||
@click.outside="open = false"
|
||||
style="display: none;"
|
||||
>
|
||||
|
||||
{% include 'task/new/filter_task.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{% comment %} for create task {% endcomment %}
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div>
|
||||
<a
|
||||
class="oh-btn oh-btn--secondary oh-btn--shadow"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskModal"
|
||||
hx-get="{% url 'create-project-stage' project_id %}"
|
||||
hx-target="#TaskFormTarget"
|
||||
title="Create stage"
|
||||
>
|
||||
<ion-icon class="me-2" name="add-outline"></ion-icon>{% trans "Create" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% load i18n taskfilters %}
|
||||
<form
|
||||
hx-get='{% url "task-filter" project_id %}?view={{request.GET.view}}'
|
||||
hx-target="#viewContainer"
|
||||
hx-swap="innerHTML"
|
||||
id="taskFilterForm">
|
||||
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
||||
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">{{project}}</h1>
|
||||
<a class="oh-main__titlebar-search-toggle" role="button" aria-label="Toggle Search"
|
||||
@click="searchShow = !searchShow">
|
||||
<ion-icon name="search-outline" class="oh-main__titlebar-serach-icon md hydrated" role="img"
|
||||
aria-label="search outline"></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right">
|
||||
<div class="oh-input-group oh-input__search-group" :class="searchShow ? 'oh-input__search-group--show' : ''">
|
||||
<ion-icon name="search-outline" class="oh-input-group__icon oh-input-group__icon--left"></ion-icon>
|
||||
<input type="text" class="oh-input oh-input__icon" aria-label="Search Input"
|
||||
name="search" placeholder="{% trans 'Search' %}"
|
||||
onkeyup="$('#taskFilterButton').click()"
|
||||
hx-target="#viewContainer" autocomplete="off" />
|
||||
</div>
|
||||
<input name='view' id="viewInput" hidden>
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<ul class="oh-view-types ml-2">
|
||||
<li class="oh-view-type task-view-type" data-view="list">
|
||||
<a
|
||||
onclick="$('#viewInput').val('list');$('#taskFilterButton').click()"
|
||||
class="oh-btn oh-btn--view">
|
||||
<ion-icon name="list-outline"></ion-icon>
|
||||
</a>
|
||||
</li>
|
||||
<li class="oh-view-type task-view-type" data-view="card">
|
||||
<a
|
||||
onclick="$('#viewInput').val('card');$('#taskFilterButton').click()"
|
||||
class="oh-btn oh-btn--view">
|
||||
<ion-icon name="grid-outline"></ion-icon>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn ml-2" @click="open = !open"
|
||||
onclick="event.preventDefault()"
|
||||
>
|
||||
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4" x-show="open"
|
||||
@click.outside="open = false" style="display: none;">
|
||||
{% include 'task/new/filter_task.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{% if request.user|is_project_manager:project or request.user.is_superuser %}
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div>
|
||||
<a class="oh-btn oh-btn--secondary oh-btn--shadow" title="{% trans 'Create task' %}"
|
||||
hx-get='{% url "create-task" project_id %}' hx-target='#TaskFormTarget'
|
||||
data-toggle="oh-modal-toggle" data-target="#TaskModal">
|
||||
<ion-icon class="me-2" name="add-outline"></ion-icon>{% trans "Create" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
@@ -1,140 +1,137 @@
|
||||
{% load i18n %} {% load yes_no %} {% load static %}
|
||||
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5 style="margin-bottom: 20px;">Time Sheet</h5>
|
||||
</span>
|
||||
{% comment %} for add timesheet {% endcomment %}
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div>
|
||||
<a
|
||||
class="oh-btn oh-btn--secondary oh-btn--shadow"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TimesheetUpdateModal"
|
||||
hx-get="{% url 'create-timesheet-task' task_id %}"
|
||||
hx-target="#TimesheetUpdateTarget"
|
||||
>
|
||||
<ion-icon class="me-2" name="add-outline"></ion-icon>{% trans " Add" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
style="
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 1.5rem;
|
||||
opacity: 0.7;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 15px;
|
||||
cursor: pointer;
|
||||
-webkit-transition: all 0.3s ease-in-out;
|
||||
transition: all 0.3s ease-in-out;"
|
||||
onclick="event.stopPropagation(); $(this).parents().find('.oh-modal--show:last').toggleClass('oh-modal--show')"
|
||||
id="close"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
{% if time_sheets %}
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">
|
||||
<div class="d-flex">
|
||||
<div>{% trans "Employee" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% comment %}
|
||||
<div class="oh-sticky-table__th">{% trans "Employee" %}</div>
|
||||
{% endcomment %}
|
||||
<div class="oh-sticky-table__th">{% trans "Project" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Task" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Time Spent" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Status" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Description" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Actions" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% for time_sheet in time_sheets %}
|
||||
<div class="oh-sticky-table__tr ui-sortable-handle"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TimeSheetModal"
|
||||
hx-get="{% url 'view-single-time-sheet' time_sheet.id %}"
|
||||
hx-target="#TimeSheetFormTarget"
|
||||
>
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="{{time_sheet.employee_id.get_avatar}}"
|
||||
class="oh-kanban-card__profile-image"
|
||||
alt="Username"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{time_sheet.employee_id.employee_first_name}}
|
||||
{{time_sheet.employee_id.employee_last_name|default:""}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.project_id.title}}</div>
|
||||
|
||||
<div class="oh-sticky-table__td">{{time_sheet.task_id}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.date}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.time_spent}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.get_status_display}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.description|truncatechars:15}}</div>
|
||||
<div class="oh-sticky-table__td" onclick="event.stopPropagation();">
|
||||
<div class="oh-btn-group " >
|
||||
<a
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TimesheetUpdateModal"
|
||||
hx-get="{% url 'update-timesheet-task' time_sheet.id %}"
|
||||
hx-target="#TimesheetUpdateTarget"
|
||||
|
||||
>
|
||||
<ion-icon
|
||||
name="create-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
style="color: blue"
|
||||
aria-label="create outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
<button
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
id="time_sheet.id"
|
||||
onclick=" deleteTimeSheet(this,{{time_sheet.id}});"
|
||||
>
|
||||
<ion-icon
|
||||
name="trash-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="trash outline"
|
||||
></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 80px;margin: 10px auto ;" src="{% static 'images/ui/project/timesheet.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:15px" class="oh-404__subtitle">{% trans "There are currently no available timesheets; please create a new one." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% load i18n %} {% load horillafilters %} {% load static %}
|
||||
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5 style="margin-bottom: 20px;">Time Sheet</h5>
|
||||
</span>
|
||||
{% comment %} for add timesheet {% endcomment %}
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div>
|
||||
<a
|
||||
class="oh-btn oh-btn--secondary oh-btn--shadow"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TimesheetUpdateModal"
|
||||
hx-get="{% url 'create-timesheet-task' task_id %}"
|
||||
hx-target="#TimesheetUpdateTarget"
|
||||
>
|
||||
<ion-icon class="me-2" name="add-outline"></ion-icon>{% trans " Add" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
style="
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 1.5rem;
|
||||
opacity: 0.7;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 15px;
|
||||
cursor: pointer;
|
||||
-webkit-transition: all 0.3s ease-in-out;
|
||||
transition: all 0.3s ease-in-out;"
|
||||
onclick="event.stopPropagation(); $(this).parents().find('.oh-modal--show:last').toggleClass('oh-modal--show')"
|
||||
id="close"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
{% if time_sheets %}
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">
|
||||
<div class="d-flex">
|
||||
<div>{% trans "Employee" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% comment %}
|
||||
<div class="oh-sticky-table__th">{% trans "Employee" %}</div>
|
||||
{% endcomment %}
|
||||
<div class="oh-sticky-table__th">{% trans "Project" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Task" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Time Spent" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Status" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Description" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Actions" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% for time_sheet in time_sheets %}
|
||||
<div class="oh-sticky-table__tr ui-sortable-handle"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TimeSheetModal"
|
||||
hx-get="{% url 'view-single-time-sheet' time_sheet.id %}"
|
||||
hx-target="#TimeSheetFormTarget"
|
||||
>
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="{{time_sheet.employee_id.get_avatar}}"
|
||||
class="oh-kanban-card__profile-image"
|
||||
alt="Username"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{time_sheet.employee_id.employee_first_name}}
|
||||
{{time_sheet.employee_id.employee_last_name|default:""}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.project_id.title}}</div>
|
||||
|
||||
<div class="oh-sticky-table__td">{{time_sheet.task_id}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.date}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.time_spent}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.get_status_display}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.description|truncatechars:15}}</div>
|
||||
<div class="oh-sticky-table__td" onclick="event.stopPropagation();">
|
||||
<div class="oh-btn-group " >
|
||||
<a
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TimesheetUpdateModal"
|
||||
hx-get="{% url 'update-timesheet-task' time_sheet.id %}"
|
||||
hx-target="#TimesheetUpdateTarget"
|
||||
|
||||
>
|
||||
<ion-icon
|
||||
name="create-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
style="color: blue"
|
||||
aria-label="create outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
<button
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
id="time_sheet.id"
|
||||
onclick=" deleteTimeSheet(this,{{time_sheet.id}});"
|
||||
>
|
||||
<ion-icon
|
||||
name="trash-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="trash outline"
|
||||
></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 80px;margin: 10px auto ;" src="{% static 'images/ui/project/timesheet.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:15px" class="oh-404__subtitle">{% trans "There are currently no available timesheets; please create a new one." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,115 +1,115 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Task" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#TaskFormTarget"
|
||||
onclick="location.reload()"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'create-task-all' %}"
|
||||
hx-target="#TaskUpdateTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% comment %} modals for showing new project and new task creation {% endcomment %}
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectModal"
|
||||
role="dialog"
|
||||
aria-labelledby="ProjectModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="ProjectFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectStageModal"
|
||||
role="dialog"
|
||||
aria-labelledby="ProjectStageModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="ProjectStageFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
|
||||
$("#project_stage").html("<option>-------</option>");
|
||||
$(document).on("change","#id_project",function(){
|
||||
project_id = $(this).val()
|
||||
if (project_id === 'create_new_project'){
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/project/create-project-time-sheet',
|
||||
success: function (response) {
|
||||
$("#ProjectModal").addClass("oh-modal--show");
|
||||
$("#ProjectFormTarget").html(response);
|
||||
},
|
||||
});0
|
||||
|
||||
}
|
||||
if ($.isNumeric(project_id)) {
|
||||
$.ajax({
|
||||
type:"GET",
|
||||
url:"/project/get-stages",
|
||||
data:{'project_id':project_id},
|
||||
beforeSend: function () {
|
||||
$(".errorlist").remove();
|
||||
},
|
||||
success:function(response){
|
||||
$('#project_stage').html("<option>---------</option>");
|
||||
|
||||
for (let i = 0; i < response.data.length; i++) {
|
||||
const element = response.data[i];
|
||||
$("#project_stage").append("<option value="+ element.id + ">"+element.title+"</option>");
|
||||
}
|
||||
$("#project_stage").append( "<option value='create_new_stage'>Create a new Stage</option>");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$("#project_stage").html("<option>---------</option>");
|
||||
$("#project_stage").append( "<option value='create_new_stage'>Create a new Stage</option>")
|
||||
}
|
||||
});
|
||||
$(document).on("change","#project_stage",function(e){
|
||||
stage_id = $(this).val()
|
||||
if (stage_id === "create_new_stage") {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/project/create-stage-taskall',
|
||||
data: { project_id: project_id },
|
||||
success: function (response) {
|
||||
$("#ProjectStageModal").addClass("oh-modal--show");
|
||||
$("#ProjectStageFormTarget").html(response);
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Task" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#TaskFormTarget"
|
||||
onclick="location.reload()"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'create-task-all' %}"
|
||||
hx-target="#TaskUpdateTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% comment %} modals for showing new project and new task creation {% endcomment %}
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectModal"
|
||||
role="dialog"
|
||||
aria-labelledby="ProjectModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="ProjectFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectStageModal"
|
||||
role="dialog"
|
||||
aria-labelledby="ProjectStageModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="ProjectStageFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
|
||||
$("#project_stage").html("<option>-------</option>");
|
||||
$(document).on("change","#id_project",function(){
|
||||
project_id = $(this).val()
|
||||
if (project_id === 'create_new_project'){
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/project/create-project-time-sheet',
|
||||
success: function (response) {
|
||||
$("#ProjectModal").addClass("oh-modal--show");
|
||||
$("#ProjectFormTarget").html(response);
|
||||
},
|
||||
});0
|
||||
|
||||
}
|
||||
if ($.isNumeric(project_id)) {
|
||||
$.ajax({
|
||||
type:"GET",
|
||||
url:"/project/get-stages",
|
||||
data:{'project_id':project_id},
|
||||
beforeSend: function () {
|
||||
$(".errorlist").remove();
|
||||
},
|
||||
success:function(response){
|
||||
$('#project_stage').html("<option>---------</option>");
|
||||
|
||||
for (let i = 0; i < response.data.length; i++) {
|
||||
const element = response.data[i];
|
||||
$("#project_stage").append("<option value="+ element.id + ">"+element.title+"</option>");
|
||||
}
|
||||
$("#project_stage").append( "<option value='create_new_stage'>Create a new Stage</option>");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$("#project_stage").html("<option>---------</option>");
|
||||
$("#project_stage").append( "<option value='create_new_stage'>Create a new Stage</option>")
|
||||
}
|
||||
});
|
||||
$(document).on("change","#project_stage",function(e){
|
||||
stage_id = $(this).val()
|
||||
if (stage_id === "create_new_stage") {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/project/create-stage-taskall',
|
||||
data: { project_id: project_id },
|
||||
success: function (response) {
|
||||
$("#ProjectStageModal").addClass("oh-modal--show");
|
||||
$("#ProjectStageFormTarget").html(response);
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,110 +1,110 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Task" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#TaskFormTarget"
|
||||
onclick="location.reload()"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'update-task-all' task_id %}"
|
||||
hx-target="#TaskUpdateTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% comment %} modals for showing new project and new task creation {% endcomment %}
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectModal"
|
||||
role="dialog"
|
||||
aria-labelledby="ProjectModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="ProjectFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectStageModal"
|
||||
role="dialog"
|
||||
aria-labelledby="ProjectStageModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="ProjectStageFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
{% comment %} $("#project_stage").html("<option>-------</option>"); {% endcomment %}
|
||||
$(document).on("change","#id_project",function(){
|
||||
project_id = $(this).val()
|
||||
if (project_id === 'create_new_project'){
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/project/create-project-time-sheet',
|
||||
success: function (response) {
|
||||
$("#ProjectModal").addClass("oh-modal--show");
|
||||
$("#ProjectFormTarget").html(response);
|
||||
},
|
||||
});0
|
||||
}
|
||||
if ($.isNumeric(project_id)) {
|
||||
$.ajax({
|
||||
type:"GET",
|
||||
url:"/project/get-stages",
|
||||
data:{'project_id':project_id},
|
||||
beforeSend: function () {
|
||||
$(".errorlist").remove();
|
||||
},
|
||||
success:function(response){
|
||||
$('#project_stage').html("<option>---------</option>");
|
||||
for (let i = 0; i < response.data.length; i++) {
|
||||
const element = response.data[i];
|
||||
$("#project_stage").append("<option value="+ element.id + ">"+element.title+"</option>");
|
||||
}
|
||||
$("#project_stage").append( "<option value='create_new_stage'>Create a new Stage</option>");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$("#project_stage").html("<option>---------</option>");
|
||||
$("#project_stage").append( "<option value='create_new_stage'>Create a new Stage</option>")
|
||||
}
|
||||
});
|
||||
$(document).on("change","#project_stage",function(e){
|
||||
task_id = $(this).val()
|
||||
if (task_id === "create_new_stage") {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/project/create-stage-taskall',
|
||||
data: { project_id: project_id },
|
||||
success: function (response) {
|
||||
$("#ProjectStageModal").addClass("oh-modal--show");
|
||||
$("#ProjectStageFormTarget").html(response);
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Task" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#TaskFormTarget"
|
||||
onclick="location.reload()"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'update-task-all' task_id %}"
|
||||
hx-target="#TaskUpdateTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% comment %} modals for showing new project and new task creation {% endcomment %}
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectModal"
|
||||
role="dialog"
|
||||
aria-labelledby="ProjectModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="ProjectFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectStageModal"
|
||||
role="dialog"
|
||||
aria-labelledby="ProjectStageModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="ProjectStageFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
{% comment %} $("#project_stage").html("<option>-------</option>"); {% endcomment %}
|
||||
$(document).on("change","#id_project",function(){
|
||||
project_id = $(this).val()
|
||||
if (project_id === 'create_new_project'){
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/project/create-project-time-sheet',
|
||||
success: function (response) {
|
||||
$("#ProjectModal").addClass("oh-modal--show");
|
||||
$("#ProjectFormTarget").html(response);
|
||||
},
|
||||
});0
|
||||
}
|
||||
if ($.isNumeric(project_id)) {
|
||||
$.ajax({
|
||||
type:"GET",
|
||||
url:"/project/get-stages",
|
||||
data:{'project_id':project_id},
|
||||
beforeSend: function () {
|
||||
$(".errorlist").remove();
|
||||
},
|
||||
success:function(response){
|
||||
$('#project_stage').html("<option>---------</option>");
|
||||
for (let i = 0; i < response.data.length; i++) {
|
||||
const element = response.data[i];
|
||||
$("#project_stage").append("<option value="+ element.id + ">"+element.title+"</option>");
|
||||
}
|
||||
$("#project_stage").append( "<option value='create_new_stage'>Create a new Stage</option>");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$("#project_stage").html("<option>---------</option>");
|
||||
$("#project_stage").append( "<option value='create_new_stage'>Create a new Stage</option>")
|
||||
}
|
||||
});
|
||||
$(document).on("change","#project_stage",function(e){
|
||||
task_id = $(this).val()
|
||||
if (task_id === "create_new_stage") {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/project/create-stage-taskall',
|
||||
data: { project_id: project_id },
|
||||
success: function (response) {
|
||||
$("#ProjectStageModal").addClass("oh-modal--show");
|
||||
$("#ProjectStageFormTarget").html(response);
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,157 +1,157 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% if messages %}
|
||||
<div class="oh-wrapper">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "filter_tags.html" %}
|
||||
|
||||
{% comment %} easy filters {% endcomment %}
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<span class="m-3 draft" hx-get="{% url 'task-all-filter' %}?{{pd}}&status=to_do&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:yellowgreen"></span>
|
||||
{% trans "To Do" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'task-all-filter' %}?{{pd}}&status=in_progress&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:Orange"></span>
|
||||
{% trans "In progress" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'task-all-filter' %}?{{pd}}&status=completed&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:DodgerBlue"></span>
|
||||
{% trans "Completed" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'task-all-filter' %}?{{pd}}&status=expired&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:Tomato"></span>
|
||||
{% trans "Expired" %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% if tasks %}
|
||||
<div class="oh-layout--grid-3">
|
||||
{% for task in tasks %}
|
||||
<div class="oh-kanban-card {% if task.status == 'to_do'%} to-do-task
|
||||
{% elif task.status == 'in_progress' %} in-progress-task
|
||||
{% elif task.status == 'completed' %} completed-task
|
||||
{% else %} expired-task
|
||||
{% endif %} "
|
||||
style="color: inherit;text-decoration: none;">
|
||||
<a hx-get="{% url 'task-details' task.id %}" hx-target="#TaskDetailsTarget" data-toggle='oh-modal-toggle' data-target = '#TaskModal' style="color: inherit;text-decoration: none; display: flex; width:550px">
|
||||
|
||||
<div class="oh-kanban-card__avatar">
|
||||
<div class="oh-kanban-card__profile-container">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{task.title}}&background=random"
|
||||
class="oh-kanban-card__profile-image"
|
||||
alt="Username"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-kanban-card__details">
|
||||
<span class="oh-kanban-card__title">{{task.title}}</span>
|
||||
<span class="oh-kanban-card__subtitle">{% trans "Project Name" %}: {{task.project}}</span><br>
|
||||
<span class="oh-kanban-card__subtitle">{% trans "Stage Name" %} : {{task.stage}}</span><br/>
|
||||
<span class="oh-kanban-card__subtitle">{% trans "End Date" %} : {{task.end_date}}</span>
|
||||
</div>
|
||||
</a>
|
||||
<div class="oh-kanban-card__dots">
|
||||
<div class="oh-dropdown" x-data="{show: false}">
|
||||
<button class="oh-btn oh-btn--transparent text-muted p-3" @click="show = !show">
|
||||
<ion-icon name="ellipsis-vertical-sharp" title="Options" role="img" class="md hydrated" aria-label="ellipsis vertical sharp"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--dark-border oh-dropdown__menu--right" x-show="show" @click.outside="show = false" style="display: none;">
|
||||
<ul class="oh-dropdown__items">
|
||||
{% if perms.recruitment.change_candidate %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a hx-get="{% url 'update-task-all' task.id %}" hx-target='#TaskDetailsTarget' data-toggle = 'oh-modal-toggle' data-target="#TaskModal" hx-swap='innerHTML' class="oh-dropdown__link">{% trans "Edit" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="oh-dropdown__item">
|
||||
{% if task.is_active %}
|
||||
<a href="{% url 'task-all-archive' task.id %}" onclick="return confirm('{% trans 'Do you want to archive this task?' %}')" class="oh-dropdown__link">
|
||||
{% trans "Archive" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'task-all-archive' task.id %}" onclick="return confirm('{% trans 'Do you want to un archive this task?' %}')" class="oh-dropdown__link">
|
||||
{% trans "Un-Archive" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
</li>
|
||||
{% if perms.recruitment.delete_candidate %}
|
||||
<li class="oh-dropdown__item">
|
||||
<form action="{% url 'delete-task' task.id %}?task_all=true" onsubmit="return confirm('{% trans "Do you want to delete this candidate?" %}')" method="post">
|
||||
{% csrf_token %}
|
||||
<button class="oh-dropdown__link oh-dropdown__link--danger ">{% trans "Delete" %}</button>
|
||||
</form>
|
||||
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
data-toggle="modal"
|
||||
data-target="#addEmployeeModal"
|
||||
>
|
||||
{% trans "Page" %} {{ data.number }} {% trans "of" %} {{ data.paginator.num_pages }}.
|
||||
</span
|
||||
>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{data.number}}"
|
||||
hx-get="{% url 'search-candidate' %}?{{pd}}&view=card"
|
||||
hx-target="#section"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{data.paginator.num_pages}}</span>
|
||||
</div>
|
||||
|
||||
<ul class="oh-pagination__items">
|
||||
{% if data.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#section' hx-get="{% url 'search-candidate' %}?{{pd}}&page=1&view=card" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#section' hx-get="{% url 'search-candidate' %}?{{pd}}&page={{ data.previous_page_number }}&view=card" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if data.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#section' hx-get="{% url 'search-candidate' %}?{{pd}}&page={{ data.next_page_number }}&view=card" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#section' hx-get="{% url 'search-candidate' %}?{{pd}}&page={{ data.paginator.num_pages }}&view=card" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 200px;margin: 10px auto ;" src="{% static 'images/ui/project/task.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">{% trans "There are currently no available tasks; please create a new one." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% if messages %}
|
||||
<div class="oh-wrapper">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "filter_tags.html" %}
|
||||
|
||||
{% comment %} easy filters {% endcomment %}
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<span class="m-3 draft" hx-get="{% url 'task-all-filter' %}?{{pd}}&status=to_do&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:yellowgreen"></span>
|
||||
{% trans "To Do" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'task-all-filter' %}?{{pd}}&status=in_progress&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:Orange"></span>
|
||||
{% trans "In progress" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'task-all-filter' %}?{{pd}}&status=completed&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:DodgerBlue"></span>
|
||||
{% trans "Completed" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'task-all-filter' %}?{{pd}}&status=expired&view=card" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:Tomato"></span>
|
||||
{% trans "Expired" %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% if tasks %}
|
||||
<div class="oh-layout--grid-3">
|
||||
{% for task in tasks %}
|
||||
<div class="oh-kanban-card {% if task.status == 'to_do'%} to-do-task
|
||||
{% elif task.status == 'in_progress' %} in-progress-task
|
||||
{% elif task.status == 'completed' %} completed-task
|
||||
{% else %} expired-task
|
||||
{% endif %} "
|
||||
style="color: inherit;text-decoration: none;">
|
||||
<a hx-get="{% url 'task-details' task.id %}" hx-target="#TaskDetailsTarget" data-toggle='oh-modal-toggle' data-target = '#TaskModal' style="color: inherit;text-decoration: none; display: flex; width:550px">
|
||||
|
||||
<div class="oh-kanban-card__avatar">
|
||||
<div class="oh-kanban-card__profile-container">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{task.title}}&background=random"
|
||||
class="oh-kanban-card__profile-image"
|
||||
alt="Username"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-kanban-card__details">
|
||||
<span class="oh-kanban-card__title">{{task.title}}</span>
|
||||
<span class="oh-kanban-card__subtitle">{% trans "Project Name" %}: {{task.project}}</span><br>
|
||||
<span class="oh-kanban-card__subtitle">{% trans "Stage Name" %} : {{task.stage}}</span><br/>
|
||||
<span class="oh-kanban-card__subtitle">{% trans "End Date" %} : {{task.end_date}}</span>
|
||||
</div>
|
||||
</a>
|
||||
<div class="oh-kanban-card__dots">
|
||||
<div class="oh-dropdown" x-data="{show: false}">
|
||||
<button class="oh-btn oh-btn--transparent text-muted p-3" @click="show = !show">
|
||||
<ion-icon name="ellipsis-vertical-sharp" title="Options" role="img" class="md hydrated" aria-label="ellipsis vertical sharp"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--dark-border oh-dropdown__menu--right" x-show="show" @click.outside="show = false" style="display: none;">
|
||||
<ul class="oh-dropdown__items">
|
||||
{% if perms.recruitment.change_candidate %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a hx-get="{% url 'update-task-all' task.id %}" hx-target='#TaskDetailsTarget' data-toggle = 'oh-modal-toggle' data-target="#TaskModal" hx-swap='innerHTML' class="oh-dropdown__link">{% trans "Edit" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="oh-dropdown__item">
|
||||
{% if task.is_active %}
|
||||
<a href="{% url 'task-all-archive' task.id %}" onclick="return confirm('{% trans 'Do you want to archive this task?' %}')" class="oh-dropdown__link">
|
||||
{% trans "Archive" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'task-all-archive' task.id %}" onclick="return confirm('{% trans 'Do you want to un archive this task?' %}')" class="oh-dropdown__link">
|
||||
{% trans "Un-Archive" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
</li>
|
||||
{% if perms.recruitment.delete_candidate %}
|
||||
<li class="oh-dropdown__item">
|
||||
<form action="{% url 'delete-task' task.id %}?task_all=true" onsubmit="return confirm('{% trans "Do you want to delete this candidate?" %}')" method="post">
|
||||
{% csrf_token %}
|
||||
<button class="oh-dropdown__link oh-dropdown__link--danger ">{% trans "Delete" %}</button>
|
||||
</form>
|
||||
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
data-toggle="modal"
|
||||
data-target="#addEmployeeModal"
|
||||
>
|
||||
{% trans "Page" %} {{ data.number }} {% trans "of" %} {{ data.paginator.num_pages }}.
|
||||
</span
|
||||
>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{data.number}}"
|
||||
hx-get="{% url 'search-candidate' %}?{{pd}}&view=card"
|
||||
hx-target="#section"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{data.paginator.num_pages}}</span>
|
||||
</div>
|
||||
|
||||
<ul class="oh-pagination__items">
|
||||
{% if data.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#section' hx-get="{% url 'search-candidate' %}?{{pd}}&page=1&view=card" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#section' hx-get="{% url 'search-candidate' %}?{{pd}}&page={{ data.previous_page_number }}&view=card" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if data.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#section' hx-get="{% url 'search-candidate' %}?{{pd}}&page={{ data.next_page_number }}&view=card" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#section' hx-get="{% url 'search-candidate' %}?{{pd}}&page={{ data.paginator.num_pages }}&view=card" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 200px;margin: 10px auto ;" src="{% static 'images/ui/project/task.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">{% trans "There are currently no available tasks; please create a new one." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
{% load i18n %} {% load basefilters %}
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header"
|
||||
onclick="event.stopImmediatePropagation();$(this).parent().toggleClass('oh-accordion--show');"
|
||||
>{% trans "Task" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Manager" %}</label>
|
||||
{{f.form.task_manager}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Stage" %}</label>
|
||||
{{f.form.stage}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Project" %}</label>
|
||||
{{f.form.project}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Status" %}</label>
|
||||
{{f.form.status}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "End Date" %}</label>
|
||||
{{f.form.end_till}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button
|
||||
class="oh-btn oh-btn--secondary oh-btn--small w-100 filterButton"
|
||||
type="submit"
|
||||
>
|
||||
{% trans "Filter" %}
|
||||
</button>
|
||||
</div>
|
||||
{% load i18n %} {% load basefilters %}
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header"
|
||||
onclick="event.stopImmediatePropagation();$(this).parent().toggleClass('oh-accordion--show');"
|
||||
>{% trans "Task" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Manager" %}</label>
|
||||
{{f.form.task_manager}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Stage" %}</label>
|
||||
{{f.form.stage}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Project" %}</label>
|
||||
{{f.form.project}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Status" %}</label>
|
||||
{{f.form.status}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "End Date" %}</label>
|
||||
{{f.form.end_till}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button
|
||||
class="oh-btn oh-btn--secondary oh-btn--small w-100 filterButton"
|
||||
type="submit"
|
||||
>
|
||||
{% trans "Filter" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,180 +1,180 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% if messages %}
|
||||
<div class="oh-wrapper">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "filter_tags.html" %}
|
||||
{% if tasks %}
|
||||
<div class="oh-tabs__content oh-tabs__content--active" style="padding-top: 10px;">
|
||||
{% comment %} easy filters {% endcomment %}
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<span class="m-3 draft" hx-get="{% url 'task-all-filter' %}?{{pd}}&status=to_do&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:yellowgreen"></span>
|
||||
{% trans "To Do" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'task-all-filter' %}?{{pd}}&status=in_progress&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:Orange"></span>
|
||||
{% trans "In progress" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'task-all-filter' %}?{{pd}}&status=completed&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:DodgerBlue"></span>
|
||||
{% trans "Completed" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'task-all-filter' %}?{{pd}}&status=expired&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:Tomato"></span>
|
||||
{% trans "Expired" %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th" >
|
||||
<div class="d-flex">
|
||||
<div class="">
|
||||
<input type="checkbox" class="oh-input oh-input__checkbox mt-1 mr-2 all-task-all" />
|
||||
</div>
|
||||
<div hx-target='#section' hx-get="{% url 'search-candidate' %}?{{pd}}&orderby=name&view=list">
|
||||
{% trans "Task" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Project" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Stage" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Mangers" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Members" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "End Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Status" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Description" %}</div>
|
||||
{% comment %} <div class="oh-sticky-table__th"></div> {% endcomment %}
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% for task in tasks %}
|
||||
<div class="oh-sticky-table__tbody ui-sortable" hx-get="{% url 'task-details' task.id %}" hx-target="#TaskDetailsTarget" data-toggle='oh-modal-toggle' data-target = '#TaskModal'>
|
||||
<div class="oh-sticky-table__tr ui-sortable-handle">
|
||||
<div class="oh-sticky-table__sd {% if task.status == 'to_do'%} to-do-task
|
||||
{% elif task.status == 'in_progress' %} in-progress-task
|
||||
{% elif task.status == 'completed' %} completed-task
|
||||
{% else %} expired-task
|
||||
{% endif %}">
|
||||
<div class="d-flex">
|
||||
<div onclick="event.stopPropagation();">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="{{task.id}}"
|
||||
class="oh-input candidate-checkbox oh-input__checkbox mt-2 mr-2 all-task-all-row"
|
||||
/>
|
||||
</div>
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<span class="oh-profile__name oh-text--dark">{{task.title}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="#" style="color: inherit;text-decoration: none;" class="oh-sticky-table__td">{{task.project}}</a>
|
||||
<a href="#" style="color: inherit;text-decoration: none;" class="oh-sticky-table__td">{{task.stage}}</a>
|
||||
|
||||
<a href="#" style="color: inherit;text-decoration: none;"
|
||||
class="oh-sticky-table__td">{{task.task_manager}} </a>
|
||||
<a href="#" style="color: inherit;text-decoration: none;"
|
||||
class="oh-sticky-table__td">
|
||||
{% for member in task.task_members.all %}
|
||||
{{member}},
|
||||
{% endfor %}
|
||||
</a>
|
||||
|
||||
<a href="#" style="color: inherit;text-decoration: none;" class="oh-sticky-table__td">{{task.end_date}}</a>
|
||||
<a href="#" style="color: inherit;text-decoration: none;" class="oh-sticky-table__td">{{task.get_status_display}}</a>
|
||||
<a href="#" style="color: inherit;text-decoration: none;" class="oh-sticky-table__td">{{task.description}}</a>
|
||||
|
||||
{% comment %} <a href="#" style="color: inherit;text-decoration: none;" class="oh-sticky-table__td">
|
||||
{% if perms.recruitment.view_history %}
|
||||
<button hx-get="{% url 'candidate-history' cand.id %}" hx-target='#section'
|
||||
class="oh-btn oh-btn--info">history</button>
|
||||
{% endif %}
|
||||
</a> {% endcomment %}
|
||||
<div href="#" style="color: inherit;text-decoration: none;" class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group">
|
||||
{% comment %} {% if perms.recruitment.change_candidate %} {% endcomment %}
|
||||
<a hx-get="{% url 'update-task-all' task.id %}" hx-target='#TaskDetailsTarget' data-toggle = 'oh-modal-toggle' data-target="#TaskModal" hx-swap='innerHTML'
|
||||
class="oh-btn oh-btn--light-bkg w-100" title="Edit"><ion-icon name="create-outline"></ion-icon></a>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.recruitment.delete_candidate %} {% endcomment %}
|
||||
<form action="{% url 'delete-task' task.id %}?task_all=true" method='post'
|
||||
onsubmit="Are you sure want to delete this candidate?">
|
||||
{% csrf_token %}
|
||||
<button type='submit' class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
title="Remove"><ion-icon name="trash-outline"></ion-icon></button>
|
||||
</form>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% comment %} pagination {% endcomment %}
|
||||
<div class="oh-pagination">
|
||||
<span class="oh-pagination__page" data-toggle="modal" data-target="#addEmployeeModal">
|
||||
{% trans "Page" %} {{ data.number }} {% trans "of" %} {{ data.paginator.num_pages }}.
|
||||
</span>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
|
||||
<input type="number" name="page" class="oh-pagination__input" value="{{data.number}}"
|
||||
hx-get="{% url 'search-candidate' %}?{{pd}}&view=list" hx-target="#section" min="1" />
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{data.paginator.num_pages}}</span>
|
||||
</div>
|
||||
|
||||
<ul class="oh-pagination__items">
|
||||
{% if data.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#section' hx-get="{% url 'search-candidate' %}?{{pd}}&page=1&view=list"
|
||||
class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#section'
|
||||
hx-get="{% url 'search-candidate' %}?{{pd}}&page={{ data.previous_page_number }}&view=list"
|
||||
class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if data.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#section' hx-get="{% url 'search-candidate' %}?{{pd}}&page={{ data.next_page_number }}&view=list"
|
||||
class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#section'
|
||||
hx-get="{% url 'search-candidate' %}?{{pd}}&page={{ data.paginator.num_pages }}&view=list"
|
||||
class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 200px;margin: 10px auto ;" src="{% static 'images/ui/project/task.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">{% trans "There are currently no available tasks; please create a new one." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script src="{% static '/task_all/task_all_action.js' %}"></script>
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% if messages %}
|
||||
<div class="oh-wrapper">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "filter_tags.html" %}
|
||||
{% if tasks %}
|
||||
<div class="oh-tabs__content oh-tabs__content--active" style="padding-top: 10px;">
|
||||
{% comment %} easy filters {% endcomment %}
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<span class="m-3 draft" hx-get="{% url 'task-all-filter' %}?{{pd}}&status=to_do&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:yellowgreen"></span>
|
||||
{% trans "To Do" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'task-all-filter' %}?{{pd}}&status=in_progress&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:Orange"></span>
|
||||
{% trans "In progress" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'task-all-filter' %}?{{pd}}&status=completed&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:DodgerBlue"></span>
|
||||
{% trans "Completed" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'task-all-filter' %}?{{pd}}&status=expired&view=list" hx-target="#view-container" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:Tomato"></span>
|
||||
{% trans "Expired" %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th" >
|
||||
<div class="d-flex">
|
||||
<div class="">
|
||||
<input type="checkbox" class="oh-input oh-input__checkbox mt-1 mr-2 all-task-all" />
|
||||
</div>
|
||||
<div hx-target='#section' hx-get="{% url 'search-candidate' %}?{{pd}}&orderby=name&view=list">
|
||||
{% trans "Task" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Project" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Stage" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Mangers" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Members" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "End Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Status" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Description" %}</div>
|
||||
{% comment %} <div class="oh-sticky-table__th"></div> {% endcomment %}
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% for task in tasks %}
|
||||
<div class="oh-sticky-table__tbody ui-sortable" hx-get="{% url 'task-details' task.id %}" hx-target="#TaskDetailsTarget" data-toggle='oh-modal-toggle' data-target = '#TaskModal'>
|
||||
<div class="oh-sticky-table__tr ui-sortable-handle">
|
||||
<div class="oh-sticky-table__sd {% if task.status == 'to_do'%} to-do-task
|
||||
{% elif task.status == 'in_progress' %} in-progress-task
|
||||
{% elif task.status == 'completed' %} completed-task
|
||||
{% else %} expired-task
|
||||
{% endif %}">
|
||||
<div class="d-flex">
|
||||
<div onclick="event.stopPropagation();">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="{{task.id}}"
|
||||
class="oh-input candidate-checkbox oh-input__checkbox mt-2 mr-2 all-task-all-row"
|
||||
/>
|
||||
</div>
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<span class="oh-profile__name oh-text--dark">{{task.title}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="#" style="color: inherit;text-decoration: none;" class="oh-sticky-table__td">{{task.project}}</a>
|
||||
<a href="#" style="color: inherit;text-decoration: none;" class="oh-sticky-table__td">{{task.stage}}</a>
|
||||
|
||||
<a href="#" style="color: inherit;text-decoration: none;"
|
||||
class="oh-sticky-table__td">{{task.task_manager}} </a>
|
||||
<a href="#" style="color: inherit;text-decoration: none;"
|
||||
class="oh-sticky-table__td">
|
||||
{% for member in task.task_members.all %}
|
||||
{{member}},
|
||||
{% endfor %}
|
||||
</a>
|
||||
|
||||
<a href="#" style="color: inherit;text-decoration: none;" class="oh-sticky-table__td">{{task.end_date}}</a>
|
||||
<a href="#" style="color: inherit;text-decoration: none;" class="oh-sticky-table__td">{{task.get_status_display}}</a>
|
||||
<a href="#" style="color: inherit;text-decoration: none;" class="oh-sticky-table__td">{{task.description}}</a>
|
||||
|
||||
{% comment %} <a href="#" style="color: inherit;text-decoration: none;" class="oh-sticky-table__td">
|
||||
{% if perms.recruitment.view_history %}
|
||||
<button hx-get="{% url 'candidate-history' cand.id %}" hx-target='#section'
|
||||
class="oh-btn oh-btn--info">history</button>
|
||||
{% endif %}
|
||||
</a> {% endcomment %}
|
||||
<div href="#" style="color: inherit;text-decoration: none;" class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group">
|
||||
{% comment %} {% if perms.recruitment.change_candidate %} {% endcomment %}
|
||||
<a hx-get="{% url 'update-task-all' task.id %}" hx-target='#TaskDetailsTarget' data-toggle = 'oh-modal-toggle' data-target="#TaskModal" hx-swap='innerHTML'
|
||||
class="oh-btn oh-btn--light-bkg w-100" title="Edit"><ion-icon name="create-outline"></ion-icon></a>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.recruitment.delete_candidate %} {% endcomment %}
|
||||
<form action="{% url 'delete-task' task.id %}?task_all=true" method='post'
|
||||
onsubmit="Are you sure want to delete this candidate?">
|
||||
{% csrf_token %}
|
||||
<button type='submit' class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
title="Remove"><ion-icon name="trash-outline"></ion-icon></button>
|
||||
</form>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% comment %} pagination {% endcomment %}
|
||||
<div class="oh-pagination">
|
||||
<span class="oh-pagination__page" data-toggle="modal" data-target="#addEmployeeModal">
|
||||
{% trans "Page" %} {{ data.number }} {% trans "of" %} {{ data.paginator.num_pages }}.
|
||||
</span>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
|
||||
<input type="number" name="page" class="oh-pagination__input" value="{{data.number}}"
|
||||
hx-get="{% url 'search-candidate' %}?{{pd}}&view=list" hx-target="#section" min="1" />
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{data.paginator.num_pages}}</span>
|
||||
</div>
|
||||
|
||||
<ul class="oh-pagination__items">
|
||||
{% if data.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#section' hx-get="{% url 'search-candidate' %}?{{pd}}&page=1&view=list"
|
||||
class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#section'
|
||||
hx-get="{% url 'search-candidate' %}?{{pd}}&page={{ data.previous_page_number }}&view=list"
|
||||
class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if data.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#section' hx-get="{% url 'search-candidate' %}?{{pd}}&page={{ data.next_page_number }}&view=list"
|
||||
class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#section'
|
||||
hx-get="{% url 'search-candidate' %}?{{pd}}&page={{ data.paginator.num_pages }}&view=list"
|
||||
class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 200px;margin: 10px auto ;" src="{% static 'images/ui/project/task.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">{% trans "There are currently no available tasks; please create a new one." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script src="{% static '/task_all/task_all_action.js' %}"></script>
|
||||
|
||||
@@ -1,168 +1,168 @@
|
||||
{% load i18n %}
|
||||
|
||||
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
||||
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">{% trans "Tasks" %}</h1>
|
||||
<a
|
||||
class="oh-main__titlebar-search-toggle"
|
||||
role="button"
|
||||
aria-label="Toggle Search"
|
||||
@click="searchShow = !searchShow"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-main__titlebar-serach-icon md hydrated"
|
||||
role="img"
|
||||
aria-label="search outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right">
|
||||
{% if tasks %}
|
||||
<form
|
||||
hx-get="{% url 'task-all-filter' %}?view={{view_type}}""
|
||||
hx-target="#view-container"
|
||||
hx-swap="innerHTML"
|
||||
id="taskAllFilterForm"
|
||||
class="d-flex"
|
||||
onsubmit="event.preventDefault()"
|
||||
>
|
||||
{% comment %} for search{% endcomment %}
|
||||
|
||||
<div
|
||||
class="oh-input-group oh-input__search-group"
|
||||
:class="searchShow ? 'oh-input__search-group--show' : ''"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-input-group__icon oh-input-group__icon--left"
|
||||
></ion-icon>
|
||||
<input
|
||||
type="text"
|
||||
class="oh-input oh-input__icon"
|
||||
aria-label="Search Input"
|
||||
id="filter-task-all"
|
||||
name="search"
|
||||
placeholder="{% trans 'Search' %}"
|
||||
onkeyup="$('.filterButton')[0].click()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<ul class="oh-view-types ml-2">
|
||||
{% comment %} for list view {% endcomment %}
|
||||
<li class="oh-view-type task-all-view-type" data-view="list">
|
||||
<a
|
||||
data-view="list"
|
||||
class="oh-btn oh-btn--view"
|
||||
hx-get= "{% url 'task-all-filter' %}?view=list"
|
||||
hx-target="#view-container"
|
||||
>
|
||||
<ion-icon name="list-outline"></ion-icon>
|
||||
</a>
|
||||
</li>
|
||||
{% comment %} for card view {% endcomment %}
|
||||
<li class="oh-view-type task-all-view-type" data-view="card">
|
||||
<a
|
||||
class="oh-btn oh-btn--view"
|
||||
hx-get= "{% url 'task-all-filter' %}"
|
||||
hx-target="#view-container"
|
||||
>
|
||||
<ion-icon name="grid-outline"></ion-icon
|
||||
|
||||
></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% comment %} for filtering {% endcomment %}
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button
|
||||
class="oh-btn ml-2"
|
||||
@click="open = !open"
|
||||
onclick="event.preventDefault()"
|
||||
>
|
||||
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
||||
<div id="filterCount"></div>
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
|
||||
x-show="open"
|
||||
@click.outside="open = false"
|
||||
style="display: none;"
|
||||
>
|
||||
|
||||
{% include 'task_all/task_all_filter.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% comment %} for actions {% endcomment %}
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button
|
||||
class="oh-btn oh-btn--dropdown oh-btn oh-btn--shadow"
|
||||
@click="open = !open"
|
||||
@click.outside="open = false"
|
||||
>
|
||||
{% trans "Actions" %}
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open" style="display: none;">
|
||||
<ul class="oh-dropdown__items">
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link " id="archiveTaskAll"
|
||||
>{% trans "Archive" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link " id="unArchiveTaskAll"
|
||||
>{% trans "Un-Archive" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a
|
||||
href="#"
|
||||
class="oh-dropdown__link oh-dropdown__link--danger"
|
||||
id="deleteTaskAll"
|
||||
>{% trans "Delete" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
{% comment %} for create {% endcomment %}
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div>
|
||||
<a
|
||||
class="oh-btn oh-btn--secondary oh-btn--shadow"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskUpdateModal"
|
||||
hx-get="{% url 'create-task-all' %}"
|
||||
hx-target="#TaskUpdateTarget"
|
||||
>
|
||||
<ion-icon class="me-2" name="add-outline"></ion-icon>{% trans " Task" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
{% load i18n %}
|
||||
|
||||
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
||||
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">{% trans "Tasks" %}</h1>
|
||||
<a
|
||||
class="oh-main__titlebar-search-toggle"
|
||||
role="button"
|
||||
aria-label="Toggle Search"
|
||||
@click="searchShow = !searchShow"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-main__titlebar-serach-icon md hydrated"
|
||||
role="img"
|
||||
aria-label="search outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right">
|
||||
{% if tasks %}
|
||||
<form
|
||||
hx-get="{% url 'task-all-filter' %}?view={{view_type}}""
|
||||
hx-target="#view-container"
|
||||
hx-swap="innerHTML"
|
||||
id="taskAllFilterForm"
|
||||
class="d-flex"
|
||||
onsubmit="event.preventDefault()"
|
||||
>
|
||||
{% comment %} for search{% endcomment %}
|
||||
|
||||
<div
|
||||
class="oh-input-group oh-input__search-group"
|
||||
:class="searchShow ? 'oh-input__search-group--show' : ''"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-input-group__icon oh-input-group__icon--left"
|
||||
></ion-icon>
|
||||
<input
|
||||
type="text"
|
||||
class="oh-input oh-input__icon"
|
||||
aria-label="Search Input"
|
||||
id="filter-task-all"
|
||||
name="search"
|
||||
placeholder="{% trans 'Search' %}"
|
||||
onkeyup="$('.filterButton')[0].click()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<ul class="oh-view-types ml-2">
|
||||
{% comment %} for list view {% endcomment %}
|
||||
<li class="oh-view-type task-all-view-type" data-view="list">
|
||||
<a
|
||||
data-view="list"
|
||||
class="oh-btn oh-btn--view"
|
||||
hx-get= "{% url 'task-all-filter' %}?view=list"
|
||||
hx-target="#view-container"
|
||||
>
|
||||
<ion-icon name="list-outline"></ion-icon>
|
||||
</a>
|
||||
</li>
|
||||
{% comment %} for card view {% endcomment %}
|
||||
<li class="oh-view-type task-all-view-type" data-view="card">
|
||||
<a
|
||||
class="oh-btn oh-btn--view"
|
||||
hx-get= "{% url 'task-all-filter' %}"
|
||||
hx-target="#view-container"
|
||||
>
|
||||
<ion-icon name="grid-outline"></ion-icon
|
||||
|
||||
></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% comment %} for filtering {% endcomment %}
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button
|
||||
class="oh-btn ml-2"
|
||||
@click="open = !open"
|
||||
onclick="event.preventDefault()"
|
||||
>
|
||||
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
||||
<div id="filterCount"></div>
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
|
||||
x-show="open"
|
||||
@click.outside="open = false"
|
||||
style="display: none;"
|
||||
>
|
||||
|
||||
{% include 'task_all/task_all_filter.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% comment %} for actions {% endcomment %}
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button
|
||||
class="oh-btn oh-btn--dropdown oh-btn oh-btn--shadow"
|
||||
@click="open = !open"
|
||||
@click.outside="open = false"
|
||||
>
|
||||
{% trans "Actions" %}
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open" style="display: none;">
|
||||
<ul class="oh-dropdown__items">
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link " id="archiveTaskAll"
|
||||
>{% trans "Archive" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link " id="unArchiveTaskAll"
|
||||
>{% trans "Un-Archive" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a
|
||||
href="#"
|
||||
class="oh-dropdown__link oh-dropdown__link--danger"
|
||||
id="deleteTaskAll"
|
||||
>{% trans "Delete" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
{% comment %} for create {% endcomment %}
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div>
|
||||
<a
|
||||
class="oh-btn oh-btn--secondary oh-btn--shadow"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TaskUpdateModal"
|
||||
hx-get="{% url 'create-task-all' %}"
|
||||
hx-target="#TaskUpdateTarget"
|
||||
>
|
||||
<ion-icon class="me-2" name="add-outline"></ion-icon>{% trans " Task" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,123 +1,122 @@
|
||||
{% extends 'index.html' %}
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
<style>
|
||||
.to-do-task{
|
||||
border-left: solid 5px yellowgreen !important;
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
.in-progress-task{
|
||||
border-left: solid 5px Orange !important;
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
.completed-task{
|
||||
border-left: solid 5px DodgerBlue !important;
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
.expired-task{
|
||||
border-left: solid 5px Tomato !important;
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
</style>
|
||||
<main :class="sidebarOpen ? 'oh-main__sidebar-visible' : ''">
|
||||
{% include 'task_all/task_all_navbar.html' %}
|
||||
|
||||
</main>
|
||||
|
||||
<div id="view-container" class="oh-wrapper">
|
||||
{% if view_type == 'list' %}
|
||||
{% include 'task_all/task_all_list.html' %}
|
||||
{% else %}
|
||||
{% include 'task_all/task_all_card.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="TaskModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TaskModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="TaskDetailsTarget"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="TaskUpdateModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TaskUpdateModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="TaskUpdateTarget"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="TaskTimesheetModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TaskTimesheetModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width:1350px"
|
||||
id="TaskTimesheetTarget"
|
||||
></div>
|
||||
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="TimesheetUpdateModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TaskUpdateModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="TimesheetUpdateTarget"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$("#filter-task-all").keyup(function (e) {
|
||||
let search = $(this).val()
|
||||
$(".task-all-view-type").attr("hx-vals", `{"search":"${search}"}`);
|
||||
$("#filter-task-all").attr("hx-vals",`{'search':"${search}"}`)
|
||||
});
|
||||
$(".task-all-view-type").click(function (e) {
|
||||
let view = $(this).data("view");
|
||||
var currentURL = window.location.href;
|
||||
if (view != undefined){
|
||||
// Check if the query string already exists in the URL
|
||||
if (/\?view=[^&]+/.test(currentURL)) {
|
||||
// If the query parameter ?view exists, replace it with the new value
|
||||
newURL = currentURL.replace(/\?view=[^&]+/, "?view="+view);
|
||||
}
|
||||
else {
|
||||
// If the query parameter ?view does not exist, add it to the URL
|
||||
var separator = currentURL.includes('?') ? '&' : '?';
|
||||
newURL = currentURL + separator + "view="+view;
|
||||
}
|
||||
history.pushState({}, "", newURL);
|
||||
console.log(view)
|
||||
$("#taskAllFilterForm").attr("hx-vals", `{"view":"${view}"}`);
|
||||
$('#filter-task-all').attr("hx-vals", `{"view":"${view}"}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock content %}
|
||||
|
||||
{% extends 'index.html' %}
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
<style>
|
||||
.to-do-task{
|
||||
border-left: solid 5px yellowgreen !important;
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
.in-progress-task{
|
||||
border-left: solid 5px Orange !important;
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
.completed-task{
|
||||
border-left: solid 5px DodgerBlue !important;
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
.expired-task{
|
||||
border-left: solid 5px Tomato !important;
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
</style>
|
||||
<main :class="sidebarOpen ? 'oh-main__sidebar-visible' : ''">
|
||||
{% include 'task_all/task_all_navbar.html' %}
|
||||
|
||||
</main>
|
||||
|
||||
<div id="view-container" class="oh-wrapper">
|
||||
{% if view_type == 'list' %}
|
||||
{% include 'task_all/task_all_list.html' %}
|
||||
{% else %}
|
||||
{% include 'task_all/task_all_card.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="TaskModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TaskModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="TaskDetailsTarget"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="TaskUpdateModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TaskUpdateModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="TaskUpdateTarget"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="TaskTimesheetModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TaskTimesheetModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width:1350px"
|
||||
id="TaskTimesheetTarget"
|
||||
></div>
|
||||
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="TimesheetUpdateModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TaskUpdateModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="TimesheetUpdateTarget"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$("#filter-task-all").keyup(function (e) {
|
||||
let search = $(this).val()
|
||||
$(".task-all-view-type").attr("hx-vals", `{"search":"${search}"}`);
|
||||
$("#filter-task-all").attr("hx-vals",`{'search':"${search}"}`)
|
||||
});
|
||||
$(".task-all-view-type").click(function (e) {
|
||||
let view = $(this).data("view");
|
||||
var currentURL = window.location.href;
|
||||
if (view != undefined){
|
||||
// Check if the query string already exists in the URL
|
||||
if (/\?view=[^&]+/.test(currentURL)) {
|
||||
// If the query parameter ?view exists, replace it with the new value
|
||||
newURL = currentURL.replace(/\?view=[^&]+/, "?view="+view);
|
||||
}
|
||||
else {
|
||||
// If the query parameter ?view does not exist, add it to the URL
|
||||
var separator = currentURL.includes('?') ? '&' : '?';
|
||||
newURL = currentURL + separator + "view="+view;
|
||||
}
|
||||
history.pushState({}, "", newURL);
|
||||
console.log(view)
|
||||
$("#taskAllFilterForm").attr("hx-vals", `{"view":"${view}"}`);
|
||||
$('#filter-task-all').attr("hx-vals", `{"view":"${view}"}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header ">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Task" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
id="close1"
|
||||
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'update-task' task_id %}"
|
||||
hx-target="#TaskFormTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('#close1').click(function(e){
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
var closestModal = $(this).closest('.oh-modal--show');
|
||||
closestModal.removeClass('oh-modal--show')
|
||||
// e.preventDefault()
|
||||
// var parentDiv = $(this).parents().closest('oh-modal--show')
|
||||
// console.log(parentDiv)
|
||||
// parentDiv.classList.remove('oh-modal--show')
|
||||
})
|
||||
});
|
||||
</script>
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header ">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Task" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
id="close1"
|
||||
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{%url 'update-task' task_id %}"
|
||||
hx-target="#TaskFormTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('#close1').click(function(e){
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
var closestModal = $(this).closest('.oh-modal--show');
|
||||
closestModal.removeClass('oh-modal--show')
|
||||
// e.preventDefault()
|
||||
// var parentDiv = $(this).parents().closest('oh-modal--show')
|
||||
// console.log(parentDiv)
|
||||
// parentDiv.classList.remove('oh-modal--show')
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,252 +1,252 @@
|
||||
{% extends 'index.html' %}{% block content %}{% load static %} {% load i18n %}
|
||||
{% load basefilters %} {% if request.user.employee_get.id == emp_id or perms.project.view_timesheet or request.user|is_reportingmanager %}
|
||||
<div
|
||||
class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent"
|
||||
>
|
||||
<div
|
||||
class="oh-card-dashboard__header oh-card-dashboard__header--divider d-flex justify-content-between align-items-center"
|
||||
>
|
||||
<div class="">
|
||||
<h5 class="oh-card-dashboard__title">
|
||||
Personal Timesheet of {{emp_name|capfirst}}
|
||||
</h5>
|
||||
</div>
|
||||
<div>
|
||||
<span class="oh-card-dashboard__title me-5"
|
||||
><a id="previous">Previous</a></span
|
||||
>
|
||||
<span class="oh-card-dashboard__title me-5"><a id="next">Next</a></span>
|
||||
<select
|
||||
class="oh-select oh-select--sm me-5"
|
||||
name=""
|
||||
id="personalTimesheetSelect"
|
||||
>
|
||||
<option value="week" selected>Week</option>
|
||||
<option value="month">Month</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body" style="width: 99%">
|
||||
<canvas id="personalChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
{% else %} {% include '404.html' %} {% endif %}
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
Date.prototype.getWeek = function () { return $.datepicker.iso8601Week(this); }
|
||||
|
||||
var myDate = new Date();
|
||||
var myWeek=myDate.getWeek();
|
||||
var year = myDate. getFullYear()
|
||||
var month = myDate.getMonth()
|
||||
|
||||
|
||||
|
||||
function personalChart(dataSet, labels) {
|
||||
const data = {
|
||||
labels: labels,
|
||||
datasets: dataSet,
|
||||
|
||||
};
|
||||
// Create chart using the Chart.js library
|
||||
window["personalChart"] = {};
|
||||
const ctx = document.getElementById("personalChart").getContext("2d");
|
||||
personalChart = new Chart(ctx, {
|
||||
type: "bar",
|
||||
data: data,
|
||||
options: {
|
||||
animation: {
|
||||
duration: 1000, // Duration of the animation in milliseconds
|
||||
easing: 'easeInOutQuart', // Easing function for the animation
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function personalChart_one(dataSet, labels) {
|
||||
personalChart.destroy()
|
||||
const data = {
|
||||
labels: labels,
|
||||
datasets: dataSet,
|
||||
|
||||
};
|
||||
// Create chart using the Chart.js library
|
||||
window["personalChart"] = {};
|
||||
const ctx = document.getElementById("personalChart").getContext("2d");
|
||||
personalChart = new Chart(ctx, {
|
||||
type: "bar",
|
||||
data: data,
|
||||
options: {
|
||||
animation: {
|
||||
duration: 1000, // Duration of the animation in milliseconds
|
||||
easing: 'easeInOutQuart', // Easing function for the animation
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
},
|
||||
y: {
|
||||
stacked: true
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/project/personal-time-sheet/" ,
|
||||
data :{
|
||||
emp_id : {{emp_id}},
|
||||
year : year,
|
||||
week:myWeek,
|
||||
month:month,
|
||||
selected : $("#personalTimesheetSelect").val(),
|
||||
},
|
||||
success: function (response) {
|
||||
// Code to handle the response
|
||||
dataSet = response.dataSet;
|
||||
labels = response.labels;
|
||||
|
||||
personalChart(dataSet, labels);
|
||||
},
|
||||
error: function(response){
|
||||
console.log("error")
|
||||
},
|
||||
});
|
||||
|
||||
$("#previous").click(function(){
|
||||
if ($("#personalTimesheetSelect").val() == "week"){
|
||||
myWeek-=1
|
||||
if (myWeek<1){
|
||||
year = year - 1;
|
||||
myWeek = 52+myWeek;
|
||||
}
|
||||
}
|
||||
else if ($("#personalTimesheetSelect").val() == "month"){
|
||||
month-=1
|
||||
if (month<0){
|
||||
year=year-1;
|
||||
month = 12+month
|
||||
}
|
||||
}
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/project/personal-time-sheet/",
|
||||
data :{
|
||||
emp_id : {{emp_id}},
|
||||
year : year,
|
||||
week:myWeek,
|
||||
month:month,
|
||||
selected : $("#personalTimesheetSelect").val()
|
||||
|
||||
},
|
||||
success: function (response) {
|
||||
// Code to handle the response
|
||||
dataSet = response.dataSet;
|
||||
labels = response.labels;
|
||||
|
||||
personalChart_one(dataSet, labels);
|
||||
|
||||
},
|
||||
error: function(response){
|
||||
console.log("error")
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
|
||||
$("#next").click(function(){
|
||||
this_week = myDate.getWeek()
|
||||
this_month = myDate.getMonth()
|
||||
|
||||
if ($("#personalTimesheetSelect").val() == "week"){
|
||||
if (myWeek < this_week){
|
||||
|
||||
myWeek+=1
|
||||
} else {
|
||||
myWeek = this_week
|
||||
}
|
||||
}
|
||||
else if ($("#personalTimesheetSelect").val() == "month"){
|
||||
if (month < this_month){
|
||||
|
||||
month+=1
|
||||
} else {
|
||||
month = this_month
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
year = myDate. getFullYear()
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/project/personal-time-sheet/",
|
||||
data :{
|
||||
emp_id : {{emp_id}},
|
||||
year:year,
|
||||
week:myWeek,
|
||||
month:month,
|
||||
selected : $("#personalTimesheetSelect").val()
|
||||
|
||||
},
|
||||
success: function (response) {
|
||||
// Code to handle the response
|
||||
dataSet = response.dataSet;
|
||||
labels = response.labels;
|
||||
|
||||
personalChart_one(dataSet, labels);
|
||||
},
|
||||
error: function(response){
|
||||
console.log("error")
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
|
||||
$("#personalTimesheetSelect").change(function(){
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/project/personal-time-sheet/",
|
||||
data :{
|
||||
emp_id : {{emp_id}},
|
||||
year:year,
|
||||
week:myWeek,
|
||||
week:myWeek,
|
||||
month:month,
|
||||
selected:$("#personalTimesheetSelect").val(),
|
||||
},
|
||||
success: function (response) {
|
||||
// Code to handle the response.labels
|
||||
dataSet = response.dataSet;
|
||||
labels = response.labels;
|
||||
|
||||
personalChart_one(dataSet, labels);
|
||||
},
|
||||
error: function(response){
|
||||
console.log("error")
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% extends 'index.html' %}{% block content %}{% load static %} {% load i18n %}
|
||||
{% load basefilters %} {% if request.user.employee_get.id == emp_id or perms.project.view_timesheet or request.user|is_reportingmanager %}
|
||||
<div
|
||||
class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent"
|
||||
>
|
||||
<div
|
||||
class="oh-card-dashboard__header oh-card-dashboard__header--divider d-flex justify-content-between align-items-center"
|
||||
>
|
||||
<div class="">
|
||||
<h5 class="oh-card-dashboard__title">
|
||||
Personal Timesheet of {{emp_name|capfirst}}
|
||||
</h5>
|
||||
</div>
|
||||
<div>
|
||||
<span class="oh-card-dashboard__title me-5"
|
||||
><a id="previous">Previous</a></span
|
||||
>
|
||||
<span class="oh-card-dashboard__title me-5"><a id="next">Next</a></span>
|
||||
<select
|
||||
class="oh-select oh-select--sm me-5"
|
||||
name=""
|
||||
id="personalTimesheetSelect"
|
||||
>
|
||||
<option value="week" selected>Week</option>
|
||||
<option value="month">Month</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body" style="width: 99%">
|
||||
<canvas id="personalChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
{% else %} {% include '404.html' %} {% endif %}
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
Date.prototype.getWeek = function () { return $.datepicker.iso8601Week(this); }
|
||||
|
||||
var myDate = new Date();
|
||||
var myWeek=myDate.getWeek();
|
||||
var year = myDate. getFullYear()
|
||||
var month = myDate.getMonth()
|
||||
|
||||
|
||||
|
||||
function personalChart(dataSet, labels) {
|
||||
const data = {
|
||||
labels: labels,
|
||||
datasets: dataSet,
|
||||
|
||||
};
|
||||
// Create chart using the Chart.js library
|
||||
window["personalChart"] = {};
|
||||
const ctx = document.getElementById("personalChart").getContext("2d");
|
||||
personalChart = new Chart(ctx, {
|
||||
type: "bar",
|
||||
data: data,
|
||||
options: {
|
||||
animation: {
|
||||
duration: 1000, // Duration of the animation in milliseconds
|
||||
easing: 'easeInOutQuart', // Easing function for the animation
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function personalChart_one(dataSet, labels) {
|
||||
personalChart.destroy()
|
||||
const data = {
|
||||
labels: labels,
|
||||
datasets: dataSet,
|
||||
|
||||
};
|
||||
// Create chart using the Chart.js library
|
||||
window["personalChart"] = {};
|
||||
const ctx = document.getElementById("personalChart").getContext("2d");
|
||||
personalChart = new Chart(ctx, {
|
||||
type: "bar",
|
||||
data: data,
|
||||
options: {
|
||||
animation: {
|
||||
duration: 1000, // Duration of the animation in milliseconds
|
||||
easing: 'easeInOutQuart', // Easing function for the animation
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
},
|
||||
y: {
|
||||
stacked: true
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/project/personal-time-sheet/" ,
|
||||
data :{
|
||||
emp_id : {{emp_id}},
|
||||
year : year,
|
||||
week:myWeek,
|
||||
month:month,
|
||||
selected : $("#personalTimesheetSelect").val(),
|
||||
},
|
||||
success: function (response) {
|
||||
// Code to handle the response
|
||||
dataSet = response.dataSet;
|
||||
labels = response.labels;
|
||||
|
||||
personalChart(dataSet, labels);
|
||||
},
|
||||
error: function(response){
|
||||
console.log("error")
|
||||
},
|
||||
});
|
||||
|
||||
$("#previous").click(function(){
|
||||
if ($("#personalTimesheetSelect").val() == "week"){
|
||||
myWeek-=1
|
||||
if (myWeek<1){
|
||||
year = year - 1;
|
||||
myWeek = 52+myWeek;
|
||||
}
|
||||
}
|
||||
else if ($("#personalTimesheetSelect").val() == "month"){
|
||||
month-=1
|
||||
if (month<0){
|
||||
year=year-1;
|
||||
month = 12+month
|
||||
}
|
||||
}
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/project/personal-time-sheet/",
|
||||
data :{
|
||||
emp_id : {{emp_id}},
|
||||
year : year,
|
||||
week:myWeek,
|
||||
month:month,
|
||||
selected : $("#personalTimesheetSelect").val()
|
||||
|
||||
},
|
||||
success: function (response) {
|
||||
// Code to handle the response
|
||||
dataSet = response.dataSet;
|
||||
labels = response.labels;
|
||||
|
||||
personalChart_one(dataSet, labels);
|
||||
|
||||
},
|
||||
error: function(response){
|
||||
console.log("error")
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
|
||||
$("#next").click(function(){
|
||||
this_week = myDate.getWeek()
|
||||
this_month = myDate.getMonth()
|
||||
|
||||
if ($("#personalTimesheetSelect").val() == "week"){
|
||||
if (myWeek < this_week){
|
||||
|
||||
myWeek+=1
|
||||
} else {
|
||||
myWeek = this_week
|
||||
}
|
||||
}
|
||||
else if ($("#personalTimesheetSelect").val() == "month"){
|
||||
if (month < this_month){
|
||||
|
||||
month+=1
|
||||
} else {
|
||||
month = this_month
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
year = myDate. getFullYear()
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/project/personal-time-sheet/",
|
||||
data :{
|
||||
emp_id : {{emp_id}},
|
||||
year:year,
|
||||
week:myWeek,
|
||||
month:month,
|
||||
selected : $("#personalTimesheetSelect").val()
|
||||
|
||||
},
|
||||
success: function (response) {
|
||||
// Code to handle the response
|
||||
dataSet = response.dataSet;
|
||||
labels = response.labels;
|
||||
|
||||
personalChart_one(dataSet, labels);
|
||||
},
|
||||
error: function(response){
|
||||
console.log("error")
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
|
||||
$("#personalTimesheetSelect").change(function(){
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/project/personal-time-sheet/",
|
||||
data :{
|
||||
emp_id : {{emp_id}},
|
||||
year:year,
|
||||
week:myWeek,
|
||||
week:myWeek,
|
||||
month:month,
|
||||
selected:$("#personalTimesheetSelect").val(),
|
||||
},
|
||||
success: function (response) {
|
||||
// Code to handle the response.labels
|
||||
dataSet = response.dataSet;
|
||||
labels = response.labels;
|
||||
|
||||
personalChart_one(dataSet, labels);
|
||||
},
|
||||
error: function(response){
|
||||
console.log("error")
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
{% load i18n %} {% load basefilters %}
|
||||
<form hx-get='{% url "filter-time-sheet" %}' hx-target="#TimeSheetList" id="timesheetForm">
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Time Sheet" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Project" %}</label>
|
||||
{{f.form.project_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Status" %}</label>
|
||||
{{f.form.status}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Task" %}</label>
|
||||
{{f.form.task}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Date" %}</label>
|
||||
{{f.form.date}}
|
||||
</div>
|
||||
</div>
|
||||
{% if perms.project.view_timesheet or request.user|is_reportingmanager %}
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Employee" %}</label>
|
||||
{{f.form.employee_id}}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Advanced" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Start Date From" %}</label>
|
||||
{{f.form.start_from}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Till End Date" %}</label>
|
||||
{{f.form.end_till}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button class="oh-btn oh-btn--secondary oh-btn--small w-100 filterButton">
|
||||
{% trans "Filter" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% load i18n %} {% load basefilters %}
|
||||
<form hx-get='{% url "filter-time-sheet" %}' hx-target="#TimeSheetList" id="timesheetForm">
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Time Sheet" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Project" %}</label>
|
||||
{{f.form.project_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Status" %}</label>
|
||||
{{f.form.status}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Task" %}</label>
|
||||
{{f.form.task}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Date" %}</label>
|
||||
{{f.form.date}}
|
||||
</div>
|
||||
</div>
|
||||
{% if perms.project.view_timesheet or request.user|is_reportingmanager %}
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Employee" %}</label>
|
||||
{{f.form.employee_id}}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Advanced" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Start Date From" %}</label>
|
||||
{{f.form.start_from}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Till End Date" %}</label>
|
||||
{{f.form.end_till}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button class="oh-btn oh-btn--secondary oh-btn--small w-100 filterButton">
|
||||
{% trans "Filter" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,126 +1,126 @@
|
||||
{% block content %} {% load static %} {% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Time Sheet" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#TimeSheetFormTarget"
|
||||
onclick="location.reload()"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{% url 'create-time-sheet' %}"
|
||||
hx-target="#TimeSheetFormTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{form.as_p}}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectModal"
|
||||
role="dialog"
|
||||
aria-labelledby="ProjectModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="ProjectFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="TaskModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TaskModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="TaskFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectStageModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TaskModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="ProjectStageFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
var project_id;
|
||||
$("#id_task_id").html("<option>---------</option>");
|
||||
$("#id_project").change(function (e) {
|
||||
project_id = $(this).val(); // Set the project_id value here
|
||||
var createProjectUrl = "{% url 'create-project-time-sheet' %}";
|
||||
if (project_id === "create_new_project") {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: createProjectUrl,
|
||||
success: function (response) {
|
||||
$("#ProjectModal").addClass("oh-modal--show");
|
||||
$("#ProjectFormTarget").html(response);
|
||||
},
|
||||
});0
|
||||
}
|
||||
if (project_id != "create_new_project") {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "{% url 'time-sheet-initial' %}",
|
||||
data: { project_id: project_id },
|
||||
beforeSend: function () {
|
||||
$(".errorlist").remove();
|
||||
},
|
||||
success: function (response) {
|
||||
$("#id_task_id").html("<option>---------</option>");
|
||||
for (let i = 0; i < response.data.length; i++) {
|
||||
const element = response.data[i];
|
||||
$("#id_task_id").append("<option value="+ element.id + ">"+element.title+"</option>");
|
||||
}
|
||||
$("#id_task_id").append( "<option value='create_new_task'>Create a new task</option>");
|
||||
},
|
||||
});
|
||||
} else {
|
||||
$("#id_task_id").html("<option>---------</option>");
|
||||
}
|
||||
});
|
||||
|
||||
$("#id_task_id").change(function (e) {
|
||||
var task_id = $(this).val();
|
||||
var createTaskUrl = "{% url 'create-task-time-sheet' %}";
|
||||
if (task_id === "create_new_task") {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: createTaskUrl,
|
||||
data: { project_id: project_id },
|
||||
success: function (response) {
|
||||
$("#TaskModal").addClass("oh-modal--show");
|
||||
$("#TaskFormTarget").html(response);
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock content %}
|
||||
{% block content %} {% load static %} {% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Time Sheet" %}</h5>
|
||||
<br />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#TimeSheetFormTarget"
|
||||
onclick="location.reload()"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<form
|
||||
hx-post="{% url 'create-time-sheet' %}"
|
||||
hx-target="#TimeSheetFormTarget"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %} {{form.as_p}}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectModal"
|
||||
role="dialog"
|
||||
aria-labelledby="ProjectModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="ProjectFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="TaskModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TaskModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="TaskFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="ProjectStageModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TaskModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="ProjectStageFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
var project_id;
|
||||
$("#id_task_id").html("<option>---------</option>");
|
||||
$("#id_project").change(function (e) {
|
||||
project_id = $(this).val(); // Set the project_id value here
|
||||
var createProjectUrl = "{% url 'create-project-time-sheet' %}";
|
||||
if (project_id === "create_new_project") {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: createProjectUrl,
|
||||
success: function (response) {
|
||||
$("#ProjectModal").addClass("oh-modal--show");
|
||||
$("#ProjectFormTarget").html(response);
|
||||
},
|
||||
});0
|
||||
}
|
||||
if (project_id != "create_new_project") {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "{% url 'time-sheet-initial' %}",
|
||||
data: { project_id: project_id },
|
||||
beforeSend: function () {
|
||||
$(".errorlist").remove();
|
||||
},
|
||||
success: function (response) {
|
||||
$("#id_task_id").html("<option>---------</option>");
|
||||
for (let i = 0; i < response.data.length; i++) {
|
||||
const element = response.data[i];
|
||||
$("#id_task_id").append("<option value="+ element.id + ">"+element.title+"</option>");
|
||||
}
|
||||
$("#id_task_id").append( "<option value='create_new_task'>Create a new task</option>");
|
||||
},
|
||||
});
|
||||
} else {
|
||||
$("#id_task_id").html("<option>---------</option>");
|
||||
}
|
||||
});
|
||||
|
||||
$("#id_task_id").change(function (e) {
|
||||
var task_id = $(this).val();
|
||||
var createTaskUrl = "{% url 'create-task-time-sheet' %}";
|
||||
if (task_id === "create_new_task") {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: createTaskUrl,
|
||||
data: { project_id: project_id },
|
||||
success: function (response) {
|
||||
$("#TaskModal").addClass("oh-modal--show");
|
||||
$("#TaskFormTarget").html(response);
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
-webkit-transition: all 0.3s ease-in-out;
|
||||
transition: all 0.3s ease-in-out;"
|
||||
onclick="event.stopPropagation(); $(this).parents().find('.oh-modal--show:last').toggleClass('oh-modal--show')"
|
||||
id="close1"
|
||||
|
||||
id="close1"
|
||||
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,149 +1,149 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load basefilters %}
|
||||
{% if messages %}
|
||||
<div class="oh-alert-container">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load basefilters %}
|
||||
{% if messages %}
|
||||
<div class="oh-alert-container">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "filter_tags.html" %}
|
||||
{% comment %} easy filters {% endcomment %}
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<span class="m-3 draft" hx-get="{% url 'filter-time-sheet' %}?{{pd}}&status=in_progress&view=card" hx-target="#TimeSheetList" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:Orange"></span>
|
||||
{% trans "In progress" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'filter-time-sheet' %}?{{pd}}&status=completed&view=card" hx-target="#TimeSheetList" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:DodgerBlue"></span>
|
||||
{% trans "Completed" %}
|
||||
</span>
|
||||
</div>
|
||||
{% if time_sheets %}
|
||||
<div class="oh-layout--grid-3">
|
||||
{% for time_sheet in time_sheets %}
|
||||
<div class="oh-kanban-card
|
||||
{% if time_sheet.status == 'in_Progress' %} in-progress-time-sheet
|
||||
{% else %} completed-time-sheet
|
||||
{% endif %}"
|
||||
style="color: inherit;text-decoration: none;">
|
||||
<a data-toggle="oh-modal-toggle"
|
||||
data-target="#TimeSheetModal"
|
||||
hx-get="{% url 'view-single-time-sheet' time_sheet.id %}"
|
||||
hx-target="#TimeSheetFormTarget"
|
||||
style="color: inherit;text-decoration: none; display: flex;">
|
||||
<div class="oh-kanban-card__avatar">
|
||||
<div class="oh-kanban-card__profile-container">
|
||||
<img
|
||||
src="{{time_sheet.employee_id.get_avatar}}"
|
||||
class="oh-kanban-card__profile-image"
|
||||
alt="Username"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-kanban-card__details">
|
||||
<span class="oh-kanban-card__title">{{time_sheet.employee_id}}</span>
|
||||
<span class="oh-kanban-card__subtitle">{{time_sheet.date}}</span><br>
|
||||
<span class="oh-kanban-card__subtitle"><b>{{time_sheet.project_id}}</b></span><br>
|
||||
<span class="oh-kanban-card__subtitle"><b>{{time_sheet.task_id}}</b> | </span>
|
||||
<span class="oh-kanban-card__subtitle">{% trans "Time Spent" %} : <b>{{time_sheet.time_spent}}</b></span>
|
||||
</div>
|
||||
</a>
|
||||
<div class="oh-kanban-card__dots">
|
||||
<div class="oh-dropdown" x-data="{show: false}">
|
||||
<button class="oh-btn oh-btn--transparent text-muted p-3" @click="show = !show">
|
||||
<ion-icon name="ellipsis-vertical-sharp" role="img" class="md hydrated" aria-label="ellipsis vertical sharp"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--dark-border oh-dropdown__menu--right" x-show="show" @click.outside="show = false" style="display: none;">
|
||||
<ul class="oh-dropdown__items">
|
||||
<li class="oh-dropdown__item">
|
||||
<a hx-get="{% url 'update-time-sheet' time_sheet.id %}?view=card" hx-target="#TimeSheetFormTarget" class="oh-dropdown__link" data-toggle="oh-modal-toggle" data-target="#TimeSheetModal">{% trans "Edit" %}</a>
|
||||
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<form action="{% url 'delete-time-sheet' time_sheet.id %}?view=card" method="post" onsubmit="return confirm('{% trans "Do you want to delete this employee?" %}')">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="oh-dropdown__link oh-dropdown__link--danger">{% trans "Delete" %}</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="oh-pagination">
|
||||
<span class="oh-pagination__page">
|
||||
{% trans "Page" %} {{ time_sheets.number }} {% trans "of" %} {{ time_sheets.paginator.num_pages }}.
|
||||
</span>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{time_sheets.number}}"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&view=card"
|
||||
hx-target="#TimeSheetList"
|
||||
min="1"
|
||||
/>
|
||||
|
||||
<span class="oh-pagination__label"
|
||||
>{% trans "of" %} {{time_sheet.paginator.num_pages}}</span
|
||||
>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if time_sheets.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#TimeSheetList"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&page=1&view=card"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "First" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#TimeSheetList"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&page={{ time_sheets.previous_page_number }}&view=card"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Previous" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %} {% if time_sheets.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#TimeSheetList"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&page={{ time_sheets.next_page_number }}&view=card"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Next" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#TimeSheetList"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&page={{ time_sheets.paginator.num_pages }}&view=card"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Last" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 200px;margin: 10px auto ;" src="{% static 'images/ui/project/timesheet.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">{% trans "There are currently no available timesheets; please create a new one." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "filter_tags.html" %}
|
||||
{% comment %} easy filters {% endcomment %}
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<span class="m-3 draft" hx-get="{% url 'filter-time-sheet' %}?{{pd}}&status=in_progress&view=card" hx-target="#TimeSheetList" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:Orange"></span>
|
||||
{% trans "In progress" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'filter-time-sheet' %}?{{pd}}&status=completed&view=card" hx-target="#TimeSheetList" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:DodgerBlue"></span>
|
||||
{% trans "Completed" %}
|
||||
</span>
|
||||
</div>
|
||||
{% if time_sheets %}
|
||||
<div class="oh-layout--grid-3">
|
||||
{% for time_sheet in time_sheets %}
|
||||
<div class="oh-kanban-card
|
||||
{% if time_sheet.status == 'in_Progress' %} in-progress-time-sheet
|
||||
{% else %} completed-time-sheet
|
||||
{% endif %}"
|
||||
style="color: inherit;text-decoration: none;">
|
||||
<a data-toggle="oh-modal-toggle"
|
||||
data-target="#TimeSheetModal"
|
||||
hx-get="{% url 'view-single-time-sheet' time_sheet.id %}"
|
||||
hx-target="#TimeSheetFormTarget"
|
||||
style="color: inherit;text-decoration: none; display: flex;">
|
||||
<div class="oh-kanban-card__avatar">
|
||||
<div class="oh-kanban-card__profile-container">
|
||||
<img
|
||||
src="{{time_sheet.employee_id.get_avatar}}"
|
||||
class="oh-kanban-card__profile-image"
|
||||
alt="Username"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-kanban-card__details">
|
||||
<span class="oh-kanban-card__title">{{time_sheet.employee_id}}</span>
|
||||
<span class="oh-kanban-card__subtitle">{{time_sheet.date}}</span><br>
|
||||
<span class="oh-kanban-card__subtitle"><b>{{time_sheet.project_id}}</b></span><br>
|
||||
<span class="oh-kanban-card__subtitle"><b>{{time_sheet.task_id}}</b> | </span>
|
||||
<span class="oh-kanban-card__subtitle">{% trans "Time Spent" %} : <b>{{time_sheet.time_spent}}</b></span>
|
||||
</div>
|
||||
</a>
|
||||
<div class="oh-kanban-card__dots">
|
||||
<div class="oh-dropdown" x-data="{show: false}">
|
||||
<button class="oh-btn oh-btn--transparent text-muted p-3" @click="show = !show">
|
||||
<ion-icon name="ellipsis-vertical-sharp" role="img" class="md hydrated" aria-label="ellipsis vertical sharp"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--dark-border oh-dropdown__menu--right" x-show="show" @click.outside="show = false" style="display: none;">
|
||||
<ul class="oh-dropdown__items">
|
||||
<li class="oh-dropdown__item">
|
||||
<a hx-get="{% url 'update-time-sheet' time_sheet.id %}?view=card" hx-target="#TimeSheetFormTarget" class="oh-dropdown__link" data-toggle="oh-modal-toggle" data-target="#TimeSheetModal">{% trans "Edit" %}</a>
|
||||
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<form action="{% url 'delete-time-sheet' time_sheet.id %}?view=card" method="post" onsubmit="return confirm('{% trans "Do you want to delete this employee?" %}')">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="oh-dropdown__link oh-dropdown__link--danger">{% trans "Delete" %}</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="oh-pagination">
|
||||
<span class="oh-pagination__page">
|
||||
{% trans "Page" %} {{ time_sheets.number }} {% trans "of" %} {{ time_sheets.paginator.num_pages }}.
|
||||
</span>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{time_sheets.number}}"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&view=card"
|
||||
hx-target="#TimeSheetList"
|
||||
min="1"
|
||||
/>
|
||||
|
||||
<span class="oh-pagination__label"
|
||||
>{% trans "of" %} {{time_sheet.paginator.num_pages}}</span
|
||||
>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if time_sheets.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#TimeSheetList"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&page=1&view=card"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "First" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#TimeSheetList"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&page={{ time_sheets.previous_page_number }}&view=card"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Previous" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %} {% if time_sheets.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#TimeSheetList"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&page={{ time_sheets.next_page_number }}&view=card"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Next" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#TimeSheetList"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&page={{ time_sheets.paginator.num_pages }}&view=card"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Last" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 200px;margin: 10px auto ;" src="{% static 'images/ui/project/timesheet.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">{% trans "There are currently no available timesheets; please create a new one." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -1,188 +1,188 @@
|
||||
{% load i18n %} {% load yes_no %} {% load static %}
|
||||
{% include "filter_tags.html" %}
|
||||
|
||||
{% if time_sheets %}
|
||||
<div class="oh-tabs__content oh-tabs__content--active" style="padding-top: 10px;">
|
||||
{% comment %} easy filters {% endcomment %}
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<span class="m-3 draft" hx-get="{% url 'filter-time-sheet' %}?{{pd}}&status=in_progress&view=list" hx-target="#TimeSheetList" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:Orange"></span>
|
||||
{% trans "In progress" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'filter-time-sheet' %}?{{pd}}&status=completed&view=list" hx-target="#TimeSheetList" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:DodgerBlue"></span>
|
||||
{% trans "Completed" %}
|
||||
</span>
|
||||
</div>
|
||||
{% comment %} table of contents {% endcomment %}
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr ">
|
||||
<div class="oh-sticky-table__th">
|
||||
<div class="d-flex">
|
||||
<div class="">
|
||||
<input type="checkbox" class="oh-input oh-input__checkbox mt-1 mr-2 all-time-sheet" />
|
||||
</div>
|
||||
<div>{% trans "Employee" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% comment %}
|
||||
<div class="oh-sticky-table__th">{% trans "Employee" %}</div>
|
||||
{% endcomment %}
|
||||
<div class="oh-sticky-table__th">{% trans "Project" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Task" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Time Spent" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Status" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Description" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Actions" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% for time_sheet in time_sheets %}
|
||||
<div class="oh-sticky-table__tr ui-sortable-handle"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TimeSheetModal"
|
||||
hx-get="{% url 'view-single-time-sheet' time_sheet.id %}"
|
||||
hx-target="#TimeSheetFormTarget"
|
||||
>
|
||||
<div class="oh-sticky-table__sd {% if time_sheet.status == 'in_Progress' %} in-progress-time-sheet
|
||||
{% else %} completed-time-sheet
|
||||
{% endif %} ">
|
||||
<div class="d-flex">
|
||||
<div onclick="event.stopPropagation();">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="{{time_sheet.id}}"
|
||||
class="oh-input candidate-checkbox oh-input__checkbox mt-2 mr-2 all-time-sheet-row"
|
||||
/>
|
||||
</div>
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="{{time_sheet.employee_id.get_avatar}}"
|
||||
class="oh-kanban-card__profile-image"
|
||||
alt="Username"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{time_sheet.employee_id.employee_first_name}}
|
||||
{{time_sheet.employee_id.employee_last_name|default:""}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.project_id.title}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.task_id}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.date}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.time_spent}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.get_status_display}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.description|truncatechars:15}}</div>
|
||||
<div class="oh-sticky-table__td" onclick="event.stopPropagation();">
|
||||
<div class="oh-btn-group " >
|
||||
<a
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TimeSheetModal"
|
||||
hx-get="{% url 'update-time-sheet' time_sheet.id %}"
|
||||
hx-target="#TimeSheetFormTarget"
|
||||
|
||||
>
|
||||
<ion-icon
|
||||
name="create-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
style="color: blue"
|
||||
aria-label="create outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
<a
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
href="{% url 'delete-time-sheet' time_sheet.id %}"
|
||||
onclick="event.preventDefault();event.stopPropagation(); confirm(`{% trans 'Do you want to delete this time sheet?' %}`)"
|
||||
>
|
||||
<ion-icon
|
||||
name="trash-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="trash outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-pagination">
|
||||
<span class="oh-pagination__page">
|
||||
{% trans "Page" %} {{ time_sheets.number }} {% trans "of" %} {{ time_sheets.paginator.num_pages }}.
|
||||
</span>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{time_sheets.number}}"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}"
|
||||
hx-target="#TimeSheetList"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label"
|
||||
>{% trans "of" %} {{time_sheet.paginator.num_pages}}</span
|
||||
>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if time_sheets.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#TimeSheetList"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&page=1"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "First" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#TimeSheetList"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&page={{ time_sheets.previous_page_number }}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Previous" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %} {% if time_sheets.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#TimeSheetList"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&page={{ time_sheets.next_page_number }}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Next" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#TimeSheetList"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&page={{ time_sheets.paginator.num_pages }}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Last" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 200px;margin: 10px auto ;" src="{% static 'images/ui/project/timesheet.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">{% trans "There are currently no available timesheets; please create a new one." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script src="{% static '/time_sheet/time_sheet_action.js' %}"></script>
|
||||
{% load i18n %} {% load horillafilters %} {% load static %}
|
||||
{% include "filter_tags.html" %}
|
||||
|
||||
{% if time_sheets %}
|
||||
<div class="oh-tabs__content oh-tabs__content--active" style="padding-top: 10px;">
|
||||
{% comment %} easy filters {% endcomment %}
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<span class="m-3 draft" hx-get="{% url 'filter-time-sheet' %}?{{pd}}&status=in_progress&view=list" hx-target="#TimeSheetList" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:Orange"></span>
|
||||
{% trans "In progress" %}
|
||||
</span>
|
||||
<span class="m-3 draft" hx-get="{% url 'filter-time-sheet' %}?{{pd}}&status=completed&view=list" hx-target="#TimeSheetList" style="cursor: pointer">
|
||||
<span class="oh-dot oh-dot--small me-1" style="background-color:DodgerBlue"></span>
|
||||
{% trans "Completed" %}
|
||||
</span>
|
||||
</div>
|
||||
{% comment %} table of contents {% endcomment %}
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr ">
|
||||
<div class="oh-sticky-table__th">
|
||||
<div class="d-flex">
|
||||
<div class="">
|
||||
<input type="checkbox" class="oh-input oh-input__checkbox mt-1 mr-2 all-time-sheet" />
|
||||
</div>
|
||||
<div>{% trans "Employee" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% comment %}
|
||||
<div class="oh-sticky-table__th">{% trans "Employee" %}</div>
|
||||
{% endcomment %}
|
||||
<div class="oh-sticky-table__th">{% trans "Project" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Task" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Time Spent" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Status" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Description" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Actions" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% for time_sheet in time_sheets %}
|
||||
<div class="oh-sticky-table__tr ui-sortable-handle"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TimeSheetModal"
|
||||
hx-get="{% url 'view-single-time-sheet' time_sheet.id %}"
|
||||
hx-target="#TimeSheetFormTarget"
|
||||
>
|
||||
<div class="oh-sticky-table__sd {% if time_sheet.status == 'in_Progress' %} in-progress-time-sheet
|
||||
{% else %} completed-time-sheet
|
||||
{% endif %} ">
|
||||
<div class="d-flex">
|
||||
<div onclick="event.stopPropagation();">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="{{time_sheet.id}}"
|
||||
class="oh-input candidate-checkbox oh-input__checkbox mt-2 mr-2 all-time-sheet-row"
|
||||
/>
|
||||
</div>
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="{{time_sheet.employee_id.get_avatar}}"
|
||||
class="oh-kanban-card__profile-image"
|
||||
alt="Username"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{time_sheet.employee_id.employee_first_name}}
|
||||
{{time_sheet.employee_id.employee_last_name|default:""}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.project_id.title}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.task_id}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.date}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.time_spent}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.get_status_display}}</div>
|
||||
<div class="oh-sticky-table__td">{{time_sheet.description|truncatechars:15}}</div>
|
||||
<div class="oh-sticky-table__td" onclick="event.stopPropagation();">
|
||||
<div class="oh-btn-group " >
|
||||
<a
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TimeSheetModal"
|
||||
hx-get="{% url 'update-time-sheet' time_sheet.id %}"
|
||||
hx-target="#TimeSheetFormTarget"
|
||||
|
||||
>
|
||||
<ion-icon
|
||||
name="create-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
style="color: blue"
|
||||
aria-label="create outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
<a
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
href="{% url 'delete-time-sheet' time_sheet.id %}"
|
||||
onclick="event.preventDefault();event.stopPropagation(); confirm(`{% trans 'Do you want to delete this time sheet?' %}`)"
|
||||
>
|
||||
<ion-icon
|
||||
name="trash-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="trash outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-pagination">
|
||||
<span class="oh-pagination__page">
|
||||
{% trans "Page" %} {{ time_sheets.number }} {% trans "of" %} {{ time_sheets.paginator.num_pages }}.
|
||||
</span>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{time_sheets.number}}"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}"
|
||||
hx-target="#TimeSheetList"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label"
|
||||
>{% trans "of" %} {{time_sheet.paginator.num_pages}}</span
|
||||
>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if time_sheets.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#TimeSheetList"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&page=1"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "First" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#TimeSheetList"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&page={{ time_sheets.previous_page_number }}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Previous" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %} {% if time_sheets.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#TimeSheetList"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&page={{ time_sheets.next_page_number }}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Next" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#TimeSheetList"
|
||||
hx-get="{% url 'filter-time-sheet' %}?{{pd}}&page={{ time_sheets.paginator.num_pages }}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Last" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
|
||||
<div style="" class="">
|
||||
<img style="display: block;width: 200px;margin: 10px auto ;" src="{% static 'images/ui/project/timesheet.png' %}" class="" alt="Page not found. 404."/>
|
||||
<h3 style="font-size:20px" class="oh-404__subtitle">{% trans "There are currently no available timesheets; please create a new one." %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script src="{% static '/time_sheet/time_sheet_action.js' %}"></script>
|
||||
|
||||
@@ -1,144 +1,144 @@
|
||||
{% load i18n %}
|
||||
{% load basefilters %}
|
||||
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">{% trans "Time Sheet" %}</h1>
|
||||
<a
|
||||
class="oh-main__titlebar-search-toggle"
|
||||
role="button"
|
||||
aria-label="Toggle Search"
|
||||
@click="searchShow = !searchShow"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-main__titlebar-serach-icon md hydrated"
|
||||
role="img"
|
||||
aria-label="search outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
{% if perms.project.view_timesheet or request.user|is_reportingmanager %}
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right">
|
||||
<div
|
||||
class="oh-input-group oh-input__search-group"
|
||||
:class="searchShow ? 'oh-input__search-group--show' : ''"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-input-group__icon oh-input-group__icon--left"
|
||||
></ion-icon>
|
||||
<input
|
||||
type="text"
|
||||
class="oh-input oh-input__icon"
|
||||
aria-label="Search Input"
|
||||
id="filter-time-sheet"
|
||||
name="search"
|
||||
placeholder="{% trans 'Search' %}"
|
||||
hx-get="{% url 'filter-time-sheet' %}"
|
||||
hx-trigger="keyup changed delay:500ms, search"
|
||||
hx-target="#TimeSheetList"
|
||||
hx-swap="innerHTML"
|
||||
/>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<ul class="oh-view-types ml-2">
|
||||
<li class="oh-view-type time-sheet-view-type" data-view="list">
|
||||
<a
|
||||
hx-get="{% url 'filter-time-sheet' %}?view=list"
|
||||
hx-target="#TimeSheetList"
|
||||
class="oh-btn oh-btn--view"
|
||||
><ion-icon name="list-outline"></ion-icon
|
||||
></a>
|
||||
</li>
|
||||
<li class="oh-view-type time-sheet-view-type" data-view="card">
|
||||
<a
|
||||
hx-get="{% url 'filter-time-sheet' %}?view=card"
|
||||
hx-target="#TimeSheetList"
|
||||
class="oh-btn oh-btn--view"
|
||||
><ion-icon name="grid-outline"></ion-icon
|
||||
></a>
|
||||
</li>
|
||||
<li class="oh-view-type time-sheet-view-type">
|
||||
<a
|
||||
href="{% url 'personal-time-sheet-view' request.user.employee_get.id %}"
|
||||
|
||||
class="oh-btn oh-btn--view"
|
||||
><ion-icon name="bar-chart"></ion-icon
|
||||
></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn ml-2" @click="open = !open">
|
||||
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
|
||||
x-show="open"
|
||||
@click.outside="open = false"
|
||||
style="display: none;"
|
||||
>
|
||||
|
||||
{% include 'time_sheet/filters.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{% comment %} for actions {% endcomment %}
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button
|
||||
class="oh-btn oh-btn--dropdown oh-btn oh-btn--shadow"
|
||||
@click="open = !open"
|
||||
@click.outside="open = false"
|
||||
>
|
||||
{% trans "Actions" %}
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open" style="display: none;">
|
||||
<ul class="oh-dropdown__items">
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
{% comment %} <li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link " id="archiveEmployees"
|
||||
>{% trans "Archive" %}</a
|
||||
>
|
||||
</li> {% endcomment %}
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
{% comment %} <li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link " id="unArchiveEmployees"
|
||||
>{% trans "Un-Archive" %}</a
|
||||
>
|
||||
</li> {% endcomment %}
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a
|
||||
href="#"
|
||||
class="oh-dropdown__link oh-dropdown__link--danger"
|
||||
id="deleteTimeSheet"
|
||||
>{% trans "Delete" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div>
|
||||
<a
|
||||
class="oh-btn oh-btn--secondary oh-btn--shadow"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TimeSheetModal"
|
||||
hx-get="{% url 'create-time-sheet' %}"
|
||||
hx-target="#TimeSheetFormTarget"
|
||||
>
|
||||
<ion-icon class="me-2" name="add-outline"></ion-icon>{% trans "Create" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% load i18n %}
|
||||
{% load basefilters %}
|
||||
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">{% trans "Time Sheet" %}</h1>
|
||||
<a
|
||||
class="oh-main__titlebar-search-toggle"
|
||||
role="button"
|
||||
aria-label="Toggle Search"
|
||||
@click="searchShow = !searchShow"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-main__titlebar-serach-icon md hydrated"
|
||||
role="img"
|
||||
aria-label="search outline"
|
||||
></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
{% if perms.project.view_timesheet or request.user|is_reportingmanager %}
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right">
|
||||
<div
|
||||
class="oh-input-group oh-input__search-group"
|
||||
:class="searchShow ? 'oh-input__search-group--show' : ''"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-input-group__icon oh-input-group__icon--left"
|
||||
></ion-icon>
|
||||
<input
|
||||
type="text"
|
||||
class="oh-input oh-input__icon"
|
||||
aria-label="Search Input"
|
||||
id="filter-time-sheet"
|
||||
name="search"
|
||||
placeholder="{% trans 'Search' %}"
|
||||
hx-get="{% url 'filter-time-sheet' %}"
|
||||
hx-trigger="keyup changed delay:500ms, search"
|
||||
hx-target="#TimeSheetList"
|
||||
hx-swap="innerHTML"
|
||||
/>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<ul class="oh-view-types ml-2">
|
||||
<li class="oh-view-type time-sheet-view-type" data-view="list">
|
||||
<a
|
||||
hx-get="{% url 'filter-time-sheet' %}?view=list"
|
||||
hx-target="#TimeSheetList"
|
||||
class="oh-btn oh-btn--view"
|
||||
><ion-icon name="list-outline"></ion-icon
|
||||
></a>
|
||||
</li>
|
||||
<li class="oh-view-type time-sheet-view-type" data-view="card">
|
||||
<a
|
||||
hx-get="{% url 'filter-time-sheet' %}?view=card"
|
||||
hx-target="#TimeSheetList"
|
||||
class="oh-btn oh-btn--view"
|
||||
><ion-icon name="grid-outline"></ion-icon
|
||||
></a>
|
||||
</li>
|
||||
<li class="oh-view-type time-sheet-view-type">
|
||||
<a
|
||||
href="{% url 'personal-time-sheet-view' request.user.employee_get.id %}"
|
||||
|
||||
class="oh-btn oh-btn--view"
|
||||
><ion-icon name="bar-chart"></ion-icon
|
||||
></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn ml-2" @click="open = !open">
|
||||
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
|
||||
x-show="open"
|
||||
@click.outside="open = false"
|
||||
style="display: none;"
|
||||
>
|
||||
|
||||
{% include 'time_sheet/filters.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{% comment %} for actions {% endcomment %}
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button
|
||||
class="oh-btn oh-btn--dropdown oh-btn oh-btn--shadow"
|
||||
@click="open = !open"
|
||||
@click.outside="open = false"
|
||||
>
|
||||
{% trans "Actions" %}
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open" style="display: none;">
|
||||
<ul class="oh-dropdown__items">
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
{% comment %} <li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link " id="archiveEmployees"
|
||||
>{% trans "Archive" %}</a
|
||||
>
|
||||
</li> {% endcomment %}
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
{% comment %} <li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link " id="unArchiveEmployees"
|
||||
>{% trans "Un-Archive" %}</a
|
||||
>
|
||||
</li> {% endcomment %}
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
{% comment %} {% if perms.delete_employee %} {% endcomment %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a
|
||||
href="#"
|
||||
class="oh-dropdown__link oh-dropdown__link--danger"
|
||||
id="deleteTimeSheet"
|
||||
>{% trans "Delete" %}</a
|
||||
>
|
||||
</li>
|
||||
{% comment %} {% endif %} {% endcomment %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div>
|
||||
<a
|
||||
class="oh-btn oh-btn--secondary oh-btn--shadow"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TimeSheetModal"
|
||||
hx-get="{% url 'create-time-sheet' %}"
|
||||
hx-target="#TimeSheetFormTarget"
|
||||
>
|
||||
<ion-icon class="me-2" name="add-outline"></ion-icon>{% trans "Create" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,94 +1,92 @@
|
||||
{% load i18n %} {% load yes_no %} {% load basefilters %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5 style="margin-bottom: 20px;">Timesheet Details</h5>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#OneContractTarget"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-timeoff-modal__stats-container mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Employee" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{time_sheet.employee_id}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Project" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{time_sheet.project_id}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Task" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{time_sheet.task_id}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Date" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{time_sheet.date}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Time Spent" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{time_sheet.time_spent}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Status" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{time_sheet.status}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 mb-5" style="width:100%;">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Description" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{time_sheet.description}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-modal__button-container text-center">
|
||||
<div class="oh-btn-group">
|
||||
<a hx-get="{% url 'update-time-sheet' time_sheet.id %}"
|
||||
hx-target="#TimeSheetFormTarget"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TimeSheetModal"
|
||||
class="oh-btn oh-btn--warning"
|
||||
style="{% if perms.project.view_timesheet or request.user|is_reportingmanager %} width: 25%;{% else %}width: 50%;{% endif %}">
|
||||
<ion-icon
|
||||
name="create-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="create outline"
|
||||
></ion-icon>{% trans "Edit" %}
|
||||
</a>
|
||||
{% if perms.project.view_timesheet or request.user|is_reportingmanager %}
|
||||
<a href="{% url 'personal-time-sheet-view' time_sheet.employee_id.id %}"
|
||||
class="oh-btn oh-btn--info me-1 ms-1"
|
||||
style="width: 48%;">
|
||||
<ion-icon
|
||||
name="bar-chart"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="create outline"
|
||||
></ion-icon>{% trans "View Timesheet Chart" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'delete-time-sheet' time_sheet.id %}"
|
||||
class="oh-btn oh-btn--danger"
|
||||
style="{% if perms.project.view_timesheet or request.user|is_reportingmanager %} width: 25%;{% else %}width: 50%;{% endif %}"
|
||||
onclick="event.preventDefault();event.stopPropagation(); confirm(`{% trans 'Do you want to delete this project?' %}`)">
|
||||
<ion-icon
|
||||
name="trash-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="create outline"
|
||||
></ion-icon>{% trans "Delete" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% load i18n %} {% load horillafilters %} {% load basefilters %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5 style="margin-bottom: 20px;">Timesheet Details</h5>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
data-toggle="oh-modal-toggle"
|
||||
hx-target="#OneContractTarget"
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-timeoff-modal__stats-container mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Employee" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{time_sheet.employee_id}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Project" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{time_sheet.project_id}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Task" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{time_sheet.task_id}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Date" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{time_sheet.date}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3 mb-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Time Spent" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{time_sheet.time_spent}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Status" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{time_sheet.status}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 mb-5" style="width:100%;">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Description" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{time_sheet.description}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-modal__button-container text-center">
|
||||
<div class="oh-btn-group">
|
||||
<a hx-get="{% url 'update-time-sheet' time_sheet.id %}"
|
||||
hx-target="#TimeSheetFormTarget"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#TimeSheetModal"
|
||||
class="oh-btn oh-btn--warning"
|
||||
style="{% if perms.project.view_timesheet or request.user|is_reportingmanager %} width: 25%;{% else %}width: 50%;{% endif %}">
|
||||
<ion-icon
|
||||
name="create-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="create outline"
|
||||
></ion-icon>{% trans "Edit" %}
|
||||
</a>
|
||||
{% if perms.project.view_timesheet or request.user|is_reportingmanager %}
|
||||
<a href="{% url 'personal-time-sheet-view' time_sheet.employee_id.id %}"
|
||||
class="oh-btn oh-btn--info me-1 ms-1"
|
||||
style="width: 48%;">
|
||||
<ion-icon
|
||||
name="bar-chart"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="create outline"
|
||||
></ion-icon>{% trans "View Timesheet Chart" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'delete-time-sheet' time_sheet.id %}"
|
||||
class="oh-btn oh-btn--danger"
|
||||
style="{% if perms.project.view_timesheet or request.user|is_reportingmanager %} width: 25%;{% else %}width: 50%;{% endif %}"
|
||||
onclick="event.preventDefault();event.stopPropagation(); confirm(`{% trans 'Do you want to delete this project?' %}`)">
|
||||
<ion-icon
|
||||
name="trash-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="create outline"
|
||||
></ion-icon>{% trans "Delete" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,65 +1,65 @@
|
||||
{% extends 'index.html' %}
|
||||
{% block content %}
|
||||
{% load i18n %}
|
||||
{% load basefilters %}
|
||||
<style>
|
||||
.in-progress-time-sheet{
|
||||
border-left: solid 5px Orange !important;
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
.completed-time-sheet{
|
||||
border-left: solid 5px DodgerBlue !important;
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
</style>
|
||||
<main :class="sidebarOpen ? 'oh-main__sidebar-visible' : ''">
|
||||
{% include 'time_sheet/time_sheet_navbar.html' %}
|
||||
|
||||
<div id="TimeSheetList" class="oh-wrapper">
|
||||
{% if view_type == "card" %}
|
||||
{% include 'time_sheet/time_sheet_card_view.html' %}
|
||||
{% else %}
|
||||
{% include 'time_sheet/time_sheet_list_view.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="TimeSheetModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TimeSheetModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="TimeSheetFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$("#filter-time-sheet").keyup(function (e) {
|
||||
$(".time-sheet-view-type").attr("hx-vals", `{"search":"${$(this).val()}"}`);
|
||||
});
|
||||
$(".time-sheet-view-type").click(function (e) {
|
||||
let view = $(this).data("view");
|
||||
var currentURL = window.location.href;
|
||||
if (view != undefined){
|
||||
if (/\?view=[^&]+/.test(currentURL)) {
|
||||
newURL = currentURL.replace(/\?view=[^&]+/, "?view="+view);
|
||||
}
|
||||
else {
|
||||
var separator = currentURL.includes('?') ? '&' : '?';
|
||||
newURL = currentURL + separator + "view="+view;
|
||||
}
|
||||
|
||||
history.pushState({}, "", newURL);
|
||||
$("#filter-time-sheet").attr("hx-vals", `{"view":"${view}"}`);
|
||||
$('#timesheetForm').attr("hx-vals", `{"view":"${view}"}`);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock content %}
|
||||
{% extends 'index.html' %}
|
||||
{% block content %}
|
||||
{% load i18n %}
|
||||
{% load basefilters %}
|
||||
<style>
|
||||
.in-progress-time-sheet{
|
||||
border-left: solid 5px Orange !important;
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
.completed-time-sheet{
|
||||
border-left: solid 5px DodgerBlue !important;
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
</style>
|
||||
<main :class="sidebarOpen ? 'oh-main__sidebar-visible' : ''">
|
||||
{% include 'time_sheet/time_sheet_navbar.html' %}
|
||||
|
||||
<div id="TimeSheetList" class="oh-wrapper">
|
||||
{% if view_type == "card" %}
|
||||
{% include 'time_sheet/time_sheet_card_view.html' %}
|
||||
{% else %}
|
||||
{% include 'time_sheet/time_sheet_list_view.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="TimeSheetModal"
|
||||
role="dialog"
|
||||
aria-labelledby="TimeSheetModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
style="max-width: 550px"
|
||||
id="TimeSheetFormTarget"
|
||||
></div>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$("#filter-time-sheet").keyup(function (e) {
|
||||
$(".time-sheet-view-type").attr("hx-vals", `{"search":"${$(this).val()}"}`);
|
||||
});
|
||||
$(".time-sheet-view-type").click(function (e) {
|
||||
let view = $(this).data("view");
|
||||
var currentURL = window.location.href;
|
||||
if (view != undefined){
|
||||
if (/\?view=[^&]+/.test(currentURL)) {
|
||||
newURL = currentURL.replace(/\?view=[^&]+/, "?view="+view);
|
||||
}
|
||||
else {
|
||||
var separator = currentURL.includes('?') ? '&' : '?';
|
||||
newURL = currentURL + separator + "view="+view;
|
||||
}
|
||||
|
||||
history.pushState({}, "", newURL);
|
||||
$("#filter-time-sheet").attr("hx-vals", `{"view":"${view}"}`);
|
||||
$('#timesheetForm').attr("hx-vals", `{"view":"${view}"}`);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock content %}
|
||||
|
||||
75
project/templatetags/taskfilters.py
Normal file
75
project/templatetags/taskfilters.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
This module is used to write custom template filters.
|
||||
"""
|
||||
from django.template.defaultfilters import register
|
||||
|
||||
from project.models import Project
|
||||
|
||||
@register.filter(name="task_crud_perm")
|
||||
def task_crud_perm(user,task):
|
||||
"""
|
||||
This method is used to check the requested user is task manager or project manager or has permission
|
||||
"""
|
||||
try:
|
||||
employee = user.employee_get
|
||||
is_task_manager = (employee in task.task_managers.all())
|
||||
is_project_manager = (employee in task.project.managers.all())
|
||||
return is_task_manager or is_project_manager
|
||||
|
||||
except Exception as _:
|
||||
return False
|
||||
|
||||
|
||||
@register.filter(name="time_sheet_crud_perm")
|
||||
def time_sheet_crud_perm(user,timesheet):
|
||||
"""
|
||||
This method is used to check the requested user is task manager or project manager or has permission
|
||||
"""
|
||||
try:
|
||||
employee = user.employee_get
|
||||
is_task_manager = (employee in timesheet.task_id.task_managers.all())
|
||||
is_project_manager = (employee in timesheet.project_id.managers.all())
|
||||
is_own_timesheet = (timesheet.employee_id == employee )
|
||||
|
||||
return is_task_manager or is_project_manager or is_own_timesheet
|
||||
|
||||
except Exception as _:
|
||||
return False
|
||||
|
||||
|
||||
@register.filter(name="is_project_manager_or_member")
|
||||
def is_project_manager_or_member(user,project):
|
||||
"""
|
||||
This method will return true, if the user is manger or member of the project
|
||||
"""
|
||||
employee = user.employee_get
|
||||
|
||||
return (
|
||||
Project.objects.filter(
|
||||
id=project.id, managers=employee
|
||||
).exists()
|
||||
or Project.objects.filter(
|
||||
id=project.id, members=employee
|
||||
).exists()
|
||||
)
|
||||
|
||||
|
||||
@register.filter(name="is_project_manager")
|
||||
def is_project_manager(user,project):
|
||||
"""
|
||||
This method will return true, if the user is manager of the project
|
||||
"""
|
||||
employee = user.employee_get
|
||||
return Project.objects.filter(id=project.id,managers=employee).exists()
|
||||
|
||||
@register.filter(name="is_task_manager")
|
||||
def is_task_manager(user, task):
|
||||
"""
|
||||
This method will return True if the user is a manager of the task.
|
||||
"""
|
||||
try:
|
||||
employee = user.employee_get
|
||||
return employee in task.task_managers.all()
|
||||
except AttributeError:
|
||||
# Handle cases where user or task might not have the expected structure
|
||||
return False
|
||||
@@ -1,3 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
315
project/urls.py
315
project/urls.py
@@ -1,86 +1,252 @@
|
||||
from django.urls import path
|
||||
|
||||
from project.cbv import dashboard, project_stage, projects, tasks, timesheet
|
||||
from project.models import Project
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
# Dashboard
|
||||
path('project-dashboard-view',views.dashboard_view,name='project-dashboard-view'),
|
||||
path('project-status-chart',views.project_status_chart,name='project-status-chart'),
|
||||
path('task-status-chart',views.task_status_chart,name='task-status-chart'),
|
||||
path("project-dashboard-view", views.dashboard_view, name="project-dashboard-view"),
|
||||
path(
|
||||
'project-detailed-view/<int:project_id>/',
|
||||
views.project_detailed_view,
|
||||
name='project-detailed-view'
|
||||
"projects-due-in-this-month",
|
||||
dashboard.ProjectsDueInMonth.as_view(),
|
||||
name="projects-due-in-this-month",
|
||||
),
|
||||
path(
|
||||
"project-status-chart", views.project_status_chart, name="project-status-chart"
|
||||
),
|
||||
path("task-status-chart", views.task_status_chart, name="task-status-chart"),
|
||||
# path(
|
||||
# "project-detailed-view/<int:project_id>/",
|
||||
# views.project_detailed_view,
|
||||
# name="project-detailed-view",
|
||||
# ),
|
||||
path(
|
||||
"project-detailed-view/<int:pk>/",
|
||||
dashboard.ProjectDetailView.as_view(),
|
||||
name="project-detailed-view",
|
||||
),
|
||||
|
||||
# Project
|
||||
path('project-view/',views.project_view,name='project-view'),
|
||||
path("create-project", views.create_project, name="create-project"),
|
||||
# path("project-view/", views.project_view, name="project-view"),
|
||||
path(
|
||||
"update-project/<int:project_id>/",
|
||||
views.project_update,
|
||||
name="update-project"
|
||||
"project-nav-view/", projects.ProjectsNavView.as_view(), name="project-nav-view"
|
||||
),
|
||||
path(
|
||||
"project-list-view/", projects.ProjectsList.as_view(), name="project-list-view"
|
||||
),
|
||||
path(
|
||||
"project-card-view/",
|
||||
projects.ProjectCardView.as_view(),
|
||||
name="project-card-view",
|
||||
),
|
||||
path("project-view/", projects.ProjectsView.as_view(), name="project-view"),
|
||||
# path("create-project", views.create_project, name="create-project"),
|
||||
path("create-project", projects.ProjectFormView.as_view(), name="create-project"),
|
||||
# path(
|
||||
# "update-project/<int:project_id>/",
|
||||
# views.project_update,
|
||||
# name="update-project"
|
||||
# ),
|
||||
path(
|
||||
"update-project/<int:pk>/",
|
||||
projects.ProjectFormView.as_view(),
|
||||
name="update-project",
|
||||
),
|
||||
path(
|
||||
"change-project-status/<int:project_id>/",
|
||||
views.change_project_status,
|
||||
name="change-project-status",
|
||||
),
|
||||
path(
|
||||
"delete-project/<int:project_id>/", views.project_delete, name="delete-project"
|
||||
),
|
||||
path("delete-project/<int:project_id>/", views.project_delete, name="delete-project"),
|
||||
path("project-filter", views.project_filter, name="project-filter"),
|
||||
path("project-import", views.project_import, name="project-import"),
|
||||
path("project-bulk-export",views.project_bulk_export,name="project-bulk-export"),
|
||||
path("project-bulk-archive",views.project_bulk_archive,name="project-bulk-archive"),
|
||||
path("project-bulk-delete",views.project_bulk_delete,name="project-bulk-delete"),
|
||||
path('project-archive/<int:project_id>/',views.project_archive,name='project-archive'),
|
||||
|
||||
|
||||
# Task
|
||||
path('task-view/<int:project_id>/',views.task_view,name='task-view',kwargs={"model":Project}),
|
||||
path('create-task/<int:stage_id>/',views.create_task,name='create-task'),
|
||||
path('create-task-in-project/<int:project_id>/',views.create_task_in_project,name='create-task-in-project'),
|
||||
path('update-task/<int:task_id>/',views.update_task,name='update-task'),
|
||||
path('delete-task/<int:task_id>/',views.delete_task,name='delete-task'),
|
||||
path('task-details/<int:task_id>/',views.task_details,name='task-details'),
|
||||
path('task-filter/<int:project_id>/',views.task_filter,name='task-filter'),
|
||||
path('task-stage-change',views.task_stage_change,name='task-stage-change'),
|
||||
path('task-timesheet/<int:task_id>/',views.task_timesheet,name='task-timesheet'),
|
||||
path("create-timesheet-task/<int:task_id>/",views.create_timesheet_task,name="create-timesheet-task"),
|
||||
path("update-timesheet-task/<int:timesheet_id>/",views.update_timesheet_task,name="update-timesheet-task"),
|
||||
path('drag-and-drop-task',views.drag_and_drop_task,name='drag-and-drop-task'),
|
||||
|
||||
# Task-all
|
||||
path('task-all',views.task_all,name='task-all'),
|
||||
path('create-task-all',views.task_all_create,name='create-task-all'),
|
||||
path('update-task-all/<int:task_id>/',views.update_task_all,name='update-task-all'),
|
||||
path('task-all-filter/',views.task_all_filter,name='task-all-filter'),
|
||||
path("task-all-bulk-archive",views.task_all_bulk_archive,name="task-all-bulk-archive"),
|
||||
path("task-all-bulk-delete",views.task_all_bulk_delete,name="task-all-bulk-delete"),
|
||||
path('task-all-archive/<int:task_id>/',views.task_all_archive,name='task-all-archive'),
|
||||
|
||||
|
||||
|
||||
# Project stage
|
||||
path('create-project-stage/<int:project_id>/',views.create_project_stage,name='create-project-stage'),
|
||||
path('update-project-stage/<int:stage_id>/',views.update_project_stage,name='update-project-stage'),
|
||||
path('delete-project-stage/<int:stage_id>/',views.delete_project_stage,name='delete-project-stage'),
|
||||
path('get-stages',views.get_stages,name="get-stages"),
|
||||
path('create-stage-taskall',views.create_stage_taskall,name='create-stage-taskall'),
|
||||
path('drag-and-drop-stage',views.drag_and_drop_stage,name='drag-and-drop-stage'),
|
||||
|
||||
|
||||
# Timesheet
|
||||
path("view-time-sheet", views.time_sheet_view, name="view-time-sheet"),
|
||||
path("create-time-sheet", views.time_sheet_creation, name="create-time-sheet"),
|
||||
path("project-bulk-export", views.project_bulk_export, name="project-bulk-export"),
|
||||
path(
|
||||
"update-time-sheet/<int:time_sheet_id>/",
|
||||
views.time_sheet_update,
|
||||
"project-bulk-archive", views.project_bulk_archive, name="project-bulk-archive"
|
||||
),
|
||||
path("project-bulk-delete", views.project_bulk_delete, name="project-bulk-delete"),
|
||||
path(
|
||||
"project-archive/<int:project_id>/",
|
||||
views.project_archive,
|
||||
name="project-archive",
|
||||
),
|
||||
# Task
|
||||
path(
|
||||
"task-view/<int:project_id>/",
|
||||
views.task_view,
|
||||
name="task-view",
|
||||
kwargs={"model": Project},
|
||||
),
|
||||
path(
|
||||
"create-task/<int:project_id>/",
|
||||
tasks.TaskCreateForm.as_view(),
|
||||
name="create-task",
|
||||
),
|
||||
path(
|
||||
"quick-create-task/<int:stage_id>/",
|
||||
views.quick_create_task,
|
||||
name="quick-create-task",
|
||||
),
|
||||
# path("create-task/<int:stage_id>/", views.create_task, name="create-task"),
|
||||
path(
|
||||
"create-task-in-project/<int:project_id>/",
|
||||
views.create_task_in_project,
|
||||
name="create-task-in-project",
|
||||
),
|
||||
path(
|
||||
"create-stage-task/<int:stage_id>/",
|
||||
tasks.TaskCreateForm.as_view(),
|
||||
name="create-stage-task",
|
||||
),
|
||||
path("update-task/<int:pk>/", tasks.TaskCreateForm.as_view(), name="update-task"),
|
||||
# path("update-task/<int:task_id>/", views.update_task, name="update-task"),
|
||||
path("delete-task/<int:task_id>/", views.delete_task, name="delete-task"),
|
||||
path("task-details/<int:task_id>/", views.task_details, name="task-details"),
|
||||
path("task-filter/<int:project_id>/", views.task_filter, name="task-filter"),
|
||||
path("task-stage-change", views.task_stage_change, name="task-stage-change"),
|
||||
# path("task-timesheet/<int:task_id>/", views.task_timesheet, name="task-timesheet"),
|
||||
path(
|
||||
"task-timesheet/<int:task_id>/",
|
||||
timesheet.TaskTimeSheet.as_view(),
|
||||
name="task-timesheet",
|
||||
),
|
||||
# path(
|
||||
# "create-timesheet-task/<int:task_id>/",
|
||||
# views.create_timesheet_task,
|
||||
# name="create-timesheet-task",
|
||||
# ),
|
||||
path(
|
||||
"update-timesheet-task/<int:timesheet_id>/",
|
||||
views.update_timesheet_task,
|
||||
name="update-timesheet-task",
|
||||
),
|
||||
path("drag-and-drop-task", views.drag_and_drop_task, name="drag-and-drop-task"),
|
||||
# Task-all
|
||||
path("task-all/", tasks.TasksTemplateView.as_view(), name="task-all"),
|
||||
path("tasks-list-view/", tasks.TaskListView.as_view(), name="tasks-list-view"),
|
||||
path(
|
||||
"tasks-list-individual-view/",
|
||||
tasks.TasksInIndividualView.as_view(),
|
||||
name="tasks-list-individual-view",
|
||||
),
|
||||
path("tasks-card-view/", tasks.TaskCardView.as_view(), name="tasks-card-view"),
|
||||
path("tasks-navbar/", tasks.TasksNavBar.as_view(), name="tasks-navbar"),
|
||||
path("create-task-all/", tasks.TaskCreateForm.as_view(), name="create-task-all"),
|
||||
path(
|
||||
"update-task-all/<int:pk>/",
|
||||
tasks.TaskCreateForm.as_view(),
|
||||
name="update-task-all",
|
||||
),
|
||||
path(
|
||||
"task-detail-view/<int:pk>/",
|
||||
tasks.TaskDetailView.as_view(),
|
||||
name="task-detail-view",
|
||||
),
|
||||
path(
|
||||
"update-project-task-status/<int:task_id>/",
|
||||
views.update_project_task_status,
|
||||
name="update-project-task-status",
|
||||
),
|
||||
# path("task-all", views.task_all, name="task-all"),
|
||||
# path("create-task-all", views.task_all_create, name="create-task-all"),
|
||||
# path(
|
||||
# "update-task-all/<int:task_id>/", views.update_task_all, name="update-task-all"
|
||||
# ),
|
||||
path("task-all-filter/", views.task_all_filter, name="task-all-filter"),
|
||||
path(
|
||||
"task-all-bulk-archive",
|
||||
views.task_all_bulk_archive,
|
||||
name="task-all-bulk-archive",
|
||||
),
|
||||
path(
|
||||
"task-all-bulk-delete", views.task_all_bulk_delete, name="task-all-bulk-delete"
|
||||
),
|
||||
path(
|
||||
"task-all-archive/<int:task_id>/",
|
||||
views.task_all_archive,
|
||||
name="task-all-archive",
|
||||
),
|
||||
# Project stage
|
||||
path(
|
||||
"create-project-stage/<int:project_id>/",
|
||||
project_stage.ProjectStageCreateForm.as_view(),
|
||||
name="create-project-stage",
|
||||
),
|
||||
# path(
|
||||
# "create-project-stage/<int:project_id>/",
|
||||
# views.create_project_stage,
|
||||
# name="create-project-stage",
|
||||
# ),
|
||||
path(
|
||||
"update-project-stage/<int:pk>/",
|
||||
project_stage.ProjectStageCreateForm.as_view(),
|
||||
name="update-project-stage",
|
||||
),
|
||||
# path(
|
||||
# "update-project-stage/<int:stage_id>/",
|
||||
# views.update_project_stage,
|
||||
# name="update-project-stage",
|
||||
# ),
|
||||
path(
|
||||
"delete-project-stage/<int:stage_id>/",
|
||||
views.delete_project_stage,
|
||||
name="delete-project-stage",
|
||||
),
|
||||
path("get-stages", views.get_stages, name="get-stages"),
|
||||
path(
|
||||
"create-stage-taskall", views.create_stage_taskall, name="create-stage-taskall"
|
||||
),
|
||||
path("drag-and-drop-stage", views.drag_and_drop_stage, name="drag-and-drop-stage"),
|
||||
# Timesheet
|
||||
# path("view-time-sheet", views.time_sheet_view, name="view-time-sheet"),
|
||||
path("view-time-sheet/", timesheet.TimeSheetView.as_view(), name="view-time-sheet"),
|
||||
path("get-members-of-project/", views.get_members, name="get-members-of-project"),
|
||||
path(
|
||||
"get-tasks-of-project/",
|
||||
views.get_tasks_in_timesheet,
|
||||
name="get-tasks-of-project",
|
||||
),
|
||||
path(
|
||||
"time-sheet-nav/", timesheet.TimeSheetNavView.as_view(), name="time-sheet-nav"
|
||||
),
|
||||
path("time-sheet-list/", timesheet.TimeSheetList.as_view(), name="time-sheet-list"),
|
||||
path(
|
||||
"time-sheet-card/",
|
||||
timesheet.TimeSheetCardView.as_view(),
|
||||
name="time-sheet-card",
|
||||
),
|
||||
path(
|
||||
"time-sheet-detail-view/<int:pk>/",
|
||||
timesheet.TimeSheetDetailView.as_view(),
|
||||
name="time-sheet-detail-view",
|
||||
),
|
||||
# path("create-time-sheet", views.time_sheet_creation, name="create-time-sheet"),
|
||||
path(
|
||||
"create-time-sheet",
|
||||
timesheet.TimeSheetFormView.as_view(),
|
||||
name="create-time-sheet",
|
||||
),
|
||||
path(
|
||||
"create-timesheet-task/<int:task_id>/",
|
||||
timesheet.TimeSheetFormView.as_view(),
|
||||
name="create-timesheet-task",
|
||||
),
|
||||
path(
|
||||
"update-time-sheet/<int:pk>/",
|
||||
timesheet.TimeSheetFormView.as_view(),
|
||||
name="update-time-sheet",
|
||||
),
|
||||
path(
|
||||
"delete-time-sheet-ajax/<int:time_sheet_id>/",
|
||||
views.time_sheet_delete_ajax,
|
||||
name="delete-time-sheet-ajax",
|
||||
),
|
||||
# path(
|
||||
# "update-time-sheet/<int:time_sheet_id>/",
|
||||
# views.time_sheet_update,
|
||||
# name="update-time-sheet",
|
||||
# ),
|
||||
path("filter-time-sheet", views.time_sheet_filter, name="filter-time-sheet"),
|
||||
path("time-sheet-initial", views.time_sheet_initial, name="time-sheet-initial"),
|
||||
|
||||
path("view-time-sheet", views.time_sheet_view, name="view-time-sheet"),
|
||||
# path("get-project", views.get_project, name="get-project"),
|
||||
path("create-time-sheet", views.time_sheet_creation, name="create-time-sheet"),
|
||||
path(
|
||||
"create-project-time-sheet",
|
||||
@@ -92,11 +258,6 @@ urlpatterns = [
|
||||
views.time_sheet_task_creation,
|
||||
name="create-task-time-sheet",
|
||||
),
|
||||
path(
|
||||
"update-time-sheet/<int:time_sheet_id>/",
|
||||
views.time_sheet_update,
|
||||
name="update-time-sheet",
|
||||
),
|
||||
path(
|
||||
"delete-time-sheet/<int:time_sheet_id>/",
|
||||
views.time_sheet_delete,
|
||||
@@ -110,8 +271,14 @@ urlpatterns = [
|
||||
name="personal-time-sheet-view",
|
||||
),
|
||||
path("personal-time-sheet/", views.personal_time_sheet, name="personal-time-sheet"),
|
||||
path("view-single-time-sheet/<int:time_sheet_id>", views.time_sheet_single_view, name="view-single-time-sheet"),
|
||||
path('time-sheet-bulk-delete',views.time_sheet_bulk_delete,name="time-sheet-bulk-delete"),
|
||||
|
||||
|
||||
path(
|
||||
"view-single-time-sheet/<int:time_sheet_id>",
|
||||
views.time_sheet_single_view,
|
||||
name="view-single-time-sheet",
|
||||
),
|
||||
path(
|
||||
"time-sheet-bulk-delete",
|
||||
views.time_sheet_bulk_delete,
|
||||
name="time-sheet-bulk-delete",
|
||||
),
|
||||
]
|
||||
|
||||
1375
project/views.py
1375
project/views.py
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user