dev #13

Merged
JoeKung merged 23 commits from dev into main 2026-03-03 18:28:30 +01:00
9 changed files with 70 additions and 23 deletions
Showing only changes of commit 04cbf00a2d - Show all commits

View File

@@ -3,6 +3,8 @@ name: Build, Test, Deploy and Analysis
on:
push:
branches: [main, int, dev]
pull_request:
branches: [main, int, dev]
workflow_dispatch:
concurrency:
@@ -12,6 +14,7 @@ concurrency:
jobs:
# --- JOB DI ANALISI (In parallelo) ---
qodana:
if: ${{ gitea.event_name == 'pull_request' }}
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -19,20 +22,19 @@ jobs:
with:
fetch-depth: 0 # Fondamentale per Qodana per analizzare la storia
- name: Prepare Qodana dirs
- name: Prepare Qodana directories
shell: bash
run: |
mkdir -p /tmp/qodana/caches /tmp/qodana/results
mkdir -p .qodana/cache .qodana/results
mkdir -p .qodana/caches .qodana/results
- name: 'Qodana Scan'
uses: JetBrains/qodana-action@v2025.3
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
with:
project-dir: backend
cache-dir: .qodana/cache
cache-dir: .qodana/caches
results-dir: .qodana/results
args: -i,backend
# In Gitea, pr-mode funziona se il runner ha accesso ai dati del clone
pr-mode: ${{ gitea.event_name == 'pull_request' }}
use-caches: false
@@ -59,7 +61,6 @@ jobs:
./gradlew test
build-and-push:
needs: test-backend
runs-on: ubuntu-latest
steps:
- name: Checkout

View File

@@ -5,8 +5,6 @@ echo "DB_URL: $DB_URL"
echo "DB_USERNAME: $DB_USERNAME"
echo "SPRING_DATASOURCE_URL: $SPRING_DATASOURCE_URL"
echo "SLICER_PATH: $SLICER_PATH"
echo "--- ALL ENV VARS ---"
env
echo "----------------------------------------------------------------"
# Determine which environment variables to use for database connection

View File

@@ -16,6 +16,7 @@ import com.printcalculator.repository.NozzleOptionRepository;
import com.printcalculator.repository.PrinterMachineRepository;
import com.printcalculator.service.OrcaProfileResolver;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@@ -54,13 +55,13 @@ public class OptionsController {
}
@GetMapping("/api/calculator/options")
@Transactional(readOnly = true)
public ResponseEntity<OptionsResponse> getOptions(
@RequestParam(value = "printerMachineId", required = false) Long printerMachineId,
@RequestParam(value = "nozzleDiameter", required = false) Double nozzleDiameter
) {
List<FilamentMaterialType> types = materialRepo.findAll();
List<FilamentVariant> allVariants = variantRepo.findAll().stream()
.filter(v -> Boolean.TRUE.equals(v.getIsActive()))
List<FilamentVariant> allVariants = variantRepo.findByIsActiveTrue().stream()
.sorted(Comparator
.comparing((FilamentVariant v) -> safeMaterialCode(v.getFilamentMaterialType()), String.CASE_INSENSITIVE_ORDER)
.thenComparing(v -> safeString(v.getVariantDisplayName()), String.CASE_INSENSITIVE_ORDER))

View File

@@ -2,11 +2,16 @@ package com.printcalculator.repository;
import com.printcalculator.entity.FilamentVariant;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.EntityGraph;
import com.printcalculator.entity.FilamentMaterialType;
import java.util.List;
import java.util.Optional;
public interface FilamentVariantRepository extends JpaRepository<FilamentVariant, Long> {
@EntityGraph(attributePaths = {"filamentMaterialType"})
List<FilamentVariant> findByIsActiveTrue();
// We try to match by color name if possible, or get first active
Optional<FilamentVariant> findByFilamentMaterialTypeAndColorName(FilamentMaterialType type, String colorName);
Optional<FilamentVariant> findByFilamentMaterialTypeAndVariantDisplayName(FilamentMaterialType type, String variantDisplayName);

View File

@@ -3,6 +3,7 @@ package com.printcalculator.repository;
import com.printcalculator.entity.FilamentMaterialType;
import com.printcalculator.entity.MaterialOrcaProfileMap;
import com.printcalculator.entity.PrinterMachineProfile;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
@@ -14,5 +15,6 @@ public interface MaterialOrcaProfileMapRepository extends JpaRepository<Material
FilamentMaterialType filamentMaterialType
);
@EntityGraph(attributePaths = {"filamentMaterialType"})
List<MaterialOrcaProfileMap> findByPrinterMachineProfileAndIsActiveTrue(PrinterMachineProfile printerMachineProfile);
}

