|
|
@@ -66,6 +66,7 @@
|
|
|
@close-chat="adminChatId = null"
|
|
|
@update-notify="(id, val) => notifyStatusMap[id] = val"
|
|
|
@update-fiscal="handleUpdateFiscal"
|
|
|
+ @edit-order="handleEditOrder"
|
|
|
/>
|
|
|
|
|
|
<MaterialsSection
|
|
|
@@ -138,7 +139,76 @@
|
|
|
<div v-if="showAddModal" class="fixed inset-0 z-[100] 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>
|
|
|
+ <h3 class="text-xl font-bold font-display mb-6">{{ editingMaterial || editingService || editingPost || editingOrder ? t('admin.actions.edit') : t("admin.addNew") }}</h3>
|
|
|
+
|
|
|
+ <!-- Order Edit Form -->
|
|
|
+ <form v-if="editingOrder" @submit.prevent="handleSaveOrder" class="space-y-4">
|
|
|
+ <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.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" />
|
|
|
+ </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" />
|
|
|
+ </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" />
|
|
|
+ </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" />
|
|
|
+ </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-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" />
|
|
|
+ </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" />
|
|
|
+ </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>
|
|
|
+ <!-- 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>
|
|
|
+
|
|
|
+ <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>
|
|
|
+ </form>
|
|
|
|
|
|
<!-- Material Modal Form -->
|
|
|
<form v-if="activeTab === 'materials'" @submit.prevent="handleSaveMaterial" class="space-y-4">
|
|
|
@@ -314,11 +384,13 @@ const showAddModal = ref(false);
|
|
|
const editingMaterial = ref<any | null>(null);
|
|
|
const editingService = ref<any | null>(null);
|
|
|
const editingPost = ref<any | null>(null);
|
|
|
+const editingOrder = ref<any | null>(null);
|
|
|
|
|
|
const matForm = reactive({ name_en: "", name_ru: "", name_ua: "", name_me: "", desc_en: "", desc_ru: "", desc_ua: "", desc_me: "", price_per_cm3: 0, available_colors: [] as string[], is_active: true });
|
|
|
const svcForm = reactive({ name_en: "", name_ru: "", name_ua: "", name_me: "", desc_en: "", desc_ru: "", desc_ua: "", desc_me: "", tech_type: "", is_active: true });
|
|
|
const postForm = reactive({ slug: "", title_en: "", title_me: "", title_ru: "", title_ua: "", category: "Technology", image_url: "", is_published: true });
|
|
|
const userForm = reactive({ email: "", password: "", first_name: "", last_name: "", phone: "" });
|
|
|
+const orderForm = reactive({ total_price: 0, material_name: "", color_name: "", quantity: 1, first_name: "", last_name: "", email: "", phone: "", shipping_address: "", notes: "", review_text: "", rating: 0 });
|
|
|
|
|
|
const newColor = ref("");
|
|
|
|
|
|
@@ -378,6 +450,25 @@ const handleUpdateFiscal = async (id: number, data: any) => {
|
|
|
} catch (err: any) { toast.error(err.message); }
|
|
|
};
|
|
|
|
|
|
+const handleEditOrder = (order: any) => {
|
|
|
+ editingOrder.value = order;
|
|
|
+ Object.assign(orderForm, {
|
|
|
+ total_price: order.invoice_amount || 0,
|
|
|
+ material_name: order.material_name || "",
|
|
|
+ color_name: order.color_name || "",
|
|
|
+ quantity: order.quantity || 1,
|
|
|
+ first_name: order.first_name || "",
|
|
|
+ last_name: order.last_name || "",
|
|
|
+ email: order.email || "",
|
|
|
+ phone: order.phone || "",
|
|
|
+ shipping_address: order.shipping_address || "",
|
|
|
+ notes: order.notes || "",
|
|
|
+ review_text: order.review_text || "",
|
|
|
+ rating: order.rating || 0
|
|
|
+ });
|
|
|
+ showAddModal.value = true;
|
|
|
+};
|
|
|
+
|
|
|
const handleApproveReview = async (id: number) => {
|
|
|
try {
|
|
|
await approveOrderReview(id); toast.success("Review approved"); fetchData();
|
|
|
@@ -445,11 +536,12 @@ const handleResetPassword = async (id: number) => {
|
|
|
const handleAddNew = () => { closeModals(); showAddModal.value = true; };
|
|
|
|
|
|
const closeModals = () => {
|
|
|
- showAddModal.value = false; editingMaterial.value = null; editingService.value = null; editingPost.value = null;
|
|
|
+ showAddModal.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 = ""; } }
|
|
|
@@ -476,6 +568,15 @@ async function handleSavePost() {
|
|
|
closeModals(); fetchData();
|
|
|
} catch (err: any) { toast.error(err.message); }
|
|
|
}
|
|
|
+async function handleSaveOrder() {
|
|
|
+ if (!editingOrder.value) return;
|
|
|
+ try {
|
|
|
+ await adminUpdateOrder(editingOrder.value.id, orderForm);
|
|
|
+ closeModals(); fetchData();
|
|
|
+ toast.success("Order updated");
|
|
|
+ } catch (err: any) { toast.error(err.message); }
|
|
|
+}
|
|
|
+
|
|
|
async function handleSaveUser() { try { await adminCreateUser(userForm); closeModals(); fetchUsers(); } catch (err: any) { toast.error(err.message); } }
|
|
|
|
|
|
// Lifecycle
|