dev #13
@@ -3,6 +3,8 @@ name: Build, Test, Deploy and Analysis
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main, int, dev]
|
branches: [main, int, dev]
|
||||||
|
pull_request:
|
||||||
|
branches: [main, int, dev]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
@@ -12,6 +14,7 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
# --- JOB DI ANALISI (In parallelo) ---
|
# --- JOB DI ANALISI (In parallelo) ---
|
||||||
qodana:
|
qodana:
|
||||||
|
if: ${{ gitea.event_name == 'pull_request' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -19,20 +22,19 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Fondamentale per Qodana per analizzare la storia
|
fetch-depth: 0 # Fondamentale per Qodana per analizzare la storia
|
||||||
|
|
||||||
- name: Prepare Qodana dirs
|
- name: Prepare Qodana directories
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /tmp/qodana/caches /tmp/qodana/results
|
mkdir -p .qodana/caches .qodana/results
|
||||||
mkdir -p .qodana/cache .qodana/results
|
|
||||||
|
|
||||||
- name: 'Qodana Scan'
|
- name: 'Qodana Scan'
|
||||||
uses: JetBrains/qodana-action@v2025.3
|
uses: JetBrains/qodana-action@v2025.3
|
||||||
env:
|
env:
|
||||||
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
||||||
with:
|
with:
|
||||||
project-dir: backend
|
cache-dir: .qodana/caches
|
||||||
cache-dir: .qodana/cache
|
|
||||||
results-dir: .qodana/results
|
results-dir: .qodana/results
|
||||||
|
args: -i,backend
|
||||||
# In Gitea, pr-mode funziona se il runner ha accesso ai dati del clone
|
# In Gitea, pr-mode funziona se il runner ha accesso ai dati del clone
|
||||||
pr-mode: ${{ gitea.event_name == 'pull_request' }}
|
pr-mode: ${{ gitea.event_name == 'pull_request' }}
|
||||||
use-caches: false
|
use-caches: false
|
||||||
@@ -59,7 +61,6 @@ jobs:
|
|||||||
./gradlew test
|
./gradlew test
|
||||||
|
|
||||||
build-and-push:
|
build-and-push:
|
||||||
needs: test-backend
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ echo "DB_URL: $DB_URL"
|
|||||||
echo "DB_USERNAME: $DB_USERNAME"
|
echo "DB_USERNAME: $DB_USERNAME"
|
||||||
echo "SPRING_DATASOURCE_URL: $SPRING_DATASOURCE_URL"
|
echo "SPRING_DATASOURCE_URL: $SPRING_DATASOURCE_URL"
|
||||||
echo "SLICER_PATH: $SLICER_PATH"
|
echo "SLICER_PATH: $SLICER_PATH"
|
||||||
echo "--- ALL ENV VARS ---"
|
|
||||||
env
|
|
||||||
echo "----------------------------------------------------------------"
|
echo "----------------------------------------------------------------"
|
||||||
|
|
||||||
# Determine which environment variables to use for database connection
|
# 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.repository.PrinterMachineRepository;
|
||||||
import com.printcalculator.service.OrcaProfileResolver;
|
import com.printcalculator.service.OrcaProfileResolver;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
@@ -54,13 +55,13 @@ public class OptionsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/api/calculator/options")
|
@GetMapping("/api/calculator/options")
|
||||||
|
@Transactional(readOnly = true)
|
||||||
public ResponseEntity<OptionsResponse> getOptions(
|
public ResponseEntity<OptionsResponse> getOptions(
|
||||||
@RequestParam(value = "printerMachineId", required = false) Long printerMachineId,
|
@RequestParam(value = "printerMachineId", required = false) Long printerMachineId,
|
||||||
@RequestParam(value = "nozzleDiameter", required = false) Double nozzleDiameter
|
@RequestParam(value = "nozzleDiameter", required = false) Double nozzleDiameter
|
||||||
) {
|
) {
|
||||||
List<FilamentMaterialType> types = materialRepo.findAll();
|
List<FilamentMaterialType> types = materialRepo.findAll();
|
||||||
List<FilamentVariant> allVariants = variantRepo.findAll().stream()
|
List<FilamentVariant> allVariants = variantRepo.findByIsActiveTrue().stream()
|
||||||
.filter(v -> Boolean.TRUE.equals(v.getIsActive()))
|
|
||||||
.sorted(Comparator
|
.sorted(Comparator
|
||||||
.comparing((FilamentVariant v) -> safeMaterialCode(v.getFilamentMaterialType()), String.CASE_INSENSITIVE_ORDER)
|
.comparing((FilamentVariant v) -> safeMaterialCode(v.getFilamentMaterialType()), String.CASE_INSENSITIVE_ORDER)
|
||||||
.thenComparing(v -> safeString(v.getVariantDisplayName()), 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 com.printcalculator.entity.FilamentVariant;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.EntityGraph;
|
||||||
|
|
||||||
import com.printcalculator.entity.FilamentMaterialType;
|
import com.printcalculator.entity.FilamentMaterialType;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface FilamentVariantRepository extends JpaRepository<FilamentVariant, Long> {
|
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
|
// We try to match by color name if possible, or get first active
|
||||||
Optional<FilamentVariant> findByFilamentMaterialTypeAndColorName(FilamentMaterialType type, String colorName);
|
Optional<FilamentVariant> findByFilamentMaterialTypeAndColorName(FilamentMaterialType type, String colorName);
|
||||||
Optional<FilamentVariant> findByFilamentMaterialTypeAndVariantDisplayName(FilamentMaterialType type, String variantDisplayName);
|
Optional<FilamentVariant> findByFilamentMaterialTypeAndVariantDisplayName(FilamentMaterialType type, String variantDisplayName);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.printcalculator.repository;
|
|||||||
import com.printcalculator.entity.FilamentMaterialType;
|
import com.printcalculator.entity.FilamentMaterialType;
|
||||||
import com.printcalculator.entity.MaterialOrcaProfileMap;
|
import com.printcalculator.entity.MaterialOrcaProfileMap;
|
||||||
import com.printcalculator.entity.PrinterMachineProfile;
|
import com.printcalculator.entity.PrinterMachineProfile;
|
||||||
|
import org.springframework.data.jpa.repository.EntityGraph;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -14,5 +15,6 @@ public interface MaterialOrcaProfileMapRepository extends JpaRepository<Material
|
|||||||
FilamentMaterialType filamentMaterialType
|
FilamentMaterialType filamentMaterialType
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@EntityGraph(attributePaths = {"filamentMaterialType"})
|
||||||
List<MaterialOrcaProfileMap> findByPrinterMachineProfileAndIsActiveTrue(PrinterMachineProfile printerMachineProfile);
|
List<MaterialOrcaProfileMap> findByPrinterMachineProfileAndIsActiveTrue(PrinterMachineProfile printerMachineProfile);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,9 +226,10 @@ export class CalculatorPageComponent implements OnInit {
|
|||||||
this.step.set('quote');
|
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)
|
// 1. Update local form for consistency (UI feedback)
|
||||||
if (this.uploadForm) {
|
if (this.uploadForm) {
|
||||||
|
this.uploadForm.updateItemQuantityByIndex(event.index, event.quantity);
|
||||||
this.uploadForm.updateItemQuantityByName(event.fileName, event.quantity);
|
this.uploadForm.updateItemQuantityByName(event.fileName, event.quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export class QuoteResultComponent implements OnDestroy {
|
|||||||
result = input.required<QuoteResult>();
|
result = input.required<QuoteResult>();
|
||||||
consult = output<void>();
|
consult = output<void>();
|
||||||
proceed = 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
|
// Local mutable state for items to handle quantity changes
|
||||||
items = signal<QuoteItem[]>([]);
|
items = signal<QuoteItem[]>([]);
|
||||||
@@ -83,6 +83,7 @@ export class QuoteResultComponent implements OnDestroy {
|
|||||||
|
|
||||||
this.itemChange.emit({
|
this.itemChange.emit({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
|
index,
|
||||||
fileName: item.fileName,
|
fileName: item.fileName,
|
||||||
quantity: normalizedQty
|
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 => {
|
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 => {
|
return current.map(item => {
|
||||||
if (item.file.name === fileName) {
|
if (!matched && this.normalizeFileName(item.file.name) === targetName) {
|
||||||
return { ...item, quantity };
|
matched = true;
|
||||||
|
return { ...item, quantity: normalizedQty };
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
@@ -239,14 +256,9 @@ export class UploadFormComponent implements OnInit {
|
|||||||
|
|
||||||
updateItemQuantity(index: number, event: Event) {
|
updateItemQuantity(index: number, event: Event) {
|
||||||
const input = event.target as HTMLInputElement;
|
const input = event.target as HTMLInputElement;
|
||||||
let val = parseInt(input.value, 10);
|
const parsed = parseInt(input.value, 10);
|
||||||
if (isNaN(val) || val < 1) val = 1;
|
const quantity = Number.isFinite(parsed) ? parsed : 1;
|
||||||
|
this.updateItemQuantityByIndex(index, quantity);
|
||||||
this.items.update(current => {
|
|
||||||
const updated = [...current];
|
|
||||||
updated[index] = { ...updated[index], quantity: val };
|
|
||||||
return updated;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateItemColor(index: number, newSelection: string | { colorName: string; filamentVariantId?: number }) {
|
updateItemColor(index: number, newSelection: string | { colorName: string; filamentVariantId?: number }) {
|
||||||
@@ -387,4 +399,19 @@ export class UploadFormComponent implements OnInit {
|
|||||||
this.form.get('itemsTouched')?.setValue(true);
|
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);
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
width: 230px; /* Increased size */
|
width: 230px; /* Increased size */
|
||||||
|
max-height: min(62vh, 360px);
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
|
||||||
// Little triangle arrow
|
// Little triangle arrow
|
||||||
&::before {
|
&::before {
|
||||||
@@ -67,6 +71,7 @@
|
|||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
width: 280px; /* Provide enough width for touch targets */
|
width: 280px; /* Provide enough width for touch targets */
|
||||||
max-width: 90vw; /* Safety constraint */
|
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 */
|
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 */
|
/* 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 {
|
.category {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user