Erweiterungen
This commit is contained in:
Binary file not shown.
@@ -1,4 +1,12 @@
|
||||
// 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() {
|
||||
var containerId = 'invoice-canvas-container-profile';
|
||||
var container = document.getElementById(containerId);
|
||||
@@ -8,12 +16,9 @@ window.initProfileInvoiceGenerator = function() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if canvas already exists
|
||||
var existingCanvas = container.querySelector('canvas');
|
||||
if (existingCanvas) {
|
||||
console.log('Canvas already exists');
|
||||
return;
|
||||
}
|
||||
// Always clear and recreate canvas to ensure clean state
|
||||
container.innerHTML = '';
|
||||
window.profileInvoiceState.initialized = false;
|
||||
|
||||
// Get container dimensions
|
||||
var rect = container.getBoundingClientRect();
|
||||
@@ -31,13 +36,13 @@ window.initProfileInvoiceGenerator = function() {
|
||||
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
// State
|
||||
var elements = [];
|
||||
var selectedElement = null;
|
||||
// Restore state from global or initialize
|
||||
var elements = window.profileInvoiceState.elements;
|
||||
var selectedElement = window.profileInvoiceState.selectedElement;
|
||||
var elementCounter = window.profileInvoiceState.elementCounter;
|
||||
var isDragging = false;
|
||||
var dragStart = { x: 0, y: 0 };
|
||||
var elementStart = { x: 0, y: 0 };
|
||||
var elementCounter = 0;
|
||||
var gridSize = 5;
|
||||
|
||||
// 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
|
||||
window.clearProfileCanvas = function() {
|
||||
elements = [];
|
||||
selectedElement = null;
|
||||
window.profileInvoiceState.elements = [];
|
||||
window.profileInvoiceState.selectedElement = null;
|
||||
notifyElementDeselected();
|
||||
draw();
|
||||
};
|
||||
|
||||
// Get canvas data
|
||||
window.getProfileCanvasData = function() {
|
||||
saveState();
|
||||
return {
|
||||
elements: elements
|
||||
};
|
||||
};
|
||||
|
||||
draw();
|
||||
console.log('Profile canvas initialized');
|
||||
window.profileInvoiceState.initialized = true;
|
||||
console.log('Profile canvas initialized with ' + elements.length + ' elements');
|
||||
|
||||
// Handle window resize
|
||||
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);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import de.assecutor.votianlt.pages.service.UserService;
|
||||
import de.assecutor.votianlt.pages.service.UserInvoiceDataService;
|
||||
import de.assecutor.votianlt.security.SecurityService;
|
||||
import de.assecutor.votianlt.service.CustomerInvoiceService;
|
||||
import de.assecutor.votianlt.service.InvoiceTemplateService;
|
||||
import com.vaadin.flow.component.dependency.JsModule;
|
||||
import com.vaadin.flow.component.ClientCallable;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
@@ -60,14 +61,17 @@ public class EditProfileView extends HorizontalLayout {
|
||||
private final User currentUser;
|
||||
private final UserInvoiceDataService userInvoiceDataService;
|
||||
private final CustomerInvoiceService customerInvoiceService;
|
||||
private final InvoiceTemplateService invoiceTemplateService;
|
||||
private UserInvoiceData currentInvoiceData;
|
||||
private Checkbox billingEnabled;
|
||||
private VerticalLayout propertiesPanelProfile;
|
||||
|
||||
public EditProfileView(UserService userService, UserInvoiceDataService userInvoiceDataService,
|
||||
CustomerInvoiceService customerInvoiceService, SecurityService securityService) {
|
||||
CustomerInvoiceService customerInvoiceService, InvoiceTemplateService invoiceTemplateService,
|
||||
SecurityService securityService) {
|
||||
this.userInvoiceDataService = userInvoiceDataService;
|
||||
this.customerInvoiceService = customerInvoiceService;
|
||||
this.invoiceTemplateService = invoiceTemplateService;
|
||||
this.currentUser = securityService.getCurrentDatabaseUser();
|
||||
setSizeFull();
|
||||
setPadding(true);
|
||||
@@ -378,16 +382,20 @@ public class EditProfileView extends HorizontalLayout {
|
||||
saveTemplateButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
saveTemplateButton.addClickListener(e -> {
|
||||
getElement().executeJs(
|
||||
"if (window.getProfileCanvasData) {" +
|
||||
" var data = JSON.stringify(window.getProfileCanvasData(), null, 2);" +
|
||||
" var blob = new Blob([data], { type: 'application/json' });" +
|
||||
" var url = URL.createObjectURL(blob);" +
|
||||
" var a = document.createElement('a');" +
|
||||
" a.href = url;" +
|
||||
" a.download = 'template.json';" +
|
||||
" a.click();" +
|
||||
" URL.revokeObjectURL(url);" +
|
||||
"}");
|
||||
"if (window.getProfileCanvasData) { return JSON.stringify(window.getProfileCanvasData()); } else { return null; }")
|
||||
.then(result -> {
|
||||
if (result == null) {
|
||||
Notification.show("Fehler: Canvas-Daten konnten nicht gelesen werden", 3000, Notification.Position.BOTTOM_CENTER);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String templateData = result.toString();
|
||||
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);
|
||||
@@ -407,6 +415,8 @@ public class EditProfileView extends HorizontalLayout {
|
||||
"setTimeout(function() { " +
|
||||
" if (window.initProfileInvoiceGenerator) { " +
|
||||
" window.initProfileInvoiceGenerator(); " +
|
||||
" console.log('Canvas initialized, now loading template...'); " +
|
||||
" $0.$server.onCanvasReady(); " +
|
||||
" } else { " +
|
||||
" 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user