Erweiterungen

This commit is contained in:
2026-02-13 11:44:09 +01:00
parent 2da3d13431
commit 09bd168c74
6 changed files with 277 additions and 22 deletions

Binary file not shown.

View File

@@ -1,4 +1,12 @@
// Profile Invoice Generator - Initializes canvas on the edit-profile page // Profile Invoice Generator - Initializes canvas on the edit-profile page
// Global state to persist between tab switches
window.profileInvoiceState = window.profileInvoiceState || {
elements: [],
selectedElement: null,
elementCounter: 0,
initialized: false
};
window.initProfileInvoiceGenerator = function() { window.initProfileInvoiceGenerator = function() {
var containerId = 'invoice-canvas-container-profile'; var containerId = 'invoice-canvas-container-profile';
var container = document.getElementById(containerId); var container = document.getElementById(containerId);
@@ -8,12 +16,9 @@ window.initProfileInvoiceGenerator = function() {
return; return;
} }
// Check if canvas already exists // Always clear and recreate canvas to ensure clean state
var existingCanvas = container.querySelector('canvas'); container.innerHTML = '';
if (existingCanvas) { window.profileInvoiceState.initialized = false;
console.log('Canvas already exists');
return;
}
// Get container dimensions // Get container dimensions
var rect = container.getBoundingClientRect(); var rect = container.getBoundingClientRect();
@@ -31,13 +36,13 @@ window.initProfileInvoiceGenerator = function() {
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext('2d');
// State // Restore state from global or initialize
var elements = []; var elements = window.profileInvoiceState.elements;
var selectedElement = null; var selectedElement = window.profileInvoiceState.selectedElement;
var elementCounter = window.profileInvoiceState.elementCounter;
var isDragging = false; var isDragging = false;
var dragStart = { x: 0, y: 0 }; var dragStart = { x: 0, y: 0 };
var elementStart = { x: 0, y: 0 }; var elementStart = { x: 0, y: 0 };
var elementCounter = 0;
var gridSize = 5; var gridSize = 5;
// Page dimensions // Page dimensions
@@ -474,23 +479,34 @@ window.initProfileInvoiceGenerator = function() {
} }
}; };
// Save state to global before functions are defined
function saveState() {
window.profileInvoiceState.elements = elements;
window.profileInvoiceState.selectedElement = selectedElement;
window.profileInvoiceState.elementCounter = elementCounter;
}
// Clear canvas function // Clear canvas function
window.clearProfileCanvas = function() { window.clearProfileCanvas = function() {
elements = []; elements = [];
selectedElement = null; selectedElement = null;
window.profileInvoiceState.elements = [];
window.profileInvoiceState.selectedElement = null;
notifyElementDeselected(); notifyElementDeselected();
draw(); draw();
}; };
// Get canvas data // Get canvas data
window.getProfileCanvasData = function() { window.getProfileCanvasData = function() {
saveState();
return { return {
elements: elements elements: elements
}; };
}; };
draw(); draw();
console.log('Profile canvas initialized'); window.profileInvoiceState.initialized = true;
console.log('Profile canvas initialized with ' + elements.length + ' elements');
// Handle window resize // Handle window resize
window.addEventListener('resize', function() { window.addEventListener('resize', function() {
@@ -531,4 +547,45 @@ window.initProfileInvoiceGenerator = function() {
} }
}); });
} }
// Load template function
window.loadProfileTemplate = function(templateData) {
try {
var data = JSON.parse(templateData);
if (data.elements && Array.isArray(data.elements)) {
// Clear existing elements
elements = [];
selectedElement = null;
// Load new elements
data.elements.forEach(function(el) {
// Ensure element has an ID
if (!el.id) {
elementCounter++;
el.id = 'element-' + elementCounter;
}
elements.push(el);
});
// Update counter to be higher than any loaded element
elements.forEach(function(el) {
if (el.id && el.id.startsWith('element-')) {
var num = parseInt(el.id.replace('element-', ''));
if (!isNaN(num) && num > elementCounter) {
elementCounter = num;
}
}
});
// Save to global state
saveState();
draw();
notifyElementDeselected();
console.log('Template loaded with ' + elements.length + ' elements');
}
} catch (e) {
console.error('Error loading template:', e);
}
};
}; };

View File

