No Description

pickup_request.html 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. {% extends 'public_frontend/base.html' %}
  2. {% load i18n %}
  3. {% block title %}{% trans "Request Pickup" %}{% endblock %}
  4. {% load crispy_forms_tags widget_tweaks %}
  5. {% block content %}
  6. <h1 class="text-xl font-semibold mb-4">{% trans "Request a Pickup" %}</h1>
  7. <div x-data="pickupForm()" x-init="setupPreview()">
  8. <div class="bg-white rounded shadow p-4" >
  9. <form x-ref="form" id="pickup-form" method="post" enctype="multipart/form-data" class="space-y-4">
  10. {% csrf_token %}
  11. {{ form.name|attr:"x-ref:name"|as_crispy_field }}
  12. <div class="grid md:grid-cols-2 gap-4">
  13. <div>
  14. {{ form.email|attr:"x-ref:email"|as_crispy_field }}
  15. </div>
  16. <div>
  17. {{ form.phone|attr:"x-ref:phone"|as_crispy_field }}
  18. </div>
  19. </div>
  20. {{ form.address|attr:"x-ref:address"|as_crispy_field }}
  21. <div class="grid md:grid-cols-2 gap-4">
  22. <div>
  23. {{ form.preferred_at|attr:"x-ref:preferred_at"|as_crispy_field }}
  24. </div>
  25. </div>
  26. {{ form.materials|attr:"x-ref:materials"|as_crispy_field }}
  27. {{ form.photos|attr:"x-ref:photos"|attr:"accept:image/*"|as_crispy_field }}
  28. <div x-ref="photos_preview" id="photos-preview" class="mt-2 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2"></div>
  29. <div>
  30. <button id="open-review" class="btn btn-primary" type="button" @click="openReview">Review &amp; Submit</button>
  31. </div>
  32. </form>
  33. </div>
  34. <dialog x-ref="dialog" id="review_modal" class="modal dark:bg-black/40">
  35. <div class="modal-box dark:bg-gray-900 dark:text-gray-100 dark:border dark:border-gray-700">
  36. <h3 class="font-bold text-lg">{% trans "Review Your Pickup Request" %}</h3>
  37. <div class="py-4 space-y-3">
  38. <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Name" %}</div><div class="font-medium" x-text="rv.name || '—'"></div></div>
  39. <div class="grid md:grid-cols-2 gap-4">
  40. <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Email" %}</div><div class="font-medium" x-text="rv.email || '—'"></div></div>
  41. <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Phone" %}</div><div class="font-medium" x-text="rv.phone || '—'"></div></div>
  42. </div>
  43. <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Pickup Address" %}</div><div class="whitespace-pre-line" x-text="rv.address || '—'"></div></div>
  44. <div class="grid md:grid-cols-2 gap-4">
  45. <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Preferred Date/Time" %}</div><div class="font-medium" x-text="rv.preferred_at || '—'"></div></div>
  46. </div>
  47. <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Materials / Notes" %}</div><div class="whitespace-pre-line" x-text="rv.materials || '—'"></div></div>
  48. <div>
  49. <div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Photos" %}</div>
  50. <ul class="list-disc list-inside text-sm text-gray-700 dark:text-gray-300">
  51. <template x-if="(rv.photos||[]).length === 0"><li>{% trans "No files attached." %}</li></template>
  52. <template x-for="fname in rv.photos" :key="fname"><li x-text="fname"></li></template>
  53. </ul>
  54. </div>
  55. </div>
  56. <div class="modal-action">
  57. <button class="btn" @click="$refs.dialog.close()">{% trans "Edit" %}</button>
  58. <button class="btn btn-primary" @click.prevent="confirm">{% trans "Confirm & Send" %}</button>
  59. </div>
  60. </div>
  61. <form method="dialog" class="modal-backdrop dark:bg-black/60">
  62. <button class="dark:text-gray-200">{% trans "close" %}</button>
  63. </form>
  64. </dialog>
  65. </div>
  66. <script>
  67. function pickupForm(){
  68. return {
  69. rv: {name:'', email:'', phone:'', address:'', preferred_at:'', materials:'', photos: []},
  70. setupPreview(){
  71. const input = this.$refs.photos;
  72. const render = () => this.renderPreviews(input && input.files ? Array.from(input.files) : []);
  73. if (input) {
  74. input.addEventListener('change', render);
  75. // Initial render (in case the browser retained selection)
  76. render();
  77. }
  78. },
  79. renderPreviews(files){
  80. const wrap = this.$refs.photos_preview;
  81. if (!wrap) return;
  82. // Clear existing
  83. while (wrap.firstChild) wrap.removeChild(wrap.firstChild);
  84. if (!files || files.length === 0) return;
  85. files.forEach(f => {
  86. const item = document.createElement('div');
  87. item.className = 'relative border rounded overflow-hidden bg-white shadow-sm';
  88. if (f.type && f.type.startsWith('image/')) {
  89. const img = document.createElement('img');
  90. img.src = URL.createObjectURL(f);
  91. img.alt = f.name;
  92. img.className = 'w-full h-24 object-cover';
  93. item.appendChild(img);
  94. } else {
  95. const box = document.createElement('div');
  96. box.className = 'h-24 flex items-center justify-center text-xs text-gray-600 p-2 text-center';
  97. box.textContent = f.name;
  98. item.appendChild(box);
  99. }
  100. const cap = document.createElement('div');
  101. cap.className = 'absolute bottom-0 left-0 right-0 bg-black/50 text-white text-[10px] px-1 py-0.5 truncate';
  102. cap.textContent = f.name;
  103. item.appendChild(cap);
  104. wrap.appendChild(item);
  105. });
  106. },
  107. openReview(){
  108. console.log(this.$refs.form)
  109. const fd = new FormData(this.$refs.form);
  110. this.rv.name = fd.get('name') || '';
  111. this.rv.email = fd.get('email') || '';
  112. this.rv.phone = fd.get('phone') || '';
  113. this.rv.address = fd.get('address') || '';
  114. this.rv.preferred_at = fd.get('preferred_at') || '';
  115. this.rv.materials = fd.get('materials') || '';
  116. const input = this.$refs.photos;
  117. this.rv.photos = input && input.files ? Array.from(input.files).map(f => f.name) : [];
  118. console.log(this.$refs)
  119. if(this.$refs.dialog.showModal){ this.$refs.dialog.showModal(); }
  120. else { this.$refs.dialog.setAttribute('open','open'); }
  121. },
  122. confirm(){
  123. if(this.$refs.dialog.close){ this.$refs.dialog.close(); } else { this.$refs.dialog.removeAttribute('open'); }
  124. this.$refs.form.submit();
  125. }
  126. }
  127. }
  128. </script>
  129. {% endblock %}