Erweiterungen
This commit is contained in:
Binary file not shown.
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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