View File

@@ -226,9 +226,10 @@ export class CalculatorPageComponent implements OnInit {
this.step.set('quote');
}
onItemChange(event: {id?: string, fileName: string, quantity: number}) {
onItemChange(event: {id?: string, index: number, fileName: string, quantity: number}) {
// 1. Update local form for consistency (UI feedback)
if (this.uploadForm) {
this.uploadForm.updateItemQuantityByIndex(event.index, event.quantity);
this.uploadForm.updateItemQuantityByName(event.fileName, event.quantity);
}

View File

@@ -22,7 +22,7 @@ export class QuoteResultComponent implements OnDestroy {
result = input.required<QuoteResult>();
consult = output<void>();
proceed = output<void>();
itemChange = output<{id?: string, fileName: string, quantity: number}>();
itemChange = output<{id?: string, index: number, fileName: string, quantity: number}>();
// Local mutable state for items to handle quantity changes
items = signal<QuoteItem[]>([]);
@@ -83,6 +83,7 @@ export class QuoteResultComponent implements OnDestroy {
this.itemChange.emit({
id: item.id,
index,
fileName: item.fileName,
quantity: normalizedQty
});

View File

@@ -199,11 +199,28 @@ export class UploadFormComponent implements OnInit {
}
}
updateItemQuantityByName(fileName: string, quantity: number) {
updateItemQuantityByIndex(index: number, quantity: number) {
if (!Number.isInteger(index) || index < 0) return;
const normalizedQty = this.normalizeQuantity(quantity);
this.items.update(current => {
if (index >= current.length) return current;
const updated = [...current];
updated[index] = { ...updated[index], quantity: normalizedQty };
return updated;
});
}
updateItemQuantityByName(fileName: string, quantity: number) {
const targetName = this.normalizeFileName(fileName);
const normalizedQty = this.normalizeQuantity(quantity);
this.items.update(current => {
let matched = false;
return current.map(item => {
if (item.file.name === fileName) {
return { ...item, quantity };
if (!matched && this.normalizeFileName(item.file.name) === targetName) {
matched = true;
return { ...item, quantity: normalizedQty };
}
return item;
});
@@ -239,14 +256,9 @@ export class UploadFormComponent implements OnInit {
updateItemQuantity(index: number, event: Event) {
const input = event.target as HTMLInputElement;
let val = parseInt(input.value, 10);
if (isNaN(val) || val < 1) val = 1;
this.items.update(current => {
const updated = [...current];
updated[index] = { ...updated[index], quantity: val };
return updated;
});
const parsed = parseInt(input.value, 10);
const quantity = Number.isFinite(parsed) ? parsed : 1;
this.updateItemQuantityByIndex(index, quantity);
}
updateItemColor(index: number, newSelection: string | { colorName: string; filamentVariantId?: number }) {
@@ -387,4 +399,19 @@ export class UploadFormComponent implements OnInit {
this.form.get('itemsTouched')?.setValue(true);
}
}
private normalizeQuantity(quantity: number): number {
if (!Number.isFinite(quantity) || quantity < 1) {
return 1;
}
return Math.floor(quantity);
}
private normalizeFileName(fileName: string): string {
return (fileName || '')
.split(/[\\/]/)
.pop()
?.trim()
.toLowerCase() ?? '';
}
}

View File

@@ -47,6 +47,10 @@
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 1000;
width: 230px; /* Increased size */
max-height: min(62vh, 360px);
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
// Little triangle arrow
&::before {
@@ -67,6 +71,7 @@
transform: translate(-50%, -50%);
width: 280px; /* Provide enough width for touch targets */
max-width: 90vw; /* Safety constraint */
max-height: min(72vh, 420px);
box-shadow: 0 10px 25px rgba(0,0,0,0.2); /* Stronger shadow for modal feel */
/* Hide arrow on mobile since it's detached from trigger */
@@ -76,6 +81,12 @@
}
}
@media (max-height: 720px) {
.color-popup {
max-height: min(56vh, 300px);
}
}
.category {
margin-bottom: 12px;