@@ -0,0 +1,66 @@
package de.assecutor.votianlt.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.time.LocalDateTime;
/**
* Stores invoice template data for a user.
* Contains the JSON representation of the canvas elements.
*/
@Document(collection = "invoice_templates")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class InvoiceTemplate {
@Id
private String id;
/**
* The user ID this template belongs to
*/
private String userId;
/**
* Template name (optional, for future use if multiple templates are supported)
*/
private String name;
/**
* JSON string containing the template data (canvas elements)
*/
private String templateData;
/**
* When the template was created
*/
private LocalDateTime createdAt;
/**
* When the template was last updated
*/
private LocalDateTime updatedAt;
/**
* Version for optimistic locking
*/
private Long version;
public InvoiceTemplate(String userId, String name, String templateData) {
this.userId = userId;
this.name = name;
this.templateData = templateData;
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
public void updateTemplate(String templateData) {
this.templateData = templateData;
this.updatedAt = LocalDateTime.now();
}
}

View File

@@ -37,6 +37,7 @@ import de.assecutor.votianlt.pages.service.UserService;
import de.assecutor.votianlt.pages.service.UserInvoiceDataService; import de.assecutor.votianlt.pages.service.UserInvoiceDataService;
import de.assecutor.votianlt.security.SecurityService; import de.assecutor.votianlt.security.SecurityService;
import de.assecutor.votianlt.service.CustomerInvoiceService; import de.assecutor.votianlt.service.CustomerInvoiceService;
import de.assecutor.votianlt.service.InvoiceTemplateService;
import com.vaadin.flow.component.dependency.JsModule; import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.ClientCallable; import com.vaadin.flow.component.ClientCallable;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
@@ -60,14 +61,17 @@ public class EditProfileView extends HorizontalLayout {
private final User currentUser; private final User currentUser;
private final UserInvoiceDataService userInvoiceDataService; private final UserInvoiceDataService userInvoiceDataService;
private final CustomerInvoiceService customerInvoiceService; private final CustomerInvoiceService customerInvoiceService;
private final InvoiceTemplateService invoiceTemplateService;
private UserInvoiceData currentInvoiceData; private UserInvoiceData currentInvoiceData;
private Checkbox billingEnabled; private Checkbox billingEnabled;
private VerticalLayout propertiesPanelProfile; private VerticalLayout propertiesPanelProfile;
public EditProfileView(UserService userService, UserInvoiceDataService userInvoiceDataService, public EditProfileView(UserService userService, UserInvoiceDataService userInvoiceDataService,
CustomerInvoiceService customerInvoiceService, SecurityService securityService) { CustomerInvoiceService customerInvoiceService, InvoiceTemplateService invoiceTemplateService,
SecurityService securityService) {
this.userInvoiceDataService = userInvoiceDataService; this.userInvoiceDataService = userInvoiceDataService;
this.customerInvoiceService = customerInvoiceService; this.customerInvoiceService = customerInvoiceService;
this.invoiceTemplateService = invoiceTemplateService;
this.currentUser = securityService.getCurrentDatabaseUser(); this.currentUser = securityService.getCurrentDatabaseUser();
setSizeFull(); setSizeFull();
setPadding(true); setPadding(true);
@@ -378,16 +382,20 @@ public class EditProfileView extends HorizontalLayout {
saveTemplateButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); saveTemplateButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
saveTemplateButton.addClickListener(e -> { saveTemplateButton.addClickListener(e -> {
getElement().executeJs( getElement().executeJs(
"if (window.getProfileCanvasData) {" + "if (window.getProfileCanvasData) { return JSON.stringify(window.getProfileCanvasData()); } else { return null; }")
" var data = JSON.stringify(window.getProfileCanvasData(), null, 2);" + .then(result -> {
" var blob = new Blob([data], { type: 'application/json' });" + if (result == null) {
" var url = URL.createObjectURL(blob);" + Notification.show("Fehler: Canvas-Daten konnten nicht gelesen werden", 3000, Notification.Position.BOTTOM_CENTER);
" var a = document.createElement('a');" + return;
" a.href = url;" + }
" a.download = 'template.json';" + try {
" a.click();" + String templateData = result.toString();
" URL.revokeObjectURL(url);" + invoiceTemplateService.saveTemplate(currentUser.getId().toString(), templateData);
"}"); Notification.show("Template erfolgreich gespeichert", 3000, Notification.Position.BOTTOM_CENTER);
} catch (Exception ex) {
Notification.show("Fehler beim Speichern: " + ex.getMessage(), 5000, Notification.Position.BOTTOM_CENTER);
}
});
}); });
actionLayout.add(clearButton, previewPdfButton, saveTemplateButton); actionLayout.add(clearButton, previewPdfButton, saveTemplateButton);
@@ -407,6 +415,8 @@ public class EditProfileView extends HorizontalLayout {
"setTimeout(function() { " + "setTimeout(function() { " +
" if (window.initProfileInvoiceGenerator) { " + " if (window.initProfileInvoiceGenerator) { " +
" window.initProfileInvoiceGenerator(); " + " window.initProfileInvoiceGenerator(); " +
" console.log('Canvas initialized, now loading template...'); " +
" $0.$server.onCanvasReady(); " +
" } else { " + " } else { " +
" console.error('initProfileInvoiceGenerator not found'); " + " console.error('initProfileInvoiceGenerator not found'); " +
" } " + " } " +
@@ -1084,4 +1094,48 @@ public class EditProfileView extends HorizontalLayout {
})); }));
} }
/**
* Called by JavaScript when the canvas is ready
*/
@ClientCallable
public void onCanvasReady() {
System.out.println("Canvas ready, loading template...");
loadInvoiceTemplate();
}
/**
* Load the saved invoice template from database and display it on the canvas
*/
private void loadInvoiceTemplate() {
try {
java.util.Optional<de.assecutor.votianlt.model.InvoiceTemplate> optionalTemplate =
invoiceTemplateService.getTemplateByUserId(currentUser.getId().toString());
if (optionalTemplate.isPresent()) {
String templateData = optionalTemplate.get().getTemplateData();
if (templateData != null && !templateData.isEmpty()) {
// Escape single quotes and newlines for JavaScript
String escapedData = templateData
.replace("\\", "\\\\")
.replace("'", "\\'")
.replace("\n", "\\n")
.replace("\r", "");
getElement().executeJs(
"setTimeout(function() { " +
" if (window.loadProfileTemplate && document.getElementById('invoice-canvas-container-profile')) { " +
" console.log('Loading template into canvas...'); " +
" window.loadProfileTemplate('" + escapedData + "'); " +
" } else { " +
" console.error('loadProfileTemplate or canvas not available'); " +
" } " +
"}, 100);");
}
} else {
System.out.println("No template found for user: " + currentUser.getId());
}
} catch (Exception ex) {
System.err.println("Fehler beim Laden des Templates: " + ex.getMessage());
ex.printStackTrace();
}
}
} }

