Merge branch 'main' into dev
All checks were successful
Build, Test, Deploy and Analysis / build-and-push (push) Successful in 18s
Build, Test, Deploy and Analysis / test-backend (push) Successful in 38s
Build, Test, Deploy and Analysis / deploy (push) Successful in 10s
Build, Test, Deploy and Analysis / qodana (push) Has been skipped
All checks were successful
Build, Test, Deploy and Analysis / build-and-push (push) Successful in 18s
Build, Test, Deploy and Analysis / test-backend (push) Successful in 38s
Build, Test, Deploy and Analysis / deploy (push) Successful in 10s
Build, Test, Deploy and Analysis / qodana (push) Has been skipped
# Conflicts: # .gitea/workflows/cicd.yaml
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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() ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user