buttons in table footer

master
kikimor 8 months ago
parent d72cf0a0b3
commit d986fdf232

@ -4,24 +4,53 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="7410a44d-51b9-408b-85ad-4fa46776b372" name="Changes" comment="Add IntelliJ IDEA project configuration files&#10;&#10;This commit adds IntelliJ IDEA-specific configuration files for the project, including module setup, version control integration, inspection profiles, and workspace settings. These files facilitate development environment configuration for contributors using IntelliJ IDEA.">
<change afterPath="$PROJECT_DIR$/static/styles/forms.css" afterDir="false" />
<change afterPath="$PROJECT_DIR$/templates/employee-base.html" afterDir="false" />
<change afterPath="$PROJECT_DIR$/templates/employee-dashboard-content.html" afterDir="false" />
<change afterPath="$PROJECT_DIR$/templates/employee-sidebar.html" afterDir="false" />
<change afterPath="$PROJECT_DIR$/templates/recent.html" afterDir="false" />
<list default="true" id="7410a44d-51b9-408b-85ad-4fa46776b372" name="Changes" comment="commit unversioned files ;)">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/accounts/admin.py" beforeDir="false" afterPath="$PROJECT_DIR$/accounts/admin.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/accounts/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/accounts/forms.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/DepoT/mixins/LineFiltweFormMixin.py" beforeDir="false" afterPath="$PROJECT_DIR$/DepoT/mixins/LineFiltweFormMixin.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/DepoT/mixins/crudListViewMixin.py" beforeDir="false" afterPath="$PROJECT_DIR$/DepoT/mixins/crudListViewMixin.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/accounts/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/accounts/models.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/accounts/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/accounts/views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/booking/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/booking/forms.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/booking/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/booking/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/booking/views/client_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/booking/views/client_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/booking/views/employee_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/booking/views/employee_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/utils/utils.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/utils/utils.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/views/barrier_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/views/barrier_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/views/client_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/views/client_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/views/employee_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/views/employee_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/views/shared_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/views/shared_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/forms.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/services.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/services.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/views/barrier_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/views/barrier_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/views/employee_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/views/employee_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/populate_database.txt" beforeDir="false" afterPath="$PROJECT_DIR$/populate_database.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/preinfo/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/forms.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/preinfo/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/preinfo/views/client_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/views/client_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/preinfo/views/employee_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/views/employee_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/js/crud-list.js" beforeDir="false" afterPath="$PROJECT_DIR$/static/js/crud-list.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/styles/base.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/base.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/styles/dashboard-content.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/dashboard-content.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/styles/forms.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/forms.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/styles/sidebar.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/sidebar.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/styles/styles.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/styles.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/styles/tables.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/tables.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/barrier/barrier-base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/barrier/barrier-base.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/barrier/barrier-dashboard.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/barrier/barrier-dashboard.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/chatgpt/round table.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/chatgpt/round table.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client-base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client-base.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client-booking-content.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client-booking-content.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client-dashboard-content.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client-dashboard-content.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client-sidebar.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client-sidebar.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/registration/login.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/registration/login.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/registration/register.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/registration/register.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client/booking-edit.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/booking-edit.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client/booking-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/booking-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client/preinfo-create.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/preinfo-create.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client/preinfo-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/preinfo-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/container-expedition.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/container-expedition.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/container-receive.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/container-receive.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee/preinfo-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/preinfo-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/list-crud.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/list-crud.html" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -35,20 +64,20 @@
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="JavaScript File" />
<option value="Python Script" />
<option value="CSS File" />
<option value="HTML File" />
<option value="CSS File" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectColorInfo"><![CDATA[{
"associatedIndex": 6
}]]></component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 6
}</component>
<component name="ProjectId" id="2yxmkOyUBM7hzsgEZWENDFkS6jw" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
@ -59,9 +88,11 @@
"Django Server.DepoT.executor": "Debug",
"RunOnceActivity.OpenDjangoStructureViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.git.unshallow": "true",
"django.template.preview.state": "SHOW_EDITOR_AND_PREVIEW",
"git-widget-placeholder": "master",
"last_opened_file_path": "C:/dev_projects/python/Django/DepoT/templates/registration",
"ignore.virus.scanning.warn.message": "true",
"last_opened_file_path": "C:/dev_projects/python/Django/DepoT/templates/client",
"list.type.of.created.stylesheet": "CSS",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
@ -78,10 +109,18 @@
}]]></component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="C:\dev_projects\python\Django\DepoT\templates\registration" />
<recent name="C:\dev_projects\python\Django\DepoT\templates\client" />
<recent name="C:\dev_projects\python\Django\DepoT\templates" />
<recent name="C:\dev_projects\python\Django\DepoT\templates\registration" />
<recent name="C:\dev_projects\python\Django\DepoT\static\styles" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="C:\dev_projects\python\Django\DepoT\common\views" />
<recent name="C:\dev_projects\python\Django\DepoT\booking\views" />
<recent name="C:\dev_projects\python\Django\DepoT\containers\views" />
<recent name="C:\dev_projects\python\Django\DepoT\preinfo\views" />
<recent name="C:\dev_projects\python\Django\DepoT\templates\barrier" />
</key>
</component>
<component name="RunManager">
<configuration name="DepoT" type="Python.DjangoServer" factoryName="Django server">
@ -113,8 +152,8 @@
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-js-predefined-d6986cc7102b-1632447f56bf-JavaScript-PY-243.26574.90" />
<option value="bundled-python-sdk-c1fac28bca04-4df51de95216-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-243.26574.90" />
<option value="bundled-js-predefined-d6986cc7102b-09060db00ec0-JavaScript-PY-251.26927.74" />
<option value="bundled-python-sdk-657d8234b839-64d779b69b7a-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-251.26927.74" />
</set>
</attachedChunks>
</component>
@ -126,7 +165,9 @@
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1750784740296</updated>
<workItem from="1750784741367" duration="164972000" />
<workItem from="1750784741367" duration="240087000" />
<workItem from="1752078951079" duration="106000" />
<workItem from="1752079161329" duration="40180000" />
</task>
<task id="LOCAL-00001" summary="Add IntelliJ IDEA project configuration files&#10;&#10;This commit adds IntelliJ IDEA-specific configuration files for the project, including module setup, version control integration, inspection profiles, and workspace settings. These files facilitate development environment configuration for contributors using IntelliJ IDEA.">
<option name="closed" value="true" />
@ -192,23 +233,64 @@
<option name="project" value="LOCAL" />
<updated>1751443902965</updated>
</task>
<option name="localTasksCounter" value="9" />
<task id="LOCAL-00009" summary="Add IntelliJ IDEA project configuration files&#10;&#10;This commit adds IntelliJ IDEA-specific configuration files for the project, including module setup, version control integration, inspection profiles, and workspace settings. These files facilitate development environment configuration for contributors using IntelliJ IDEA.">
<option name="closed" value="true" />
<created>1751557411675</created>
<option name="number" value="00009" />
<option name="presentableId" value="LOCAL-00009" />
<option name="project" value="LOCAL" />
<updated>1751557411675</updated>
</task>
<task id="LOCAL-00010" summary="commit unversioned files ;)">
<option name="closed" value="true" />
<created>1751618076902</created>
<option name="number" value="00010" />
<option name="presentableId" value="LOCAL-00010" />
<option name="project" value="LOCAL" />
<updated>1751618076902</updated>
</task>
<task id="LOCAL-00011" summary="commit unversioned files ;)">
<option name="closed" value="true" />
<created>1751877829547</created>
<option name="number" value="00011" />
<option name="presentableId" value="LOCAL-00011" />
<option name="project" value="LOCAL" />
<updated>1751877829547</updated>
</task>
<option name="localTasksCounter" value="12" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="Add IntelliJ IDEA project configuration files&#10;&#10;This commit adds IntelliJ IDEA-specific configuration files for the project, including module setup, version control integration, inspection profiles, and workspace settings. These files facilitate development environment configuration for contributors using IntelliJ IDEA." />
<option name="LAST_COMMIT_MESSAGE" value="Add IntelliJ IDEA project configuration files&#10;&#10;This commit adds IntelliJ IDEA-specific configuration files for the project, including module setup, version control integration, inspection profiles, and workspace settings. These files facilitate development environment configuration for contributors using IntelliJ IDEA." />
<MESSAGE value="commit unversioned files ;)" />
<option name="LAST_COMMIT_MESSAGE" value="commit unversioned files ;)" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/containers/views.py</url>
<line>35</line>
<option name="timeStamp" value="13" />
<url>file://$PROJECT_DIR$/containers/views/employee_views.py</url>
<line>110</line>
<option name="timeStamp" value="31" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/DepoT/mixins/crudListViewMixin.py</url>
<line>7</line>
<option name="timeStamp" value="55" />
</line-breakpoint>
</breakpoints>
<default-breakpoints>

@ -0,0 +1,15 @@
from common.models import LinesModel
class LineFilterFormMixin:
def get_form(self, form_class=None):
form = super().get_form(form_class)
user = self.request.user
if user.line:
form.fields['line'].queryset = form.fields['line'].queryset.filter(pk=user.line.pk)
form.fields['line'].initial = user.line
form.fields['line'].widget.attrs['readonly'] = True
else:
form.fields['line'].queryset = LinesModel.objects.filter(company=user.company)
form.fields['line'].widget.attrs['readonly'] = False
return form

@ -0,0 +1,21 @@
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
class CRUDListViewMixin:
...
# def post(self, request, *args, **kwargs):
# if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
# object_id = request.POST.get('object_id')
# obj = get_object_or_404(self.model, id=object_id)
# return JsonResponse(self.get_object_data(obj))
# else:
# return self.handle_form_submission(request, *args, **kwargs)
# def get_object_data(self, obj):
# """Override this method in child views to specify which data to return"""
# raise NotImplementedError
#
# def handle_form_submission(self, request, *args, **kwargs):
# """Override this method in child views to handle form submission"""
# raise NotImplementedError

@ -10,8 +10,15 @@ class ClientPermission(models.Model):
managed = True
default_permissions = ()
permissions = (
('can_book_container', 'Can book container'),
('can_view_bookings', 'Can view bookings'),
('can_view_booking', 'Can view booking'),
('can_manage_booking', 'Can book container'),
('can_view_preinfo', 'Can view preinfo'),
('can_manage_preinfo', 'Can manage preinfo'),
('can_view_payment', 'Can view payment'),
('can_manage_payment', 'Can manage payment'),
('can_manage_company_users', 'Can manage company users'),
)

@ -33,7 +33,6 @@ class RegisterView(AccessMixin, FormView):
def dispatch(self, request, *args, **kwargs):
user: DepotUser = request.user
if not (user.is_superuser or user.user_type == DepotUser.UserType.COMPANY_ADMIN):
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
@ -42,15 +41,16 @@ class RegisterView(AccessMixin, FormView):
# Create user from form data
user = form.save(commit=False)
user_type = form.cleaned_data['user_type']
user.save()
# Clear irrelevant permissions based on user type
if user_type == DepotUser.UserType.CLIENT:
user.employee_permissions.clear()
user.company_permissions.set(form.cleaned_data['company_permissions'])
elif user_type == DepotUser.UserType.EMPLOYEE:
user.company_permissions.clear()
user.employee_permissions.set(form.cleaned_data['employee_permissions'])
# user.set_password(form.cleaned_data['password'])
user.save()
return super().form_valid(form)
def get_form(self, form_class = None):

@ -17,3 +17,9 @@ class BookingCreateForm(BookingBaseForm):
class Meta(BookingBaseForm.Meta):
fields = ['number', 'vehicles', 'container_type', 'container_count', 'carrier', 'line', 'container_number']
class BookingUpdateForm(BookingBaseForm):
class Meta(BookingBaseForm.Meta):
fields = ['number', 'vehicles', 'container_type', 'container_count', 'carrier', 'line', 'container_number']

@ -1,8 +1,13 @@
from django.urls import path
from django.urls import path, include
from booking.views import CreateBookingView, BookingListView
from booking.views.client_views import CreateBookingView, ClientBookingView, ClientBookingUpdateView
from booking.views.employee_views import BookingListView
urlpatterns = [
path('client/', CreateBookingView.as_view(), name='client_booking'),
path('employee_bookings', BookingListView.as_view(), name='employee_bookings'),
path('client/', include([
path('', ClientBookingView.as_view(), name='client_booking'),
path('create/', CreateBookingView.as_view(), name='client_booking_create'),
path('update/<int:pk>/', ClientBookingUpdateView.as_view(), name='client_booking_update'),
])),
path('employee/', BookingListView.as_view(), name='employee_bookings'),
]

