Răsfoiți Sursa

feat: display pricing complexity multiplier in public calculator and return metadata in estimate API

unknown 2 luni în urmă
părinte
comite
5acafc210d
2 a modificat fișierele cu 25 adăugiri și 13 ștergeri
  1. 5 1
      backend/routers/orders.py
  2. 20 12
      src/components/ModelUploadSection.vue

+ 5 - 1
backend/routers/orders.py

@@ -247,7 +247,11 @@ async def get_price_estimate(data: schemas.EstimateRequest):
     )
     
     qts = data.file_quantities if data.file_quantities else [1]*len(data.file_sizes)
-    return {"file_prices": file_prices, "total_estimate": estimated_total}
+    return {
+        "file_prices": file_prices, 
+        "total_estimate": estimated_total,
+        "metadata": metadata
+    }
 
 # --- ADMIN ORDER ENDPOINTS ---
 

+ 20 - 12
src/components/ModelUploadSection.vue

@@ -304,14 +304,20 @@
         <!-- Estimate -->
         <Transition enter-active-class="transition duration-300" enter-from-class="opacity-0 translate-y-2" enter-to-class="opacity-100 translate-y-0">
           <div v-if="estimatedPrice !== null"
-            class="p-6 bg-gradient-to-br from-primary/20 via-primary/5 to-background border border-primary/30 rounded-2xl flex items-center justify-between">
-            <div>
+            class="p-6 bg-gradient-to-br from-primary/20 via-primary/5 to-background border border-primary/30 rounded-2xl flex flex-col sm:flex-row sm:items-center justify-between gap-4">
+            <div class="space-y-1">
               <p class="text-[10px] font-bold uppercase tracking-widest text-primary/80 mb-1">{{ t("upload.estimatedTotal") }}</p>
+              <div v-if="estimateMetadata?.speed_coeff && estimateMetadata.speed_coeff > 1" class="flex items-center gap-1.5 px-2 py-0.5 bg-primary/10 text-primary rounded-md w-fit mb-2">
+                <AlertCircle class="w-3 h-3" />
+                <span class="text-[10px] font-bold uppercase">{{ t("admin.fields.speedCoeff") }}: x{{ estimateMetadata.speed_coeff }}</span>
+              </div>
               <p class="text-xs text-muted-foreground">{{ t("upload.priceDisclaimer") }}</p>
             </div>
             <div class="text-right">
-              <span class="text-3xl font-display font-bold text-primary">{{ estimatedPrice }}</span>
-              <span class="text-xs font-bold text-primary ml-1 uppercase">EUR</span>
+              <div class="flex items-baseline justify-end gap-1">
+                <span class="text-3xl font-display font-bold text-primary">{{ estimatedPrice }}</span>
+                <span class="text-xs font-bold text-primary uppercase">EUR</span>
+              </div>
               <p class="text-[9px] text-muted-foreground mt-1 opacity-60">incl. 21% PDV</p>
             </div>
           </div>
@@ -341,7 +347,7 @@ const emit = defineEmits<{
 import { ref, computed, watch, onMounted } from "vue";
 import { useI18n } from "vue-i18n";
 import { toast } from "vue-sonner";
-import { Upload, FileBox, X, Check, Link as LinkIcon, MapPin, User, Phone, Mail, Loader2, ShieldCheck, Hash, FileText } from "lucide-vue-next";
+import { Upload, FileBox, X, Check, Link as LinkIcon, MapPin, User, Phone, Mail, Loader2, ShieldCheck, Hash, FileText, AlertCircle } from "lucide-vue-next";
 import Button from "./ui/button.vue";
 import { defineAsyncComponent } from "vue";
 const StlViewer = defineAsyncComponent(() => import("@/components/StlViewer.vue"));
@@ -373,6 +379,7 @@ const agreeToNuances = ref(false);
 const materials = ref<any[]>([]);
 const selectedMaterial = ref("1");
 const selectedColor = ref("");
+const estimateMetadata = ref<any>(null);
 const selectedMaterialColors = computed(() => {
   const mat = materials.value.find(m => String(m.id) === selectedMaterial.value);
   return mat?.available_colors || [];
@@ -425,13 +432,14 @@ watch([() => files.value.length, selectedMaterial, modelLink], async () => {
         file_quantities: files.value.map(f => 1), // Only get base unit cost
       });
       // Assign individual prices to files for local quantity multiplication
-      if (res.file_prices && Array.isArray(res.file_prices)) {
-        files.value.forEach((f, i) => {
-          f.basePrice = res.file_prices[i];
-        });
-        // Force reactivity update
-        files.value = [...files.value];
-      }
+        if (res.file_prices && Array.isArray(res.file_prices)) {
+          files.value.forEach((f, i) => {
+            f.basePrice = res.file_prices[i];
+          });
+          estimateMetadata.value = res.metadata;
+          // Force reactivity update
+          files.value = [...files.value];
+        }
     } catch { /* silent */ }
   }
 });