parent
a4a91e0053
commit
4603953458
@ -0,0 +1,20 @@
|
|||||||
|
from django import template
|
||||||
|
from django.template.defaultfilters import date
|
||||||
|
|
||||||
|
from common.utils.utils import check_container_number_validity
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def bg_date(value):
|
||||||
|
if value:
|
||||||
|
return date(value, "y.m.d h:m")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def container_validity_class(container_number):
|
||||||
|
if not check_container_number_validity(container_number):
|
||||||
|
return 'invalid-container'
|
||||||
|
return ''
|
||||||
|
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
|
from django.conf import settings
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.response import Response
|
||||||
|
import owncloud
|
||||||
|
from owncloud import (HTTPResponseError)
|
||||||
|
|
||||||
|
|
||||||
|
class Owncloud:
|
||||||
|
@staticmethod
|
||||||
|
def upload_damage_photo(filename, depot_id):
|
||||||
|
_, ext = os.path.splitext(filename)
|
||||||
|
oc = owncloud.Client(settings.OWNCLOUD_URL)
|
||||||
|
oc.login(settings.OWNCLOUD_USER, settings.OWNCLOUD_PASSWORD)
|
||||||
|
path = f"{settings.OWNCLOUD_DAMAGES_FOLDER}{str(depot_id)}"
|
||||||
|
try:
|
||||||
|
oc.file_info(path)
|
||||||
|
except HTTPResponseError:
|
||||||
|
_ = oc.mkdir(path)
|
||||||
|
owncloud_filename = f"{datetime.today().strftime('%Y%m%d')}_{uuid.uuid4()}{ext}"
|
||||||
|
owncloud_fullpath = path + "/" + owncloud_filename
|
||||||
|
oc.put_file(owncloud_fullpath, filename)
|
||||||
|
file_info = oc.share_file_with_link(owncloud_fullpath)
|
||||||
|
return file_info.get_link()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_damages(depot_id):
|
||||||
|
oc = owncloud.Client(settings.OWNCLOUD_URL)
|
||||||
|
oc.login(settings.OWNCLOUD_USER, settings.OWNCLOUD_PASSWORD)
|
||||||
|
files = oc.list(f"{settings.OWNCLOUD_DAMAGES_FOLDER}{str(depot_id)}")
|
||||||
|
damages = []
|
||||||
|
for file_info in files:
|
||||||
|
if file_info.is_dir():
|
||||||
|
continue # Пропускаме поддиректории
|
||||||
|
# Създаване на публичен линк за всеки файл
|
||||||
|
link_info = oc.share_file_with_link(file_info.path)
|
||||||
|
damages.append(link_info.get_link())
|
||||||
|
return Response(damages, status=status.HTTP_200_OK)
|
||||||
@ -1,14 +1,17 @@
|
|||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
from containers.models import Container
|
||||||
|
|
||||||
|
|
||||||
class BarrierDashboardView(TemplateView):
|
class BarrierDashboardView(TemplateView):
|
||||||
template_name = 'barrier/barrier-dashboard.html'
|
template_name = 'barrier/barrier-dashboard.html'
|
||||||
extra_context = {
|
|
||||||
'title': 'Client Dashboard',
|
|
||||||
'description': 'This is the client dashboard page.',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
recent_containers = Container.objects.select_related('line', 'booking').order_by('-expedited_on', '-received_on')[:10]
|
||||||
|
context['recent_containers'] = recent_containers
|
||||||
|
return context
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
return render(request, self.template_name, self.extra_context)
|
return render(request, self.template_name, self.get_context_data())
|
||||||
@ -1,14 +1,18 @@
|
|||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
|
|
||||||
from containers.views.employee_views import ContainersListView, ReportContainersUnpaidListView
|
from containers.views.employee_views import ContainersListView, ReportContainersUnpaidListView
|
||||||
from containers.views.barrier_views import ContainerReceive, ContainerExpedition, ContainerSearchView
|
from containers.views.barrier_views import ContainerReceiveView, ContainerExpedition, ContainerSearchView, \
|
||||||
|
ContainerPhotosView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('search/', ContainerSearchView.as_view(), name='container_search'),
|
path('container-search/', ContainerSearchView.as_view(), name='container_search'),
|
||||||
|
path('container-search/', ContainerSearchView.as_view(), name='barrier_photos'),
|
||||||
|
# path('search/', ContainerSearchView.as_view(), name='container_search'),
|
||||||
path('employee/', ContainersListView.as_view(), name='employee_containers'),
|
path('employee/', ContainersListView.as_view(), name='employee_containers'),
|
||||||
path('not-paid', ReportContainersUnpaidListView.as_view(), name='not_paid'),
|
path('not-paid', ReportContainersUnpaidListView.as_view(), name='not_paid'),
|
||||||
|
path('barrier/receive/', ContainerReceiveView.as_view(), name='container_receive'),
|
||||||
|
path('barrier/expedition/', ContainerExpedition.as_view(), name='container_expedition'),
|
||||||
path('barrier/<int:pk>/', include([
|
path('barrier/<int:pk>/', include([
|
||||||
path('receive/', ContainerReceive.as_view(), name='container_receive'),
|
path('photos/', ContainerPhotosView.as_view(), name='container_photos'),
|
||||||
path('expedition/', ContainerExpedition.as_view(), name='container_expedition'),
|
|
||||||
])),
|
])),
|
||||||
]
|
]
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
env_file:
|
||||||
|
- development.env
|
||||||
|
volumes:
|
||||||
|
- .:/DepoT
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
WORKDIR /DepoT
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
COPY . .
|
||||||
|
CMD python manage.py migrate && python manage.py runserver 0.0.0.0:8000
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import urllib.parse
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from DepoT.settings import env
|
||||||
|
|
||||||
|
|
||||||
|
class EPay:
|
||||||
|
payment_types = {
|
||||||
|
"pay_credit_card": "credit_paydirect",
|
||||||
|
"pay_epay": "paylogin",
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encode_message(message):
|
||||||
|
encoded_data = base64.b64encode(message.encode("utf-8")).decode("utf-8")
|
||||||
|
secret_key = env("EPAY_SECURITY_KEY")
|
||||||
|
checksum = hmac.new(
|
||||||
|
secret_key.encode("utf-8"), encoded_data.encode("utf-8"), hashlib.sha1
|
||||||
|
).hexdigest()
|
||||||
|
return encoded_data, checksum
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def generate_urls(self, invoice, amount):
|
||||||
|
client_id = env("EPAY_CLIENT_ID")
|
||||||
|
description = f"Invoice number: {invoice} with amount of {amount}"
|
||||||
|
currency = "BGN"
|
||||||
|
expiry = (
|
||||||
|
datetime.today() + timedelta(days=int(env("INVOICE_EXPIRE_PERIOD")))
|
||||||
|
).strftime("%d.%m.%Y")
|
||||||
|
|
||||||
|
if amount == 0:
|
||||||
|
amount = 1000
|
||||||
|
|
||||||
|
message = f"MIN={client_id}\nINVOICE={invoice}\nAMOUNT={amount}\nCURRENCY={currency}\nEXP_TIME={expiry}\nDESCR={description}"
|
||||||
|
encoded_data, checksum = EPay.encode_message(message)
|
||||||
|
result = {}
|
||||||
|
for pay_type, keyword in EPay.payment_types.items():
|
||||||
|
url = f"https://demo.epay.bg/?PAGE={keyword}&ENCODED={urllib.parse.quote(encoded_data)}&CHECKSUM={urllib.parse.quote(checksum)}"
|
||||||
|
result[pay_type] = url
|
||||||
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def payment_result(encoded_data, checksum_received):
|
||||||
|
secret_key = env("EPAY_SECURITY_KEY")
|
||||||
|
decoded_data = base64.b64decode(encoded_data).decode("utf-8")
|
||||||
|
checksum_calculated = hmac.new(
|
||||||
|
secret_key.encode("utf-8"), encoded_data.encode("utf-8"), hashlib.sha1
|
||||||
|
).hexdigest()
|
||||||
|
checksum_result = checksum_calculated == checksum_received
|
||||||
|
|
||||||
|
fields = decoded_data.split(":")
|
||||||
|
fields_dict = {}
|
||||||
|
for field in fields:
|
||||||
|
try:
|
||||||
|
name, value = field.split("=")
|
||||||
|
except ValueError:
|
||||||
|
print(fields)
|
||||||
|
print(field)
|
||||||
|
|
||||||
|
fields_dict[name] = value
|
||||||
|
|
||||||
|
return fields_dict, checksum_result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def send_response(encoded_data, checksum):
|
||||||
|
url = f"https://demo.epay.bg/?ENCODED={urllib.parse.quote(encoded_data)}&CHECKSUM={urllib.parse.quote(checksum)}"
|
||||||
|
print(url)
|
||||||
|
res = requests.post(url)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def send_ok(invoice):
|
||||||
|
message = f"INVOICE={invoice}:STATUS=OK"
|
||||||
|
encoded_data, checksum = EPay.encode_message(message)
|
||||||
|
print(f"OK {invoice}")
|
||||||
|
EPay.send_response(encoded_data, checksum)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def send_no(invoice):
|
||||||
|
message = f"INVOICE={invoice}:STATUS=NO"
|
||||||
|
encoded_data, checksum = EPay.encode_message(message)
|
||||||
|
print(f"NO {invoice}")
|
||||||
|
EPay.send_response(encoded_data, checksum)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def send_err(invoice):
|
||||||
|
message = f"INVOICE={invoice}:STATUS=ERR:ERR=BAD_CHECKSUM"
|
||||||
|
encoded_data, checksum = EPay.encode_message(message)
|
||||||
|
print(f"ERR {invoice}")
|
||||||
|
EPay.send_response(encoded_data, checksum)
|
||||||
@ -1,7 +1,8 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from payments.views import PaymentCreateView
|
from payments.views import PaymentCreateView, some_view
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("create/", PaymentCreateView.as_view(), name="payments_create"),
|
path("create/", PaymentCreateView.as_view(), name="payments_create"),
|
||||||
|
path('invoice/', some_view, name='payments_invoice'),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
from payments.models import ContainerTariffPeriod, AdditionalFees
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_charges(container):
|
||||||
|
days = (container.expedited_on.date() - container.received_on.date()).days + 1
|
||||||
|
total = 0
|
||||||
|
|
||||||
|
size = '20' if container.container_type.length == 20 else '40'
|
||||||
|
|
||||||
|
tariff = ContainerTariffPeriod.objects.get(
|
||||||
|
Q(to_days__gt=days) | Q(to_days__isnull=True),
|
||||||
|
container_size=size,
|
||||||
|
from_days__lte=days,
|
||||||
|
)
|
||||||
|
|
||||||
|
fees = AdditionalFees.objects.first()
|
||||||
|
storage_charge = days * float(tariff.daily_rate)
|
||||||
|
|
||||||
|
if container.container_type.container_type == 'reefer':
|
||||||
|
storage_charge += days * float(fees.reefer_daily_supplement)
|
||||||
|
|
||||||
|
if container.swept:
|
||||||
|
total += float(fees.sweeping_fee)
|
||||||
|
if container.washed:
|
||||||
|
total += float(fees.fumigation_fee)
|
||||||
|
|
||||||
|
total += storage_charge
|
||||||
|
|
||||||
|
return {
|
||||||
|
'days': days,
|
||||||
|
'storage_charge': storage_charge,
|
||||||
|
'services_charge': total - storage_charge,
|
||||||
|
'total': total,
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@ -0,0 +1,35 @@
|
|||||||
|
function validateContainerNumber(input) {
|
||||||
|
const containerNumber = input.value.trim();
|
||||||
|
const feedbackElement = input.nextElementSibling;
|
||||||
|
|
||||||
|
if (!containerNumber) {
|
||||||
|
input.classList.remove('invalid-container', 'incomplete-container');
|
||||||
|
if (feedbackElement) feedbackElement.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (containerNumber.length > 0 && containerNumber.length < 11) {
|
||||||
|
input.classList.remove('invalid-container');
|
||||||
|
input.classList.add('incomplete-container');
|
||||||
|
if (feedbackElement) feedbackElement.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(`${validateContainerUrl}${containerNumber}/`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
input.classList.remove('incomplete-container');
|
||||||
|
if (!data.valid) {
|
||||||
|
input.classList.add('invalid-container');
|
||||||
|
if (!feedbackElement) {
|
||||||
|
const feedback = document.createElement('div');
|
||||||
|
feedback.className = 'validation-feedback';
|
||||||
|
feedback.textContent = 'Invalid container number';
|
||||||
|
input.parentNode.insertBefore(feedback, input.nextSibling);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
input.classList.remove('invalid-container');
|
||||||
|
if (feedbackElement) feedbackElement.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,73 +1,42 @@
|
|||||||
{% extends 'barrier/barrier-base.html' %}
|
{% extends "barrier/barrier-base.html" %}
|
||||||
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="sidebar p-5 text-white border-b border-blue-700">
|
<div class="barrier-container">
|
||||||
<h1 class="text-xl font-bold">Container Depot</h1>
|
<a href="{% url 'container_receive' %}" class="barrier-button">
|
||||||
<p class="text-sm text-blue-200">Line Operator Portal</p>
|
Receive Container
|
||||||
</div>
|
</a>
|
||||||
|
<a href="{% url 'container_expedition' %}" class="barrier-button">
|
||||||
|
Expedite Container
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'barrier_photos' %}" class="barrier-button">
|
||||||
|
Take Photos
|
||||||
|
</a>
|
||||||
|
|
||||||
<div class="flex min-h-screen items-center justify-center">
|
<h2 class="text-2xl font-bold mb-4">Recent History</h2>
|
||||||
<div class="sidebar w-[400px] h-[600px] text-white flex flex-col rounded-lg overflow-hidden">
|
<table>
|
||||||
<div class="sidebar w-[100%] h-full text-white flex flex-col">
|
<thead>
|
||||||
<div class="p-5 border-b border-blue-700">
|
<tr>
|
||||||
<h1 class="text-xl font-bold">Container Depot</h1>
|
<th>Date</th>
|
||||||
<p class="text-sm text-blue-200">Line Operator Portal</p>
|
<th>Container Number</th>
|
||||||
</div>
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
<nav class="flex-grow py-4">
|
</thead>
|
||||||
<div class="px-4 py-2 text-xs text-blue-300 uppercase tracking-wider">Main</div>
|
<tbody>
|
||||||
<a href="{% url 'barrier_dashboard' %}" class="nav-item active flex items-center px-6 py-10 text-white">
|
{% for container in recent_containers %}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<tr>
|
||||||
<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>
|
<td>{{ container.number }}</td>
|
||||||
</svg>
|
{% if container.expedited %}
|
||||||
Dashboard
|
<td>{{ container.expedited_on }}</td>
|
||||||
</a>
|
<td>Expedited</td>
|
||||||
<a href="{% url 'preinfo_search' %}?param=container_receive" id="ordersNav" class="nav-item flex items-center px-6 py-10 text-white">
|
{% else %}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<td>{{ container.received_on }}</td>
|
||||||
<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>
|
<td>Received</td>
|
||||||
</svg>
|
{% endif %}
|
||||||
Receive container
|
</tr>
|
||||||
</a>
|
{% endfor %}
|
||||||
<a href="{% url 'container_search' %}?param=container_expedition" id="ordersNav" class="nav-item flex items-center px-6 py-10 text-white">
|
</tbody>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
</table>
|
||||||
<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>
|
</div>
|
||||||
</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>
|
|
||||||
</svg>
|
|
||||||
Photos
|
|
||||||
</a>
|
|
||||||
|
|
||||||
{# <div class="px-4 py-2 mt-6 text-xs text-blue-300 uppercase tracking-wider">Account</div>#}
|
|
||||||
{# <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="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#}
|
|
||||||
{# </a>#}
|
|
||||||
</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>#}
|
|
||||||
{# <div class="ml-3">#}
|
|
||||||
{# <p class="text-sm font-medium">Maersk Line</p>#}
|
|
||||||
{# <p class="text-xs text-blue-300">Line Operator</p>#}
|
|
||||||
{# </div>#}
|
|
||||||
{# <button 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">#}
|
|
||||||
{# <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>#}
|
|
||||||
{# </button>#}
|
|
||||||
{# </div>#}
|
|
||||||
{# </div>#}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@ -1,20 +1,62 @@
|
|||||||
{% extends 'barrier/barrier-base.html' %}
|
{% extends 'barrier/barrier-base.html' %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post">
|
<div class="barrier-container">
|
||||||
<h2 style="color: #a57d52; margin-bottom: 20px; text-align: center;">Container Expedition</h2>
|
{% if show_search %}
|
||||||
{% csrf_token %}
|
<form method="get" class="w-full">
|
||||||
<input type="hidden" name="booking_id" value="{{ booking.id }}">
|
<div class="p-6">
|
||||||
<input type="hidden" name="container_id" value="{{ container.id }}">
|
<label for="id_number" class="block text-2xl mb-4">
|
||||||
<p>
|
Enter Booking Number
|
||||||
<label for="id_booking_number">Booking number:</label>
|
</label>
|
||||||
<input type="text" name="booking_number" value="{{ booking.number }}" maxlength="11" required id="id_booking_number" readonly disabled>
|
<div class="form-group">
|
||||||
</p>
|
<input type="text"
|
||||||
{{ form.as_p }}
|
id="id_number"
|
||||||
<div >
|
name="number"
|
||||||
<button type="submit" >
|
value="{{ search_number }}"
|
||||||
Expedite container
|
class="px-4 py-3 text-xl border-2 rounded-lg"
|
||||||
</button>
|
required
|
||||||
</div>
|
autocomplete="off"
|
||||||
</form>
|
autofocus>
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="btn-primary">
|
||||||
|
Search
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% if error %}
|
||||||
|
<div class="text-red-600 text-xl text-center">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% elif form %}
|
||||||
|
<form method="post" class="w-full">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="booking_id" value="{{ booking.id }}">
|
||||||
|
<input type="hidden" name="container_id" value="{{ container.id }}">
|
||||||
|
<input type="hidden" name="search_number" value="{{ search_number }}">
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<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>
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" name="expedite" class="btn-primary">
|
||||||
|
Expedite container
|
||||||
|
</button>
|
||||||
|
<button type="button" onclick="window.location.href='{% url 'barrier_dashboard' %}'" class="btn-secondary">
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@ -1,10 +1,90 @@
|
|||||||
<!DOCTYPE html>
|
{# templates/container-photos.html #}
|
||||||
<html lang="en">
|
{% extends "barrier/barrier-base.html" %}
|
||||||
<head>
|
{% load static %}
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>$Title$</title>
|
{% block content %}
|
||||||
</head>
|
<div class="barrier-container">
|
||||||
<body>
|
<h2 class="text-2xl mb-4">Container Photos - {{ container.number }}</h2>
|
||||||
$END$
|
|
||||||
</body>
|
{% csrf_token %} {# Added CSRF token here #}
|
||||||
</html>
|
|
||||||
|
<div class="photo-upload-section mb-4">
|
||||||
|
<video id="video" class="mb-4 w-full" autoplay></video>
|
||||||
|
<canvas id="canvas" style="display:none;"></canvas>
|
||||||
|
|
||||||
|
<div class="flex justify-between mb-4">
|
||||||
|
<button id="capture" class="btn-primary" {% if photos_count >= 3 %}disabled{% endif %}>
|
||||||
|
Take Photo ({{ photos_count }}/3)
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'barrier_dashboard' %}" class="btn-secondary">Back to Dashboard</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="photos-preview" class="grid grid-cols-3 gap-4">
|
||||||
|
{% for photo in photos %}
|
||||||
|
<div class="photo-preview">
|
||||||
|
<img src="{{ photo.photo.url }}" alt="Container photo" class="w-full">
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const video = document.getElementById('video');
|
||||||
|
const canvas = document.getElementById('canvas');
|
||||||
|
const captureButton = document.getElementById('capture');
|
||||||
|
let photoCount = {{ photos_count }};
|
||||||
|
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
|
||||||
|
|
||||||
|
navigator.mediaDevices.getUserMedia({ video: true })
|
||||||
|
.then(stream => {
|
||||||
|
video.srcObject = stream;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Camera error:', err);
|
||||||
|
alert('Could not access camera. Please check permissions.');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
captureButton.addEventListener('click', () => {
|
||||||
|
if (photoCount >= 3) return;
|
||||||
|
|
||||||
|
canvas.width = video.videoWidth;
|
||||||
|
canvas.height = video.videoHeight;
|
||||||
|
canvas.getContext('2d').drawImage(video, 0, 0);
|
||||||
|
|
||||||
|
const photo = canvas.toDataURL('image/jpeg').split(',')[1];
|
||||||
|
|
||||||
|
fetch(`/api/damages/{{ container.pk }}/`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': csrfToken
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
depot_id: {{ container.pk }},
|
||||||
|
photo_extension: 'jpg',
|
||||||
|
photo: photo
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(async response => {
|
||||||
|
if (response.status === 201) {
|
||||||
|
photoCount++;
|
||||||
|
if (photoCount >= 3) {
|
||||||
|
captureButton.disabled = true;
|
||||||
|
}
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Upload failed');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert(error.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@ -1,593 +1,54 @@
|
|||||||
|
{% extends "barrier/barrier-base.html" %}
|
||||||
|
{% block content %}
|
||||||
<!DOCTYPE html>
|
<div class="barrier-container">
|
||||||
<html lang="en">
|
{% if show_search %}
|
||||||
<head>
|
<form method="get" class="w-full">
|
||||||
<meta charset="UTF-8">
|
<div class="p-6">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<label for="id_number" class="block text-2xl mb-4">
|
||||||
<title>Barrier Staff Interface | Container Depot</title>
|
Enter Container Number
|
||||||
{# <script src="https://cdn.tailwindcss.com"></script>#}
|
</label>
|
||||||
<style>
|
<div>
|
||||||
body {
|
<input type="text"
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
id="id_number"
|
||||||
{#background-color: #f5f7fa;#}
|
name="number"
|
||||||
}
|
value="{{ search_number }}"
|
||||||
.sidebar {
|
class="px-4 py-3 text-xl border-2 rounded-lg"
|
||||||
background: linear-gradient(180deg, #0f4c81 0%, #1a6baf 100%);
|
required
|
||||||
transition: all 0.3s;
|
autocomplete="off"
|
||||||
}
|
autofocus>
|
||||||
.content-area {
|
<div class="form-group">
|
||||||
transition: all 0.3s;
|
<button type="submit" class="btn-primary">
|
||||||
}
|
Search
|
||||||
.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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-700 font-medium">Gate North</span>
|
{% if error %}
|
||||||
</div>
|
<div class="text-red-600 text-xl text-center">
|
||||||
</div>
|
{{ error }}
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- Container Reception Content -->
|
|
||||||
<div id="receiveContent" class="tab-content active 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">
|
|
||||||
<div>
|
|
||||||
<h3 class="text-lg font-semibold text-gray-800">Container Reception</h3>
|
|
||||||
<p class="text-sm text-gray-600 mt-1">Process incoming 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>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<form id="receiveForm" class="space-y-6" method="POST">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="p-6">
|
|
||||||
<label for="id_number" class="block text-sm font-medium text-gray-700 mb-1">Container Number</label>
|
|
||||||
{# <label for="id_number" >Container Number</label>#}
|
|
||||||
{# <input type="text" id="id_number" name="number" placeholder="Enter Container Number" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 mb-4">#}
|
|
||||||
{# <button type="button" id="check-preinfo-btn">Check Preinfo</button>#}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{# <label for="containerNumber" class="block text-sm font-medium text-gray-700 mb-1">Container Number</label>#}
|
|
||||||
{# <div class="flex">#}
|
|
||||||
{# <input type="text" id="id_number" name="number" 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="check-preinfo-btn" 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>#}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div id="preinfoData">
|
|
||||||
<div class="bg-blue-50 border border-blue-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-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 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-blue-800">Preinfo Found</h3>
|
|
||||||
<div class="mt-2 text-sm text-blue-700">
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<span class="font-medium">Container Number:</span>
|
|
||||||
<input type="text" id="id_number" name="number" value="{{ preinfo.container_number }}" readonly>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span class="font-medium">Container Type:</span>
|
|
||||||
<input type="text" id="id_container_type" name="id_container_type" value="{{ preinfo.container_type }}" readonly>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span class="font-medium">Line Operator:</span>
|
|
||||||
<input type="text" id="id_line" name="id_line" value="{{ preinfo.line.name }}" readonly>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{# <p class="mt-2"><span class="font-medium">Notes:</span> <span id="preinfoNotes">Container reported with minor damage on right side panel.</span></p>#}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="id_receive_vehicle">Receive vehicle:</label>
|
|
||||||
<input type="text" name="receive_vehicle" maxlength="100" id="id_receive_vehicle">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="id_damages">Damages:</label>
|
|
||||||
<textarea name="damages" cols="40" rows="10" id="id_damages"></textarea>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="id_heavy_damaged">Heavy damaged:</label>
|
|
||||||
<input type="checkbox" name="heavy_damaged" id="id_heavy_damaged">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="id_position">Position:</label>
|
|
||||||
<input type="text" name="position" maxlength="100" id="id_position">
|
|
||||||
</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 Reception
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="hidden" name="preinfo_id" id="preinfo_id" value="{{ preinfo.pk }}">
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
<div class="bg-white rounded-lg shadow">
|
{% elif form %}
|
||||||
<div class="px-6 py-4 border-b border-gray-200">
|
<form method="post" class="w-full">
|
||||||
<h3 class="text-lg font-semibold text-gray-800">Recent Container Movements</h3>
|
{% csrf_token %}
|
||||||
</div>
|
<input type="hidden" name="search_number" value="{{ search_number }}">
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
<div class="overflow-x-auto">
|
<div class="flex flex-col gap-4">
|
||||||
<table class="min-w-full divide-y divide-gray-200">
|
<div class="form-group">
|
||||||
<thead>
|
{{ form.as_p }}
|
||||||
<tr>
|
</div>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Container</th>
|
<div class="form-group">
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>
|
<button type="submit" class="btn-primary">
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Line</th>
|
Receive Container
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Time</th>
|
</button>
|
||||||
{# <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Direction</th>#}
|
<button type="button" onclick="window.location.href='{% url 'barrier_dashboard' %}'" class="btn-secondary">
|
||||||
{# <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>#}
|
Back
|
||||||
</tr>
|
</button>
|
||||||
</thead>
|
|
||||||
<tbody class="divide-y divide-gray-200">
|
|
||||||
{% for container in containers %}
|
|
||||||
<tr>
|
|
||||||
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">
|
|
||||||
{{ container.number }}</td>
|
|
||||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">{{ container.container_type }}</td>
|
|
||||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">{{ container.line.name }}</td>
|
|
||||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">{{ container.received_on }}</td>
|
|
||||||
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">IN</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">Complete</span>#}
|
|
||||||
{# </td>#}
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Container Expedition Content -->
|
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
<div class="p-4 flex justify-center">
|
{% endif %}
|
||||||
<button id="capturePhoto" class="w-16 h-16 rounded-full bg-red-600 border-4 border-white flex items-center justify-center pulse">
|
</div>
|
||||||
<div class="w-12 h-12 rounded-full bg-red-600"></div>
|
{% endblock content %}
|
||||||
</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>#}
|
|
||||||
{##}
|
|
||||||
{#<script>#}
|
|
||||||
{#document.getElementById('check-preinfo-btn').onclick = function() {#}
|
|
||||||
{# const number = document.getElementById('id_number').value;#}
|
|
||||||
{# fetch(`/preinfo/check-preinfo/?number=${encodeURIComponent(number)}`)#}
|
|
||||||
{# .then(response => response.json())#}
|
|
||||||
{# .then(data => {#}
|
|
||||||
{# if (data.found) {#}
|
|
||||||
{# alert('Preinfo found ' + data.container_type);#}
|
|
||||||
{# document.getElementById('id_line').value = data.line;#}
|
|
||||||
{# document.getElementById('id_container_type').value = data.container_type;#}
|
|
||||||
{# document.getElementById('preinfo_id').value = data.preinfo_id;#}
|
|
||||||
{# } else {#}
|
|
||||||
{# alert('No preinfo found or already received.');#}
|
|
||||||
{# }#}
|
|
||||||
{# });#}
|
|
||||||
{#;#}
|
|
||||||
{#</script>#}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,10 +1,17 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>$Title$</title>
|
<title>Payment Request</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif;">
|
||||||
$END$
|
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||||
|
<h1>Welcome to {{ site_name }}!</h1>
|
||||||
|
<p>Hello {{ user_email }},</p>
|
||||||
|
<p>There is a new payment request, see details in the attachments. You can pay via ePay:</p>
|
||||||
|
<a href="{{ link1 }}" style="background-color: #211FA6; border: none; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; border-radius: 10px;">ePay account</a>
|
||||||
|
<p>or credit card:</p>
|
||||||
|
<a href="{{ link2 }}" style="background-color: #211FA6; border: none; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; border-radius: 10px;">Credit card</a>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -1,10 +1,47 @@
|
|||||||
<!DOCTYPE html>
|
{# templates/barrier/container-search.html #}
|
||||||
<html lang="en">
|
{% extends "barrier/barrier-base.html" %}
|
||||||
<head>
|
{% block content %}
|
||||||
<meta charset="UTF-8">
|
<div class="barrier-container">
|
||||||
<title>$Title$</title>
|
<form method="post" class="w-full">
|
||||||
</head>
|
{% csrf_token %}
|
||||||
<body>
|
<input type="hidden" name="search_type" value="{{ search_type }}">
|
||||||
$END$
|
|
||||||
</body>
|
<div class="p-6">
|
||||||
</html>
|
<label for="id_number" class="block text-2xl mb-4">
|
||||||
|
{% if search_type == 'container_receive' %}
|
||||||
|
Enter Booking Number
|
||||||
|
{% else %}
|
||||||
|
Enter Container Number
|
||||||
|
{% endif %}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<input type="text"
|
||||||
|
id="id_number"
|
||||||
|
name="number"
|
||||||
|
class="px-4 py-3 text-xl border-2 rounded-lg"
|
||||||
|
required
|
||||||
|
autocomplete="off"
|
||||||
|
autofocus>
|
||||||
|
|
||||||
|
<button type="submit"
|
||||||
|
class="barrier-button">
|
||||||
|
Search
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{% if error %}
|
||||||
|
<div class="text-red-600 text-xl text-center">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<a href="{% url 'barrier_dashboard' %}"
|
||||||
|
class="barrier-button"
|
||||||
|
style="background-color: #f5f5f5;">
|
||||||
|
Back
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@ -1,34 +1,44 @@
|
|||||||
{% extends "employee-base.html" %}
|
{% extends "employee-base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% csrf_token %}
|
<form method="POST">
|
||||||
{{ form.as_p }}
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
|
||||||
|
{% for container in containers %}
|
||||||
|
<input type="hidden" name="containers" value="{{ container.id }}">
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
<div class="selected-containers">
|
|
||||||
<h3>Selected Containers</h3>
|
<div>
|
||||||
<table class="table">
|
<button id="createPaymentBtn" class="btn btn-primary" type="submit">Create Payment</button>
|
||||||
<thead>
|
</div>
|
||||||
<tr>
|
|
||||||
<th>Container Number</th>
|
<div class="selected-containers">
|
||||||
<th>Type</th>
|
<h3>Selected Containers</h3>
|
||||||
<th>Company</th>
|
<table class="table">
|
||||||
<th>Received Date</th>
|
<thead>
|
||||||
<th>Expedited Date</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for container in containers %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ container.number }}</td>
|
<th>Container Number</th>
|
||||||
<td>{{ container.container_type }}</td>
|
<th>Type</th>
|
||||||
<td>{{ container.line.company.short_name }}</td>
|
<th>Company</th>
|
||||||
<td>{{ container.received_on|date:"Y-m-d" }}</td>
|
<th>Received Date</th>
|
||||||
<td>{{ container.expedited_on|date:"Y-m-d" }}</td>
|
<th>Expedited Date</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{% for container in containers %}
|
||||||
</div>
|
<tr>
|
||||||
|
<td>{{ container.number }}</td>
|
||||||
|
<td>{{ container.container_type }}</td>
|
||||||
|
<td>{{ container.line.company.short_name }}</td>
|
||||||
|
<td>{{ container.received_on|date:"Y-m-d" }}</td>
|
||||||
|
<td>{{ container.expedited_on|date:"Y-m-d" }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Loading…
Reference in New Issue