@ -1,52 +1,62 @@
from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.urls import reverse_lazy
from django.views.generic import CreateView, ListView
from booking.forms import BookingCreateForm
from DepoT.mixins.LineFiltweFormMixin import LineFilterFormMixin
from booking.forms import BookingCreateForm, BookingUpdateForm
from booking.models import Booking
from common.utils.utils import filter_queryset_by_user
# Create your views here.
class CreateBookingView(CreateView):
template_name = 'client-booking-content.html'
class ClientBookingView(LoginRequiredMixin, UserPassesTestMixin, ListView):
model = Booking
form_class = BookingCreateForm
success_url = reverse_lazy('client_booking')
template_name = 'client/booking-list.html'
paginate_by = 4
context_object_name = 'objects'
base_template = 'client-base.html'
def test_func(self):
return self.request.user.has_company_perm('can_view_booking') or self.request.user.user_type == 'CA'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
queryset = self.model.objects.all().order_by('-created_on')
user = self.request.user
# !!! important
queryset = filter_queryset_by_user( queryset, user)[:10]
# !!! important
context['recent'] = queryset
context['base_template'] = self.base_template
return context
def get_form(self, form_class=None):
form = super().get_form(form_class)
def get_queryset(self):
queryset = super().get_queryset()
user = self.request.user
result = filter_queryset_by_user(queryset, user)
return result
# If user has a specific line, limit the line choices
if user.line:
form.fields['line'].queryset = form.fields['line'].queryset.filter(pk=user.line.pk)
form.fields['line'].initial = user.line
form.fields['line'].widget.readonly = True #attrs['disabled'] = True
return form
class CreateBookingView(LoginRequiredMixin, UserPassesTestMixin, LineFilterFormMixin, CreateView):
template_name = 'client/booking-create.html'
model = Booking
form_class = BookingCreateForm
success_url = reverse_lazy('client_booking')
def form_valid(self, form):
# todo more validation
form.instance.created_by = self.request.user.id
form.instance.updated_by = self.request.user.id
form.instance.vehicles_left = form.cleaned_data.get('vehicles')
if self.request.user.line:
form.instance.line = self.request.user.line
return super().form_valid(form)
def test_func(self):
return True # self.request.user.has_company_perm('can_edit_preinfo') or self.request.user.user_type == 'CA'
class BookingListView(ListView):
template_name = 'employee/booking-list.html'
class ClientBookingUpdateView(LoginRequiredMixin, UserPassesTestMixin, LineFilterFormMixin, CreateView):
template_name = 'client/booking-edit.html'
model = Booking
context_object_name = 'bookings'
paginate_by = 30 # Number of containers per page
form_class = BookingUpdateForm
success_url = reverse_lazy('client_booking')
def form_valid(self, form):
# todo more validation
form.instance.updated_by = self.request.user.id
return super().form_valid(form)
def test_func(self):
return True # self.request.user.has_company_perm('can_edit_preinfo') or self.request.user.user_type == 'CA'

@ -0,0 +1,10 @@
from django.views.generic import ListView
from booking.models import Booking
class BookingListView(ListView):
template_name = 'employee/booking-list.html'
model = Booking
context_object_name = 'bookings'
paginate_by = 30 # Number of containers per page

@ -1,12 +1,14 @@
from django.urls import path, include
from common import views
from common.views.barrier_views import BarrierDashboardView
from common.views.employee_views import EmployeeDashboardView
from common.views.client_views import ClientDashboardView
from common.views.shared_views import IndexView, DashboardRedirectView
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('', IndexView.as_view(), name='index'),
path('dashboard/', include([
path('', views.DashboardRedirectView.as_view(), name='dashboard'),
path('client/', views.ClientDashboardView.as_view(), name='client_dashboard'),
path('barrier/', views.BarrierDashboardView.as_view(), name='barrier_dashboard'),
path('employee/', views.EmployeeDashboardView.as_view(), name='employee_dashboard'),
path('', DashboardRedirectView.as_view(), name='dashboard'),
path('client/', ClientDashboardView.as_view(), name='client_dashboard'),
path('barrier/', BarrierDashboardView.as_view(), name='barrier_dashboard'),
path('employee/', EmployeeDashboardView.as_view(), name='employee_dashboard'),
]))
]

@ -8,12 +8,16 @@ def filter_queryset_by_user(queryset, user):
If the user has a line, it filters by that line.
If the user has a company, it filters by all lines associated with that company.
"""
print(f'user: {user}, user company: {user.company}, user line: {user.line}')
if user.line:
return queryset.filter(line=user.line)
filtered = queryset.filter(line=user.line)
print(f"Filtering by line: {user.line.id}, count: {filtered.count()}")
return filtered
elif user.company:
company_lines = user.company.line_company.all()
return queryset.filter(line__in=company_lines)
filtered = queryset.filter(line__in=company_lines)
print(f"Filtering by company: {user.company.id}, count: {filtered.count()}")
return filtered
return queryset

@ -0,0 +1,14 @@
from django.shortcuts import render
from django.views.generic import TemplateView
class BarrierDashboardView(TemplateView):
template_name = 'barrier/barrier-dashboard.html'
extra_context = {
'title': 'Client Dashboard',
'description': 'This is the client dashboard page.',
}
def get(self, request, *args, **kwargs):
return render(request, self.template_name, self.extra_context)

@ -0,0 +1,13 @@
from django.shortcuts import render
from django.views.generic import TemplateView
class ClientDashboardView(TemplateView):
template_name = 'client-dashboard-content.html'
extra_context = {
'title': 'Client Dashboard',
'description': 'This is the client dashboard page.',
}
def get(self, request, *args, **kwargs):
return render(request, self.template_name, self.extra_context)

@ -0,0 +1,23 @@
from django.views.generic import TemplateView
from booking.models import Booking
from containers.models import Container
from preinfo.models import Preinfo
class EmployeeDashboardView(TemplateView):
template_name = 'employee-dashboard-content.html'
extra_context = {
'title': 'Employee Dashboard',
'description': 'This is the depot employee dashboard page.',
}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
containers = Container.objects.filter(expedited=False).count()
preinfos = Preinfo.objects.filter(received=False).count()
bookings = Booking.objects.filter(status='active').count()
context['containers'] = containers
context['preinfos'] = preinfos
context['bookings'] = bookings
return context

@ -39,43 +39,11 @@ class DashboardRedirectView(RedirectView):
return reverse_lazy('index')
class ClientDashboardView(TemplateView):
template_name = 'client-dashboard-content.html'
extra_context = {
'title': 'Client Dashboard',
'description': 'This is the client dashboard page.',
}
def get(self, request, *args, **kwargs):
return render(request, self.template_name, self.extra_context)
class BarrierDashboardView(TemplateView):
template_name = 'barrier/barrier-dashboard.html'
extra_context = {
'title': 'Client Dashboard',
'description': 'This is the client dashboard page.',
}
def get(self, request, *args, **kwargs):
return render(request, self.template_name, self.extra_context)
class EmployeeDashboardView(TemplateView):
template_name = 'employee-dashboard-content.html'
extra_context = {
'title': 'Employee Dashboard',
'description': 'This is the depot employee dashboard page.',
}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
containers = Container.objects.filter(expedited=False).count()
preinfos = Preinfo.objects.filter(received=False).count()
bookings = Booking.objects.filter(status='active').count()
context['containers'] = containers
context['preinfos'] = preinfos
context['bookings'] = bookings
return context
# class ClientPreinfoView(TemplateView):
# template_name = 'client-preinfo-content.html'

@ -25,9 +25,11 @@ class ContainerExpeditionForm(ContainerBaseForm):
Inherits from ContainerBaseForm.
"""
class Meta(ContainerBaseForm.Meta):
exclude = ['created_on',
'created_by',
'deleted',
'deleted_on',
'deleted_by',
'received'] # Exclude fields that should not be set by the user
fields = ['number', 'expedition_vehicle', 'position', 'line', 'container_type', 'damages', 'heavy_damaged']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
readonly_fields = ['number', 'position', 'line', 'container_type', 'damages', 'heavy_damaged']
for field in readonly_fields:
self.fields[field].widget.attrs['readonly'] = True
self.fields[field].disabled = True

@ -1,14 +1,17 @@
from booking.models import Booking
from containers.models import Container
def get_container_for_booking(booking):
def get_container_for_booking(booking_number):
filters = {
'expedited': False,
}
booking = Booking.objects.get(number=booking_number)
if not booking:
return None
if booking.container_number:
filters['container_number'] = booking.container_number
else:
filters['container_type'] = booking.container_type
filters['line'] = booking.line
return Container.objects.filter(**filters).order_by('created_at').first()
return Container.objects.filter(**filters).order_by('received_on').first()

