|
|
@@ -8,9 +8,12 @@
|
|
|
<Header />
|
|
|
<main class="container mx-auto px-4 pt-32 pb-20">
|
|
|
|
|
|
- <!-- DEPLOY INDICATOR -->
|
|
|
- <div class="bg-rose-600 text-white text-[10px] py-1 text-center font-bold uppercase tracking-widest mb-4 rounded-lg">
|
|
|
- DEPLOY VERIFIED: 12:50 CEST
|
|
|
+ <!-- DEPLOY INDICATOR & DIAGNOSTIC -->
|
|
|
+ <div class="bg-rose-600 text-white text-[10px] py-2 px-4 flex justify-between items-center font-bold uppercase tracking-widest mb-4 rounded-lg">
|
|
|
+ <span>DEPLOY VERIFIED: 12:51 CEST</span>
|
|
|
+ <button v-if="orders.length > 0" @click="handleEditOrder(orders[0])" class="bg-white text-rose-600 px-3 py-1 rounded hover:bg-rose-100 transition-colors">
|
|
|
+ DEBUG: OPEN FIRST ORDER
|
|
|
+ </button>
|
|
|
</div>
|
|
|
|
|
|
<!-- Admin Header -->
|
|
|
@@ -135,82 +138,92 @@
|
|
|
</div>
|
|
|
</main>
|
|
|
|
|
|
- <!-- Global Modals -->
|
|
|
- <div v-if="showAddModal" class="fixed inset-0 z-[99999] flex items-center justify-center p-4">
|
|
|
- <div class="absolute inset-0 bg-background/80 backdrop-blur-sm" @click="closeModals" />
|
|
|
- <div class="relative w-full max-w-lg bg-card border border-border/50 rounded-3xl p-8 shadow-2xl overflow-y-auto max-h-[90vh]">
|
|
|
- <h3 class="text-xl font-bold font-display mb-6">{{ editingMaterial || editingService || editingPost || editingOrder ? t('admin.actions.edit') : t("admin.addNew") }}</h3>
|
|
|
+ <!-- DEDICATED ORDER EDIT MODAL -->
|
|
|
+ <div v-if="showOrderEditModal" class="fixed inset-0 z-[99999] flex items-center justify-center p-4">
|
|
|
+ <div class="absolute inset-0 bg-background/90 backdrop-blur-md" @click="showOrderEditModal = false" />
|
|
|
+ <div class="relative w-full max-w-2xl bg-card border border-primary/30 rounded-3xl p-8 shadow-2xl overflow-y-auto max-h-[90vh]">
|
|
|
+ <div class="flex justify-between items-center mb-6">
|
|
|
+ <h3 class="text-2xl font-black font-display text-gradient">Edit Order #{{ editingOrder?.id }}</h3>
|
|
|
+ <button @click="showOrderEditModal = false" class="p-2 hover:bg-white/5 rounded-full transition-colors"><X class="w-6 h-6" /></button>
|
|
|
+ </div>
|
|
|
|
|
|
- <!-- Order Edit Form -->
|
|
|
- <form v-if="editingOrder" @submit.prevent="handleSaveOrder" class="space-y-4">
|
|
|
- <!-- Form items... no changes there -->
|
|
|
- <div class="grid grid-cols-2 gap-4">
|
|
|
+ <form v-if="editingOrder" @submit.prevent="handleSaveOrder" class="space-y-6">
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
+ <div class="space-y-4">
|
|
|
<div class="space-y-1">
|
|
|
- <label class="text-[10px] font-bold uppercase ml-1">{{ t("auth.fields.firstName") }}</label>
|
|
|
- <input v-model="orderForm.first_name" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm" />
|
|
|
+ <label class="text-[10px] font-bold uppercase text-muted-foreground ml-1">{{ t("auth.fields.firstName") }}</label>
|
|
|
+ <input v-model="orderForm.first_name" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm focus:ring-2 ring-primary/20 outline-none" />
|
|
|
</div>
|
|
|
<div class="space-y-1">
|
|
|
- <label class="text-[10px] font-bold uppercase ml-1">{{ t("auth.fields.lastName") }}</label>
|
|
|
- <input v-model="orderForm.last_name" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm" />
|
|
|
+ <label class="text-[10px] font-bold uppercase text-muted-foreground ml-1">{{ t("auth.fields.lastName") }}</label>
|
|
|
+ <input v-model="orderForm.last_name" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm focus:ring-2 ring-primary/20 outline-none" />
|
|
|
</div>
|
|
|
- </div>
|
|
|
- <div class="grid grid-cols-2 gap-4">
|
|
|
<div class="space-y-1">
|
|
|
- <label class="text-[10px] font-bold uppercase ml-1">{{ t("auth.fields.email") }}</label>
|
|
|
- <input v-model="orderForm.email" type="email" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm" />
|
|
|
+ <label class="text-[10px] font-bold uppercase text-muted-foreground ml-1">{{ t("auth.fields.email") }}</label>
|
|
|
+ <input v-model="orderForm.email" type="email" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm focus:ring-2 ring-primary/20 outline-none" />
|
|
|
</div>
|
|
|
<div class="space-y-1">
|
|
|
- <label class="text-[10px] font-bold uppercase ml-1">{{ t("admin.fields.phone") }}</label>
|
|
|
- <input v-model="orderForm.phone" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm" />
|
|
|
+ <label class="text-[10px] font-bold uppercase text-muted-foreground ml-1">{{ t("admin.fields.phone") }}</label>
|
|
|
+ <input v-model="orderForm.phone" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm focus:ring-2 ring-primary/20 outline-none" />
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div class="space-y-1">
|
|
|
- <label class="text-[10px] font-bold uppercase ml-1">{{ t("admin.fields.address") }}</label>
|
|
|
- <input v-model="orderForm.shipping_address" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm" />
|
|
|
- </div>
|
|
|
- <div class="space-y-1">
|
|
|
- <label class="text-[10px] font-bold uppercase ml-1">{{ t("admin.fields.projectNotes") }}</label>
|
|
|
- <textarea v-model="orderForm.notes" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm h-20" />
|
|
|
- </div>
|
|
|
|
|
|
- <div class="grid grid-cols-2 gap-4 pt-4 border-t border-border/10">
|
|
|
+ <div class="space-y-4">
|
|
|
<div class="space-y-1">
|
|
|
- <label class="text-[10px] font-bold uppercase ml-1">{{ t("admin.fields.totalPrice") }} (EUR)</label>
|
|
|
- <input v-model.number="orderForm.total_price" type="number" step="0.01" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm" />
|
|
|
+ <label class="text-[10px] font-bold uppercase text-muted-foreground ml-1">{{ t("admin.fields.address") }}</label>
|
|
|
+ <input v-model="orderForm.shipping_address" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm focus:ring-2 ring-primary/20 outline-none" />
|
|
|
</div>
|
|
|
<div class="space-y-1">
|
|
|
- <label class="text-[10px] font-bold uppercase ml-1">{{ t("admin.fields.quantity") }}</label>
|
|
|
- <input v-model.number="orderForm.quantity" type="number" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm" />
|
|
|
+ <label class="text-[10px] font-bold uppercase text-muted-foreground ml-1">{{ t("admin.fields.projectNotes") }}</label>
|
|
|
+ <textarea v-model="orderForm.notes" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm h-[132px] focus:ring-2 ring-primary/20 outline-none resize-none" />
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div class="grid grid-cols-2 gap-4">
|
|
|
- <div class="space-y-1">
|
|
|
- <label class="text-[10px] font-bold uppercase ml-1">{{ t("admin.fields.material") }}</label>
|
|
|
- <input v-model="orderForm.material_name" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm" />
|
|
|
- </div>
|
|
|
- <div class="space-y-1">
|
|
|
- <label class="text-[10px] font-bold uppercase ml-1">{{ t("admin.fields.colors") }}</label>
|
|
|
- <input v-model="orderForm.color_name" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm" />
|
|
|
- </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="p-6 bg-primary/5 rounded-2xl border border-primary/10 grid grid-cols-2 md:grid-cols-4 gap-6">
|
|
|
+ <div class="space-y-1">
|
|
|
+ <label class="text-[10px] font-bold uppercase text-primary/60 ml-1">{{ t("admin.fields.totalPrice") }} (EUR)</label>
|
|
|
+ <input v-model.number="orderForm.total_price" type="number" step="0.01" class="w-full bg-background border border-border/50 rounded-xl px-3 py-2 text-sm font-bold" />
|
|
|
</div>
|
|
|
- <!-- Review Edit fallback -->
|
|
|
- <div v-if="orderForm.review_text" class="pt-4 border-t border-border/10 space-y-4">
|
|
|
- <div class="space-y-1">
|
|
|
- <label class="text-[10px] font-bold uppercase ml-1">Review Text</label>
|
|
|
- <textarea v-model="orderForm.review_text" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm h-20" />
|
|
|
- </div>
|
|
|
- <div class="space-y-1">
|
|
|
- <label class="text-[10px] font-bold uppercase ml-1">Rating (1-5)</label>
|
|
|
- <input v-model.number="orderForm.rating" type="number" min="1" max="5" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm" />
|
|
|
- </div>
|
|
|
+ <div class="space-y-1">
|
|
|
+ <label class="text-[10px] font-bold uppercase text-primary/60 ml-1">{{ t("admin.fields.quantity") }}</label>
|
|
|
+ <input v-model.number="orderForm.quantity" type="number" class="w-full bg-background border border-border/50 rounded-xl px-3 py-2 text-sm font-bold" />
|
|
|
</div>
|
|
|
-
|
|
|
- <div class="flex gap-3 pt-6 border-t border-border/10">
|
|
|
- <Button type="button" variant="ghost" class="flex-1" @click="closeModals">{{ t("admin.actions.cancel") }}</Button>
|
|
|
- <Button type="submit" variant="hero" class="flex-1">{{ t("admin.actions.save") }}</Button>
|
|
|
+ <div class="space-y-1">
|
|
|
+ <label class="text-[10px] font-bold uppercase text-primary/60 ml-1">{{ t("admin.fields.material") }}</label>
|
|
|
+ <input v-model="orderForm.material_name" class="w-full bg-background border border-border/50 rounded-xl px-3 py-2 text-sm" />
|
|
|
</div>
|
|
|
- </form>
|
|
|
-
|
|
|
+ <div class="space-y-1">
|
|
|
+ <label class="text-[10px] font-bold uppercase text-primary/60 ml-1">{{ t("admin.fields.colors") }}</label>
|
|
|
+ <input v-model="orderForm.color_name" class="w-full bg-background border border-border/50 rounded-xl px-3 py-2 text-sm" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Review Feedback -->
|
|
|
+ <div v-if="orderForm.review_text" class="p-4 bg-amber-500/5 border border-amber-500/20 rounded-2xl space-y-3">
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <label class="text-[10px] font-bold uppercase text-amber-500 ml-1">Client Review Content</label>
|
|
|
+ <div class="flex gap-1">
|
|
|
+ <Star v-for="i in 5" :key="i" class="w-3 h-3" :class="orderForm.rating >= i ? 'text-amber-500 fill-amber-500' : 'text-muted-foreground/20'" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <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>
|
|
|
+
|
|
|
+ <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>
|
|
|
+ </div>
|
|
|
+ </form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Global Modals -->
|
|
|
+ <div v-if="showAddModal" class="fixed inset-0 z-[99999] flex items-center justify-center p-4">
|
|
|
+ <div class="absolute inset-0 bg-background/80 backdrop-blur-sm" @click="closeModals" />
|
|
|
+ <div class="relative w-full max-w-lg bg-card border border-border/50 rounded-3xl p-8 shadow-2xl overflow-y-auto max-h-[90vh]">
|
|
|
+ <h3 class="text-xl font-bold font-display mb-6">{{ editingMaterial || editingService || editingPost ? t('admin.actions.edit') : t("admin.addNew") }}</h3>
|
|
|
+
|
|
|
<!-- Material Modal Form -->
|
|
|
<form v-if="activeTab === 'materials'" @submit.prevent="handleSaveMaterial" class="space-y-4">
|
|
|
<div class="space-y-1"><label class="text-[10px] font-bold uppercase ml-1">{{ t("admin.fields.nameEn") }}</label><input v-model="matForm.name_en" required class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm" /></div>
|
|
|
@@ -279,7 +292,7 @@ import { toast } from "vue-sonner";
|
|
|
// Icons
|
|
|
import {
|
|
|
Package, Clock, RefreshCw, Search, Layers, Plus, Database,
|
|
|
- Newspaper, History, X, Users, ImageIcon, Truck, CheckCircle2, XCircle
|
|
|
+ Newspaper, History, X, Users, ImageIcon, Truck, CheckCircle2, XCircle, Star
|
|
|
} from "lucide-vue-next";
|
|
|
|
|
|
// UI Components
|
|
|
@@ -380,6 +393,7 @@ watch([searchQuery], () => {
|
|
|
|
|
|
// Modals State & Forms
|
|
|
const showAddModal = ref(false);
|
|
|
+const showOrderEditModal = ref(false);
|
|
|
const editingMaterial = ref<any | null>(null);
|
|
|
const editingService = ref<any | null>(null);
|
|
|
const editingPost = ref<any | null>(null);
|
|
|
@@ -409,7 +423,7 @@ const handleEditOrder = (order: any) => {
|
|
|
review_text: order.review_text || "",
|
|
|
rating: order.rating || 0
|
|
|
});
|
|
|
- showAddModal.value = true;
|
|
|
+ showOrderEditModal.value = true;
|
|
|
};
|
|
|
|
|
|
const newColor = ref("");
|
|
|
@@ -537,14 +551,19 @@ const handleResetPassword = async (id: number) => {
|
|
|
// Modal Actions
|
|
|
const handleAddNew = () => { closeModals(); showAddModal.value = true; };
|
|
|
|
|
|
-const closeModals = () => {
|
|
|
- showAddModal.value = false; editingMaterial.value = null; editingService.value = null; editingPost.value = null; editingOrder.value = null;
|
|
|
+function closeModals() {
|
|
|
+ showAddModal.value = false;
|
|
|
+ showOrderEditModal.value = false;
|
|
|
+ editingMaterial.value = null;
|
|
|
+ editingService.value = null;
|
|
|
+ editingPost.value = null;
|
|
|
+ editingOrder.value = null;
|
|
|
Object.assign(matForm, { name_en: "", name_ru: "", name_me: "", name_ua: "", desc_en: "", desc_ru: "", desc_ua: "", desc_me: "", price_per_cm3: 0, available_colors: [], is_active: true });
|
|
|
Object.assign(svcForm, { name_en: "", name_ru: "", name_me: "", name_ua: "", desc_en: "", desc_ru: "", desc_ua: "", desc_me: "", tech_type: "", is_active: true });
|
|
|
Object.assign(userForm, { email: "", password: "", first_name: "", last_name: "", phone: "" });
|
|
|
Object.assign(postForm, { slug: "", title_en: "", title_me: "", title_ru: "", title_ua: "", category: "Technology", image_url: "", is_published: true });
|
|
|
Object.assign(orderForm, { total_price: 0, material_name: "", color_name: "", quantity: 1, first_name: "", last_name: "", email: "", phone: "", shipping_address: "", notes: "", review_text: "", rating: 0 });
|
|
|
-};
|
|
|
+}
|
|
|
|
|
|
function addColor() { if (newColor.value) { matForm.available_colors.push(newColor.value); newColor.value = ""; } }
|
|
|
function removeColor(idx: number) { matForm.available_colors.splice(idx, 1); }
|