switch from ownCloud to minIO
buttons in table are buttons and do edit and delete properly
This commit is contained in:
Generated
+31
-43
@@ -5,58 +5,50 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="7410a44d-51b9-408b-85ad-4fa46776b372" name="Changes" comment="commit unversioned files ;)">
|
<list default="true" id="7410a44d-51b9-408b-85ad-4fa46776b372" name="Changes" comment="commit unversioned files ;)">
|
||||||
|
<change afterPath="$PROJECT_DIR$/accounts/migrations/0008_populate_permissions.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/accounts/migrations/0009_create_superuser.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/booking/templatetags/__init__.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/booking/templatetags/custom_filters.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/common/migrations/0004_populate_initial_data.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/common/utils/minio_utils.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/containers/views/common.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/docker-compose.minio.yml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/payments/migrations/0004_populate_tariffs.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/static/js/container-details.js" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/static/styles/details.css" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/templates/common/base.html" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/templates/common/container-details.html" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/DepoT/settings.py" beforeDir="false" afterPath="$PROJECT_DIR$/DepoT/settings.py" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/DepoT/settings.py" beforeDir="false" afterPath="$PROJECT_DIR$/DepoT/settings.py" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/DepoT/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/DepoT/urls.py" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/accounts/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/accounts/urls.py" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/accounts/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/accounts/forms.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/accounts/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/accounts/views.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$/common/templatetags/filters.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/templatetags/filters.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/utils/utils.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/utils/utils.py" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/common/utils/owncloud_utls.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/utils/owncloud_utls.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_api/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/common_api/urls.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/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/forms.py" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/containers/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/models.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/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$/damages_api/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/damages_api/urls.py" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/damages_api/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/damages_api/urls.py" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/damages_api/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/damages_api/views.py" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/damages_api/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/damages_api/views.py" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/docker-compose.yml" beforeDir="false" afterPath="$PROJECT_DIR$/docker-compose.yml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/docker-compose.yml" beforeDir="false" afterPath="$PROJECT_DIR$/docker-compose.yml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/dockerfile" beforeDir="false" afterPath="$PROJECT_DIR$/dockerfile" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/dockerfile" beforeDir="false" afterPath="$PROJECT_DIR$/dockerfile" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/payments/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/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$/payments/services.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/services.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/payments/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/urls.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/payments/utils.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/utils.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/payments/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/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/views/client_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/views/client_views.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$/static/images/k-depot-logo.svg" beforeDir="false" afterPath="$PROJECT_DIR$/static/images/k-depot-logo.svg" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/requirements.txt" beforeDir="false" afterPath="$PROJECT_DIR$/requirements.txt" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/static/js/container_validation.js" beforeDir="false" afterPath="$PROJECT_DIR$/static/js/container_validation.js" 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/js/crud-list.js" beforeDir="false" afterPath="$PROJECT_DIR$/static/js/crud-list.js" 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/forms.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/forms.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/client-base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client-base.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-sidebar.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client-sidebar.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/booking-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/booking-list.html" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/templates/client/line-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/line-list.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/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-photos.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/container-photos.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/email.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/email.html" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/templates/employee-base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee-base.html" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/templates/employee-base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee-base.html" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/templates/employee/container-search.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/container-search.html" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/templates/employee-sidebar.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee-sidebar.html" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/templates/employee/containers-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/containers-list.html" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/templates/employee/booking-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/booking-list.html" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/templates/employee/payment-create.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/payment-create.html" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/templates/employee/company-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/company-list.html" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/templates/employee/payment-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/payment-list.html" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/templates/employee/line-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/line-list.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/landing-page.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/landing-page.html" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/templates/landing-page.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/landing-page.html" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/templates/list-crud.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/list-crud.html" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/templates/list-crud.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/list-crud.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/user-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/registration/user-list.html" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -70,9 +62,9 @@
|
|||||||
<component name="FileTemplateManagerImpl">
|
<component name="FileTemplateManagerImpl">
|
||||||
<option name="RECENT_TEMPLATES">
|
<option name="RECENT_TEMPLATES">
|
||||||
<list>
|
<list>
|
||||||
|
<option value="HTML File" />
|
||||||
<option value="CSS File" />
|
<option value="CSS File" />
|
||||||
<option value="JavaScript File" />
|
<option value="JavaScript File" />
|
||||||
<option value="HTML File" />
|
|
||||||
<option value="Python Script" />
|
<option value="Python Script" />
|
||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
@@ -99,7 +91,7 @@
|
|||||||
"django.template.preview.state": "SHOW_EDITOR_AND_PREVIEW",
|
"django.template.preview.state": "SHOW_EDITOR_AND_PREVIEW",
|
||||||
"git-widget-placeholder": "master",
|
"git-widget-placeholder": "master",
|
||||||
"ignore.virus.scanning.warn.message": "true",
|
"ignore.virus.scanning.warn.message": "true",
|
||||||
"last_opened_file_path": "C:/dev_projects/python/Django/DepoT/templates/employee",
|
"last_opened_file_path": "C:/dev_projects/python/Django/DepoT",
|
||||||
"list.type.of.created.stylesheet": "CSS",
|
"list.type.of.created.stylesheet": "CSS",
|
||||||
"node.js.detected.package.eslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.detected.package.tslint": "true",
|
"node.js.detected.package.tslint": "true",
|
||||||
@@ -116,11 +108,11 @@
|
|||||||
}</component>
|
}</component>
|
||||||
<component name="RecentsManager">
|
<component name="RecentsManager">
|
||||||
<key name="CopyFile.RECENT_KEYS">
|
<key name="CopyFile.RECENT_KEYS">
|
||||||
|
<recent name="C:\dev_projects\python\Django\DepoT" />
|
||||||
<recent name="C:\dev_projects\python\Django\DepoT\templates\employee" />
|
<recent name="C:\dev_projects\python\Django\DepoT\templates\employee" />
|
||||||
<recent name="C:\dev_projects\python\Django\DepoT\templates\client" />
|
<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" />
|
||||||
<recent name="C:\dev_projects\python\Django\DepoT\templates\registration" />
|
<recent name="C:\dev_projects\python\Django\DepoT\templates\registration" />
|
||||||
<recent name="C:\dev_projects\python\Django\DepoT\static\styles" />
|
|
||||||
</key>
|
</key>
|
||||||
<key name="MoveFile.RECENT_KEYS">
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
<recent name="C:\dev_projects\python\Django\DepoT" />
|
<recent name="C:\dev_projects\python\Django\DepoT" />
|
||||||
@@ -179,7 +171,8 @@
|
|||||||
<workItem from="1753174764499" duration="271000" />
|
<workItem from="1753174764499" duration="271000" />
|
||||||
<workItem from="1753175068058" duration="4635000" />
|
<workItem from="1753175068058" duration="4635000" />
|
||||||
<workItem from="1753179863298" duration="8357000" />
|
<workItem from="1753179863298" duration="8357000" />
|
||||||
<workItem from="1753197869497" duration="15872000" />
|
<workItem from="1753197869497" duration="98156000" />
|
||||||
|
<workItem from="1753637487803" duration="30673000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="Add IntelliJ IDEA project configuration files 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.">
|
<task id="LOCAL-00001" summary="Add IntelliJ IDEA project configuration files 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" />
|
<option name="closed" value="true" />
|
||||||
@@ -304,11 +297,6 @@
|
|||||||
<line>118</line>
|
<line>118</line>
|
||||||
<option name="timeStamp" value="61" />
|
<option name="timeStamp" value="61" />
|
||||||
</line-breakpoint>
|
</line-breakpoint>
|
||||||
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
|
|
||||||
<url>file://$PROJECT_DIR$/common/utils/owncloud_utls.py</url>
|
|
||||||
<line>29</line>
|
|
||||||
<option name="timeStamp" value="64" />
|
|
||||||
</line-breakpoint>
|
|
||||||
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
|
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
|
||||||
<url>file://$PROJECT_DIR$/payments/views.py</url>
|
<url>file://$PROJECT_DIR$/payments/views.py</url>
|
||||||
<line>41</line>
|
<line>41</line>
|
||||||
|
|||||||
+18
-9
@@ -91,15 +91,13 @@ WSGI_APPLICATION = "DepoT.wsgi.application"
|
|||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.postgresql",
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
"NAME": os.environ.get("DB_NAME", env("DB_NAME")),
|
"NAME": env("DB_NAME"),
|
||||||
"USER": os.environ.get("DB_USER", env("DB_USER")),
|
"USER": env("DB_USER"),
|
||||||
"PASSWORD": os.environ.get("DB_PASSWORD", env("DB_PASSWORD")),
|
"PASSWORD": env("DB_PASSWORD"),
|
||||||
"HOST": os.environ.get("DB_HOST", env("DB_HOST")),
|
"HOST": env("DB_HOST"),
|
||||||
"PORT": os.environ.get("DB_PORT", env("DB_PORT")),
|
"PORT": env("DB_PORT"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
@@ -167,8 +165,9 @@ EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD", cast=str, default=None)
|
|||||||
EMAIL_USE_TLS = env("EMAIL_USE_TLS", cast=bool, default=True) # Use EMAIL_PORT 587 for TLS
|
EMAIL_USE_TLS = env("EMAIL_USE_TLS", cast=bool, default=True) # Use EMAIL_PORT 587 for TLS
|
||||||
EMAIL_USE_SSL = env("EMAIL_USE_SSL", cast=bool, default=False) # EUse MAIL_PORT 465 for SSL
|
EMAIL_USE_SSL = env("EMAIL_USE_SSL", cast=bool, default=False) # EUse MAIL_PORT 465 for SSL
|
||||||
|
|
||||||
ADMIN_USER_NAME=env("ADMIN_USER_NAME", default="Admin user")
|
ADMIN_USER_NAME=env("ADMIN_USER_NAME")
|
||||||
ADMIN_USER_EMAIL=env("ADMIN_USER_EMAIL", default=None)
|
ADMIN_USER_PASSWORD=env("ADMIN_USER_PASSWORD")
|
||||||
|
ADMIN_USER_EMAIL=env("ADMIN_USER_EMAIL")
|
||||||
|
|
||||||
MANAGERS=[]
|
MANAGERS=[]
|
||||||
ADMINS=[]
|
ADMINS=[]
|
||||||
@@ -177,3 +176,13 @@ if all([ADMIN_USER_NAME, ADMIN_USER_EMAIL]):
|
|||||||
(f'{ADMIN_USER_NAME}', f'{ADMIN_USER_EMAIL}')
|
(f'{ADMIN_USER_NAME}', f'{ADMIN_USER_EMAIL}')
|
||||||
]
|
]
|
||||||
MANAGERS=ADMINS
|
MANAGERS=ADMINS
|
||||||
|
|
||||||
|
|
||||||
|
MINIO_ENDPOINT = env('MINIO_ENDPOINT')
|
||||||
|
AWS_S3_CUSTOM_DOMAIN = env('AWS_S3_CUSTOM_DOMAIN')
|
||||||
|
MINIO_SERVER_URL = env('MINIO_SERVER_URL')
|
||||||
|
AWS_S3_URL_PROTOCOL = env('AWS_S3_URL_PROTOCOL')
|
||||||
|
MINIO_ACCESS_KEY = env('MINIO_ACCESS_KEY')
|
||||||
|
MINIO_SECRET_KEY = env('MINIO_SECRET_KEY')
|
||||||
|
MINIO_BUCKET_NAME = env('MINIO_BUCKET_NAME')
|
||||||
|
MINIO_SECURE = False # Set to True if using HTTPS
|
||||||
@@ -9,5 +9,6 @@ urlpatterns = [
|
|||||||
path('', views.UserListView.as_view(), name='user_list'),
|
path('', views.UserListView.as_view(), name='user_list'),
|
||||||
path('register/', views.RegisterView.as_view(), name='user_register'),
|
path('register/', views.RegisterView.as_view(), name='user_register'),
|
||||||
path('<int:pk>/update/', views.UserUpdateView.as_view(), name='user_update'),
|
path('<int:pk>/update/', views.UserUpdateView.as_view(), name='user_update'),
|
||||||
|
path('<int:pk>/active/', views.UserActiveView.as_view(), name='user_active'),
|
||||||
])),
|
])),
|
||||||
]
|
]
|
||||||
+19
-1
@@ -1,15 +1,20 @@
|
|||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.views import LoginView
|
from django.contrib.auth.views import LoginView
|
||||||
|
from django.http import HttpResponseForbidden, JsonResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views import View
|
||||||
from django.views.generic import TemplateView, FormView, ListView, UpdateView
|
from django.views.generic import TemplateView, FormView, ListView, UpdateView
|
||||||
|
from rest_framework.generics import get_object_or_404
|
||||||
|
|
||||||
from accounts.forms import LoginForm, RegisterForm
|
from accounts.forms import LoginForm, RegisterForm
|
||||||
from accounts.models import DepotUser
|
from accounts.models import DepotUser
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required, user_passes_test
|
from django.contrib.auth.decorators import login_required, user_passes_test
|
||||||
from django.contrib.auth.mixins import AccessMixin
|
from django.contrib.auth.mixins import AccessMixin, LoginRequiredMixin
|
||||||
|
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
|
||||||
|
|
||||||
@@ -165,5 +170,18 @@ class UserUpdateView(UpdateView):
|
|||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
class UserActiveView(LoginRequiredMixin, View):
|
||||||
|
success_url = reverse_lazy('user_list')
|
||||||
|
|
||||||
|
def post(self, request, pk, *args, **kwargs):
|
||||||
|
user = request.user
|
||||||
|
if not (user.is_superuser or getattr(user, 'user_type', None) == DepotUser.UserType.COMPANY_ADMIN):
|
||||||
|
return HttpResponseForbidden("You do not have permission to perform this action.")
|
||||||
|
|
||||||
|
target_user = get_object_or_404(get_user_model(), pk=pk)
|
||||||
|
if target_user == user:
|
||||||
|
return HttpResponseForbidden("You cannot change your own active status.")
|
||||||
|
|
||||||
|
target_user.is_active = not target_user.is_active
|
||||||
|
target_user.save()
|
||||||
|
return JsonResponse({'success': True, 'is_active': target_user.is_active})
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def distinct_vehicles(vehicles):
|
||||||
|
vehicles_set = set(vehicles.split(','))
|
||||||
|
vehicles_str = ', '.join(sorted(vehicles_set))
|
||||||
|
return vehicles_str
|
||||||
|
|
||||||
|
|
||||||
+2
-1
@@ -7,7 +7,8 @@ urlpatterns = [
|
|||||||
path('client/', include([
|
path('client/', include([
|
||||||
path('', ClientBookingView.as_view(), name='client_booking'),
|
path('', ClientBookingView.as_view(), name='client_booking'),
|
||||||
path('create/', CreateBookingView.as_view(), name='client_booking_create'),
|
path('create/', CreateBookingView.as_view(), name='client_booking_create'),
|
||||||
path('update/<int:pk>/', ClientBookingUpdateView.as_view(), name='client_booking_update'),
|
path('<int:pk>/update/', ClientBookingUpdateView.as_view(), name='client_booking_update'),
|
||||||
|
path('<int:pk>/active/', ClientBookingUpdateView.as_view(), name='client_booking_active'),
|
||||||
])),
|
])),
|
||||||
path('employee/', BookingListView.as_view(), name='employee_bookings'),
|
path('employee/', BookingListView.as_view(), name='employee_bookings'),
|
||||||
]
|
]
|
||||||
@@ -22,15 +22,18 @@ urlpatterns = [
|
|||||||
path('', ClientLineListView.as_view(), name='client_line'),
|
path('', ClientLineListView.as_view(), name='client_line'),
|
||||||
path('create/', ClientLineCreateView.as_view(), name='client_line_create'),
|
path('create/', ClientLineCreateView.as_view(), name='client_line_create'),
|
||||||
path('<int:pk>/edit/', ClientLineUpdateView.as_view(), name='client_line_update'),
|
path('<int:pk>/edit/', ClientLineUpdateView.as_view(), name='client_line_update'),
|
||||||
|
path('<int:pk>/active/', ClientLineUpdateView.as_view(), name='client_line_active'),
|
||||||
])),
|
])),
|
||||||
path('employee/company/', include([
|
path('employee/company/', include([
|
||||||
path('', EmployeeCompanyListView.as_view(), name='employee_company'),
|
path('', EmployeeCompanyListView.as_view(), name='employee_company'),
|
||||||
path('create/', EmployeeCompanyCreateView.as_view(), name='employee_company_create'),
|
path('create/', EmployeeCompanyCreateView.as_view(), name='employee_company_create'),
|
||||||
path('<int:pk>/edit/', EmployeeCompanyUpdateView.as_view(), name='employee_company_update'),
|
path('<int:pk>/edit/', EmployeeCompanyUpdateView.as_view(), name='employee_company_update'),
|
||||||
|
path('<int:pk>/active/', EmployeeCompanyUpdateView.as_view(), name='employee_company_active'),
|
||||||
])),
|
])),
|
||||||
path('employee/line/', include([
|
path('employee/line/', include([
|
||||||
path('', EmployeeLineListView.as_view(), name='employee_line'),
|
path('', EmployeeLineListView.as_view(), name='employee_line'),
|
||||||
path('create/', EmployeeLineCreateView.as_view(), name='employee_line_create'),
|
path('create/', EmployeeLineCreateView.as_view(), name='employee_line_create'),
|
||||||
path('<int:pk>/edit/', EmployeeLineUpdateView.as_view(), name='employee_line_update'),
|
path('<int:pk>/edit/', EmployeeLineUpdateView.as_view(), name='employee_line_update'),
|
||||||
|
path('<int:pk>/active/', EmployeeLineUpdateView.as_view(), name='employee_line_active'),
|
||||||
])),
|
])),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
from minio import Minio
|
||||||
|
from django.conf import settings
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status
|
||||||
|
import boto3
|
||||||
|
from botocore.client import Config
|
||||||
|
|
||||||
|
client = Minio(
|
||||||
|
settings.MINIO_ENDPOINT,
|
||||||
|
access_key=settings.MINIO_ACCESS_KEY,
|
||||||
|
secret_key=settings.MINIO_SECRET_KEY,
|
||||||
|
secure=settings.MINIO_SECURE
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def upload_damage_photo(photo_file, depot_id, content_type):
|
||||||
|
try:
|
||||||
|
s3_client = boto3.client('s3',
|
||||||
|
endpoint_url=f'http://{settings.MINIO_ENDPOINT}',
|
||||||
|
aws_access_key_id=settings.MINIO_ACCESS_KEY,
|
||||||
|
aws_secret_access_key=settings.MINIO_SECRET_KEY,
|
||||||
|
config=Config(signature_version='s3v4'),
|
||||||
|
region_name='us-east-1'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Use the full filename as the key, which includes the depot_id folder
|
||||||
|
s3_client.upload_fileobj(
|
||||||
|
photo_file,
|
||||||
|
settings.MINIO_BUCKET_NAME,
|
||||||
|
photo_file.name,
|
||||||
|
ExtraArgs={'ContentType': content_type}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate a presigned URL for the uploaded object
|
||||||
|
url = s3_client.generate_presigned_url('get_object',
|
||||||
|
Params={
|
||||||
|
'Bucket': settings.MINIO_BUCKET_NAME,
|
||||||
|
'Key': photo_file.name
|
||||||
|
},
|
||||||
|
ExpiresIn=3600
|
||||||
|
)
|
||||||
|
|
||||||
|
return url
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error uploading to MinIO: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def get_damages(depot_id):
|
||||||
|
try:
|
||||||
|
objects = client.list_objects(
|
||||||
|
settings.MINIO_BUCKET_NAME,
|
||||||
|
prefix=f"{depot_id}/"
|
||||||
|
)
|
||||||
|
|
||||||
|
damages = []
|
||||||
|
for obj in objects:
|
||||||
|
url = client.presigned_get_object(
|
||||||
|
settings.MINIO_BUCKET_NAME,
|
||||||
|
obj.object_name,
|
||||||
|
expires=timedelta(days=7)
|
||||||
|
)
|
||||||
|
damages.append({
|
||||||
|
"url": url,
|
||||||
|
"filename": obj.object_name.split('/')[-1]
|
||||||
|
})
|
||||||
|
|
||||||
|
return Response(damages, status=status.HTTP_200_OK)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error listing objects from MinIO: {str(e)}")
|
||||||
|
return Response([], status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
@@ -2,11 +2,15 @@ import os
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.http import HttpResponse
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
import owncloud
|
import owncloud
|
||||||
from owncloud import (HTTPResponseError)
|
from owncloud import (HTTPResponseError)
|
||||||
|
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
class Owncloud:
|
class Owncloud:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -33,8 +37,41 @@ class Owncloud:
|
|||||||
damages = []
|
damages = []
|
||||||
for file_info in files:
|
for file_info in files:
|
||||||
if file_info.is_dir():
|
if file_info.is_dir():
|
||||||
continue # Пропускаме поддиректории
|
continue
|
||||||
# Създаване на публичен линк за всеки файл
|
|
||||||
link_info = oc.share_file_with_link(file_info.path)
|
link_info = oc.share_file_with_link(file_info.path)
|
||||||
damages.append(link_info.get_link())
|
damages.append(link_info.get_link())
|
||||||
return Response(damages, status=status.HTTP_200_OK)
|
return Response(damages, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_damages_webdav(depot_id):
|
||||||
|
oc = owncloud.Client(settings.OWNCLOUD_URL)
|
||||||
|
oc.login(settings.OWNCLOUD_USER, settings.OWNCLOUD_PASSWORD)
|
||||||
|
path = f"{settings.OWNCLOUD_DAMAGES_FOLDER}{str(depot_id)}"
|
||||||
|
files = oc.list(path)
|
||||||
|
damages = []
|
||||||
|
|
||||||
|
for file_info in files:
|
||||||
|
if file_info.is_dir():
|
||||||
|
continue
|
||||||
|
|
||||||
|
original_filename = os.path.basename(file_info.path)
|
||||||
|
link_info = oc.share_file_with_link(file_info.path)
|
||||||
|
share_url = link_info.get_link()
|
||||||
|
token = share_url.rstrip('/').split('/')[-1]
|
||||||
|
|
||||||
|
damages.append({
|
||||||
|
"token": token,
|
||||||
|
"filename": original_filename
|
||||||
|
})
|
||||||
|
|
||||||
|
return Response(damages, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
def proxy_owncloud_image(request, token, filename):
|
||||||
|
url = f"{settings.OWNCLOUD_URL}/public.php/webdav/{filename}"
|
||||||
|
r = requests.get(url, auth=(token, ''), stream=True)
|
||||||
|
if r.status_code == 200:
|
||||||
|
content_type = r.headers.get('Content-Type', 'image/jpeg')
|
||||||
|
return HttpResponse(r.content, content_type=content_type)
|
||||||
|
return HttpResponse("Not found", status=404)
|
||||||
+12
-1
@@ -1,4 +1,4 @@
|
|||||||
from django.forms import ModelForm, TextInput
|
from django.forms import ModelForm, TextInput, CharField, Form
|
||||||
|
|
||||||
from containers.models import Container
|
from containers.models import Container
|
||||||
|
|
||||||
@@ -52,3 +52,14 @@ class ContainerExpeditionForm(ContainerBaseForm):
|
|||||||
for field in readonly_fields:
|
for field in readonly_fields:
|
||||||
self.fields[field].widget.attrs['readonly'] = True
|
self.fields[field].widget.attrs['readonly'] = True
|
||||||
# self.fields[field].disabled = True
|
# self.fields[field].disabled = True
|
||||||
|
|
||||||
|
|
||||||
|
class ContainerSearchForm(Form):
|
||||||
|
container_number = CharField(
|
||||||
|
max_length=11,
|
||||||
|
required=True,
|
||||||
|
widget=TextInput(attrs={
|
||||||
|
'class': 'form-control',
|
||||||
|
'placeholder': 'Enter container number'
|
||||||
|
})
|
||||||
|
)
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
|
|
||||||
|
from containers.views.common import ContainerDetails
|
||||||
from containers.views.employee_views import ContainersListView, ReportContainersUnpaidListView
|
from containers.views.employee_views import ContainersListView, ReportContainersUnpaidListView
|
||||||
from containers.views.barrier_views import ContainerReceiveView, ContainerExpedition, ContainerSearchView, \
|
from containers.views.barrier_views import ContainerReceiveView, ContainerExpedition, ContainerSearchView, \
|
||||||
ContainerPhotosView
|
ContainerPhotosView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('container-search/', ContainerSearchView.as_view(), name='container_search'),
|
path('container-search/', ContainerSearchView.as_view(), name='container_search'),
|
||||||
|
path('container-details/', ContainerDetails.as_view(), name='container_details'),
|
||||||
path('container-search/', ContainerSearchView.as_view(), name='barrier_photos'),
|
path('container-search/', ContainerSearchView.as_view(), name='barrier_photos'),
|
||||||
# path('search/', ContainerSearchView.as_view(), name='container_search'),
|
# path('search/', ContainerSearchView.as_view(), name='container_search'),
|
||||||
path('employee/', ContainersListView.as_view(), name='employee_containers'),
|
path('employee/', ContainersListView.as_view(), name='employee_containers'),
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
from django.views.generic import FormView, ListView
|
||||||
|
from django.views.generic.base import TemplateView
|
||||||
|
|
||||||
|
from common.utils.utils import filter_queryset_by_user
|
||||||
|
from containers.forms import ContainerSearchForm
|
||||||
|
from containers.models import Container
|
||||||
|
|
||||||
|
|
||||||
|
# class ContainerDetails(FormView):
|
||||||
|
# template_name = 'common/container-details.html'
|
||||||
|
# form_class = ContainerSearchForm
|
||||||
|
# model = Container
|
||||||
|
# base_template = 'employee-base.html'
|
||||||
|
# context_object_name = 'objects'
|
||||||
|
# paginate_by = 10
|
||||||
|
#
|
||||||
|
# def get_context_data(self, **kwargs):
|
||||||
|
# context = super().get_context_data(**kwargs)
|
||||||
|
# context['base_template'] = self.base_template
|
||||||
|
# return context
|
||||||
|
#
|
||||||
|
# def get(self, request, *args, **kwargs):
|
||||||
|
#
|
||||||
|
# referrer = request.META.get('HTTP_REFERER', '')
|
||||||
|
# if 'container-details' not in referrer:
|
||||||
|
# request.session.pop('container_number', None)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# context = self.get_context_data()
|
||||||
|
# selected_id = request.GET.get('selected')
|
||||||
|
# container_number = request.session.get('container_number')
|
||||||
|
#
|
||||||
|
# if container_number:
|
||||||
|
# containers = Container.objects.filter(number=container_number).order_by('-received_on')
|
||||||
|
# if containers.exists():
|
||||||
|
# if selected_id:
|
||||||
|
# try:
|
||||||
|
# selected_container = containers.get(id=selected_id)
|
||||||
|
# except Container.DoesNotExist:
|
||||||
|
# selected_container = containers.first()
|
||||||
|
# else:
|
||||||
|
# selected_container = containers.first()
|
||||||
|
#
|
||||||
|
# context.update({
|
||||||
|
# 'form': self.form_class(initial={'container_number': container_number}),
|
||||||
|
# 'container': selected_container,
|
||||||
|
# 'objects': containers, # For list-crud.html compatibility
|
||||||
|
# 'show_results': True
|
||||||
|
# })
|
||||||
|
# return render(request, self.template_name, context)
|
||||||
|
#
|
||||||
|
# context.update({
|
||||||
|
# 'form': self.form_class(),
|
||||||
|
# 'show_results': False
|
||||||
|
# })
|
||||||
|
# return render(request, self.template_name, context)
|
||||||
|
#
|
||||||
|
# def form_valid(self, form):
|
||||||
|
# container_number = form.cleaned_data['container_number']
|
||||||
|
# self.request.session['container_number'] = container_number # Store in session
|
||||||
|
# context = self.get_context_data()
|
||||||
|
# containers = Container.objects.filter(number=container_number).order_by('-received_on')
|
||||||
|
# if containers.exists():
|
||||||
|
# context.update({
|
||||||
|
# 'form': form,
|
||||||
|
# 'container': containers.first(),
|
||||||
|
# 'objects': containers, # For list-crud.html compatibility
|
||||||
|
# 'show_results': True
|
||||||
|
# })
|
||||||
|
# else:
|
||||||
|
# context.update({
|
||||||
|
# 'form': form,
|
||||||
|
# 'error': 'Container not found',
|
||||||
|
# 'show_results': False
|
||||||
|
# })
|
||||||
|
# return render(self.request, self.template_name, context)
|
||||||
|
|
||||||
|
|
||||||
|
class ContainerDetails(ListView):
|
||||||
|
template_name = 'common/container-details.html'
|
||||||
|
model = Container
|
||||||
|
base_template = 'employee-base.html'
|
||||||
|
context_object_name = 'objects'
|
||||||
|
paginate_by = 10
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
container_number = self.request.session.get('container_number')
|
||||||
|
if container_number:
|
||||||
|
query = Container.objects.filter(number=container_number).order_by('-received_on')
|
||||||
|
query = filter_queryset_by_user(query, self.request.user)
|
||||||
|
return query
|
||||||
|
|
||||||
|
return Container.objects.none()
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['base_template'] = self.base_template
|
||||||
|
context['form'] = ContainerSearchForm(
|
||||||
|
initial={'container_number': self.request.session.get('container_number')}
|
||||||
|
)
|
||||||
|
|
||||||
|
full_queryset = self.get_queryset()
|
||||||
|
if full_queryset.exists():
|
||||||
|
selected_id = self.request.GET.get('selected')
|
||||||
|
if selected_id:
|
||||||
|
try:
|
||||||
|
context['container'] = full_queryset.get(id=selected_id)
|
||||||
|
except Container.DoesNotExist:
|
||||||
|
context['container'] = full_queryset.first()
|
||||||
|
else:
|
||||||
|
context['container'] = full_queryset.first()
|
||||||
|
context['show_results'] = True
|
||||||
|
else:
|
||||||
|
context['show_results'] = False
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
form = ContainerSearchForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
container_number = form.cleaned_data['container_number']
|
||||||
|
request.session['container_number'] = container_number
|
||||||
|
return self.get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
context = self.get_context_data()
|
||||||
|
context['form'] = form
|
||||||
|
context['show_results'] = False
|
||||||
|
return render(request, self.template_name, context)
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
from common.utils.owncloud_utls import proxy_owncloud_image
|
||||||
from damages_api.views import Damages
|
from damages_api.views import Damages
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|||||||
+60
-27
@@ -1,52 +1,85 @@
|
|||||||
import base64
|
import base64
|
||||||
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
import boto3
|
||||||
|
from botocore.client import Config
|
||||||
from django.core.exceptions import BadRequest
|
from django.core.exceptions import BadRequest
|
||||||
|
from django.core.files.base import ContentFile
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from common.utils.owncloud_utls import Owncloud
|
# from common.utils.owncloud_utls import Owncloud
|
||||||
from containers.models import ContainerPhotos, Container
|
from containers.models import ContainerPhotos, Container
|
||||||
|
from common.utils.minio_utils import get_damages, upload_damage_photo
|
||||||
|
|
||||||
|
|
||||||
class Damages(APIView):
|
class Damages(APIView):
|
||||||
|
|
||||||
def post(self, request, depot_id):
|
def post(self, request, depot_id):
|
||||||
photo = request.data.get("photo")
|
photo = request.data.get("photo")
|
||||||
extension = request.data.get("photo_extension")
|
extension = request.data.get("photo_extension")
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=f'.{extension}', delete=False) as temp_file:
|
try:
|
||||||
try:
|
# Decode base64 and create a ContentFile
|
||||||
temp_file.write(base64.b64decode(photo.encode("utf-8")))
|
photo_data = base64.b64decode(photo.encode("utf-8"))
|
||||||
temp_file.flush()
|
|
||||||
temp_file.close() # Close the file before uploading
|
|
||||||
|
|
||||||
own_filename = Owncloud.upload_damage_photo(temp_file.name, depot_id)
|
# Generate a unique filename with proper folder structure
|
||||||
|
import uuid
|
||||||
|
filename = f"{depot_id}/{datetime.today().strftime('%Y%m%d')}_{uuid.uuid4()}.{extension}"
|
||||||
|
photo_file = ContentFile(photo_data, name=filename)
|
||||||
|
|
||||||
container_photo = ContainerPhotos()
|
# Set content type based on file extension
|
||||||
container_photo.container = Container.objects.get(pk=depot_id)
|
content_type = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
||||||
container_photo.photo = own_filename
|
|
||||||
container_photo.uploaded_on = datetime.now()
|
|
||||||
container_photo.uploaded_by = request.user
|
|
||||||
container_photo.save()
|
|
||||||
return Response(status=status.HTTP_201_CREATED)
|
|
||||||
|
|
||||||
except Exception as ex:
|
# Upload to MinIO and get URL
|
||||||
return Response(
|
url = upload_damage_photo(photo_file, depot_id, content_type)
|
||||||
{"error": "Failed to process photo"},
|
|
||||||
status=status.HTTP_400_BAD_REQUEST
|
# Save to database
|
||||||
)
|
container_photo = ContainerPhotos.objects.create(
|
||||||
|
container=Container.objects.get(pk=depot_id),
|
||||||
|
photo=url,
|
||||||
|
uploaded_by=request.user
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
except Exception as ex:
|
||||||
|
print(f"Error in damage photo upload: {str(ex)}")
|
||||||
|
return Response(
|
||||||
|
{"error": "Failed to process photo"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
finally:
|
|
||||||
if temp_file:
|
|
||||||
try:
|
|
||||||
os.unlink(temp_file.name)
|
|
||||||
except OSError:
|
|
||||||
pass # Ignore deletion errors
|
|
||||||
def get(self, request, depot_id):
|
def get(self, request, depot_id):
|
||||||
return Owncloud.get_damages(depot_id)
|
try:
|
||||||
|
s3_client = boto3.client('s3',
|
||||||
|
endpoint_url=f"http://{settings.MINIO_ENDPOINT}",
|
||||||
|
aws_access_key_id=settings.MINIO_ACCESS_KEY,
|
||||||
|
aws_secret_access_key=settings.MINIO_SECRET_KEY,
|
||||||
|
config=Config(signature_version='s3v4')
|
||||||
|
)
|
||||||
|
|
||||||
|
prefix = f"{depot_id}/"
|
||||||
|
response = s3_client.list_objects_v2(
|
||||||
|
Bucket=settings.MINIO_BUCKET_NAME,
|
||||||
|
Prefix=prefix
|
||||||
|
)
|
||||||
|
|
||||||
|
photos = []
|
||||||
|
if 'Contents' in response:
|
||||||
|
for obj in response['Contents']:
|
||||||
|
url = f"{settings.MINIO_SERVER_URL}/{settings.MINIO_BUCKET_NAME}/{obj['Key']}"
|
||||||
|
photos.append({'url': url})
|
||||||
|
|
||||||
|
return Response(photos, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"MinIO Error: {str(e)}")
|
||||||
|
return Response(
|
||||||
|
{"error": "Failed to retrieve photos"},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
database:
|
||||||
|
image: postgres
|
||||||
|
environment:
|
||||||
|
- POSTGRES_DB=depot # Matches DB_NAME in production.env
|
||||||
|
- POSTGRES_USER=postgres # Matches DB_USER in production.env
|
||||||
|
- POSTGRES_PASSWORD=admin # Matches DB_PASSWORD in production.env
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres -d depot"] # Check specific database
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
minio:
|
||||||
|
image: minio/minio:latest
|
||||||
|
ports:
|
||||||
|
- "9000:9000" # API Port
|
||||||
|
- "9001:9001" # Console Port
|
||||||
|
environment:
|
||||||
|
MINIO_ROOT_USER: kikimor # Change this
|
||||||
|
MINIO_ROOT_PASSWORD: shushunka1
|
||||||
|
MINIO_SERVER_URL: http://localhost:9000
|
||||||
|
MINIO_ADDRESS: "0.0.0.0:9000"
|
||||||
|
MINIO_BROWSER_REDIRECT_URL: "http://localhost:9001" # Console URL
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- minio_data:/data
|
||||||
|
command: server --console-address ":9001" /data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mc", "ready", "local"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 3
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
# Optional: Create buckets and users on startup
|
||||||
|
createbuckets:
|
||||||
|
image: minio/mc
|
||||||
|
depends_on:
|
||||||
|
- minio
|
||||||
|
entrypoint: >
|
||||||
|
/bin/sh -c "
|
||||||
|
sleep 10;
|
||||||
|
mc alias set myminio http://minio:9000 kikimor shushunka1;
|
||||||
|
mc mb myminio/damages;
|
||||||
|
mc anonymous set download myminio/damages;
|
||||||
|
mc policy set public myminio/damages;
|
||||||
|
echo '{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::damages/*\"]}]}' > /tmp/policy.json;
|
||||||
|
mc admin policy add myminio getonly /tmp/policy.json;
|
||||||
|
mc admin policy set myminio getonly user=kikimor;
|
||||||
|
exit 0;
|
||||||
|
"
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
minio_data:
|
||||||
|
networks:
|
||||||
|
app-network:
|
||||||
|
driver: bridge
|
||||||
+75
-2
@@ -1,11 +1,84 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
database:
|
||||||
|
image: postgres
|
||||||
|
environment:
|
||||||
|
- POSTGRES_DB=depot # Matches DB_NAME in production.env
|
||||||
|
- POSTGRES_USER=postgres # Matches DB_USER in production.env
|
||||||
|
- POSTGRES_PASSWORD=admin # Matches DB_PASSWORD in production.env
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres -d depot"] # Check specific database
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
web:
|
web:
|
||||||
build: .
|
build: .
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
env_file:
|
env_file:
|
||||||
- development.env
|
- production.env
|
||||||
|
depends_on:
|
||||||
|
database:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
command: >
|
||||||
|
bash -c "python manage.py migrate &&
|
||||||
|
python manage.py runserver 0.0.0.0:8000"
|
||||||
|
|
||||||
|
minio:
|
||||||
|
image: minio/minio:latest
|
||||||
|
ports:
|
||||||
|
- "9000:9000" # API Port
|
||||||
|
- "9001:9001" # Console Port
|
||||||
|
environment:
|
||||||
|
MINIO_ROOT_USER: kikimor # Change this
|
||||||
|
MINIO_ROOT_PASSWORD: shushunka1
|
||||||
|
MINIO_SERVER_URL: http://localhost:9000
|
||||||
|
MINIO_ADDRESS: "0.0.0.0:9000"
|
||||||
|
MINIO_BROWSER_REDIRECT_URL: "http://localhost:9001" # Console URL
|
||||||
volumes:
|
volumes:
|
||||||
- .:/DepoT
|
- minio_data:/data
|
||||||
|
command: server --console-address ":9001" /data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mc", "ready", "local"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 3
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
# Optional: Create buckets and users on startup
|
||||||
|
createbuckets:
|
||||||
|
image: minio/mc
|
||||||
|
depends_on:
|
||||||
|
- minio
|
||||||
|
entrypoint: >
|
||||||
|
/bin/sh -c "
|
||||||
|
sleep 10;
|
||||||
|
mc alias set myminio http://minio:9000 kikimor shushunka1;
|
||||||
|
mc mb myminio/damages;
|
||||||
|
mc anonymous set download myminio/damages;
|
||||||
|
mc policy set public myminio/damages;
|
||||||
|
echo '{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::damages/*\"]}]}' > /tmp/policy.json;
|
||||||
|
mc admin policy add myminio getonly /tmp/policy.json;
|
||||||
|
mc admin policy set myminio getonly user=kikimor;
|
||||||
|
exit 0;
|
||||||
|
"
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
minio_data:
|
||||||
|
networks:
|
||||||
|
app-network:
|
||||||
|
driver: bridge
|
||||||
+1
-1
@@ -4,4 +4,4 @@ WORKDIR /DepoT
|
|||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
RUN pip install -r requirements.txt
|
RUN pip install -r requirements.txt
|
||||||
COPY . .
|
COPY . .
|
||||||
CMD python manage.py migrate && python manage.py runserver 0.0.0.0:8000
|
CMD ["bash", "-c", "python manage.py migrate && python manage.py runserver 0.0.0.0:8000"]
|
||||||
|
|||||||
+5
-4
@@ -1,17 +1,18 @@
|
|||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from preinfo.views.client_views import (ClientPreinfoView, check_preinfo, PreinfoSearchView, ClientPreinfoCreateView, ClientPreinfoUpdateView)
|
from preinfo.views.client_views import (ClientPreinfoView, check_preinfo, PreinfoSearchView, ClientPreinfoCreateView,
|
||||||
|
ClientPreinfoUpdateView, ClientPreinfoDeleteView, )
|
||||||
from preinfo.views.employee_views import EmployeePreinfoView
|
from preinfo.views.employee_views import EmployeePreinfoView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('client/', include([
|
path('client/', include([
|
||||||
path('', ClientPreinfoView.as_view(), name='client_preinfo'),
|
path('', ClientPreinfoView.as_view(), name='client_preinfo'),
|
||||||
path('create/', ClientPreinfoCreateView.as_view(), name='client_preinfo_create'),
|
path('create/', ClientPreinfoCreateView.as_view(), name='client_preinfo_create'),
|
||||||
path('<int:pk>/update/', ClientPreinfoUpdateView.as_view(), name='client_preinfo_update'),
|
path('<int:pk>/delete/', ClientPreinfoDeleteView.as_view(), name='client_preinfo_delete'),
|
||||||
])),
|
])),
|
||||||
path('check-preinfo/', check_preinfo, name='check_preinfo'),
|
path('check-preinfo/', check_preinfo, name='check_preinfo'),
|
||||||
path('preinfo-search/', PreinfoSearchView.as_view(), name='preinfo_search'),
|
path('preinfo-search/', PreinfoSearchView.as_view(), name='preinfo_search'),
|
||||||
path('employee/', include([
|
path('employee/', include([
|
||||||
path('', EmployeePreinfoView.as_view(), name='employee_preinfo'),
|
path('', EmployeePreinfoView.as_view(), name='employee_preinfo'),
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
|
from datetime import timezone
|
||||||
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.timezone import now
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.generic import CreateView, ListView, UpdateView
|
from django.views.generic import CreateView, ListView, UpdateView
|
||||||
|
from rest_framework.generics import get_object_or_404
|
||||||
|
|
||||||
from DepoT.mixins.LineFiltweFormMixin import LineFilterFormMixin
|
from DepoT.mixins.LineFiltweFormMixin import LineFilterFormMixin
|
||||||
from common.utils.utils import filter_queryset_by_user, get_preinfo_by_number, send_test_email
|
from common.utils.utils import filter_queryset_by_user, get_preinfo_by_number, send_test_email
|
||||||
@@ -98,3 +102,20 @@ class PreinfoSearchView(View):
|
|||||||
return render(request, self.template_name, {'error': 'Not found'})
|
return render(request, self.template_name, {'error': 'Not found'})
|
||||||
|
|
||||||
|
|
||||||
|
class ClientPreinfoDeleteView(LoginRequiredMixin, UserPassesTestMixin, View):
|
||||||
|
success_url = reverse_lazy('client_preinfo')
|
||||||
|
|
||||||
|
def test_func(self):
|
||||||
|
return self.request.user.has_company_perm('can_manage_preinfo') or self.request.user.user_type == 'CA'
|
||||||
|
|
||||||
|
def post(self, request, pk, *args, **kwargs):
|
||||||
|
target_preinfo = get_object_or_404(Preinfo, pk=pk)
|
||||||
|
if target_preinfo.received:
|
||||||
|
return JsonResponse({'success': False, 'error': 'Cannot delete completed preinfo.'})
|
||||||
|
if target_preinfo.deleted:
|
||||||
|
return JsonResponse({'success': False, 'error': 'Preinfo already deleted.'})
|
||||||
|
target_preinfo.deleted = True
|
||||||
|
target_preinfo.deleted_on = now()
|
||||||
|
target_preinfo.deleted_by = request.user
|
||||||
|
target_preinfo.save()
|
||||||
|
return JsonResponse({'success': True, 'is_active': target_preinfo.active})
|
||||||
Binary file not shown.
@@ -0,0 +1,23 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const table = document.getElementById('objectTable');
|
||||||
|
if (!table) return;
|
||||||
|
|
||||||
|
table.addEventListener('click', function(e) {
|
||||||
|
const row = e.target.closest('.selectable-row');
|
||||||
|
if (!row) return;
|
||||||
|
|
||||||
|
// Remove selection from all rows
|
||||||
|
table.querySelectorAll('.selected-row').forEach(selectedRow => {
|
||||||
|
selectedRow.classList.remove('selected-row');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add selection to clicked row
|
||||||
|
row.classList.add('selected-row');
|
||||||
|
|
||||||
|
// Get container ID and redirect to show its details
|
||||||
|
const containerId = row.dataset.id;
|
||||||
|
const currentUrl = new URL(window.location.href);
|
||||||
|
currentUrl.searchParams.set('selected', containerId);
|
||||||
|
window.location.href = currentUrl.toString();
|
||||||
|
});
|
||||||
|
});
|
||||||
+80
-10
@@ -4,6 +4,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
const selectionMode = table.dataset.selectionMode || 'single';
|
const selectionMode = table.dataset.selectionMode || 'single';
|
||||||
const toggleSelectAllBtn = document.getElementById('toggleSelectAllBtn');
|
const toggleSelectAllBtn = document.getElementById('toggleSelectAllBtn');
|
||||||
|
const editBtn = document.getElementById('editBtn');
|
||||||
|
const deleteBtn = document.getElementById('deleteBtn');
|
||||||
|
|
||||||
if (table.dataset.selectionMode === 'multiple') {
|
if (table.dataset.selectionMode === 'multiple') {
|
||||||
toggleSelectAllBtn.style.display = 'inline-block';
|
toggleSelectAllBtn.style.display = 'inline-block';
|
||||||
@@ -26,19 +28,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
row.classList.toggle('selected-row');
|
row.classList.toggle('selected-row');
|
||||||
checkbox.checked = !checkbox.checked;
|
checkbox.checked = !checkbox.checked;
|
||||||
const selectedRows = table.querySelectorAll('.selected-row');
|
const selectedRows = table.querySelectorAll('.selected-row');
|
||||||
|
|
||||||
document.querySelectorAll('[data-requires-selection]').forEach(button => {
|
document.querySelectorAll('[data-requires-selection]').forEach(button => {
|
||||||
button.disabled = selectedRows.length === 0;
|
button.disabled = selectedRows.length === 0;
|
||||||
});
|
});
|
||||||
const objectId = row.dataset.id;
|
|
||||||
const editBtn = document.getElementById('editBtn');
|
|
||||||
const deleteBtn = document.getElementById('deleteBtn');
|
|
||||||
if (editBtn) {
|
|
||||||
editBtn.removeAttribute('disabled');
|
|
||||||
editBtn.href = editBtn.dataset.url?.replace('0', objectId);
|
|
||||||
}
|
|
||||||
if (deleteBtn) {
|
|
||||||
deleteBtn.removeAttribute('disabled');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let allSelected = false;
|
let allSelected = false;
|
||||||
@@ -56,4 +49,81 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
this.textContent = allSelected ? 'Unselect All' : 'Select All';
|
this.textContent = allSelected ? 'Unselect All' : 'Select All';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Edit button click handler
|
||||||
|
if (editBtn) {
|
||||||
|
editBtn.addEventListener('click', function() {
|
||||||
|
const selectedRow = table.querySelector('.selected-row');
|
||||||
|
if (selectedRow) {
|
||||||
|
const objectId = selectedRow.dataset.id;
|
||||||
|
const editUrl = editBtn.dataset.url.replace('0', objectId);
|
||||||
|
window.location.href = editUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete modal logic
|
||||||
|
const deleteModal = document.getElementById('deleteModal');
|
||||||
|
const cancelDelete = document.getElementById('cancelDelete');
|
||||||
|
const confirmDelete = document.getElementById('confirmDelete');
|
||||||
|
let selectedId = null;
|
||||||
|
|
||||||
|
if (deleteBtn) {
|
||||||
|
deleteBtn.addEventListener('click', function() {
|
||||||
|
const selectedRow = table.querySelector('.selected-row');
|
||||||
|
if (selectedRow) {
|
||||||
|
selectedId = selectedRow.dataset.id;
|
||||||
|
deleteModal.style.display = 'block';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelDelete.addEventListener('click', function() {
|
||||||
|
deleteModal.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
confirmDelete.addEventListener('click', function() {
|
||||||
|
if (selectedId) {
|
||||||
|
const deleteUrl = deleteBtn.dataset.url.replace('0', selectedId);
|
||||||
|
|
||||||
|
fetch(deleteUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
window.location.reload(); // This reloads the page after success
|
||||||
|
} else {
|
||||||
|
alert('Error deleting item');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('Error deleting item');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
deleteModal.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('click', function(event) {
|
||||||
|
if (event.target === deleteModal) {
|
||||||
|
deleteModal.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function getCookie(name) {
|
||||||
|
let cookieValue = null;
|
||||||
|
if (document.cookie && document.cookie !== '') {
|
||||||
|
const cookies = document.cookie.split(';');
|
||||||
|
for (let i = 0; i < cookies.length; i++) {
|
||||||
|
const cookie = cookies[i].trim();
|
||||||
|
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
||||||
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cookieValue;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
.search-card {
|
||||||
|
background: #EDDECB;
|
||||||
|
border: 1px solid #E1C6A8;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-body {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-button {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*.btn-primary {*/
|
||||||
|
/* background: #EDDECB;*/
|
||||||
|
/* color: white;*/
|
||||||
|
/* border: none;*/
|
||||||
|
/* padding: 0.5rem 1rem;*/
|
||||||
|
/* border-radius: 8px;*/
|
||||||
|
/* cursor: pointer;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.btn-primary:hover {*/
|
||||||
|
/* background: #0056b3;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
.details-card {
|
||||||
|
background: #EDDECB;
|
||||||
|
border: 1px solid #E1C6A8;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background: #E1C6A8;
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid #E1C6A8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header h5 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-body {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
flex: 0 0 45%;
|
||||||
|
text-align: right;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form styling */
|
||||||
|
input[type="text"] {
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"]:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #007bff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
||||||
|
}
|
||||||
@@ -84,6 +84,15 @@ button.btn-secondary {
|
|||||||
border: 1px solid #a57d52;
|
border: 1px solid #a57d52;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
/*background-color: #702222;*/
|
||||||
|
/*padding: 6px 20px; !* Reduced from 10px to 6px *!*/
|
||||||
|
/*border: 1px solid #ae3975;*/
|
||||||
|
/*border-radius: 4px;*/
|
||||||
|
/*cursor: pointer;*/
|
||||||
|
/*height: 32px; !* Add explicit height *!*/
|
||||||
|
/*line-height: 1.2; !* Add line height for better text alignment *!*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button[type="submit"]:hover,
|
button[type="submit"]:hover,
|
||||||
|
|||||||
@@ -1,46 +1,5 @@
|
|||||||
{% load static %}
|
{% extends "common/base.html" %}
|
||||||
<!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>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
|
{% block aside %}
|
||||||
<body>
|
|
||||||
<aside class="sidebar">
|
|
||||||
{% include 'client-sidebar.html' %}
|
{% include 'client-sidebar.html' %}
|
||||||
</aside>
|
{% endblock aside %}
|
||||||
|
|
||||||
<main class="content-area">
|
|
||||||
<div class="content">
|
|
||||||
{% block content %}
|
|
||||||
{% endblock content %}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
{% block custom_styles %}
|
|
||||||
{% endblock custom_styles %}
|
|
||||||
|
|
||||||
{% block extra_js %}
|
|
||||||
<script>
|
|
||||||
const validateContainerUrl = "{% url 'validate_container' 'placeholder' %}".replace('placeholder/', '');
|
|
||||||
</script>
|
|
||||||
<script src="{% static 'js/container_validation.js' %}"></script>
|
|
||||||
|
|
||||||
{% endblock extra_js %}
|
|
||||||
|
|
||||||
{% block crud_js %}
|
|
||||||
{% endblock crud_js %}
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|||||||
@@ -31,7 +31,12 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
<a href="{% url 'client_booking_create' %}" class="btn btn-primary">Create Preinfo</a>
|
{# <a href="{% url 'client_booking_create' %}" class="btn btn-primary">Create booking</a>#}
|
||||||
<a href="#" id="editBtn" data-url="{% url 'client_booking_update' pk=0 %}" class="btn btn-primary" disabled>Edit 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>
|
{# <button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>#}
|
||||||
|
{# #}
|
||||||
|
<button class="btn btn-primary" type="button" onclick="window.location.href='{% url 'client_booking_create' %}'">Create booking</button>
|
||||||
|
<button class="btn btn-primary" type="button" id="editBtn" data-url="{% url 'client_booking_update' pk=0 %}" data-requires-selection disabled>Edit booking</button>
|
||||||
|
<button class="btn btn-primary" type="button" id="deleteBtn" data-url="{% url 'client_booking_active' pk=0 %}" data-requires-selection disabled>Delete booking</button>
|
||||||
|
|
||||||
{% endblock buttons %}
|
{% endblock buttons %}
|
||||||
@@ -15,15 +15,12 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
<a href="{% url 'client_line_create' %}" class="btn btn-primary">Create line</a>
|
{# <a href="{% url 'client_line_create' %}" class="btn btn-primary">Create line</a>#}
|
||||||
<a href="#" id="editBtn" data-url="{% url 'client_line_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a>
|
{# <a href="#" id="editBtn" data-url="{% url 'client_line_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a>#}
|
||||||
<button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>
|
{# <button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>#}
|
||||||
|
|
||||||
|
<button class="btn btn-primary" type="button" onclick="window.location.href='{% url 'client_line_create' %}'">Create line</button>
|
||||||
|
<button class="btn btn-primary" type="button" id="editBtn" data-url="{% url 'client_line_update' pk=0 %}" data-requires-selection disabled>Edit line</button>
|
||||||
|
<button class="btn btn-primary" type="button" id="deleteBtn" data-url="{% url 'client_line_active' pk=0 %}" data-requires-selection disabled>Delete line</button>
|
||||||
{% endblock buttons %}
|
{% endblock buttons %}
|
||||||
|
|
||||||
{% block create_modal_header %}
|
|
||||||
<h2>Create line</h2>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modal_header %}
|
|
||||||
<h2>edit line</h2>
|
|
||||||
{% endblock modal_header %}
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
{% extends 'list-crud.html' %}
|
{% extends 'list-crud.html' %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
|
{% block filter %}
|
||||||
|
|
||||||
|
{% endblock filter %}
|
||||||
|
|
||||||
{% block table_header %}
|
{% block table_header %}
|
||||||
<th style="display: none;">Select</th>
|
<th style="display: none;">Select</th>
|
||||||
<th>Preinfo №</th>
|
<th>Preinfo №</th>
|
||||||
@@ -23,9 +27,12 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
<a href="{% url 'client_preinfo_create' %}" class="btn btn-primary">Create Preinfo</a>
|
{# <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>
|
{# <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>
|
{# <button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>#}
|
||||||
|
|
||||||
|
<button class="btn btn-primary" type="button" onclick="window.location.href='{% url 'client_preinfo_create' %}'">Create Preinfo</button>
|
||||||
|
<button class="btn btn-primary" type="button" id="deleteBtn" data-url="{% url 'client_preinfo_delete' pk=0 %}" data-requires-selection disabled>Delete Preinfo</button>
|
||||||
{% endblock buttons %}
|
{% endblock buttons %}
|
||||||
|
|
||||||
{% block create_modal_header %}
|
{% block create_modal_header %}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!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>
|
||||||
|
<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' %}">
|
||||||
|
{% block extra_styles %}
|
||||||
|
{% endblock extra_styles %}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<aside class="sidebar">
|
||||||
|
{% block aside %}
|
||||||
|
{% endblock aside %}
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main class="content-area">
|
||||||
|
<div class="content">
|
||||||
|
{% block content_header %}
|
||||||
|
{% endblock content_header%}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block content_footer %}
|
||||||
|
{% endblock content_footer%}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{% block custom_styles %}
|
||||||
|
{% endblock custom_styles %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
<script>
|
||||||
|
const validateContainerUrl = "{% url 'validate_container' 'placeholder' %}".replace('placeholder/', '');
|
||||||
|
</script>
|
||||||
|
<script src="{% static 'js/container_validation.js' %}"></script>
|
||||||
|
{% endblock extra_js %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,303 @@
|
|||||||
|
{% extends 'list-crud.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_styles %}
|
||||||
|
<link rel="stylesheet" href="{% static 'styles/details.css' %}">
|
||||||
|
<style>
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
position: relative;
|
||||||
|
margin: auto;
|
||||||
|
padding: 20px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 1200px;
|
||||||
|
height: 90vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-modal {
|
||||||
|
color: #fff;
|
||||||
|
position: absolute;
|
||||||
|
right: 25px;
|
||||||
|
top: 10px;
|
||||||
|
font-size: 35px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.gallery-item iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-item {
|
||||||
|
position: relative;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
background: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-item img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
.photo-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-link {
|
||||||
|
background: #E1C6A8;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-link:hover {
|
||||||
|
background: #d4b08c;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content_header %}
|
||||||
|
<div class="search-card">
|
||||||
|
<div class="search-body">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="search-group">
|
||||||
|
{{ form.container_number }}
|
||||||
|
<div class="search-button">
|
||||||
|
<button type="submit" class="btn-primary">Search</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if show_results and container %}
|
||||||
|
<div class="details-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5>Container Details</h5>
|
||||||
|
{% if container.photos.exists %}
|
||||||
|
<button type="button" class="btn-primary" onclick="openGallery()">
|
||||||
|
<i class="fas fa-images"></i> View Photos
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="details-body">
|
||||||
|
<div class="details-grid">
|
||||||
|
<div class="details-column">
|
||||||
|
<div class="detail-row">
|
||||||
|
<div class="detail-label">Container Type:</div>
|
||||||
|
<div class="detail-value">{{ container.container_type }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<div class="detail-label">Line:</div>
|
||||||
|
<div class="detail-value">{{ container.line }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<div class="detail-label">Preinfo:</div>
|
||||||
|
<div class="detail-value">{{ container.preinfo }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<div class="detail-label">Received On:</div>
|
||||||
|
<div class="detail-value">{{ container.received_on|date:"Y-m-d H:i" }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<div class="detail-label">Receive Vehicle:</div>
|
||||||
|
<div class="detail-value">{{ container.receive_vehicle }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="details-column">
|
||||||
|
<div class="detail-row">
|
||||||
|
<div class="detail-label">Swept:</div>
|
||||||
|
<div class="detail-value">{{ container.swept|yesno:"Yes,No" }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<div class="detail-label">Washed:</div>
|
||||||
|
<div class="detail-value">{{ container.washed|yesno:"Yes,No" }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<div class="detail-label">Booking:</div>
|
||||||
|
<div class="detail-value">
|
||||||
|
{% if container.booking %}
|
||||||
|
{{ container.booking.name }}
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<div class="detail-label">Expedited On:</div>
|
||||||
|
<div class="detail-value">{{ container.expedited_on|date:"Y-m-d H:i"|default:"-" }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<div class="detail-label">Expedition Vehicle:</div>
|
||||||
|
<div class="detail-value">{{ container.expedition_vehicle|default:"-" }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<div class="detail-label">Damages:</div>
|
||||||
|
<div class="detail-value">{{ container.damages|default:"-" }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<div class="detail-label">Heavy Damaged:</div>
|
||||||
|
<div class="detail-value">{{ container.heavy_damaged|yesno:"Yes,No" }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="galleryModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<span class="close-modal" onclick="closeGallery()">×</span>
|
||||||
|
<h3 style="color: white; margin-bottom: 1rem;">Photos for Container {{ container.number }}</h3>
|
||||||
|
<div class="gallery-grid">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% endblock content_header %}
|
||||||
|
|
||||||
|
{% block table_header %}
|
||||||
|
<th style="display: none;">Select</th>
|
||||||
|
<th>Company name</th>
|
||||||
|
<th>Company short name</th>
|
||||||
|
<th>Description</th>
|
||||||
|
{% endblock table_header %}
|
||||||
|
|
||||||
|
{% block table_data %}
|
||||||
|
<td>{{ object.number }}</td>
|
||||||
|
<td>{{ object.number }}</td>
|
||||||
|
<td>{{ object.number }}</td>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block buttons %}
|
||||||
|
{% endblock buttons %}
|
||||||
|
|
||||||
|
{% block crud_js %}
|
||||||
|
<script src="{% static 'js/container-details.js' %}"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function loadPhotos(containerId) {
|
||||||
|
const photoGrid = document.querySelector('.gallery-grid');
|
||||||
|
photoGrid.innerHTML = '<div class="loading">Loading photos...</div>';
|
||||||
|
|
||||||
|
fetch(`/api/damages/${containerId}/`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
photoGrid.innerHTML = '';
|
||||||
|
if (data.length === 0) {
|
||||||
|
photoGrid.innerHTML = '<div style="color: white;">No photos available</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.forEach(photo => {
|
||||||
|
const photoDiv = document.createElement('div');
|
||||||
|
photoDiv.className = 'gallery-item';
|
||||||
|
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = photo.url; // Use the presigned URL directly
|
||||||
|
img.alt = `Container photo`;
|
||||||
|
|
||||||
|
img.onerror = function() {
|
||||||
|
console.error('Failed to load image:', img.src);
|
||||||
|
this.style.display = 'none';
|
||||||
|
photoDiv.innerHTML += '<div style="color: red;">Failed to load image</div>';
|
||||||
|
};
|
||||||
|
|
||||||
|
photoDiv.appendChild(img);
|
||||||
|
photoGrid.appendChild(photoDiv);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error loading photos:', error);
|
||||||
|
photoGrid.innerHTML = '<div style="color: white;">Error loading photos</div>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openGallery() {
|
||||||
|
const container = {{ container.id|default:'null' }};
|
||||||
|
if (container) {
|
||||||
|
loadPhotos(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('galleryModal').style.display = 'block';
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeGallery() {
|
||||||
|
document.getElementById('galleryModal').style.display = 'none';
|
||||||
|
document.body.style.overflow = 'auto';
|
||||||
|
document.querySelector('.gallery-grid').innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close modal when clicking outside
|
||||||
|
window.onclick = function(event) {
|
||||||
|
const modal = document.getElementById('galleryModal');
|
||||||
|
if (event.target === modal) {
|
||||||
|
closeGallery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close modal on escape key
|
||||||
|
document.addEventListener('keydown', function(event) {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
closeGallery();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add loading indicator styles
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
.loading {
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,38 +1,5 @@
|
|||||||
{% load static %}
|
{% extends "common/base.html" %}
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Depot Employee Dashboard | Container Depot</title>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
|
{% block aside %}
|
||||||
<body>
|
|
||||||
<aside class="sidebar">
|
|
||||||
{% include 'employee-sidebar.html' %}
|
{% include 'employee-sidebar.html' %}
|
||||||
</aside>
|
{% endblock aside %}
|
||||||
|
|
||||||
<main class="content-area">
|
|
||||||
<div class="content">
|
|
||||||
{% block content %}
|
|
||||||
{% endblock content %}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% block extra_js %}
|
|
||||||
<script>
|
|
||||||
const validateContainerUrl = "{% url 'validate_container' 'placeholder' %}".replace('placeholder/', '');
|
|
||||||
</script>
|
|
||||||
<script src="{% static 'js/container_validation.js' %}"></script>
|
|
||||||
{% endblock %}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% url 'employee_dashboard' as dashboard_url %}
|
{% url 'employee_dashboard' as dashboard_url %}
|
||||||
{% url 'employee_containers' as employee_containers_url %}
|
{% url 'employee_containers' as employee_containers_url %}
|
||||||
|
{% url 'container_details' as container_details_url %}
|
||||||
{% url 'employee_bookings' as employee_bookings_url %}
|
{% url 'employee_bookings' as employee_bookings_url %}
|
||||||
{% url 'employee_preinfo' as employee_preinfo_url %}
|
{% url 'employee_preinfo' as employee_preinfo_url %}
|
||||||
{% url 'register' as register_url %}
|
{% url 'register' as register_url %}
|
||||||
@@ -28,6 +29,12 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Containers
|
Containers
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{{ container_details_url }}" class="nav-item {% if request.path == container_details_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 details
|
||||||
|
</a>
|
||||||
<a href="{{ employee_preinfo_url }}" class="nav-item {% if request.path == employee_preinfo_url %}active{% endif %}" id="preinfoNav">
|
<a href="{{ employee_preinfo_url }}" class="nav-item {% if request.path == employee_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">
|
<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" />
|
<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" />
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends 'list-crud.html' %}
|
{% extends 'list-crud.html' %}
|
||||||
|
{% load custom_filters %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
{% block table_header %}
|
{% block table_header %}
|
||||||
@@ -13,6 +14,7 @@
|
|||||||
<th>Vehicles left</th>
|
<th>Vehicles left</th>
|
||||||
<th>Created on</th>
|
<th>Created on</th>
|
||||||
<th>Created by</th>
|
<th>Created by</th>
|
||||||
|
<th>Status</th>
|
||||||
{% endblock table_header %}
|
{% endblock table_header %}
|
||||||
|
|
||||||
{% block table_data %}
|
{% block table_data %}
|
||||||
@@ -22,10 +24,11 @@
|
|||||||
<td>{{ object.container_number }}</td>
|
<td>{{ object.container_number }}</td>
|
||||||
<td>{{ object.container_count }}</td>
|
<td>{{ object.container_count }}</td>
|
||||||
<td>{{ object.containers_left }}</td>
|
<td>{{ object.containers_left }}</td>
|
||||||
<td>{{ object.vehicles }}</td>
|
<td>{{ object.vehicles|distinct_vehicles }}</td>
|
||||||
<td>{{ object.vehicles_left }}</td>
|
<td>{{ object.vehicles_left|distinct_vehicles }}</td>
|
||||||
<td>{{ object.created_on }}</td>
|
<td>{{ object.created_on }}</td>
|
||||||
<td>{{ object.created_by.username }}</td>
|
<td>{{ object.created_by.username }}</td>
|
||||||
|
<td>{{ object.status }}</td>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
@@ -34,10 +37,3 @@
|
|||||||
{# <button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>#}
|
{# <button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>#}
|
||||||
{% endblock buttons %}
|
{% endblock buttons %}
|
||||||
|
|
||||||
{% block create_modal_header %}
|
|
||||||
<h2>Create Booking</h2>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modal_header %}
|
|
||||||
<h2>Edit Booking</h2>
|
|
||||||
{% endblock modal_header %}
|
|
||||||
|
|||||||
@@ -15,9 +15,14 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
<a href="{% url 'employee_company_create' %}" class="btn btn-primary">Create company</a>
|
{# <a href="{% url 'employee_company_create' %}" class="btn btn-primary">Create company</a>#}
|
||||||
<a href="#" id="editBtn" data-url="{% url 'employee_company_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a>
|
{# <a href="#" id="editBtn" data-url="{% url 'employee_company_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a>#}
|
||||||
<button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>
|
{# <button id="deleteButton" class="btn btn-danger">Delete company</button>#}
|
||||||
|
|
||||||
|
<button class="btn btn-primary" type="button" onclick="window.location.href='{% url 'employee_company_create' %}'">Create company</button>
|
||||||
|
<button class="btn btn-primary" type="button" id="editBtn" data-url="{% url 'employee_company_update' pk=0 %}" data-requires-selection disabled>Edit company</button>
|
||||||
|
<button class="btn btn-primary" type="button" id="deleteBtn" data-url="{% url 'employee_company_active' pk=0 %}" data-requires-selection disabled>Delete company</button>
|
||||||
|
|
||||||
{% endblock buttons %}
|
{% endblock buttons %}
|
||||||
|
|
||||||
{% block create_modal_header %}
|
{% block create_modal_header %}
|
||||||
|
|||||||
@@ -17,9 +17,14 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
<a href="{% url 'employee_line_create' %}" class="btn btn-primary">Create line</a>
|
{# <a href="{% url 'employee_line_create' %}" class="btn btn-primary">Create line</a>#}
|
||||||
<a href="#" id="editBtn" data-url="{% url 'employee_line_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a>
|
{# <a href="#" id="editBtn" data-url="{% url 'employee_line_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a>#}
|
||||||
<button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>
|
{# <button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>#}
|
||||||
|
|
||||||
|
<button class="btn btn-primary" type="button" onclick="window.location.href='{% url 'employee_line_create' %}'">Create line</button>
|
||||||
|
<button class="btn btn-primary" type="button" id="editBtn" data-url="{% url 'employee_line_update' pk=0 %}" data-requires-selection disabled>Edit line</button>
|
||||||
|
<button class="btn btn-primary" type="button" id="deleteBtn" data-url="{% url 'employee_line_active' pk=0 %}" data-requires-selection disabled>Delete line</button>
|
||||||
|
|
||||||
{% endblock buttons %}
|
{% endblock buttons %}
|
||||||
|
|
||||||
{% block create_modal_header %}
|
{% block create_modal_header %}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<th>Line</th>
|
<th>Line</th>
|
||||||
<th>Created on</th>
|
<th>Created on</th>
|
||||||
<th>Created by</th>
|
<th>Created by</th>
|
||||||
|
<th>Status</th>
|
||||||
{% endblock table_header %}
|
{% endblock table_header %}
|
||||||
|
|
||||||
{% block table_data %}
|
{% block table_data %}
|
||||||
@@ -18,8 +19,6 @@
|
|||||||
<td>{{ object.line.short_name }}</td>
|
<td>{{ object.line.short_name }}</td>
|
||||||
<td>{{ object.created_on }}</td>
|
<td>{{ object.created_on }}</td>
|
||||||
<td>{{ object.created_by.username }}</td>
|
<td>{{ object.created_by.username }}</td>
|
||||||
|
<td>{{ object.received|yesno:"Received,Pending" }}</td>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block modal_header %}
|
|
||||||
<h2>Edit Preinfo</h2>
|
|
||||||
{% endblock modal_header %}
|
|
||||||
|
|||||||
+15
-20
@@ -128,27 +128,22 @@ header {
|
|||||||
© 2025 Kikimor EOOD | K-DepoT - All rights reserved.
|
© 2025 Kikimor EOOD | K-DepoT - All rights reserved.
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script>
|
|
||||||
function initMap() {
|
|
||||||
const map = new google.maps.Map(document.getElementById("map"), {
|
|
||||||
center: { lat: 43.2121, lng: 27.9204 },
|
|
||||||
zoom: 15
|
|
||||||
});
|
|
||||||
|
|
||||||
new google.maps.Marker({
|
|
||||||
position: { lat: 43.2121, lng: 27.9204 },
|
|
||||||
map: map,
|
|
||||||
title: "K-DepoT - Kikimor EOOD"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script
|
|
||||||
src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBboGDgiCrc9yp2uSLmVcfVVIVK-kOQqc4&callback=initMap"
|
|
||||||
async
|
|
||||||
defer>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function initMap() {
|
||||||
|
const depot = { lat: 43.2121, lng: 27.9204 };
|
||||||
|
const map = new google.maps.Map(document.getElementById('map'), {
|
||||||
|
zoom: 15,
|
||||||
|
center: depot,
|
||||||
|
});
|
||||||
|
const marker = new google.maps.Marker({
|
||||||
|
position: depot,
|
||||||
|
map: map,
|
||||||
|
title: 'K-DepoT'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBboGDgiCrc9yp2uSLmVcfVVIVK-kOQqc4&callback=initMap" async defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,51 @@
|
|||||||
<script src="{% static 'js/crud-list.js' %}"></script>
|
<script src="{% static 'js/crud-list.js' %}"></script>
|
||||||
{% endblock crud_js %}
|
{% endblock crud_js %}
|
||||||
|
|
||||||
|
|
||||||
|
<div id="deleteModal" class="modal" style="display: none;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h3>Confirm Delete</h3>
|
||||||
|
<p>Are you sure you want to delete this item?</p>
|
||||||
|
<div class="modal-buttons">
|
||||||
|
<button class="btn btn-secondary" id="cancelDelete">Cancel</button>
|
||||||
|
<button class="btn btn-danger" id="confirmDelete">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% block modal_styles %}
|
||||||
|
<style>
|
||||||
|
.modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
position: relative;
|
||||||
|
background-color: #EDDECB;
|
||||||
|
margin: 15% auto;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #a57d52;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 80%;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock modal_styles %}
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,12 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
<a href="{% url 'user_register' %}" class="btn btn-primary" type="button">Create user</a>
|
{# <a href="{% url 'user_register' %}" class="btn btn-primary" type="button">Create user</a>#}
|
||||||
<a href="#" id="editBtn" data-url="{% url 'user_update' pk=0 %}" class="btn btn-primary" type="button" disabled>Edit user</a>
|
{# <a href="#" id="editBtn" data-url="{% url 'user_update' pk=0 %}" class="btn btn-primary" type="button" disabled>Edit user</a>#}
|
||||||
<button id="deleteButton" class="btn btn-danger">Delete user</button>
|
{# <button id="deleteButton" class="btn btn-danger">Delete user</button>#}
|
||||||
|
<button class="btn btn-primary" type="button" onclick="window.location.href='{% url 'user_register' %}'">Create user</button>
|
||||||
|
<button class="btn btn-primary" type="button" id="editBtn" data-url="{% url 'user_update' pk=0 %}" data-requires-selection disabled>Edit user</button>
|
||||||
|
<button class="btn btn-primary" type="button" id="deleteBtn" data-url="{% url 'user_active' pk=0 %}" data-requires-selection disabled>Delete user</button>
|
||||||
{% endblock buttons %}
|
{% endblock buttons %}
|
||||||
|
|
||||||
{% block create_modal_header %}
|
{% block create_modal_header %}
|
||||||
|
|||||||
Reference in New Issue
Block a user