@ -1,12 +1,13 @@
from django.urls import include, path
from containers.views import ContainerReceive, ContainerExpedition, ContainerSearchView, ContainersListView
from containers.views.employee_views import ContainersListView
from containers.views.barrier_views import ContainerReceive, ContainerExpedition, ContainerSearchView
urlpatterns = [
path('search', ContainerSearchView.as_view(), name='container_search'),
path('employee_containers', ContainersListView.as_view(), name='employee_containers'),
path('<int:pk>/', include([
path('receive', ContainerReceive.as_view(), name='container_receive'),
path('expedition', ContainerExpedition.as_view(), name='container_expedition'),
path('search/', ContainerSearchView.as_view(), name='container_search'),
path('employee/', ContainersListView.as_view(), name='employee_containers'),
path('barrier/<int:pk>/', include([
path('receive/', ContainerReceive.as_view(), name='container_receive'),
path('expedition/', ContainerExpedition.as_view(), name='container_expedition'),
])),
]

@ -0,0 +1,164 @@
from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.utils import timezone
from django.views import View
from django.views.generic import CreateView, UpdateView, FormView, ListView
from booking.models import Booking
from common.utils.utils import get_container_by_number
from containers.forms import ContainerReceiveForm, ContainerExpeditionForm
from containers.models import Container
from containers.services import get_container_for_booking
from preinfo.models import Preinfo
# Create your views here.
class ContainerReceive(CreateView):
template_name = 'container-receive.html'
model = Container
form_class = ContainerReceiveForm
success_url = reverse_lazy('dashboard')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
pk = self.kwargs.get('pk')
try:
preinfo =Preinfo.objects.filter(pk=pk, received=False).first()
except Preinfo.DoesNotExist:
preinfo = None
if preinfo:
context['preinfo'] = preinfo
context['containers'] = Container.objects.order_by('-received_on').all()[:10] # Fetch the last 10 containers
return context
return redirect(reverse_lazy('container_search'))
def form_valid(self, form):
# Get the preinfo_id from the POST data
preinfo_id = self.request.POST.get('preinfo_id')
try:
preinfo = Preinfo.objects.get(id=preinfo_id)
except Preinfo.DoesNotExist:
preinfo = None
# validate if data is correct, comparing user data with preinfo data
if preinfo and preinfo.container_number == form.cleaned_data.get('number') and not preinfo.received:
preinfo.received = True
preinfo.save()
form.instance.received_by = self.request.user
form.instance.line = preinfo.line
form.instance.container_type = preinfo.container_type
else:
form.add_error('number', 'Invalid data')
return super().form_valid(form)
class ContainerSearchView(View):
template_name = 'container-search.html'
def get(self, request):
return render(request, self.template_name)
def post(self, request):
booking = Booking.objects.get(number=request.POST.get('number'))
if not booking:
return render(request, self.template_name, {'error': 'Booking not found'})
filters = {
'expedited': False,
}
if booking.container_number:
filters['container_number'] = booking.container_number
else:
filters['container_type'] = booking.container_type
filters['line'] = booking.line
container = Container.objects.filter(**filters).order_by('received_on').first()
if not container:
return render(request, self.template_name, {'error': 'Not found'})
next_url = request.POST.get('param')
return redirect(next_url, pk=booking.pk)
class ContainerExpedition(UpdateView):
template_name = 'container-expedition.html'
model = Container
form_class = ContainerExpeditionForm
success_url = reverse_lazy('dashboard')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
pk = self.kwargs.get('pk')
booking = Booking.objects.get(pk=pk)
filters = {
'expedited': False,
}
if booking.container_number:
filters['container_number'] = booking.container_number
else:
filters['container_type'] = booking.container_type
filters['line'] = booking.line
container = Container.objects.filter(**filters).order_by('received_on').first()
context['container'] = container
context['booking'] = booking
return context
def form_valid(self, form):
booking_id = self.request.POST.get('booking_id')
container_id = self.request.POST.get('container_id')
try:
booking = Booking.objects.get(id=booking_id)
container = Container.objects.get(id=container_id)
vehicle = form.cleaned_data['expedition_vehicle']
# Update container
container.expedited = True
container.expedited_on = timezone.now()
container.expedited_by = self.request.user
container.expedition_vehicle = vehicle
container.booking = booking
container.save()
# Update booking status if needed
booking.container_expedited_count += 1
if booking.vehicles:
vehicles_list = booking.vehicles.split(',')
if vehicle in vehicles_list:
vehicles_list.remove(vehicle)
booking.vehicles = ','.join(vehicles_list)
if booking.container_expedited_count >= booking.container_count:
booking.status = 'completed' # or any other status you want to set
booking.save()
return redirect(self.success_url)
except (Booking.DoesNotExist, Container.DoesNotExist):
form.add_error(None, 'Invalid booking or container')
return self.form_invalid(form)
# try:
# booking = Booking.objects.get(id=booking_id)
# except Booking.DoesNotExist:
# booking = None
# # validate if data is correct, comparing user data with booking data
# if booking and booking.container_number == form.cleaned_data.get('container_number') and not booking.expedited:
# booking.expedited = True
# booking.save()
#
# form.instance.expedited_by = self.request.user
# form.instance.line = booking.line
# form.instance.container_type = booking.container_type
# else:
# form.add_error('container_number', 'Invalid data')
# if preinfo:
# context['preinfo'] = preinfo
# context['containers'] = Container.objects.order_by('-received_on').all()[:10] # Fetch the last 10 containers
# return context
# return redirect(reverse_lazy('container_search'))

@ -1,167 +1,6 @@
from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.utils import timezone
from django.views import View
from django.views.generic import CreateView, UpdateView, FormView, ListView
from django.views.generic import ListView
from booking.models import Booking
from common.utils.utils import get_container_by_number
from containers.forms import ContainerReceiveForm, ContainerExpeditionForm
from containers.models import Container
from containers.services import get_container_for_booking
from preinfo.models import Preinfo
# Create your views here.
class ContainerReceive(CreateView):
template_name = 'container-receive.html'
model = Container
form_class = ContainerReceiveForm
success_url = reverse_lazy('dashboard')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
pk = self.kwargs.get('pk')
try:
preinfo =Preinfo.objects.filter(pk=pk, received=False).first()
except Preinfo.DoesNotExist:
preinfo = None
if preinfo:
context['preinfo'] = preinfo
context['containers'] = Container.objects.order_by('-received_on').all()[:10] # Fetch the last 10 containers
return context
return redirect(reverse_lazy('container_search'))
def form_valid(self, form):
# Get the preinfo_id from the POST data
preinfo_id = self.request.POST.get('preinfo_id')
try:
preinfo = Preinfo.objects.get(id=preinfo_id)
except Preinfo.DoesNotExist:
preinfo = None
# validate if data is correct, comparing user data with preinfo data
if preinfo and preinfo.container_number == form.cleaned_data.get('number') and not preinfo.received:
preinfo.received = True
preinfo.save()
form.instance.received_by = self.request.user
form.instance.line = preinfo.line
form.instance.container_type = preinfo.container_type
else:
form.add_error('number', 'Invalid data')
return super().form_valid(form)
class ContainerSearchView(View):
template_name = 'container-search.html'
def get(self, request):
return render(request, self.template_name)
def post(self, request):
booking = Booking.objects.get(number=request.POST.get('number'))
if not booking:
return render(request, self.template_name, {'error': 'Booking not found'})
filters = {
'expedited': False,
}
if booking.container_number:
filters['container_number'] = booking.container_number
else:
filters['container_type'] = booking.container_type
filters['line'] = booking.line
container = Container.objects.filter(**filters).order_by('received_on').first()
if not container:
return render(request, self.template_name, {'error': 'Not found'})
next_url = request.POST.get('param')
return redirect(next_url, pk=booking.pk)
class ContainerExpedition(UpdateView):
template_name = 'container-expedition.html'
model = Container
form_class = ContainerExpeditionForm
success_url = reverse_lazy('dashboard')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
pk = self.kwargs.get('pk')
booking = Booking.objects.get(pk=pk)
filters = {
'expedited': False,
}
if booking.container_number:
filters['container_number'] = booking.container_number
else:
filters['container_type'] = booking.container_type
filters['line'] = booking.line
container = Container.objects.filter(**filters).order_by('received_on').first()
context['container'] = container
context['booking'] = booking
return context
def form_valid(self, form):
booking_id = self.request.POST.get('booking_id')
container_id = self.request.POST.get('container_id')
try:
booking = Booking.objects.get(id=booking_id)
container = Container.objects.get(id=container_id)
vehicle = form.cleaned_data['expedition_vehicle']
# Update container
container.expedited = True
container.expedited_on = timezone.now()
container.expedited_by = self.request.user
container.expedition_vehicle = vehicle
container.booking = booking
container.save()
# Update booking status if needed
booking.container_expedited_count += 1
if booking.vehicles:
vehicles_list = booking.vehicles.split(',')
if vehicle in vehicles_list:
vehicles_list.remove(vehicle)
booking.vehicles = ','.join(vehicles_list)
if booking.container_expedited_count >= booking.container_count:
booking.status = 'completed' # or any other status you want to set
booking.save()
return redirect(self.success_url)
except (Booking.DoesNotExist, Container.DoesNotExist):
form.add_error(None, 'Invalid booking or container')
return self.form_invalid(form)
# try:
# booking = Booking.objects.get(id=booking_id)
# except Booking.DoesNotExist:
# booking = None
# # validate if data is correct, comparing user data with booking data
# if booking and booking.container_number == form.cleaned_data.get('container_number') and not booking.expedited:
# booking.expedited = True
# booking.save()
#
# form.instance.expedited_by = self.request.user
# form.instance.line = booking.line
# form.instance.container_type = booking.container_type
# else:
# form.add_error('container_number', 'Invalid data')
# if preinfo:
# context['preinfo'] = preinfo
# context['containers'] = Container.objects.order_by('-received_on').all()[:10] # Fetch the last 10 containers
# return context
# return redirect(reverse_lazy('container_search'))
class ContainersListView(ListView):

@ -29,6 +29,12 @@ insert into accounts_employeepermission (id, codename, name) values (1, 'can_man
insert into accounts_employeepermission (id, codename, name) values (2, 'can_view_reports', 'Can view reports');
insert into accounts_employeepermission (id, codename, name) values (3, 'can_handle_operations', 'Can handle operations');
insert into accounts_clientpermission (id, codename, name) values (1, 'can_book_container', 'Can book container');
insert into accounts_clientpermission (id, codename, name) values (2, 'can_view_bookings', 'Can view bookings');
insert into accounts_clientpermission (id, codename, name) values (3, 'can_manage_company_users', 'Can manage company users');
insert into accounts_clientpermission (id, codename, name) values (1, 'can_view_booking', 'Can view booking');
insert into accounts_clientpermission (id, codename, name) values (2, 'can_manage_booking', 'Can book container');
insert into accounts_clientpermission (id, codename, name) values (3, 'can_view_preinfo', 'Can view preinfo');
insert into accounts_clientpermission (id, codename, name) values (4, 'can_manage_preinfo', 'Can manage preinfo');
insert into accounts_clientpermission (id, codename, name) values (5, 'can_view_payment', 'Can view payment');
insert into accounts_clientpermission (id, codename, name) values (6, 'can_manage_payment', 'Can manage payment');
insert into accounts_clientpermission (id, codename, name) values (7, 'can_manage_company_users', 'Can manage company users');

@ -22,3 +22,9 @@ class PreinfoCreateForm(PreinfoBaseForm):
'deleted_on',
'deleted_by',
'received'] # Exclude fields that should not be set by the user
class PreinfoEditForm(ModelForm):
class Meta:
model = Preinfo
fields = ['container_number', 'container_type', 'line']

@ -1,9 +1,17 @@
from django.urls import path
from preinfo.views import ClientPreinfoView, check_preinfo, PreinfoSearchView, PreinfoListView
from django.urls import path, include
from preinfo.views.client_views import (ClientPreinfoView, check_preinfo, PreinfoSearchView, ClientPreinfoCreateView, ClientPreinfoUpdateView)
from preinfo.views.employee_views import EmployeePreinfoView
urlpatterns = [
path('client/', ClientPreinfoView.as_view(), name='client_preinfo'),
path('client/', include([
path('', ClientPreinfoView.as_view(), name='client_preinfo'),
path('create/', ClientPreinfoCreateView.as_view(), name='client_preinfo_create'),
path('update/<int:pk>/', ClientPreinfoUpdateView.as_view(), name='client_preinfo_update'),
])),
path('check-preinfo/', check_preinfo, name='check_preinfo'),
path('preinfo-search/', PreinfoSearchView.as_view(), name='preinfo_search'),
path('employee_preinfo/', PreinfoListView.as_view(), name='employee_preinfo'),
path('employee/', include([
path('', EmployeePreinfoView.as_view(), name='employee_preinfo'),
])
)
]

@ -1,85 +1,72 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import LoginRequiredMixin
from django.forms import forms
from django.forms.widgets import HiddenInput
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.http import JsonResponse
from django.shortcuts import render, redirect, get_object_or_404
from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.utils import timezone
from django.views import View
from django.views.generic import TemplateView, FormView, CreateView, ListView
from django.views.generic import CreateView, ListView, UpdateView
from DepoT.mixins.crudListViewMixin import CRUDListViewMixin
from common.models import ContainerTypeModel, LinesModel
from DepoT.mixins.LineFiltweFormMixin import LineFilterFormMixin
from common.utils.utils import filter_queryset_by_user, get_preinfo_by_number
from preinfo.forms import PreinfoBaseForm, PreinfoCreateForm, PreinfoEditForm
from preinfo.forms import PreinfoCreateForm, PreinfoEditForm
from preinfo.models import Preinfo
class ClientPreinfoView(LoginRequiredMixin, CRUDListViewMixin, ListView):
# template_name = 'client-preinfo-content.html'
class ClientPreinfoView(LoginRequiredMixin, UserPassesTestMixin, ListView):
# ListView
model = Preinfo
template_name = 'client/preinfo-list.html'
create_form_class = PreinfoCreateForm
form_class = PreinfoEditForm
paginate_by = 4
# CRUDListView template
context_object_name = 'objects'
success_url = reverse_lazy('client_preinfo')
base_template = 'client-base.html'
def get_object_data(self, obj):
return {
'container_number': obj.container_number,
'container_type': obj.container_type.id,
'line': obj.line.id
}
def handle_form_submission(self, request, *args, **kwargs):
object_id = request.POST.get('object_id')
if object_id:
obj = get_object_or_404(self.model, id=object_id)
form = self.form_class(request.POST, instance=obj)
else:
form = self.create_form_class(request.POST)
if form.is_valid():
form.save()
return redirect(self.success_url)
return self.get(request, *args, **kwargs)
def test_func(self):
return self.request.user.has_company_perm('can_view_preinfo') or self.request.user.user_type == 'CA'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = self.form_class() # Add empty form to context
context['create_form'] = self.create_form_class() # Add empty form to context
context['base_template'] = self.base_template # Add empty form to context
queryset = self.model.objects.all().order_by('-created_on')
user = self.request.user
# !!! important
queryset = filter_queryset_by_user( queryset, user)[:10]
# !!! important
context['recent'] = queryset
context['base_template'] = self.base_template
return context
def get_form(self, form_class=None):
form = super().get_form(form_class)
def get_queryset(self):
queryset = super().get_queryset()
user = self.request.user
result = filter_queryset_by_user(queryset, user)
return result
# If user has a specific line, limit the line choices
if user.line:
form.fields['line'].queryset = form.fields['line'].queryset.filter(pk=user.line.pk)
form.fields['line'].initial = user.line
form.fields['line'].widget.readonly = True #form.fields['line'].widget.attrs['disabled'] = True
# Keep the value when form is submitted
# form.fields['line'].widget = HiddenInput()
return form
class ClientPreinfoCreateView(LoginRequiredMixin, UserPassesTestMixin, LineFilterFormMixin, CreateView):
model = Preinfo
template_name = 'client/preinfo-create.html'
form_class = PreinfoCreateForm
success_url = reverse_lazy('client_preinfo')
def test_func(self):
return True # self.request.user.has_company_perm('can_create_preinfo') or self.request.user.user_type == 'CA'
def form_valid(self, form):
form.instance.created_by = self.request.user
form.instance.updated_by = self.request.user
return super().form_valid(form)
# Check if a preinfo exists for the given container number
class ClientPreinfoUpdateView(LoginRequiredMixin, UserPassesTestMixin, LineFilterFormMixin, UpdateView):
model = Preinfo
template_name = 'client/preinfo-edit.html'
form_class = PreinfoEditForm
success_url = reverse_lazy('client_preinfo')
def test_func(self):
return True # self.request.user.ha.s_company_perm('can_edit_preinfo') or self.request.user.user_type == 'CA'
def form_valid(self, form):
form.instance.updated_by = self.request.user
return super().form_valid(form)
def check_preinfo(request):
number = request.GET.get('number')
preinfo = Preinfo.objects.filter(container_number=number, received=False).first()
@ -110,33 +97,3 @@ class PreinfoSearchView(View):
return render(request, self.template_name, {'error': 'Not found'})
class PreinfoListView(CRUDListViewMixin, ListView):
model = Preinfo
template_name = 'employee/preinfo-list.html'
context_object_name = 'objects'
paginate_by = 30
form_class = PreinfoEditForm
base_template = 'employee-base.html'
def get_object_data(self, obj):
return {
'container_number': obj.container_number,
'container_type': obj.container_type.id,
'line': obj.line.id
}
def handle_form_submission(self, request, *args, **kwargs):
object_id = request.POST.get('object_id')
if object_id:
obj = get_object_or_404(self.model, id=object_id)
form = self.form_class(request.POST, instance=obj)
if form.is_valid():
form.save()
return redirect('employee_preinfo')
return self.get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = self.form_class() # Add empty form to context
context['base_template'] = self.base_template # Add empty form to context
return context

@ -0,0 +1,32 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.forms import forms
from django.forms.widgets import HiddenInput
from django.http import JsonResponse
from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse_lazy
from django.utils import timezone
from django.views import View
from django.views.generic import TemplateView, FormView, CreateView, ListView
from DepoT.mixins.crudListViewMixin import CRUDListViewMixin
from common.models import ContainerTypeModel, LinesModel
from common.utils.utils import filter_queryset_by_user, get_preinfo_by_number
from preinfo.forms import PreinfoBaseForm, PreinfoCreateForm, PreinfoEditForm
from preinfo.models import Preinfo
class EmployeePreinfoView(LoginRequiredMixin, UserPassesTestMixin, ListView):
model = Preinfo
template_name = 'employee/preinfo-list.html'
context_object_name = 'objects'
paginate_by = 30
form_class = PreinfoEditForm
base_template = 'employee-base.html'
def test_func(self):
return True # self.request.user.has_employee_perm('can_view_preinfo') or self.request.user.user_type == 'CA'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['base_template'] = self.base_template
return context

@ -0,0 +1,36 @@
document.addEventListener('DOMContentLoaded', function() {
const editBtn = document.getElementById('editBtn');
const deleteBtn = document.getElementById('deleteBtn');
const objectIdInput = document.getElementById('objectIdInput');
const rows = document.querySelectorAll('.selectable-row');
if (editBtn) {
editBtn.setAttribute('disabled', '');
}
if (deleteBtn) {
deleteBtn.setAttribute('disabled', '');
}
rows.forEach(row => {
row.addEventListener('click', function() {
// Remove previous selection
document.querySelector('.selected-row')?.classList.remove('selected-row');
// Select current row
this.classList.add('selected-row');
const objectId = this.dataset.id;
if (editBtn) {
editBtn.removeAttribute('disabled'); // Remove disabled attribute completely
editBtn.href = editBtn.dataset.url.replace('0', objectId);
}
if (deleteBtn) {
deleteBtn.removeAttribute('disabled');
}
// Check the hidden radio button
const radio = this.querySelector('input[type="radio"]');
radio.checked = true;
});
});
});

@ -0,0 +1,59 @@
body {
display: flex;
height: 100vh;
overflow: hidden;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #F2E8DB; /* Add this */
margin: 0;
}
.content-area {
flex: 1;
overflow-y: auto;
}
.top-nav {
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.nav-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
}
.nav-right {
display: flex;
align-items: center;
gap: 1rem;
}
.page-title {
font-size: 1.25rem;
font-weight: 600;
color: #1a202c;
margin: 0;
}
.icon-button {
background: none;
border: none;
cursor: pointer;
color: #718096;
padding: 0.5rem;
transition: color 0.2s;
}
.icon-button:hover {
color: #4a5568;
}
.icon {
width: 1.5rem;
height: 1.5rem;
}
.content {
padding: 1.5rem;
}

@ -0,0 +1,116 @@
.dashboard-wrapper {
padding: 1.5rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
margin-bottom: 1.5rem;
}
.tables-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
gap: 1.5rem;
}
.dashboard-card {
/*background-color: #ece1d8;*/
border: 1px solid #a57d52;
border-radius: 0.5rem;
padding: 1.5rem;
}
.card-content {
display: flex;
align-items: center;
}
.icon-circle {
padding: 0.75rem;
border-radius: 50%;
background-color: #ead9cb;
margin-right: 1rem;
}
.dashboard-icon {
width: 2rem;
height: 2rem;
stroke: #a57d52;
}
.stat-info h3 {
font-size: 1.125rem;
font-weight: 600;
/*color: #a57d52;*/
margin: 0;
}
.stat-number {
font-size: 1.875rem;
font-weight: bold;
/*color: #a57d52;*/
margin: 0.25rem 0;
}
.stat-change {
font-size: 0.875rem;
color: #68a357;
margin: 0;
}
.card-header {
padding-bottom: 1rem;
border-bottom: 1px solid #a57d52;
margin-bottom: 1rem;
}
.card-header h3 {
font-size: 1.125rem;
font-weight: 600;
/*color: #a57d52;*/
margin: 0;
}
/*.dashboard-table {*/
/* width: 100%;*/
/* border-collapse: collapse;*/
/*}*/
/*.dashboard-table th {*/
/* text-align: left;*/
/* padding: 0.75rem;*/
/* font-size: 0.75rem;*/
/* text-transform: uppercase;*/
/* !*color: #a57d52;*!*/
/*}*/
/*.dashboard-table td {*/
/* padding: 0.75rem;*/
/* font-size: 0.875rem;*/
/* !*color: #666;*!*/
/*}*/
/*.status-tag {*/
/* display: inline-block;*/
/* padding: 0.25rem 0.75rem;*/
/* border-radius: 9999px;*/
/* font-size: 0.75rem;*/
/* font-weight: 600;*/
/*}*/
/*.status-received {*/
/* background-color: #e8f5e9;*/
/* color: #2e7d32;*/
/*}*/
/*.status-pending {*/
/* background-color: #fff3e0;*/
/* color: #ef6c00;*/
/*}*/
/*.status-overdue {*/
/* background-color: #ffebee;*/
/* color: #c62828;*/
/*}*/

@ -1,18 +1,35 @@
/* Basic form styling */
:root {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 16px;
font-weight: 600; /* Makes all text semi-bold by default */
line-height: 1.5;
}
form {
max-width: 600px;
margin: 20px auto;
padding: 20px;
/*background-color: #ead9cb;; !* Add this line *!*/
border-radius: 8px; /* Optional: for better appearance */
/*box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);*/
border: 1px solid #a57d52;
/*border-width: 3px;*/
/*border-color: #00aaff;*/
}
/* Label styling */
label {
display: block;
margin-bottom: 2px;
font-weight: 500;
/*font-weight: 500;*/
color: #a57d52;
}
/* Input field styling */
input[type="text"],
input[type="email"],
@ -23,10 +40,18 @@ select,
textarea {
width: 100%;
padding: 8px;
margin-bottom: 10px;
margin-bottom: 0px;
border: 1px solid #a57d52;
border-radius: 4px;
box-sizing: border-box;
font-family: "Noto Sans", sans-serif;
font-optical-sizing: auto;
font-weight: 500;
font-style: normal;
font-size: 1.1rem;
font-variation-settings:
"wdth" 100;
}
/* Submit button styling */
@ -82,68 +107,142 @@ button[type="submit"]:hover {
}
.card-body {
padding: 24px;
padding: 15px;
overflow-x: auto; /* Allow horizontal scroll if table is wide */
}
.table-container {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
}
thead {
background-color: #f9f9f9;
}
th, td {
padding: 12px 16px;
text-align: left;
font-size: 14px;
}
th {
font-weight: bold;
text-transform: uppercase;
color: #666;
border-bottom: 1px solid #ddd;
}
tbody tr {
border-bottom: 1px solid #eee;
}
.status {
display: inline-block;
padding: 4px 8px;
font-size: 12px;
font-weight: bold;
border-radius: 12px;
background-color: #e0f0ff;
color: #0066cc;
}
.actions button {
background: none;
border: none;
color: #0066cc;
font-size: 14px;
cursor: pointer;
margin-right: 10px;
}
.actions button:hover {
color: #004999;
}
.actions button:last-child {
color: #cc0000;
margin-right: 0;
}
.actions button:last-child:hover {
color: #990000;
/*table {*/
/* width: 100%;*/
/* border-collapse: collapse;*/
/*}*/
/*thead {*/
/* background-color: #f9f9f9;*/
/*}*/
/*th, td {*/
/* padding: 12px 16px;*/
/* text-align: left;*/
/* font-size: 14px;*/
/*}*/
/*th {*/
/* font-weight: bold;*/
/* text-transform: uppercase;*/
/* color: #666;*/
/* border-bottom: 1px solid #ddd;*/
/*}*/
/*tbody tr {*/
/* border-bottom: 1px solid #eee;*/
/*}*/
/* Rename dashboard-table to match exactly with your HTML */
/*.dashboard-table,*/
/*table {*/
/* border-collapse: separate;*/
/* border-spacing: 0;*/
/* margin: 10px auto;*/
/* width: 100%;*/
/* border: 1px solid #E1C6A8; !* Make border more visible *!*/
/* border-radius: 10px;*/
/* overflow: hidden;*/
/* line-height: 1.2;*/
/*}*/
/*!* Keep header and footer styles *!*/
/*thead {*/
/* background-color: #E1C6A8;*/
/*}*/
/*tfoot {*/
/* background-color: #E1C6A8;*/
/*}*/
/*!* Update cell styles *!*/
/*.dashboard-table th,*/
/*.dashboard-table td,*/
/*table th,*/
/*table td {*/
/* padding: 8px;*/
/* text-align: center;*/
/* border-right: 1px solid #E1C6A8; !* Add vertical borders *!*/
/* border-bottom: 1px solid #E1C6A8; !* Add horizontal borders *!*/
/*}*/
/*!* Remove right border from last cell in each row *!*/
/*.dashboard-table th:last-child,*/
/*.dashboard-table td:last-child,*/
/*table th:last-child,*/
/*table td:last-child {*/
/* border-right: none;*/
/*}*/
/*!* Remove bottom border from last row cells *!*/
/*.dashboard-table tr:last-child td,*/
/*table tr:last-child td {*/
/* border-bottom: none;*/
/*}*/
/*.footer-container {*/
/* display: flex;*/
/* justify-content: space-between;*/
/* align-items: center;*/
/*}*/
/*.footer-left button,*/
/*.footer-right button {*/
/* margin: 0 3px;*/
/* padding: 3px 6px;*/
/* font-size: 12px;*/
/*}*/
/*.page-number {*/
/* margin: 0 10px;*/
/* font-weight: bold;*/
/*}*/
/*.status {*/
/* display: inline-block;*/
/* padding: 4px 8px;*/
/* font-size: 12px;*/
/* font-weight: bold;*/
/* border-radius: 12px;*/
/* background-color: #e0f0ff;*/
/* color: #0066cc;*/
/*}*/
/*.actions button {*/
/* background: none;*/
/* border: none;*/
/* color: #0066cc;*/
/* font-size: 14px;*/
/* cursor: pointer;*/
/* margin-right: 10px;*/
/*}*/
/*.actions button:hover {*/
/* color: #004999;*/
/*}*/
/*.actions button:last-child {*/
/* color: #cc0000;*/
/* margin-right: 0;*/
/*}*/
/*.actions button:last-child:hover {*/
/* color: #990000;*/
/*}*/
/* Update dashboard card styles */
.dashboard-card {
margin: 15px;
background-color: white;
border-radius: 10px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
overflow: hidden; /* Ensure content doesn't overflow */
}

@ -0,0 +1,107 @@
.sidebar {
width: 16rem;
height: 100%;
display: flex;
flex-direction: column;
}
.header {
padding: 1.25rem;
border-bottom: 1px solid #a57d52;
}
.header h1 {
font-size: 1.25rem;
font-weight: bold;
/*color: #a57d52;*/
}
.subtitle {
font-size: 0.875rem;
/*color: #a57d52;*/
}
.section-title {
padding: 0.5rem 1rem;
font-size: 0.75rem;
/*color: #a57d52;*/
text-transform: uppercase;
letter-spacing: 0.05em;
}
.nav-item {
display: flex;
align-items: center;
padding: 0.75rem 1.5rem;
text-decoration: none;
border-radius: 0.5rem;
margin: 0 0.5rem;
}
.nav-item:hover {
border: 1px solid #a57d52;
background-color: transparent;
}
.nav-item.active {
background-color: rgba(165, 125, 82, 0.1);
}
.nav-menu {
flex: 1 1 auto; /* Changed from flex-grow: 1 */
padding: 1rem 0;
}
.icon {
height: 1.25rem;
width: 1.25rem;
margin-right: 0.75rem;
stroke: currentColor;
}
.user-profile {
margin-top: auto; /* Added this line */
padding: 1rem;
border-top: 1px solid #a57d52;
display: flex;
align-items: center;
}
.avatar {
width: 2.5rem;
height: 2.5rem;
background-color: #a57d52;
color: #ead9cb;
border-radius: 9999px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.user-info {
margin-left: 0.75rem;
}
.username {
font-size: 0.875rem;
font-weight: 500;
/*color: #a57d52;*/
}
.company {
font-size: 0.75rem;
/*color: #a57d52;*/
}
.logout {
margin-left: auto;
color: #a57d52;
padding: 0.5rem;
border-radius: 0.5rem;
/*transition: all 0.2s;*/
}
.logout:hover {
border: 1px solid #a57d52;
}

@ -1,5 +1,5 @@
body {
background: linear-gradient(135deg, #EDDECB 0%, #E1C6A8 100%);
/*background: linear-gradient(135deg, #EDDECB 0%, #E1C6A8 100%);*/
min-height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;

@ -0,0 +1,196 @@
/*.table {*/
/* width: 100%;*/
/* border-collapse: separate;*/
/* border-spacing: 0;*/
/* margin: 10px auto;*/
/* border: 1px solid #a57d52;*/
/* border-radius: 10px;*/
/* overflow: hidden;*/
/*}*/
/*.table th {*/
/* text-align: left;*/
/* padding: 0.75rem;*/
/* font-size: 0.75rem;*/
/* text-transform: uppercase;*/
/* font-weight: 600;*/
/* border-bottom: 1px solid #a57d52;*/
/* background-color: #F2E8DB;*/
/*}*/
/*.table td {*/
/* padding: 0.75rem;*/
/* font-size: 0.875rem;*/
/* border-bottom: 1px solid #a57d52;*/
/*}*/
/*!* Last row shouldn't have bottom border *!*/
/*.table tr:last-child td {*/
/* border-bottom: none;*/
/*}*/
/*!* Selection styles *!*/
/*.selectable-row {*/
/* cursor: pointer;*/
/*}*/
/*.selected-row {*/
/* background-color: rgba(165, 125, 82, 0.1);*/
/*}*/
/*.status-received {*/
/* background-color: #e8f5e9;*/
/* color: #2e7d32;*/
/*}*/
/*.status-pending {*/
/* background-color: #fff3e0;*/
/* color: #ef6c00;*/
/*}*/
/*.status-overdue {*/
/* background-color: #ffebee;*/
/* color: #c62828;*/
/*}*/
table {
width: 100%;
border-collapse: collapse;
}
thead {
background-color: #f9f9f9;
}
th, td {
padding: 12px 16px;
text-align: left;
font-size: 14px;
}
th {
font-weight: bold;
text-transform: uppercase;
color: #666;
border-bottom: 1px solid #ddd;
}
tbody tr {
border-bottom: 1px solid #eee;
}
/*Rename dashboard-table to match exactly with your HTML */
.dashboard-table,
table {
border-collapse: separate;
border-spacing: 0;
margin: 10px auto;
width: 100%;
border: 1px solid #E1C6A8; /* Make border more visible */
border-radius: 10px;
overflow: hidden;
line-height: 1.2;
}
/* Keep header and footer styles */
thead {
background-color: #E1C6A8;
}
tfoot {
background-color: #E1C6A8;
}
/* Update cell styles */
.dashboard-table th,
.dashboard-table td,
table th,
table td {
padding: 8px;
text-align: center;
border-right: 1px solid #E1C6A8; /* Add vertical borders */
border-bottom: 1px solid #E1C6A8; /* Add horizontal borders */
}
/* Remove right border from last cell in each row */
.dashboard-table th:last-child,
.dashboard-table td:last-child,
table th:last-child,
table td:last-child {
border-right: none;
}
/* Remove bottom border from last row cells */
.dashboard-table tr:last-child td,
table tr:last-child td {
border-bottom: none;
}
.footer-container {
display: flex;
justify-content: space-between;
align-items: center;
}
.footer-left button,
.footer-right button {
margin: 0 3px;
padding: 3px 6px;
font-size: 12px;
}
.page-number {
margin: 0 10px;
font-weight: bold;
}
.status {
display: inline-block;
padding: 4px 8px;
font-size: 12px;
font-weight: bold;
border-radius: 12px;
background-color: #e0f0ff;
color: #0066cc;
}
.actions button {
background: none;
border: none;
color: #0066cc;
font-size: 14px;
cursor: pointer;
margin-right: 10px;
}
.actions button:hover {
color: #004999;
}
.actions button:last-child {
color: #cc0000;
margin-right: 0;
}
.actions button:last-child:hover {
color: #990000;
}

@ -1,10 +1,23 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>$Title$</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Line Operator Dashboard | Container Depot</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&family=Urbanist:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{% static 'styles/forms.css' %}?v={% now 'U' %}">
{# <script src="https://cdn.tailwindcss.com"></script>#}
</head>
<body>
$END$
{% block content %}
{% endblock content %}
</body>
</html>

@ -1,64 +1,6 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Line Operator Dashboard | Container Depot</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f7fa;
}
.sidebar {
background: linear-gradient(180deg, #0f4c81 0%, #1a6baf 100%);
transition: all 0.3s;
}
.content-area {
transition: all 0.3s;
}
.nav-item {
transition: all 0.2s;
}
.nav-item:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.nav-item.active {
background-color: rgba(255, 255, 255, 0.2);
border-left: 4px solid white;
}
.card {
transition: all 0.3s;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.tab {
transition: all 0.2s;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.form-section {
animation: slideIn 0.5s ease-in-out;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body>
{% load static %}
{% extends 'barrier/barrier-base.html' %}
{% block content %}
<div class="sidebar p-5 text-white border-b border-blue-700">
<h1 class="text-xl font-bold">Container Depot</h1>
<p class="text-sm text-blue-200">Line Operator Portal</p>
@ -76,25 +18,25 @@
<div class="px-4 py-2 text-xs text-blue-300 uppercase tracking-wider">Main</div>
<a href="{% url 'barrier_dashboard' %}" class="nav-item active flex items-center px-6 py-10 text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg>
Dashboard
</a>
<a href="{% url 'preinfo_search' %}?param=container_receive" id="ordersNav" class="nav-item flex items-center px-6 py-10 text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"></path>
</svg>
Receive container
</a>
<a href="{% url 'container_search' %}?param=container_expedition" id="ordersNav" class="nav-item flex items-center px-6 py-10 text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"></path>
</svg>
Expedite container
</a>
<a href="#" class="nav-item flex items-center px-6 py-3 text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
Photos
</a>
@ -126,8 +68,6 @@
{# </div>#}
{# </div>#}
</div>
<div class="flex-grow"></div>
</div>
</div>
</body>
</html>
{% endblock content %}

@ -13,17 +13,17 @@ table {
border-spacing: 0;
margin: 10px auto;
width: 80%;
border: 1px solid #ddd;
border: 1px solid #E1C6A8;
border-radius: 10px;
overflow: hidden;
line-height: 1.2;
font-size: 12px; /* по-малък текст навсякъде */
{#font-size: 12px; /* по-малък текст навсякъде */ #}
}
thead {
background-color: #f2f2f2;
background-color: #E1C6A8;
}
tfoot {
background-color: #f9f9f9;
background-color: #E1C6A8;
}
th, td {
padding: 4px 6px;

@ -5,192 +5,45 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Line Operator Dashboard | Container Depot</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f7fa;
}
.sidebar {
background: linear-gradient(180deg, #0f4c81 0%, #1a6baf 100%);
transition: all 0.3s;
}
.content-area {
transition: all 0.3s;
}
.nav-item {
transition: all 0.2s;
}
.nav-item:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.nav-item.active {
background-color: rgba(255, 255, 255, 0.2);
border-left: 4px solid white;
}
.card {
transition: all 0.3s;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.tab {
transition: all 0.2s;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.form-section {
animation: slideIn 0.5s ease-in-out;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
<link rel="stylesheet" href="{% static 'styles/tables.css' %}">
<link rel="stylesheet" href="{% static 'styles/forms.css' %}">
<link rel="stylesheet" href="{% static 'styles/sidebar.css' %}">
<link rel="stylesheet" href="{% static 'styles/base.css' %}">
<link rel="stylesheet" href="{% static 'styles/dashboard-content.css' %}">
</head>
<body class="flex h-screen overflow-hidden">
<!-- Sidebar -->
<body>
<aside class="sidebar">
{% include 'client-sidebar.html' %}
<!-- Main Content -->
<main class="content-area flex-1 overflow-y-auto">
<!-- Top Navigation -->
<header class="bg-white shadow-sm">
<div class="flex items-center justify-between px-6 py-4">
<div class="flex items-center">
<h2 class="text-xl font-semibold text-gray-800">Dashboard</h2>
</div>
<div class="flex items-center space-x-4">
<button class="text-gray-500 hover:text-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
</button>
<button class="text-gray-500 hover:text-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</button>
</div>
</div>
</header>
</aside>
<main class="content-area">
{# <header class="top-nav">#}
{# <div class="nav-container">#}
{# <div class="nav-left">#}
{# <h2 class="page-title">Dashboard</h2>#}
{# </div>#}
{# <div class="nav-right">#}
{# <button class="icon-button">#}
{# <svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">#}
{# <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />#}
{# </svg>#}
{# </button>#}
{# <button class="icon-button">#}
{# <svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">#}
{# <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />#}
{# </svg>#}
{# </button>#}
{# </div>#}
{# </div>#}
{# </header>#}
<!-- Dashboard Content -->
<div class="p-6">
<div class="content">
{% block content %}
{# {% include 'client-dashboard-content.html' %}#}
{# {% include 'client-preinfo-content.html' %}#}
{# {% include 'client-orders-content.html' %}#}
{% endblock content %}
</div>
</main>
{# <script>#}
{# // Tab Navigation#}
{# const dashboardContent = document.getElementById('dashboardContent');#}
{# const preinfoContent = document.getElementById('preinfoContent');#}
{# const ordersContent = document.getElementById('ordersContent');#}
{# #}
{# const preinfoNav = document.getElementById('preinfoNav');#}
{# const ordersNav = document.getElementById('ordersNav');#}
{# #}
{# preinfoNav.addEventListener('click', function(e) {#}
{# e.preventDefault();#}
{# #}
{# // Update content visibility#}
{# dashboardContent.classList.remove('active');#}
{# preinfoContent.classList.add('active');#}
{# ordersContent.classList.remove('active');#}
{# #}
{# // Update navigation styling#}
{# document.querySelector('.nav-item.active').classList.remove('active');#}
{# preinfoNav.classList.add('active');#}
{# #}
{# // Update header title#}
{# document.querySelector('header h2').textContent = 'Container Preinfo';#}
{# });#}
{# #}
{# ordersNav.addEventListener('click', function(e) {#}
{# e.preventDefault();#}
{# #}
{# // Update content visibility#}
{# dashboardContent.classList.remove('active');#}
{# preinfoContent.classList.remove('active');#}
{# ordersContent.classList.add('active');#}
{# #}
{# // Update navigation styling#}
{# document.querySelector('.nav-item.active').classList.remove('active');#}
{# ordersNav.classList.add('active');#}
{# #}
{# // Update header title#}
{# document.querySelector('header h2').textContent = 'Expedition Orders';#}
{# });#}
{# #}
{# // Damage checkbox toggle#}
{# const damageCheck = document.getElementById('damageCheck');#}
{# const damageDetails = document.getElementById('damageDetails');#}
{# #}
{# damageCheck.addEventListener('change', function() {#}
{# if (this.checked) {#}
{# damageDetails.classList.remove('hidden');#}
{# } else {#}
{# damageDetails.classList.add('hidden');#}
{# }#}
{# });#}
{# #}
{# // Order type toggle#}
{# const specificContainer = document.getElementById('specificContainer');#}
{# const anyContainer = document.getElementById('anyContainer');#}
{# const specificContainerFields = document.getElementById('specificContainerFields');#}
{# const anyContainerFields = document.getElementById('anyContainerFields');#}
{# #}
{# specificContainer.addEventListener('change', function() {#}
{# if (this.checked) {#}
{# specificContainerFields.classList.remove('hidden');#}
{# anyContainerFields.classList.add('hidden');#}
{# }#}
{# });#}
{# #}
{# anyContainer.addEventListener('change', function() {#}
{# if (this.checked) {#}
{# specificContainerFields.classList.add('hidden');#}
{# anyContainerFields.classList.remove('hidden');#}
{# }#}
{# });#}
{# #}
{# // Form submissions#}
{# document.getElementById('preinfoForm').addEventListener('submit', function(e) {#}
{# e.preventDefault();#}
{# #}
{# // In a real app, this would send data to the server#}
{# alert('Preinfo submitted successfully!');#}
{# this.reset();#}
{# });#}
{# #}
{# document.getElementById('orderForm').addEventListener('submit', function(e) {#}
{# e.preventDefault();#}
{# #}
{# // In a real app, this would send data to the server#}
{# alert('Expedition order submitted successfully!');#}
{# this.reset();#}
{# });#}
{# #}
{# // Add more container button#}
{# document.getElementById('addMoreContainer').addEventListener('click', function() {#}
{# alert('In a real app, this would add fields for another container');#}
{# });#}
{# </script>#}
{#<script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'947f86ac452d3130',t:'MTc0ODYyMTY4Mi4wMDAwMDA='};var a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>#}
</body>
</html>

@ -1,162 +1,269 @@
{% extends 'client-base.html' %}
{% block content %}
<div id="dashboardContent" class="tab-content active">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
<div class="card bg-white rounded-lg shadow p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-blue-100 text-blue-600">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
{# <div id="dashboardContent" class="tab-content active">#}
{# <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">#}
{# <div class="card bg-white rounded-lg shadow p-6">#}
{# <div class="flex items-center">#}
{# <div class="p-3 rounded-full bg-blue-100 text-blue-600">#}
{# <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">#}
{# <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />#}
{# </svg>#}
{# </div>#}
{# <div class="ml-4">#}
{# <h3 class="text-lg font-semibold text-gray-700">Active Containers</h3>#}
{# <p class="text-3xl font-bold text-gray-900">42</p>#}
{# <p class="text-sm text-green-600">+3 since last week</p>#}
{# </div>#}
{# </div>#}
{# </div>#}
{##}
{# <div class="card bg-white rounded-lg shadow p-6">#}
{# <div class="flex items-center">#}
{# <div class="p-3 rounded-full bg-green-100 text-green-600">#}
{# <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">#}
{# <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />#}
{# </svg>#}
{# </div>#}
{# <div class="ml-4">#}
{# <h3 class="text-lg font-semibold text-gray-700">Preinfo Sent</h3>#}
{# <p class="text-3xl font-bold text-gray-900">18</p>#}
{# <p class="text-sm text-green-600">+5 since last week</p>#}
{# </div>#}
{# </div>#}
{# </div>#}
{##}
{# <div class="card bg-white rounded-lg shadow p-6">#}
{# <div class="flex items-center">#}
{# <div class="p-3 rounded-full bg-orange-100 text-orange-600">#}
{# <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">#}
{# <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />#}
{# </svg>#}
{# </div>#}
{# <div class="ml-4">#}
{# <h3 class="text-lg font-semibold text-gray-700">Bookings</h3>#}
{# <p class="text-3xl font-bold text-gray-900">7</p>#}
{# <p class="text-sm text-orange-600">2 require attention</p>#}
{# </div>#}
{# </div>#}
{# </div>#}
{# </div>#}
{##}
{# <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">#}
{# <div class="bg-white rounded-lg shadow">#}
{# <div class="px-6 py-4 border-b border-gray-200">#}
{# <h3 class="text-lg font-semibold text-gray-800">Recent Container Activity</h3>#}
{# </div>#}
{# <div class="p-6">#}
{# <div class="overflow-x-auto">#}
{# <table class="min-w-full divide-y divide-gray-200">#}
{# <thead>#}
{# <tr>#}
{# <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Container</th>#}
{# <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>#}
{# <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>#}
{# <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>#}
{# </tr>#}
{# </thead>#}
{# <tbody class="divide-y divide-gray-200">#}
{# <tr>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">MSCU1234567</td>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">40HC</td>#}
{# <td class="px-4 py-3 whitespace-nowrap">#}
{# <span class="px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">Received</span>#}
{# </td>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-06-15</td>#}
{# </tr>#}
{# <tr>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">MSCU7654321</td>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">20DV</td>#}
{# <td class="px-4 py-3 whitespace-nowrap">#}
{# <span class="px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800">Preinfo</span>#}
{# </td>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-06-14</td>#}
{# </tr>#}
{# <tr>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">MSCU2468135</td>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">40DV</td>#}
{# <td class="px-4 py-3 whitespace-nowrap">#}
{# <span class="px-2 py-1 text-xs font-semibold rounded-full bg-orange-100 text-orange-800">Order</span>#}
{# </td>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-06-13</td>#}
{# </tr>#}
{# <tr>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">MSCU1357924</td>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">20RF</td>#}
{# <td class="px-4 py-3 whitespace-nowrap">#}
{# <span class="px-2 py-1 text-xs font-semibold rounded-full bg-red-100 text-red-800">Expedited</span>#}
{# </td>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-06-12</td>#}
{# </tr>#}
{# </tbody>#}
{# </table>#}
{# </div>#}
{# </div>#}
{# </div>#}
{##}
{# <div class="bg-white rounded-lg shadow">#}
{# <div class="px-6 py-4 border-b border-gray-200">#}
{# <h3 class="text-lg font-semibold text-gray-800">Payment Status</h3>#}
{# </div>#}
{# <div class="p-6">#}
{# <div class="overflow-x-auto">#}
{# <table class="min-w-full divide-y divide-gray-200">#}
{# <thead>#}
{# <tr>#}
{# <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Invoice</th>#}
{# <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Amount</th>#}
{# <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>#}
{# <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Due Date</th>#}
{# </tr>#}
{# </thead>#}
{# <tbody class="divide-y divide-gray-200">#}
{# <tr>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">INV-2023-0042</td>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">$1,250.00</td>#}
{# <td class="px-4 py-3 whitespace-nowrap">#}
{# <span class="px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">Paid</span>#}
{# </td>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-06-10</td>#}
{# </tr>#}
{# <tr>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">INV-2023-0041</td>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">$875.50</td>#}
{# <td class="px-4 py-3 whitespace-nowrap">#}
{# <span class="px-2 py-1 text-xs font-semibold rounded-full bg-yellow-100 text-yellow-800">Pending</span>#}
{# </td>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-06-20</td>#}
{# </tr>#}
{# <tr>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">INV-2023-0040</td>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">$2,100.00</td>#}
{# <td class="px-4 py-3 whitespace-nowrap">#}
{# <span class="px-2 py-1 text-xs font-semibold rounded-full bg-red-100 text-red-800">Overdue</span>#}
{# </td>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-06-05</td>#}
{# </tr>#}
{# <tr>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">INV-2023-0039</td>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">$950.25</td>#}
{# <td class="px-4 py-3 whitespace-nowrap">#}
{# <span class="px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">Paid</span>#}
{# </td>#}
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-05-28</td>#}
{# </tr>#}
{# </tbody>#}
{# </table>#}
{# </div>#}
{# </div>#}
{# </div>#}
{# </div>#}
{# </div>#}
<div class="dashboard-wrapper">
<div class="stats-grid">
<div class="dashboard-card">
<div class="card-content">
<div class="icon-circle">
<svg xmlns="http://www.w3.org/2000/svg" class="dashboard-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
</div>
<div class="ml-4">
<h3 class="text-lg font-semibold text-gray-700">Active Containers</h3>
<p class="text-3xl font-bold text-gray-900">42</p>
<p class="text-sm text-green-600">+3 since last week</p>
<div class="stat-info">
<h3>Active Containers</h3>
<p class="stat-number">42</p>
<p class="stat-change">+3 since last week</p>
</div>
</div>
</div>
<div class="card bg-white rounded-lg shadow p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-green-100 text-green-600">
<div class="dashboard-card">
<div class="card-content">
<div class="icon-circle">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-4">
<h3 class="text-lg font-semibold text-gray-700">Preinfo Sent</h3>
<p class="text-3xl font-bold text-gray-900">18</p>
<p class="text-sm text-green-600">+5 since last week</p>
<div class="stat-info">
<h3>Preinfo sent</h3>
<p class="stat-number">17</p>
<p class="stat-change">+7 since last week</p>
</div>
</div>
</div>
<div class="card bg-white rounded-lg shadow p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-orange-100 text-orange-600">
<div class="dashboard-card">
<div class="card-content">
<div class="icon-circle">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
</svg>
</div>
<div class="ml-4">
<h3 class="text-lg font-semibold text-gray-700">Bookings</h3>
<p class="text-3xl font-bold text-gray-900">7</p>
<p class="text-sm text-orange-600">2 require attention</p>
<div class="stat-info">
<h3>Bookings active</h3>
<p class="stat-number">4</p>
<p class="stat-change">+8 since last week</p>
</div>
</div>
</div>
<!-- Other two cards similar structure -->
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="bg-white rounded-lg shadow">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-semibold text-gray-800">Recent Container Activity</h3>
</div>
<div class="p-6">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<div class="tables-grid">
<div class="dashboard-card">
<div class="card-header">
<h3>Recent Container Activity</h3>
</div>
<div class="card-body">
<table class="table">
<thead>
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Container</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
<th>Container</th>
<th>Type</th>
<th>Status</th>
<th>Date</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tr>
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">MSCU1234567</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">40HC</td>
<td class="px-4 py-3 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">Received</span>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-06-15</td>
</tr>
<tr>
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">MSCU7654321</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">20DV</td>
<td class="px-4 py-3 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800">Preinfo</span>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-06-14</td>
</tr>
<tbody>
<tr>
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">MSCU2468135</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">40DV</td>
<td class="px-4 py-3 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-semibold rounded-full bg-orange-100 text-orange-800">Order</span>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-06-13</td>
</tr>
<tr>
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">MSCU1357924</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">20RF</td>
<td class="px-4 py-3 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-semibold rounded-full bg-red-100 text-red-800">Expedited</span>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-06-12</td>
<td>MSCU1234567</td>
<td>40HC</td>
<td><span class="status-tag status-received">Received</span></td>
<td>2023-06-15</td>
</tr>
<!-- Other rows similar structure -->
</tbody>
</table>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-semibold text-gray-800">Payment Status</h3>
<div class="dashboard-card">
<div class="card-header">
<h3>Recent payments</h3>
</div>
<div class="p-6">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<div class="card-body">
<table class="table">
<thead>
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Invoice</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Amount</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Due Date</th>
<th>Container</th>
<th>Type</th>
<th>Status</th>
<th>Date</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tbody>
<tr>
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">INV-2023-0042</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">$1,250.00</td>
<td class="px-4 py-3 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">Paid</span>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-06-10</td>
</tr>
<tr>
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">INV-2023-0041</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">$875.50</td>
<td class="px-4 py-3 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-semibold rounded-full bg-yellow-100 text-yellow-800">Pending</span>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-06-20</td>
</tr>
<tr>
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">INV-2023-0040</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">$2,100.00</td>
<td class="px-4 py-3 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-semibold rounded-full bg-red-100 text-red-800">Overdue</span>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-06-05</td>
</tr>
<tr>
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">INV-2023-0039</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">$950.25</td>
<td class="px-4 py-3 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">Paid</span>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-05-28</td>
<td>MSCU1234567</td>
<td>40HC</td>
<td><span class="status-tag status-received">Received</span></td>
<td>2023-06-15</td>
</tr>
<!-- Other rows similar structure -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

@ -1,64 +1,66 @@
{% load static %}
<aside class="sidebar w-64 h-full text-white flex flex-col">
<div class="p-5 border-b border-blue-700">
<h1 class="text-xl font-bold">Container Depot</h1>
<p class="text-sm text-blue-200">Line Operator Portal</p>
<aside class="sidebar">
<div class="header">
<h1>Container Depot</h1>
<p class="subtitle">Line Operator Portal</p>
</div>
{% url 'dashboard' as dashboard_url %}
{% url 'client_preinfo' as client_preinfo_url %}
{% url 'client_booking' as client_booking_url %}
{% url 'register' as register_url %}
<nav class="flex-grow py-4">
<div class="px-4 py-2 text-xs text-blue-300 uppercase tracking-wider">Main</div>
<a href="{% url 'dashboard' %}" class="nav-item active flex items-center px-6 py-3 text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<nav class="nav-menu">
<div class="section-title">Main</div>
<a href="{{ dashboard_url }}" class="nav-item {% if request.path == dashboard_url %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Dashboard
</a>
<a href="{% url 'client_preinfo' %}" id="preinfoNav" class="nav-item flex items-center px-6 py-3 text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<a href="{{ client_preinfo_url }}" class="nav-item {% if request.path == client_preinfo_url %}active{% endif %}" id="preinfoNav">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
Container Preinfo
</a>
<a href="{% url 'client_booking' %}" id="ordersNav" class="nav-item flex items-center px-6 py-3 text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<a href="{{ client_booking_url }}" class="nav-item {% if request.path == client_booking_url %}active{% endif %}" id="ordersNav">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
</svg>
Bookings
</a>
<a href="#" class="nav-item flex items-center px-6 py-3 text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
<div class="section-title account">Reports</div>
<a href="#" class="nav-item">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm-1.95-9H15v2h-4.95a2.5 2.5 0 0 0 4.064 1.41l1.7 1.133A4.5 4.5 0 0 1 8.028 13H7v-2h1.027a4.5 4.5 0 0 1 7.788-2.543L14.114 9.59A2.5 2.5 0 0 0 10.05 11z"/>
</g>
</svg>
Reports
</a>
{% if request.user.UserType.COMPANY_ADMIN %}
<div class="px-4 py-2 mt-6 text-xs text-blue-300 uppercase tracking-wider">Account</div>
<a href="{% url 'register' %}" class="nav-item flex items-center px-6 py-3 text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
{% if request.user.UserType == 'CA' %}
<div class="section-title account">Account</div>
<a href="{{ register_url }}" class="nav-item {% if request.path == register_url %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
Settings
Accounts
</a>
{% endif %}
</nav>
<div class="p-4 border-t border-blue-700">
<div class="flex items-center">
<div class="w-10 h-10 rounded-full bg-blue-500 flex items-center justify-center text-white font-bold">
LO
<div class="user-profile">
<div class="avatar">LO</div>
<div class="user-info">
<p class="username">{{ request.user }}</p>
<p class="company">{{ request.user.company }}</p>
</div>
<div class="ml-3">
<p class="text-sm font-medium">{{ request.user }}</p>
<p class="text-xs text-blue-300">{{ request.user.company }}</p>
</div>
<a href="{% url 'login' %}" class="ml-auto text-white" >
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<a href="{% url 'login' %}" class="logout">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
</a>
</div>
</div>
</aside>

@ -1,10 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>$Title$</title>
</head>
<body>
$END$
</body>
</html>
{% extends 'client-base.html' %}
{% load static %}
{% block content %}
<div class="container">
<h2>Booking</h2>
<p>Manage your container booking details here.</p>
</div>
<form id="preinfoForm" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit booking">
</form>
{%endblock content %}

@ -1,10 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>$Title$</title>
</head>
<body>
$END$
</body>
</html>
{% extends 'list-crud.html' %}
{% load static %}
{% block table_header %}
<th style="display: none;">Select</th>
<th>Line</th>
<th>Booking №</th>
<th>Container type</th>
<th>Container №</th>
<th>Containers count</th>
<th>Containers left</th>
<th>Vehicles</th>
<th>Vehicles left</th>
<th>Created on</th>
<th>Created by</th>
{% endblock table_header %}
{% block table_data %}
<td>{{ object.line.short_name }}</td>
<td>{{ object.number }}</td>
<td>{{ object.container_type }}</td>
<td>{{ object.container_number }}</td>
<td>{{ object.container_count }}</td>
<td>{{ object.containers_left }}</td>
<td>{{ object.vehicles }}</td>
<td>{{ object.vehicles_left }}</td>
<td>{{ object.created_on }}</td>
<td>{{ object.created_by.username }}</td>
{% endblock %}
{% block buttons %}
<a href="{% url 'client_booking_create' %}" class="btn btn-primary">Create Preinfo</a>
<a href="#" id="editBtn" data-url="{% url 'client_booking_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a>
<button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>
{% endblock buttons %}
{% block create_modal_header %}
<h2>Create Booking</h2>
{% endblock %}
{% block modal_header %}
<h2>Edit Booking</h2>
{% endblock modal_header %}

@ -1,10 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>$Title$</title>
</head>
<body>
$END$
</body>
</html>
{% extends 'client-base.html' %}
{% load static %}
{% block content %}
<div class="container">
<h2>Container Preinfo</h2>
<p>Manage your container preinfo details here.</p>
</div>
<form id="preinfoForm" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit preinfo">
</form>
{%endblock content %}

@ -20,6 +20,16 @@
<td>{{ object.created_by.username }}</td>
{% endblock %}
{% block buttons %}
<a href="{% url 'client_preinfo_create' %}" class="btn btn-primary">Create Preinfo</a>
<a href="#" id="editBtn" data-url="{% url 'client_preinfo_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a>
<button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>
{% endblock buttons %}
{% block create_modal_header %}
<h2>Create Preinfo</h2>
{% endblock %}
{% block modal_header %}
<h2>Edit Preinfo</h2>
{% endblock modal_header %}

@ -1,629 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Barrier Staff Interface | Container Depot</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f7fa;
}
.sidebar {
background: linear-gradient(180deg, #0f4c81 0%, #1a6baf 100%);
transition: all 0.3s;
}
.content-area {
transition: all 0.3s;
}
.nav-item {
transition: all 0.2s;
}
.nav-item:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.nav-item.active {
background-color: rgba(255, 255, 255, 0.2);
border-left: 4px solid white;
}
.card {
transition: all 0.3s;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.tab {
transition: all 0.2s;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.form-section {
animation: slideIn 0.5s ease-in-out;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.camera-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.9);
z-index: 50;
display: none;
animation: fadeIn 0.3s ease-in-out;
}
.camera-frame {
border: 2px solid #fff;
box-shadow: 0 0 0 2000px rgba(0, 0, 0, 0.5);
}
.image-preview {
width: 120px;
height: 120px;
background-color: #f3f4f6;
border: 2px dashed #d1d5db;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.image-preview svg {
color: #9ca3af;
}
.image-preview.has-image {
border: none;
}
.image-preview.has-image svg {
display: none;
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.7);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 10px rgba(255, 0, 0, 0);
}
100% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(255, 0, 0, 0);
}
}
</style>
</head>
<body class="flex h-screen overflow-hidden">
<!-- Sidebar -->
<!-- Main Content -->
<main class="content-area flex-1 overflow-y-auto">
<!-- Top Navigation -->
<header class="bg-white shadow-sm">
<div class="flex items-center justify-between px-6 py-4">
<div class="flex items-center">
<h2 class="text-xl font-semibold text-gray-800">Receive Container</h2>
</div>
<div class="flex items-center space-x-4">
<div class="relative">
<span class="absolute top-0 right-0 -mt-1 -mr-1 flex h-4 w-4">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-4 w-4 bg-red-500 text-xs text-white justify-center items-center">3</span>
</span>
<button class="text-gray-500 hover:text-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
</button>
</div>
<span class="text-gray-700 font-medium">Gate North</span>
</div>
</div>
</header>
<!-- Container Expedition Content -->
<div id="expediteContent" class="tab-content p-6">
<div class="bg-white rounded-lg shadow mb-6">
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
{% extends 'barrier/barrier-base.html' %}
{% load static %}
{% block content %}
<form method="post">
<h2 style="color: #a57d52; margin-bottom: 20px; text-align: center;">Container Expedition</h2>
{% csrf_token %}
<input type="hidden" name="booking_id" value="{{ booking.id }}">
<input type="hidden" name="container_id" value="{{ container.id }}">
<p>
<label for="id_booking_number">Booking number:</label>
<input type="text" name="booking_number" value="{{ booking.number }}" maxlength="11" required id="id_booking_number" readonly disabled>
</p>
{{ form.as_p }}
<div >
<h3 class="text-lg font-semibold text-gray-800">Container Expedition</h3>
<p class="text-sm text-gray-600 mt-1">Process outgoing containers</p>
</div>
<div class="flex items-center">
<span class="bg-green-100 text-green-800 text-xs font-semibold px-3 py-1 rounded-full">Gate Open</span>
</div>
</div>
<div class="p-6">
<form id="expediteForm" class="space-y-6">
<div class="form-section grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="md:col-span-2">
<label for="expediteContainerNumber" class="block text-sm font-medium text-gray-700 mb-1">Container Number</label>
<div class="flex">
<input type="text" id="expediteContainerNumber" name="expediteContainerNumber" placeholder="e.g. MSCU1234567" class="flex-1 px-4 py-2 border border-gray-300 rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500" required>
<button type="button" id="searchExpediteContainer" class="px-4 py-2 bg-blue-600 text-white rounded-r-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
Search
</button>
</div>
<p class="mt-1 text-xs text-gray-500">Enter the full container number including prefix</p>
</div>
<div>
<label for="expeditionDate" class="block text-sm font-medium text-gray-700 mb-1">Expedition Date</label>
<input type="date" id="expeditionDate" name="expeditionDate" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required value="">
</div>
</div>
<!-- Order Data (initially hidden) -->
<div id="orderData" class="hidden">
<div class="bg-green-50 border border-green-200 rounded-md p-4 mb-6">
<div class="flex items-start">
<div class="flex-shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-3 flex-1">
<h3 class="text-sm font-medium text-green-800">Expedition Order Found</h3>
<div class="mt-2 text-sm text-green-700">
<div class="grid grid-cols-2 gap-4">
<div>
<p><span class="font-medium">Order ID:</span> <span id="orderID">ORD-2023-0015</span></p>
<p><span class="font-medium">Line Operator:</span> <span id="orderOperator">Maersk Line</span></p>
</div>
<div>
<p><span class="font-medium">Requested Pickup:</span> <span id="orderPickup">2023-06-20</span></p>
<p><span class="font-medium">Transport Company:</span> <span id="orderTransport">ABC Trucking</span></p>
</div>
</div>
<p class="mt-2"><span class="font-medium">Driver Info:</span> <span id="orderDriver">John Doe, +1 555-123-4567</span></p>
</div>
</div>
</div>
</div>
<div class="form-section grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label for="expediteContainerType" class="block text-sm font-medium text-gray-700 mb-1">Container Type</label>
<input type="text" id="expediteContainerType" name="expediteContainerType" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 bg-gray-100" value="40HC" readonly>
</div>
<div>
<label for="expediteTruckInfo" class="block text-sm font-medium text-gray-700 mb-1">Truck Information</label>
<input type="text" id="expediteTruckInfo" name="expediteTruckInfo" placeholder="License plate / Company" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required>
</div>
</div>
<div class="form-section">
<label for="driverIdentification" class="block text-sm font-medium text-gray-700 mb-1">Driver Identification</label>
<input type="text" id="driverIdentification" name="driverIdentification" placeholder="Driver's license or ID number" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required>
</div>
<div class="form-section">
<label class="block text-sm font-medium text-gray-700 mb-3">Container Condition at Departure</label>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="flex items-center">
<input type="radio" id="departureConditionGood" name="departureCondition" value="good" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" checked>
<label for="departureConditionGood" class="ml-2 block text-sm text-gray-700">Good Condition</label>
</div>
<div class="flex items-center">
<input type="radio" id="departureConditionMinor" name="departureCondition" value="minor" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300">
<label for="departureConditionMinor" class="ml-2 block text-sm text-gray-700">Minor Damage</label>
</div>
<div class="flex items-center">
<input type="radio" id="departureConditionMajor" name="departureCondition" value="major" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300">
<label for="departureConditionMajor" class="ml-2 block text-sm text-gray-700">Major Damage</label>
</div>
</div>
</div>
<div class="form-section">
<label class="block text-sm font-medium text-gray-700 mb-3">Container Images at Departure</label>
<p class="text-sm text-gray-600 mb-3">Take photos of the container before it leaves the depot</p>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
<div class="image-preview" id="expeditePreview1">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
</div>
<div class="image-preview" id="expeditePreview2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
</div>
</div>
<button type="button" id="expediteTakePhoto" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
Take Photo
</button>
</div>
<div class="form-section">
<label for="expediteNotes" class="block text-sm font-medium text-gray-700 mb-1">Additional Notes</label>
<textarea id="expediteNotes" name="expediteNotes" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Any additional information about this expedition"></textarea>
</div>
<div class="form-section flex justify-end">
<button type="submit" class="px-6 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
Complete Expedition
<button type="submit" >
Expedite container
</button>
</div>
</div>
</form>
</div>
</div>
<div class="bg-white rounded-lg shadow">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-semibold text-gray-800">Pending Expedition Orders</h3>
</div>
<div class="p-6">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead>
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Order ID</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Container</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Line</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Pickup Date</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Transport</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tr>
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">ORD-2023-0015</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">MSCU2468135</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">Maersk</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-06-20</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">ABC Trucking</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-blue-600">
<button class="hover:text-blue-800">Process</button>
</td>
</tr>
<tr>
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">ORD-2023-0014</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">Any 40HC</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">Maersk</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-06-19</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">XYZ Logistics</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-blue-600">
<button class="hover:text-blue-800">Process</button>
</td>
</tr>
<tr>
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">ORD-2023-0016</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">CMAU5678901</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">CMA CGM</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">2023-06-21</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">Fast Transport</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-blue-600">
<button class="hover:text-blue-800">Process</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</main>
<!-- Camera Overlay -->
<div id="cameraOverlay" class="camera-overlay">
<div class="h-full flex flex-col">
<div class="p-4 flex justify-between items-center">
<h3 class="text-lg font-semibold text-white">Take Container Photo</h3>
<button id="closeCamera" class="text-white hover:text-gray-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="flex-grow flex items-center justify-center p-4">
<div class="relative">
<div class="camera-frame w-full max-w-2xl aspect-[4/3] bg-gray-800 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-24 w-24 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<!-- Guide lines for container positioning -->
<div class="absolute inset-0 border-2 border-dashed border-white opacity-50 m-8"></div>
<!-- Positioning guides -->
<div class="absolute top-8 left-8 border-t-2 border-l-2 border-white w-8 h-8"></div>
<div class="absolute top-8 right-8 border-t-2 border-r-2 border-white w-8 h-8"></div>
<div class="absolute bottom-8 left-8 border-b-2 border-l-2 border-white w-8 h-8"></div>
<div class="absolute bottom-8 right-8 border-b-2 border-r-2 border-white w-8 h-8"></div>
</div>
</div>
</div>
<div class="p-4 flex justify-center">
<button id="capturePhoto" class="w-16 h-16 rounded-full bg-red-600 border-4 border-white flex items-center justify-center pulse">
<div class="w-12 h-12 rounded-full bg-red-600"></div>
</button>
</div>
</div>
</div>
<script>
// Set current date as default
const today = new Date().toISOString().split('T')[0];
document.getElementById('arrivalDate').value = today;
document.getElementById('expeditionDate').value = today;
// Tab Navigation
const receiveContent = document.getElementById('receiveContent');
const expediteContent = document.getElementById('expediteContent');
const receiveNav = document.getElementById('receiveNav');
const expediteNav = document.getElementById('expediteNav');
receiveNav.addEventListener('click', function(e) {
e.preventDefault();
// Update content visibility
receiveContent.classList.add('active');
expediteContent.classList.remove('active');
// Update navigation styling
document.querySelector('.nav-item.active').classList.remove('active');
receiveNav.classList.add('active');
// Update header title
document.querySelector('header h2').textContent = 'Receive Container';
});
expediteNav.addEventListener('click', function(e) {
e.preventDefault();
// Update content visibility
receiveContent.classList.remove('active');
expediteContent.classList.add('active');
// Update navigation styling
document.querySelector('.nav-item.active').classList.remove('active');
expediteNav.classList.add('active');
// Update header title
document.querySelector('header h2').textContent = 'Expedite Container';
});
// Container search functionality
document.getElementById('searchContainer').addEventListener('click', function() {
const containerNumber = document.getElementById('containerNumber').value;
if (containerNumber) {
document.getElementById('preinfoData').classList.remove('hidden');
} else {
alert('Please enter a container number');
}
});
document.getElementById('searchExpediteContainer').addEventListener('click', function() {
const containerNumber = document.getElementById('expediteContainerNumber').value;
if (containerNumber) {
document.getElementById('orderData').classList.remove('hidden');
} else {
alert('Please enter a container number');
}
});
// Camera functionality
const cameraOverlay = document.getElementById('cameraOverlay');
const takePhotoBtn = document.getElementById('takePhoto');
const expediteTakePhotoBtn = document.getElementById('expediteTakePhoto');
const closeCamera = document.getElementById('closeCamera');
const capturePhoto = document.getElementById('capturePhoto');
let currentImageTarget = null;
function openCamera(imageTarget) {
cameraOverlay.style.display = 'block';
currentImageTarget = imageTarget;
}
takePhotoBtn.addEventListener('click', function() {
// Find the first empty preview slot
for (let i = 1; i <= 4; i++) {
const previewEl = document.getElementById(`preview${i}`);
if (!previewEl.classList.contains('has-image')) {
openCamera(`preview${i}`);
break;
}
}
});
expediteTakePhotoBtn.addEventListener('click', function() {
// Find the first empty preview slot
for (let i = 1; i <= 2; i++) {
const previewEl = document.getElementById(`expeditePreview${i}`);
if (!previewEl.classList.contains('has-image')) {
openCamera(`expeditePreview${i}`);
break;
}
}
});
closeCamera.addEventListener('click', function() {
cameraOverlay.style.display = 'none';
});
capturePhoto.addEventListener('click', function() {
if (currentImageTarget) {
const previewEl = document.getElementById(currentImageTarget);
// Create a container SVG image
const containerSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
containerSvg.setAttribute("class", "w-full h-full");
containerSvg.setAttribute("viewBox", "0 0 120 120");
// Random container color
const colors = ['#2563eb', '#059669', '#d97706', '#7c3aed'];
const randomColor = colors[Math.floor(Math.random() * colors.length)];
// Create container body
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("x", "10");
rect.setAttribute("y", "20");
rect.setAttribute("width", "100");
rect.setAttribute("height", "80");
rect.setAttribute("fill", randomColor);
containerSvg.appendChild(rect);
// Add some container details
const detail1 = document.createElementNS("http://www.w3.org/2000/svg", "rect");
detail1.setAttribute("x", "20");
detail1.setAttribute("y", "30");
detail1.setAttribute("width", "80");
detail1.setAttribute("height", "20");
detail1.setAttribute("fill", shadeColor(randomColor, -20));
containerSvg.appendChild(detail1);
const detail2 = document.createElementNS("http://www.w3.org/2000/svg", "rect");
detail2.setAttribute("x", "20");
detail2.setAttribute("y", "60");
detail2.setAttribute("width", "80");
detail2.setAttribute("height", "30");
detail2.setAttribute("fill", shadeColor(randomColor, -20));
containerSvg.appendChild(detail2);
// Clear the preview and add the new SVG
previewEl.innerHTML = '';
previewEl.appendChild(containerSvg);
previewEl.classList.add('has-image');
// Close the camera
cameraOverlay.style.display = 'none';
}
});
// Helper function to darken/lighten colors
function shadeColor(color, percent) {
let R = parseInt(color.substring(1,3), 16);
let G = parseInt(color.substring(3,5), 16);
let B = parseInt(color.substring(5,7), 16);
R = parseInt(R * (100 + percent) / 100);
G = parseInt(G * (100 + percent) / 100);
B = parseInt(B * (100 + percent) / 100);
R = (R<255)?R:255;
G = (G<255)?G:255;
B = (B<255)?B:255;
R = Math.round(R);
G = Math.round(G);
B = Math.round(B);
const RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16));
const GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16));
const BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16));
return "#"+RR+GG+BB;
}
// Form submissions
document.getElementById('receiveForm').addEventListener('submit', function(e) {
e.preventDefault();
// Check if container data is loaded
if (document.getElementById('preinfoData').classList.contains('hidden')) {
alert('Please search for a container first');
return;
}
// In a real app, this would send data to the server
alert('Container reception completed successfully!');
this.reset();
document.getElementById('preinfoData').classList.add('hidden');
// Reset image previews
for (let i = 1; i <= 4; i++) {
const previewEl = document.getElementById(`preview${i}`);
previewEl.classList.remove('has-image');
previewEl.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
`;
}
// Add the first two images back
document.getElementById('preview1').classList.add('has-image');
document.getElementById('preview1').innerHTML = `
<svg class="w-full h-full" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
<rect x="10" y="20" width="100" height="80" fill="#2563eb" />
<rect x="20" y="30" width="30" height="20" fill="#1e40af" />
<rect x="70" y="30" width="30" height="20" fill="#1e40af" />
<rect x="20" y="60" width="80" height="30" fill="#1e40af" />
<circle cx="85" cy="45" r="8" fill="#ef4444" />
</svg>
`;
document.getElementById('preview2').classList.add('has-image');
document.getElementById('preview2').innerHTML = `
<svg class="w-full h-full" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
<rect x="10" y="20" width="100" height="80" fill="#2563eb" />
<rect x="20" y="30" width="80" height="20" fill="#1e40af" />
<rect x="20" y="60" width="80" height="30" fill="#1e40af" />
<path d="M85,40 Q95,50 85,60 Q75,50 85,40" fill="#ef4444" />
</svg>
`;
});
document.getElementById('expediteForm').addEventListener('submit', function(e) {
e.preventDefault();
// Check if order data is loaded
if (document.getElementById('orderData').classList.contains('hidden')) {
alert('Please search for a container first');
return;
}
// In a real app, this would send data to the server
alert('Container expedition completed successfully!');
this.reset();
document.getElementById('orderData').classList.add('hidden');
// Reset image previews
for (let i = 1; i <= 2; i++) {
const previewEl = document.getElementById(`expeditePreview${i}`);
previewEl.classList.remove('has-image');
previewEl.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
`;
}
});
</script>
<script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'9498ddc2d702313f',t:'MTc0ODg4NzM5My4wMDAwMDA='};var a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>
</html>
{% endblock content %}

@ -6,11 +6,11 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Barrier Staff Interface | Container Depot</title>
<script src="https://cdn.tailwindcss.com"></script>
{# <script src="https://cdn.tailwindcss.com"></script>#}
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f7fa;
{#background-color: #f5f7fa;#}
}
.sidebar {
background: linear-gradient(180deg, #0f4c81 0%, #1a6baf 100%);

@ -1,32 +1,25 @@
{% extends 'employee-base.html' %}
{% extends 'list-crud.html' %}
{% load static %}
{% block content %}
<table>
<tr>
<th>Preinfo ID</th>
{% block table_header %}
<th style="display: none;">Select</th>
<th>Preinfo №</th>
<th>Container №</th>
<th>Container type</th>
<th>Line</th>
<th>Created on</th>
<th>Created by</th>
</tr>
{% endblock table_header %}
{% for preinfo in preinfos %}
<tr>
<td>{{ preinfo.id }}</td>
<td>{{ preinfo.container_number }}</td>
<td>{{ preinfo.container_type }}</td>
<td>{{ preinfo.line.short_name }}</td>
<td>{{ preinfo.created_on }}</td>
<td>{{ preinfo.created_by.username }}</td>
<td>
{# <a href="{% url 'preinfo_edit' preinfo.id %}">Edit</a> |#}
{# <a href="{% url 'preinfo_delete' preinfo.id %}">Delete</a>#}
</td>
</tr>
{% block table_data %}
<td>{{ object.id }}</td>
<td>{{ object.container_number }}</td>
<td>{{ object.container_type }}</td>
<td>{{ object.line.short_name }}</td>
<td>{{ object.created_on }}</td>
<td>{{ object.created_by.username }}</td>
{% endblock %}
{% endfor %}
</table>
{% endblock content %}
{% block modal_header %}
<h2>Edit Preinfo</h2>
{% endblock modal_header %}

@ -1,50 +1,85 @@
{% extends 'employee-base.html' %}
{% extends base_template %}
{% load static %}
{% block content %}
<table id="preinfoTable" class="table">
{# <div class="pagination">#}
{# <span class="step-links">#}
{# {% if page_obj.has_previous %}#}
{# <a href="?page=1">&laquo; first</a>#}
{# <a href="?page={{ page_obj.previous_page_number }}">previous</a>#}
{# {% endif %}#}
{##}
{# <span class="current">#}
{# Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.#}
{# </span>#}
{##}
{# {% if page_obj.has_next %}#}
{# <a href="?page={{ page_obj.next_page_number }}">next</a>#}
{# <a href="?page={{ page_obj.paginator.num_pages }}">last &raquo;</a>#}
{# {% endif %}#}
{# </span>#}
{# </div>#}
<table id="objectTable" class="table">
<thead>
<tr>
<th style="display: none;">Select</th>
<th>Preinfo №</th>
<th>Container №</th>
<th>Container type</th>
<th>Line</th>
<th>Created on</th>
<th>Created by</th>
{% block table_header %}
{% endblock table_header %}
</tr>
{% for preinfo in preinfos %}
<tr class="selectable-row" data-preinfo-id="{{ preinfo.id }}">
<td style="display: none;"><input type="radio" name="preinfo_select" value="{{ preinfo.id }}"></td>
<td>{{ preinfo.id }}</td>
<td>{{ preinfo.container_number }}</td>
<td>{{ preinfo.container_type }}</td>
<td>{{ preinfo.line.short_name }}</td>
<td>{{ preinfo.created_on }}</td>
<td>{{ preinfo.created_by.username }}</td>
</thead>
<tbody>
{% for object in objects %}
<tr class="selectable-row" data-id="{{ object.id }}">
<td style="display: none;"><input type="radio" name="object_select" value="{{ object.id }}"></td>
{% block table_data %}
{% endblock table_data %}
</tr>
{% endfor %}
</table>
<div class="buttons-container">
<button id="editBtn" class="btn" disabled>Edit</button>
<button id="deleteBtn" class="btn" disabled>Delete</button>
</tbody>
<tfoot>
<tr>
<td id="footerCell">
<div class="footer-container">
<div class="footer-left">
{% block buttons %}
{% endblock buttons %}
{# <button>Добави</button>#}
{# <button>Изтрий</button>#}
{# <button>Редактирай</button>#}
</div>
<!-- Modal -->
<div id="editModal" class="modal" style="display: none;">
<div class="modal-content">
<span class="close">&times;</span>
<h2>Edit Preinfo</h2>
<form method="post">
{% csrf_token %}
<input type="hidden" name="preinfo_id" id="preinfoIdInput">
{{ form.as_p }}
<button type="submit">Save</button>
</form>
<div class="footer-right">
{% if page_obj.has_previous %}
<a href="?page=1">&laquo; first</a>
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
{# <button>&laquo; Назад</button>#}
<span class="page-number">Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
<a href="?page={{ page_obj.paginator.num_pages }}">last &raquo;</a>
{% endif %}
{# <button>Напред &raquo;</button>#}
</div>
</div>
</td>
</tr>
</tfoot>
</table>
<script>
const table = document.getElementById('objectTable');
const headerCells = table.querySelector('thead tr').children.length;
const footerCell = document.getElementById('footerCell');
footerCell.colSpan = headerCells;
{#footerCell.style.textAlign = 'center';#}
</script>
{# <div class="buttons-container">#}
{# {% block buttons %}#}
{# {% endblock buttons %}#}
{# </div>#}
{% block custom_styles %}
<style>
.selectable-row {
cursor: pointer;
@ -52,106 +87,14 @@
.selected-row {
background-color: #e6f3ff;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 500px;
}
.close {
float: right;
cursor: pointer;
}
.btn[disabled] {
opacity: 0.5;
cursor: not-allowed;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
const editBtn = document.getElementById('editBtn');
const deleteBtn = document.getElementById('deleteBtn');
const modal = document.getElementById('editModal');
const closeBtn = document.querySelector('.close');
const preinfoIdInput = document.getElementById('preinfoIdInput');
const rows = document.querySelectorAll('.selectable-row');
rows.forEach(row => {
row.addEventListener('click', function() {
// Remove previous selection
document.querySelector('.selected-row')?.classList.remove('selected-row');
// Select current row
this.classList.add('selected-row');
// Check the hidden radio button
const radio = this.querySelector('input[type="radio"]');
radio.checked = true;
// Enable buttons
editBtn.disabled = false;
deleteBtn.disabled = false;
});
});
// Enable/disable buttons based on selection
document.querySelectorAll('input[name="preinfo_select"]').forEach(radio => {
radio.addEventListener('change', function() {
console.log('Radio button changed:', this.value); // Debug log
editBtn.disabled = false;
deleteBtn.disabled = false;
});
});
// Edit button click
editBtn.addEventListener('click', function() {
const selectedId = document.querySelector('input[name="preinfo_select"]:checked').value;
preinfoIdInput.value = selectedId;
// Fetch preinfo data via AJAX
const formData = new FormData();
formData.append('preinfo_id', selectedId);
fetch('', {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
}
})
.then(response => response.json())
.then(data => {
document.getElementById('id_container_number').value = data.container_number;
document.getElementById('id_container_type').value = data.container_type;
document.getElementById('id_line').value = data.line;
modal.style.display = 'block';
});
});
// Close modal
closeBtn.addEventListener('click', function() {
modal.style.display = 'none';
});
// Close modal when clicking outside
window.addEventListener('click', function(event) {
if (event.target == modal) {
modal.style.display = 'none';
}
});
});
</script>
{% endblock custom_styles %}
{% block extra_js %}
<script src="{% static 'js/crud-list.js' %}"></script>
{% endblock extra_js %}
{% block custom_js %}{% endblock custom_js %}
{% endblock content %}
Loading…
Cancel
Save