| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- {% extends 'public_frontend/base.html' %}
- {% load i18n %}
- {% block title %}{% trans "Request Pickup" %}{% endblock %}
- {% load crispy_forms_tags widget_tweaks %}
- {% block content %}
- <h1 class="text-xl font-semibold mb-4">{% trans "Request a Pickup" %}</h1>
- <div x-data="pickupForm()" x-init="setupPreview()">
- <div class="bg-white rounded shadow p-4" >
- <form x-ref="form" id="pickup-form" method="post" enctype="multipart/form-data" class="space-y-4">
- {% csrf_token %}
- {{ form.name|attr:"x-ref:name"|as_crispy_field }}
- <div class="grid md:grid-cols-2 gap-4">
- <div>
- {{ form.email|attr:"x-ref:email"|as_crispy_field }}
- </div>
- <div>
- {{ form.phone|attr:"x-ref:phone"|as_crispy_field }}
- </div>
- </div>
- {{ form.address|attr:"x-ref:address"|as_crispy_field }}
- <div class="grid md:grid-cols-2 gap-4">
- <div>
- {{ form.preferred_at|attr:"x-ref:preferred_at"|as_crispy_field }}
- </div>
- </div>
- {{ form.materials|attr:"x-ref:materials"|as_crispy_field }}
- {{ form.photos|attr:"x-ref:photos"|attr:"accept:image/*"|as_crispy_field }}
- <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>
- <div>
- <button id="open-review" class="btn btn-primary" type="button" @click="openReview">Review & Submit</button>
- </div>
- </form>
- </div>
- <dialog x-ref="dialog" id="review_modal" class="modal dark:bg-black/40">
- <div class="modal-box dark:bg-gray-900 dark:text-gray-100 dark:border dark:border-gray-700">
- <h3 class="font-bold text-lg">{% trans "Review Your Pickup Request" %}</h3>
- <div class="py-4 space-y-3">
- <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>
- <div class="grid md:grid-cols-2 gap-4">
- <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>
- <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>
- </div>
- <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>
- <div class="grid md:grid-cols-2 gap-4">
- <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>
- </div>
- <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>
- <div>
- <div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Photos" %}</div>
- <ul class="list-disc list-inside text-sm text-gray-700 dark:text-gray-300">
- <template x-if="(rv.photos||[]).length === 0"><li>{% trans "No files attached." %}</li></template>
- <template x-for="fname in rv.photos" :key="fname"><li x-text="fname"></li></template>
- </ul>
- </div>
- </div>
- <div class="modal-action">
- <button class="btn" @click="$refs.dialog.close()">{% trans "Edit" %}</button>
- <button class="btn btn-primary" @click.prevent="confirm">{% trans "Confirm & Send" %}</button>
- </div>
- </div>
- <form method="dialog" class="modal-backdrop dark:bg-black/60">
- <button class="dark:text-gray-200">{% trans "close" %}</button>
- </form>
- </dialog>
- </div>
- <script>
- function pickupForm(){
- return {
- rv: {name:'', email:'', phone:'', address:'', preferred_at:'', materials:'', photos: []},
- setupPreview(){
- const input = this.$refs.photos;
- const render = () => this.renderPreviews(input && input.files ? Array.from(input.files) : []);
- if (input) {
- input.addEventListener('change', render);
- // Initial render (in case the browser retained selection)
- render();
- }
- },
- renderPreviews(files){
- const wrap = this.$refs.photos_preview;
- if (!wrap) return;
- // Clear existing
- while (wrap.firstChild) wrap.removeChild(wrap.firstChild);
- if (!files || files.length === 0) return;
- files.forEach(f => {
- const item = document.createElement('div');
- item.className = 'relative border rounded overflow-hidden bg-white shadow-sm';
- if (f.type && f.type.startsWith('image/')) {
- const img = document.createElement('img');
- img.src = URL.createObjectURL(f);
- img.alt = f.name;
- img.className = 'w-full h-24 object-cover';
- item.appendChild(img);
- } else {
- const box = document.createElement('div');
- box.className = 'h-24 flex items-center justify-center text-xs text-gray-600 p-2 text-center';
- box.textContent = f.name;
- item.appendChild(box);
- }
- const cap = document.createElement('div');
- cap.className = 'absolute bottom-0 left-0 right-0 bg-black/50 text-white text-[10px] px-1 py-0.5 truncate';
- cap.textContent = f.name;
- item.appendChild(cap);
- wrap.appendChild(item);
- });
- },
- openReview(){
- console.log(this.$refs.form)
- const fd = new FormData(this.$refs.form);
- this.rv.name = fd.get('name') || '';
- this.rv.email = fd.get('email') || '';
- this.rv.phone = fd.get('phone') || '';
- this.rv.address = fd.get('address') || '';
- this.rv.preferred_at = fd.get('preferred_at') || '';
- this.rv.materials = fd.get('materials') || '';
- const input = this.$refs.photos;
- this.rv.photos = input && input.files ? Array.from(input.files).map(f => f.name) : [];
- console.log(this.$refs)
- if(this.$refs.dialog.showModal){ this.$refs.dialog.showModal(); }
- else { this.$refs.dialog.setAttribute('open','open'); }
- },
- confirm(){
- if(this.$refs.dialog.close){ this.$refs.dialog.close(); } else { this.$refs.dialog.removeAttribute('open'); }
- this.$refs.form.submit();
- }
- }
- }
- </script>
- {% endblock %}
|