Kaynağa Gözat

feat: stabilized order editing with dedicated modal, smart selectors, and file management

unknown 1 gün önce
ebeveyn
işleme
fd07a6958f
1 değiştirilmiş dosya ile 64 ekleme ve 5 silme
  1. 64 5
      src/pages/Admin.vue

+ 64 - 5
src/pages/Admin.vue

@@ -57,7 +57,7 @@
           :searchQuery="searchQuery"
           @update-status="handleUpdateStatus"
           @delete-order="handleDeleteOrder"
-          @attach-file="handleAttachFile"
+          @attach-file="handleAttachFiles"
           @upload-photo="handleUploadPhoto"
           @delete-file="handleDeleteFile"
           @delete-photo="handleDeletePhoto"
@@ -210,6 +210,50 @@
                <textarea v-model="orderForm.review_text" class="w-full bg-background/50 border border-border/50 rounded-xl px-4 py-3 text-xs h-20 italic" />
             </div>
 
+            <!-- Management Sections (Files & Photos) -->
+            <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
+               <!-- Files Section -->
+               <div class="p-4 bg-muted/30 rounded-2xl border border-border/20 space-y-4">
+                  <div class="flex items-center justify-between">
+                    <span class="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">{{ t("admin.fields.sourceFiles") }}</span>
+                    <label class="p-1.5 bg-blue-500/10 text-blue-500 rounded-lg cursor-pointer hover:bg-blue-500 hover:text-white transition-all shadow-sm">
+                      <Plus class="w-3.5 h-3.5" />
+                      <input type="file" class="hidden" multiple @change="e => handleAttachFiles((e.target as HTMLInputElement).files)" />
+                    </label>
+                  </div>
+                  <div class="space-y-2 max-h-[200px] overflow-y-auto pr-1 custom-scrollbar">
+                    <div v-for="f in (editingOrder?.files || [])" :key="f.id" class="flex items-center justify-between p-2 bg-background/50 rounded-xl border border-border/50 text-[11px]">
+                       <span class="truncate max-w-[150px] font-medium">{{ f.filename }}</span>
+                       <div class="flex gap-1">
+                          <a :href="`${RESOURCES_BASE_URL}/${f.file_path}`" target="_blank" class="p-1.5 hover:bg-primary/10 rounded-md text-primary transition-colors"><Database class="w-3 h-3" /></a>
+                          <button type="button" @click="handleDeleteFile(editingOrder.id, f.id, f.filename)" class="p-1.5 hover:bg-rose-500/10 rounded-md text-rose-500 transition-colors"><Trash2 class="w-3 h-3" /></button>
+                       </div>
+                    </div>
+                    <p v-if="!editingOrder?.files?.length" class="text-[10px] text-muted-foreground italic text-center py-4">No files attached</p>
+                  </div>
+               </div>
+
+               <!-- Photos Section -->
+               <div class="p-4 bg-muted/30 rounded-2xl border border-border/20 space-y-4">
+                  <div class="flex items-center justify-between">
+                    <span class="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">{{ t("admin.fields.photoReport") }}</span>
+                    <label class="p-1.5 bg-emerald-500/10 text-emerald-500 rounded-lg cursor-pointer hover:bg-emerald-500 hover:text-white transition-all shadow-sm">
+                      <Plus class="w-3.5 h-3.5" />
+                      <input type="file" class="hidden" accept="image/*" @change="e => handleUploadPhoto(editingOrder.id, (e.target as HTMLInputElement).files?.[0])" />
+                    </label>
+                  </div>
+                  <div class="flex flex-wrap gap-2 max-h-[200px] overflow-y-auto pr-1">
+                     <div v-for="p in (editingOrder?.photos || [])" :key="p.id" class="relative group">
+                        <img :src="`${RESOURCES_BASE_URL}/${p.file_path}`" class="w-12 h-12 object-cover rounded-lg border border-border/50 shadow-sm" />
+                        <button type="button" @click="handleDeletePhoto(editingOrder.id, p.id)" class="absolute -top-1 -right-1 p-0.5 bg-rose-500 text-white rounded-full opacity-0 group-hover:opacity-100 transition-opacity shadow-lg">
+                           <X class="w-2.5 h-2.5" />
+                        </button>
+                     </div>
+                     <p v-if="!editingOrder?.photos?.length" class="text-[10px] text-muted-foreground italic text-center py-4 w-full">No photos uploaded</p>
+                  </div>
+               </div>
+            </div>
+
             <div class="flex gap-4 pt-4">
               <Button type="button" variant="hero" class="flex-1 rounded-2xl h-12 bg-muted hover:bg-muted/80 text-foreground" @click="showOrderEditModal = false">{{ t("admin.actions.cancel") }}</Button>
               <Button type="submit" variant="hero" class="flex-[2] rounded-2xl h-12 shadow-glow">{{ t("admin.actions.save") }}</Button>
@@ -517,11 +561,26 @@ const handleDeleteOrder = async (id: number) => {
   }
 };
 
-const handleAttachFile = async (id: number, file?: File) => {
-  if (!file) return;
+const handleAttachFiles = async (files: FileList | File | null, orderId?: number) => {
+  if (!files) return;
+  const id = orderId || editingOrder.value?.id;
+  if (!id) return;
+
   try {
-    const fd = new FormData(); fd.append("file", file);
-    await adminAttachFile(id, fd); toast.success("File attached"); fetchData();
+    const fileArray = files instanceof FileList ? Array.from(files) : [files];
+    for (const file of fileArray) {
+      const fd = new FormData();
+      fd.append("file", file);
+      await adminAttachFile(id, fd);
+    }
+    toast.success(`${fileArray.length} file(s) attached`);
+    await fetchData();
+    
+    // Re-sync editingOrder if open
+    if (editingOrder.value?.id === id) {
+      const updatedOrder = orders.value.find(o => o.id === id);
+      if (updatedOrder) editingOrder.value = updatedOrder;
+    }
   } catch (err: any) { toast.error(err.message); }
 };