View File

@@ -0,0 +1,26 @@
package de.assecutor.votianlt.repository;
import de.assecutor.votianlt.model.InvoiceTemplate;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface InvoiceTemplateRepository extends MongoRepository<InvoiceTemplate, String> {
/**
* Find the invoice template for a specific user
*/
Optional<InvoiceTemplate> findByUserId(String userId);
/**
* Check if a template exists for a user
*/
boolean existsByUserId(String userId);
/**
* Delete the template for a user
*/
void deleteByUserId(String userId);
}

View File

@@ -0,0 +1,52 @@
package de.assecutor.votianlt.service;
import de.assecutor.votianlt.model.InvoiceTemplate;
import de.assecutor.votianlt.repository.InvoiceTemplateRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class InvoiceTemplateService {
private final InvoiceTemplateRepository invoiceTemplateRepository;
/**
* Save or update the invoice template for a user
*/
public InvoiceTemplate saveTemplate(String userId, String templateData) {
Optional<InvoiceTemplate> existing = invoiceTemplateRepository.findByUserId(userId);
if (existing.isPresent()) {
InvoiceTemplate template = existing.get();
template.updateTemplate(templateData);
return invoiceTemplateRepository.save(template);
} else {
InvoiceTemplate newTemplate = new InvoiceTemplate(userId, "Standard", templateData);
return invoiceTemplateRepository.save(newTemplate);
}
}
/**
* Get the invoice template for a user
*/
public Optional<InvoiceTemplate> getTemplateByUserId(String userId) {
return invoiceTemplateRepository.findByUserId(userId);
}
/**
* Check if a template exists for a user
*/
public boolean hasTemplate(String userId) {
return invoiceTemplateRepository.existsByUserId(userId);
}
/**
* Delete the template for a user
*/
public void deleteTemplate(String userId) {
invoiceTemplateRepository.deleteByUserId(userId);
}
}