here we go

This commit is contained in:
pacnpal
2024-10-31 22:32:01 +00:00
parent c52f14e700
commit 80a9d61ca2
68 changed files with 3114 additions and 1485 deletions

44
static/css/alerts.css Normal file
View File

@@ -0,0 +1,44 @@
/* Alert Styles */
.alert {
@apply fixed z-50 px-4 py-3 transition-all duration-500 transform rounded-lg shadow-lg right-4 top-4;
animation: slideIn 0.5s ease-out forwards;
}
.alert-success {
@apply text-white bg-green-500;
}
.alert-error {
@apply text-white bg-red-500;
}
.alert-info {
@apply text-white bg-blue-500;
}
.alert-warning {
@apply text-white bg-yellow-500;
}
/* Animation keyframes */
@keyframes slideIn {
0% {
transform: translateX(100%);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
0% {
transform: translateX(0);
opacity: 1;
}
100% {
transform: translateX(100%);
opacity: 0;
}
}

View File

@@ -1,187 +0,0 @@
/* Inline editing styles */
.editable-container {
position: relative;
}
[data-editable] {
position: relative;
padding: 0.25rem;
border-radius: 0.25rem;
transition: background-color 0.2s;
}
[data-editable]:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.dark [data-editable]:hover {
background-color: rgba(255, 255, 255, 0.05);
}
[data-edit-button] {
opacity: 0;
position: absolute;
right: 0.5rem;
top: 0.5rem;
transition: opacity 0.2s;
padding: 0.5rem;
border-radius: 0.375rem;
background-color: rgba(255, 255, 255, 0.9);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.dark [data-edit-button] {
background-color: rgba(31, 41, 55, 0.9);
}
.editable-container:hover [data-edit-button] {
opacity: 1;
}
.form-input, .form-textarea, .form-select {
width: 100%;
padding: 0.5rem;
border: 1px solid #e2e8f0;
border-radius: 0.375rem;
background-color: white;
transition: border-color 0.2s, box-shadow 0.2s;
}
.dark .form-input, .dark .form-textarea, .dark .form-select {
background-color: #1f2937;
border-color: #374151;
color: white;
}
.form-input:focus, .form-textarea:focus, .form-select:focus {
outline: none;
border-color: #4f46e5;
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
}
.dark .form-input:focus, .dark .form-textarea:focus, .dark .form-select:focus {
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
/* Notifications */
.notification {
position: fixed;
bottom: 1rem;
right: 1rem;
padding: 1rem;
border-radius: 0.5rem;
color: white;
max-width: 24rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
z-index: 50;
animation: slide-in 0.3s ease-out;
}
.notification-success {
background-color: #059669;
}
.dark .notification-success {
background-color: #047857;
}
.notification-error {
background-color: #dc2626;
}
.dark .notification-error {
background-color: #b91c1c;
}
.notification-info {
background-color: #3b82f6;
}
.dark .notification-info {
background-color: #2563eb;
}
@keyframes slide-in {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* Add/Edit Form Styles */
.form-section {
@apply space-y-6;
}
.form-group {
@apply space-y-2;
}
.form-label {
@apply block text-sm font-medium text-gray-700 dark:text-gray-300;
}
.form-error {
@apply mt-1 text-sm text-red-600 dark:text-red-400;
}
.form-help {
@apply mt-1 text-sm text-gray-500 dark:text-gray-400;
}
/* Button Styles */
.btn {
@apply inline-flex items-center justify-center px-4 py-2 font-medium transition-colors rounded-lg;
}
.btn-primary {
@apply text-white bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600;
}
.btn-secondary {
@apply text-gray-700 bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:text-gray-200 dark:hover:bg-gray-500;
}
.btn-danger {
@apply text-white bg-red-600 hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600;
}
/* Status Badges */
.status-badge {
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium;
}
.status-operating {
@apply text-green-800 bg-green-100 dark:bg-green-900 dark:text-green-200;
}
.status-closed {
@apply text-red-800 bg-red-100 dark:bg-red-900 dark:text-red-200;
}
.status-construction {
@apply text-yellow-800 bg-yellow-100 dark:bg-yellow-900 dark:text-yellow-200;
}
/* Navigation Links */
.nav-link {
@apply flex items-center px-3 py-2 text-gray-700 transition-colors rounded-lg dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700;
}
.nav-link i {
@apply mr-2;
}
/* Menu Items */
.menu-item {
@apply flex items-center w-full px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700;
}
.menu-item i {
@apply mr-3;
}

View File

@@ -1513,116 +1513,6 @@ select {
padding-bottom: 1.5rem;
}
.mobile-nav-link {
display: flex;
align-items: center;
justify-content: center;
border-radius: 0.5rem;
border-width: 1px;
border-color: transparent;
padding-left: 1.5rem;
padding-right: 1.5rem;
padding-top: 0.75rem;
padding-bottom: 0.75rem;
--tw-text-opacity: 1;
color: rgb(55 65 81 / var(--tw-text-opacity));
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.mobile-nav-link:hover {
border-color: rgb(79 70 229 / 0.2);
background-color: rgb(79 70 229 / 0.1);
--tw-text-opacity: 1;
color: rgb(79 70 229 / var(--tw-text-opacity));
}
.mobile-nav-link:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(229 231 235 / var(--tw-text-opacity));
}
.mobile-nav-link:hover:is(.dark *) {
border-color: rgb(79 70 229 / 0.3);
background-color: rgb(79 70 229 / 0.2);
--tw-text-opacity: 1;
color: rgb(79 70 229 / var(--tw-text-opacity));
}
.mobile-nav-link i {
font-size: 1.25rem;
line-height: 1.75rem;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
@media (max-width: 540px) {
.mobile-nav-link i {
font-size: 1.125rem;
line-height: 1.75rem;
}
}
.mobile-nav-link.primary {
background-image: linear-gradient(to right, var(--tw-gradient-stops));
--tw-gradient-from: #4f46e5 var(--tw-gradient-from-position);
--tw-gradient-to: rgb(79 70 229 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
--tw-gradient-to: #e11d48 var(--tw-gradient-to-position);
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.mobile-nav-link.primary:hover {
--tw-gradient-from: rgb(79 70 229 / 0.9) var(--tw-gradient-from-position);
--tw-gradient-to: rgb(79 70 229 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
--tw-gradient-to: rgb(225 29 72 / 0.9) var(--tw-gradient-to-position);
}
.mobile-nav-link.primary i {
margin-right: 0.75rem;
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.mobile-nav-link.secondary {
--tw-bg-opacity: 1;
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
--tw-text-opacity: 1;
color: rgb(55 65 81 / var(--tw-text-opacity));
}
.mobile-nav-link.secondary:hover {
--tw-bg-opacity: 1;
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
}
.mobile-nav-link.secondary:is(.dark *) {
--tw-bg-opacity: 1;
background-color: rgb(55 65 81 / var(--tw-bg-opacity));
--tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity));
}
.mobile-nav-link.secondary:hover:is(.dark *) {
--tw-bg-opacity: 1;
background-color: rgb(75 85 99 / var(--tw-bg-opacity));
}
.mobile-nav-link.secondary i {
margin-right: 0.75rem;
--tw-text-opacity: 1;
color: rgb(107 114 128 / var(--tw-text-opacity));
}
.mobile-nav-link.secondary i:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(156 163 175 / var(--tw-text-opacity));
}
/* Theme Toggle */
#theme-toggle+.theme-toggle-btn i::before {
@@ -2243,6 +2133,10 @@ select {
position: static;
}
.fixed {
position: fixed;
}
.absolute {
position: absolute;
}
@@ -2251,14 +2145,30 @@ select {
position: relative;
}
.sticky {
position: sticky;
}
.right-0 {
right: 0px;
}
.top-0 {
top: 0px;
}
.z-10 {
z-index: 10;
}
.z-40 {
z-index: 40;
}
.z-50 {
z-index: 50;
}
.col-span-2 {
grid-column: span 2 / span 2;
}
@@ -2281,6 +2191,10 @@ select {
margin-right: auto;
}
.-mb-px {
margin-bottom: -1px;
}
.mb-1 {
margin-bottom: 0.25rem;
}
@@ -2317,6 +2231,10 @@ select {
margin-left: 0.5rem;
}
.ml-4 {
margin-left: 1rem;
}
.ml-6 {
margin-left: 1.5rem;
}
@@ -2409,6 +2327,10 @@ select {
height: 2rem;
}
.max-h-60 {
max-height: 15rem;
}
.min-h-\[calc\(100vh-16rem\)\] {
min-height: calc(100vh - 16rem);
}
@@ -2421,12 +2343,12 @@ select {
width: 6rem;
}
.w-4 {
width: 1rem;
.w-32 {
width: 8rem;
}
.w-48 {
width: 12rem;
.w-4 {
width: 1rem;
}
.w-5 {
@@ -2465,6 +2387,18 @@ select {
flex-grow: 1;
}
.scale-100 {
--tw-scale-x: 1;
--tw-scale-y: 1;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.scale-95 {
--tw-scale-x: .95;
--tw-scale-y: .95;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
@@ -2473,6 +2407,10 @@ select {
cursor: pointer;
}
.list-disc {
list-style-type: disc;
}
.grid-cols-1 {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
@@ -2531,12 +2469,6 @@ select {
margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-x-3 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(0.75rem * var(--tw-space-x-reverse));
margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
@@ -2573,6 +2505,10 @@ select {
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
}
.overflow-auto {
overflow: auto;
}
.overflow-hidden {
overflow: hidden;
}
@@ -2603,6 +2539,11 @@ select {
border-bottom-right-radius: 0.5rem;
}
.rounded-t-lg {
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
}
.border {
border-width: 1px;
}
@@ -2611,10 +2552,24 @@ select {
border-bottom-width: 1px;
}
.border-b-2 {
border-bottom-width: 2px;
}
.border-t {
border-top-width: 1px;
}
.border-blue-400 {
--tw-border-opacity: 1;
border-color: rgb(96 165 250 / var(--tw-border-opacity));
}
.border-blue-600 {
--tw-border-opacity: 1;
border-color: rgb(37 99 235 / var(--tw-border-opacity));
}
.border-gray-200 {
--tw-border-opacity: 1;
border-color: rgb(229 231 235 / var(--tw-border-opacity));
@@ -2644,6 +2599,10 @@ select {
border-color: rgb(239 68 68 / var(--tw-border-opacity));
}
.border-transparent {
border-color: transparent;
}
.bg-blue-100 {
--tw-bg-opacity: 1;
background-color: rgb(219 234 254 / var(--tw-bg-opacity));
@@ -2654,16 +2613,16 @@ select {
background-color: rgb(239 246 255 / var(--tw-bg-opacity));
}
.bg-blue-500 {
--tw-bg-opacity: 1;
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
}
.bg-blue-600 {
--tw-bg-opacity: 1;
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
}
.bg-gray-100 {
--tw-bg-opacity: 1;
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
}
.bg-gray-200 {
--tw-bg-opacity: 1;
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
@@ -2679,6 +2638,11 @@ select {
background-color: rgb(220 252 231 / var(--tw-bg-opacity));
}
.bg-green-500 {
--tw-bg-opacity: 1;
background-color: rgb(34 197 94 / var(--tw-bg-opacity));
}
.bg-green-600 {
--tw-bg-opacity: 1;
background-color: rgb(22 163 74 / var(--tw-bg-opacity));
@@ -2689,6 +2653,11 @@ select {
background-color: rgb(254 226 226 / var(--tw-bg-opacity));
}
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.bg-red-600 {
--tw-bg-opacity: 1;
background-color: rgb(220 38 38 / var(--tw-bg-opacity));
@@ -2708,6 +2677,11 @@ select {
background-color: rgb(254 249 195 / var(--tw-bg-opacity));
}
.bg-yellow-500 {
--tw-bg-opacity: 1;
background-color: rgb(234 179 8 / var(--tw-bg-opacity));
}
.bg-gradient-to-br {
background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
}
@@ -2755,6 +2729,10 @@ select {
padding: 0.5rem;
}
.p-3 {
padding: 0.75rem;
}
.p-4 {
padding: 1rem;
}
@@ -2907,6 +2885,11 @@ select {
color: rgb(30 64 175 / var(--tw-text-opacity));
}
.text-gray-300 {
--tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity));
}
.text-gray-400 {
--tw-text-opacity: 1;
color: rgb(156 163 175 / var(--tw-text-opacity));
@@ -2981,6 +2964,14 @@ select {
color: rgb(133 77 14 / var(--tw-text-opacity));
}
.opacity-0 {
opacity: 0;
}
.opacity-100 {
opacity: 1;
}
.shadow {
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
@@ -3015,6 +3006,11 @@ select {
--tw-ring-color: rgb(79 70 229 / 0.2);
}
.ring-blue-500 {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
}
.filter {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
@@ -3025,6 +3021,14 @@ select {
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.transition {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.transition-all {
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
@@ -3043,6 +3047,22 @@ select {
transition-duration: 150ms;
}
.duration-100 {
transition-duration: 100ms;
}
.duration-75 {
transition-duration: 75ms;
}
.ease-in {
transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
}
.ease-out {
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
}
.dark\:prose-invert:is(.dark *) {
--tw-prose-body: var(--tw-prose-invert-body);
--tw-prose-headings: var(--tw-prose-invert-headings);
@@ -3084,6 +3104,11 @@ select {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.hover\:border-gray-300:hover {
--tw-border-opacity: 1;
border-color: rgb(209 213 219 / var(--tw-border-opacity));
}
.hover\:bg-blue-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
@@ -3129,6 +3154,11 @@ select {
color: rgb(29 78 216 / var(--tw-text-opacity));
}
.hover\:text-gray-600:hover {
--tw-text-opacity: 1;
color: rgb(75 85 99 / var(--tw-text-opacity));
}
.hover\:text-primary:hover {
--tw-text-opacity: 1;
color: rgb(79 70 229 / var(--tw-text-opacity));
@@ -3175,6 +3205,11 @@ select {
--tw-ring-offset-width: 2px;
}
.dark\:border-blue-700:is(.dark *) {
--tw-border-opacity: 1;
border-color: rgb(29 78 216 / var(--tw-border-opacity));
}
.dark\:border-gray-600:is(.dark *) {
--tw-border-opacity: 1;
border-color: rgb(75 85 99 / var(--tw-border-opacity));
@@ -3189,6 +3224,11 @@ select {
border-color: rgb(55 65 81 / 0.5);
}
.dark\:border-red-700:is(.dark *) {
--tw-border-opacity: 1;
border-color: rgb(185 28 28 / var(--tw-border-opacity));
}
.dark\:bg-blue-400\/30:is(.dark *) {
background-color: rgb(96 165 250 / 0.3);
}
@@ -3284,6 +3324,11 @@ select {
--tw-gradient-to: #3b0764 var(--tw-gradient-to-position);
}
.dark\:text-blue-100:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(219 234 254 / var(--tw-text-opacity));
}
.dark\:text-blue-200:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(191 219 254 / var(--tw-text-opacity));
@@ -3299,6 +3344,11 @@ select {
color: rgb(239 246 255 / var(--tw-text-opacity));
}
.dark\:text-blue-500:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(59 130 246 / var(--tw-text-opacity));
}
.dark\:text-gray-200:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(229 231 235 / var(--tw-text-opacity));
@@ -3319,6 +3369,11 @@ select {
color: rgb(187 247 208 / var(--tw-text-opacity));
}
.dark\:text-red-100:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(254 226 226 / var(--tw-text-opacity));
}
.dark\:text-red-200:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(254 202 202 / var(--tw-text-opacity));

18
static/js/alerts.js Normal file
View File

@@ -0,0 +1,18 @@
document.addEventListener('DOMContentLoaded', function() {
// Get all alert elements
const alerts = document.querySelectorAll('.alert');
// For each alert
alerts.forEach(alert => {
// After 5 seconds
setTimeout(() => {
// Add slideOut animation
alert.style.animation = 'slideOut 0.5s ease-out forwards';
// Remove the alert after animation completes
setTimeout(() => {
alert.remove();
}, 500);
}, 5000);
});
});

5
static/js/alpine.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,262 +0,0 @@
document.addEventListener('DOMContentLoaded', function() {
// Handle edit button clicks
document.querySelectorAll('[data-edit-button]').forEach(button => {
button.addEventListener('click', function() {
const contentId = this.dataset.contentId;
const contentType = this.dataset.contentType;
const editableFields = document.querySelectorAll(`[data-editable][data-content-id="${contentId}"]`);
// Toggle edit mode
editableFields.forEach(field => {
const currentValue = field.textContent.trim();
const fieldName = field.dataset.fieldName;
const fieldType = field.dataset.fieldType || 'text';
// Create input field
let input;
if (fieldType === 'textarea') {
input = document.createElement('textarea');
input.value = currentValue;
input.rows = 4;
} else if (fieldType === 'select') {
input = document.createElement('select');
// Get options from data attribute
const options = JSON.parse(field.dataset.options || '[]');
options.forEach(option => {
const optionEl = document.createElement('option');
optionEl.value = option.value;
optionEl.textContent = option.label;
optionEl.selected = option.value === currentValue;
input.appendChild(optionEl);
});
} else if (fieldType === 'date') {
input = document.createElement('input');
input.type = 'date';
input.value = currentValue;
} else if (fieldType === 'number') {
input = document.createElement('input');
input.type = 'number';
input.value = currentValue;
if (field.dataset.min) input.min = field.dataset.min;
if (field.dataset.max) input.max = field.dataset.max;
if (field.dataset.step) input.step = field.dataset.step;
} else {
input = document.createElement('input');
input.type = fieldType;
input.value = currentValue;
}
input.className = 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white';
input.dataset.originalValue = currentValue;
input.dataset.fieldName = fieldName;
// Replace content with input
field.textContent = '';
field.appendChild(input);
});
// Show save/cancel buttons
const actionButtons = document.createElement('div');
actionButtons.className = 'flex gap-2 mt-2';
actionButtons.innerHTML = `
<button class="px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600" data-save-button>
<i class="mr-2 fas fa-save"></i>Save Changes
</button>
<button class="px-4 py-2 text-gray-700 bg-gray-200 rounded-lg hover:bg-gray-300 dark:bg-gray-600 dark:text-gray-200 dark:hover:bg-gray-500" data-cancel-button>
<i class="mr-2 fas fa-times"></i>Cancel
</button>
${this.dataset.requireReason ? `
<div class="flex-grow">
<input type="text" class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
placeholder="Reason for changes (required)"
data-reason-input>
<input type="text" class="w-full mt-1 border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
placeholder="Source (optional)"
data-source-input>
</div>
` : ''}
`;
const container = editableFields[0].closest('.editable-container');
container.appendChild(actionButtons);
// Hide edit button while editing
this.style.display = 'none';
});
});
// Handle form submissions
document.querySelectorAll('form[data-submit-type]').forEach(form => {
form.addEventListener('submit', function(e) {
e.preventDefault();
const submitType = this.dataset.submitType;
const formData = new FormData(this);
const data = {};
formData.forEach((value, key) => {
data[key] = value;
});
// Get CSRF token from meta tag
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
// Submit form
fetch(this.action, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify({
submission_type: submitType,
...data
})
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
showNotification(data.message, 'success');
if (data.redirect_url) {
window.location.href = data.redirect_url;
}
} else {
showNotification(data.message, 'error');
}
})
.catch(error => {
showNotification('An error occurred while submitting the form.', 'error');
console.error('Error:', error);
});
});
});
// Handle save button clicks using event delegation
document.addEventListener('click', function(e) {
if (e.target.matches('[data-save-button]')) {
const container = e.target.closest('.editable-container');
const contentId = container.querySelector('[data-editable]').dataset.contentId;
const contentType = container.querySelector('[data-edit-button]').dataset.contentType;
const editableFields = container.querySelectorAll('[data-editable]');
// Collect changes
const changes = {};
editableFields.forEach(field => {
const input = field.querySelector('input, textarea, select');
if (input && input.value !== input.dataset.originalValue) {
changes[input.dataset.fieldName] = input.value;
}
});
// If no changes, just cancel
if (Object.keys(changes).length === 0) {
cancelEdit(container);
return;
}
// Get reason and source if required
const reasonInput = container.querySelector('[data-reason-input]');
const sourceInput = container.querySelector('[data-source-input]');
const reason = reasonInput ? reasonInput.value : '';
const source = sourceInput ? sourceInput.value : '';
// Validate reason if required
if (reasonInput && !reason) {
alert('Please provide a reason for your changes.');
return;
}
// Get CSRF token from meta tag
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
// Submit changes
fetch(window.location.pathname, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify({
content_type: contentType,
content_id: contentId,
changes,
reason,
source
})
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
if (data.auto_approved) {
// Update the display immediately
Object.entries(changes).forEach(([field, value]) => {
const element = container.querySelector(`[data-editable][data-field-name="${field}"]`);
if (element) {
element.textContent = value;
}
});
}
showNotification(data.message, 'success');
if (data.redirect_url) {
window.location.href = data.redirect_url;
}
} else {
showNotification(data.message, 'error');
}
cancelEdit(container);
})
.catch(error => {
showNotification('An error occurred while saving changes.', 'error');
console.error('Error:', error);
cancelEdit(container);
});
}
});
// Handle cancel button clicks using event delegation
document.addEventListener('click', function(e) {
if (e.target.matches('[data-cancel-button]')) {
const container = e.target.closest('.editable-container');
cancelEdit(container);
}
});
});
function cancelEdit(container) {
// Restore original content
container.querySelectorAll('[data-editable]').forEach(field => {
const input = field.querySelector('input, textarea, select');
if (input) {
field.textContent = input.dataset.originalValue;
}
});
// Remove action buttons
const actionButtons = container.querySelector('.flex.gap-2');
if (actionButtons) {
actionButtons.remove();
}
// Show edit button
const editButton = container.querySelector('[data-edit-button]');
if (editButton) {
editButton.style.display = '';
}
}
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `fixed bottom-4 right-4 p-4 rounded-lg shadow-lg text-white ${
type === 'success' ? 'bg-green-600 dark:bg-green-500' :
type === 'error' ? 'bg-red-600 dark:bg-red-500' :
'bg-blue-600 dark:bg-blue-500'
}`;
notification.textContent = message;
document.body.appendChild(notification);
// Remove after 5 seconds
setTimeout(() => {
notification.remove();
}, 5000);
}

View File

@@ -0,0 +1,81 @@
function locationAutocomplete(field, filterParks = false) {
return {
query: '',
suggestions: [],
fetchSuggestions() {
let url;
const params = new URLSearchParams({
q: this.query,
filter_parks: filterParks
});
switch (field) {
case 'country':
url = '/parks/ajax/countries/';
break;
case 'region':
url = '/parks/ajax/regions/';
// Add country parameter if we're fetching regions
const countryInput = document.getElementById(filterParks ? 'country' : 'id_country_name');
if (countryInput && countryInput.value) {
params.append('country', countryInput.value);
}
break;
case 'city':
url = '/parks/ajax/cities/';
// Add country and region parameters if we're fetching cities
const regionInput = document.getElementById(filterParks ? 'region' : 'id_region_name');
const cityCountryInput = document.getElementById(filterParks ? 'country' : 'id_country_name');
if (regionInput && regionInput.value && cityCountryInput && cityCountryInput.value) {
params.append('country', cityCountryInput.value);
params.append('region', regionInput.value);
}
break;
}
if (url) {
fetch(`${url}?${params}`)
.then(response => response.json())
.then(data => {
this.suggestions = data;
});
}
},
selectSuggestion(suggestion) {
this.query = suggestion.name;
this.suggestions = [];
// If this is a form field (not filter), update hidden fields
if (!filterParks) {
const hiddenField = document.getElementById(`id_${field}`);
if (hiddenField) {
hiddenField.value = suggestion.id;
}
// Clear dependent fields when parent field changes
if (field === 'country') {
const regionInput = document.getElementById('id_region_name');
const cityInput = document.getElementById('id_city_name');
const regionHidden = document.getElementById('id_region');
const cityHidden = document.getElementById('id_city');
if (regionInput) regionInput.value = '';
if (cityInput) cityInput.value = '';
if (regionHidden) regionHidden.value = '';
if (cityHidden) cityHidden.value = '';
} else if (field === 'region') {
const cityInput = document.getElementById('id_city_name');
const cityHidden = document.getElementById('id_city');
if (cityInput) cityInput.value = '';
if (cityHidden) cityHidden.value = '';
}
}
// Trigger form submission for filters
if (filterParks) {
htmx.trigger('#park-filters', 'change');
}
}
};
}