|
@@ -14,13 +14,20 @@
|
|
|
</h1>
|
|
</h1>
|
|
|
<p class="text-muted-foreground mt-2">{{ t("orders.titleSubtitle") }}</p>
|
|
<p class="text-muted-foreground mt-2">{{ t("orders.titleSubtitle") }}</p>
|
|
|
</div>
|
|
</div>
|
|
|
- <Button @click="router.push('/#upload')" class="bg-primary hover:bg-primary/90 text-primary-foreground px-6 py-2.5 rounded-xl font-bold shadow-lg shadow-primary/20 flex items-center gap-2 group shrink-0">
|
|
|
|
|
- <Plus class="w-4 h-4 group-hover:rotate-90 transition-transform" />
|
|
|
|
|
- {{ t("orders.newOrder") }}
|
|
|
|
|
|
|
+ <Button @click="showNewOrderForm = !showNewOrderForm" :variant="showNewOrderForm ? 'outline' : 'hero'" class="px-6 py-2.5 rounded-xl font-bold shadow-lg flex items-center gap-2 group shrink-0">
|
|
|
|
|
+ <component :is="showNewOrderForm ? X : Plus" class="w-4 h-4 transition-transform group-hover:scale-110" />
|
|
|
|
|
+ {{ showNewOrderForm ? t("common.cancel") : t("orders.newOrder") }}
|
|
|
</Button>
|
|
</Button>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
+ <div v-if="showNewOrderForm"
|
|
|
|
|
+ v-motion :initial="{ opacity: 0, height: 0 }" :enter="{ opacity: 1, height: 'auto' }"
|
|
|
|
|
+ class="mb-12 bg-card/40 backdrop-blur-xl border border-primary/20 rounded-3xl p-6 sm:p-10 relative overflow-hidden">
|
|
|
|
|
+ <div class="absolute top-0 right-0 w-64 h-64 bg-primary/5 rounded-full blur-3xl -mr-32 -mt-32" />
|
|
|
|
|
+ <ModelUploadSection embedded @success="onOrderSuccess" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
<div v-if="isLoading" class="flex flex-col items-center justify-center py-20 gap-4">
|
|
<div v-if="isLoading" class="flex flex-col items-center justify-center py-20 gap-4">
|
|
|
<Loader2 class="w-10 h-10 animate-spin text-primary" />
|
|
<Loader2 class="w-10 h-10 animate-spin text-primary" />
|
|
|
<p class="text-muted-foreground animate-pulse">{{ t("orders.loading") }}</p>
|
|
<p class="text-muted-foreground animate-pulse">{{ t("orders.loading") }}</p>
|
|
@@ -222,6 +229,7 @@ import Footer from "@/components/Footer.vue";
|
|
|
import OrderChat from "@/components/OrderChat.vue";
|
|
import OrderChat from "@/components/OrderChat.vue";
|
|
|
import OrderTracker from "@/components/OrderTracker.vue";
|
|
import OrderTracker from "@/components/OrderTracker.vue";
|
|
|
import OrderReviewForm from "@/components/OrderReviewForm.vue";
|
|
import OrderReviewForm from "@/components/OrderReviewForm.vue";
|
|
|
|
|
+import ModelUploadSection from "@/components/ModelUploadSection.vue";
|
|
|
import { getMyOrders, API_BASE_URL, RESOURCES_BASE_URL } from "@/lib/api";
|
|
import { getMyOrders, API_BASE_URL, RESOURCES_BASE_URL } from "@/lib/api";
|
|
|
import { useAuthStore } from "@/stores/auth";
|
|
import { useAuthStore } from "@/stores/auth";
|
|
|
import { toast } from "vue-sonner";
|
|
import { toast } from "vue-sonner";
|
|
@@ -235,6 +243,7 @@ const totalOrders = ref(0);
|
|
|
const currentPage = ref(1);
|
|
const currentPage = ref(1);
|
|
|
const pageSize = 10;
|
|
const pageSize = 10;
|
|
|
const isLoading = ref(true);
|
|
const isLoading = ref(true);
|
|
|
|
|
+const showNewOrderForm = ref(false);
|
|
|
const openChatId = ref<number | null>(route.query.chat ? parseInt(route.query.chat.toString()) : null);
|
|
const openChatId = ref<number | null>(route.query.chat ? parseInt(route.query.chat.toString()) : null);
|
|
|
|
|
|
|
|
async function toggleChat(orderId: number) {
|
|
async function toggleChat(orderId: number) {
|
|
@@ -261,6 +270,11 @@ watch(openChatId, (newId) => {
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+function onOrderSuccess() {
|
|
|
|
|
+ showNewOrderForm.value = false;
|
|
|
|
|
+ fetchOrders();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// StatusBadge component defined inline
|
|
// StatusBadge component defined inline
|
|
|
const StatusBadge = defineComponent({
|
|
const StatusBadge = defineComponent({
|
|
|
props: { status: String },
|
|
props: { status: String },
|