Refactor API endpoints and update invoice generation

- Updated API calls in equipment.php, equipment_manage.php, and equipments_mass_update.php to use v2 endpoints.
- Changed payload decoding from decode_payload to json_decode for consistency.
- Enhanced invoice generation in factuur.php and webhook files to use a new email template and PDF structure.
- Added new email and PDF templates for invoices to improve formatting and readability.
- Improved marketing folder handling in marketing.php with better payload management.
- Updated CSS for marketing to enhance UI interactions.
- Added JavaScript checks for browser compatibility in softwaretool.php.
- Adjusted user permissions in settingsprofiles.php to reflect new features.
This commit is contained in:
“VeLiTi”
2026-01-14 13:31:22 +01:00
parent a0e1d386ad
commit 7aebb762d3
19 changed files with 1141 additions and 631 deletions

View File

@@ -1771,12 +1771,12 @@ function warrantyStatus($input){
//INCLUDE TRANSLATION FILE
if(isset($_SESSION['country_code'])){
$api_file_language = dirname(__FILE__,2).'/settings/translations/translations_'.strtoupper($_SESSION['country_code']).'.php';
if (file_exists($api_file_language)){
include $api_file_language; //Include the code
}
else {
include dirname(__FILE__,2).'/settings/translations/translations_US.php';
}
if (file_exists($api_file_language)){
include $api_file_language; //Include the code
}
else {
include dirname(__FILE__,2).'/settings/translations/translations_US.php';
}
}
else {
include dirname(__FILE__,2).'/settings/translations/translations_US.php';
@@ -1787,7 +1787,7 @@ function warrantyStatus($input){
if (!empty($input) && $input < $warrantydate){
$warranty_date_due = '<span class="status warranty_outdated">'.$warranty_outdated_text.'</span>';
} else {
$warranty_date_due ='<span class="">'.$warranty_recent.' ('.date('Y-m-d', strtotime($input. ' + 365 days')).')</span>';
$warranty_date_due = '<span class="">'.$warranty_recent.' ('.date('Y-m-d', strtotime($input. ' + 365 days')).')</span>';
}
return $warranty_date_due;
@@ -5526,312 +5526,12 @@ function generateSoftwareInvoice($invoice_data, $order_id, $language = 'US') {
}
}
// Build HTML invoice
$html = '<!DOCTYPE html>
<html lang="' . strtolower($language) . '">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>' . htmlspecialchars($lbl_invoice) . ' - Total Safety Solutions</title>
<style>
@page {margin: 220px 50px; }
// Build HTML for PDF and EMAIL
include dirname(__FILE__,2).'/assets/mail/email_template_invoice.php';
include dirname(__FILE__,2).'/assets/mail/pdf_template_invoice.php';
body {
font-family: "DejaVu Sans", Arial, sans-serif;
color: #000;
font-size: 11px;
line-height: 1.4;
margin: 0;
padding: 0;
}
#header {
position: fixed;
left: -52px;
top: -220px;
right: -50px;
height: 200px;
text-align: center;
border-radius: 5px;
}
#header img {
width: 100%;
}
#footer {
position: fixed;
left: -50px;
bottom: -250px;
right: -50px;
height: 150px;
border-radius: 5px;
}
#footer img {
width: 100%;
}
.invoice-title {
font-size: 18px;
font-weight: bold;
color: #2c5f5d;
margin-bottom: 20px;
}
.company-header {
display: table;
width: 100%;
margin-bottom: 25px;
}
.company-info, .contact-details {
display: table-cell;
vertical-align: top;
width: 50%;
}
.company-info h3 {
font-weight: bold;
margin: 0 0 5px 0;
color: #2c5f5d;
font-size: 12px;
}
.company-info p, .contact-details p {
margin: 0;
line-height: 1.2;
}
.contact-details {
text-align: right;
}
.contact-details h3 {
font-weight: bold;
margin: 0 0 5px 0;
color: #2c5f5d;
font-size: 12px;
}
.invoice-details {
display: table;
width: 100%;
margin-bottom: 25px;
}
.invoice-left, .invoice-right {
display: table-cell;
vertical-align: top;
width: 50%;
}
.invoice-right {
text-align: left;
}
.detail-row {
margin-bottom: 2px;
display: table;
width: 100%;
}
.detail-label {
display: table-cell;
font-weight: normal;
width: 140px;
padding-right: 10px;
}
.detail-value {
display: table-cell;
}
.detail-value {
display: table-cell;
}
.items-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 30px;
border-bottom: 1px solid #999;
}
.items-table th {
padding: 8px 6px;
font-weight: bold;
text-align: left;
font-size: 10px;
}
.items-table td {
padding: 6px;
border-bottom: 1px solid #999;
font-size: 10px;
}
.items-table .qty-col {
text-align: center;
width: 70px;
}
.items-table .price-col, .items-table .total-col {
text-align: right;
width: 80px;
}
.items-table th.qty-col {
text-align: center;
}
.items-table th.price-col, .items-table th.total-col {
text-align: right;
}
.totals-section {
float: right;
width: 250px;
margin-top: 20px;
clear: both;
}
.total-row {
display: table;
width: 100%;
margin-bottom: 3px;
}
.total-label {
display: table-cell;
text-align: left;
width: 150px;
}
.total-amount {
display: table-cell;
text-align: right;
font-weight: normal;
}
.final-total {
border-top: 1px solid #000;
padding-top: 5px;
margin-top: 8px;
}
.final-total .total-amount {
font-weight: bold;
}
</style>
</head>
<body>
<div id="header">
<img src="https://'.$portalURL.'/assets/images/TSS_invoice_header.png" alt="Invoice header">
</div>
<div id="footer">
<img src="https://'.$portalURL.'/assets/images/TSS_invoice_footer.png" alt="Invoice footer">
</div>
<div class="invoice-title">' . htmlspecialchars($lbl_invoice) . '</div>
<div class="company-header">
<div class="company-info">
<h3>Total Safety Solutions B.V.</h3>
<p>Laarakkerweg 8</p>
<p>5061 JR OISTERWIJK</p>
<p>Nederland</p>
</div>
<div class="contact-details">
</div>
</div>
<div class="invoice-details">
<div class="invoice-left">
<div class="detail-row">
<div class="detail-label">Invoice Date</div>
<div class="detail-value">: ' . htmlspecialchars(date('d-m-Y', strtotime($invoice_date))) . '</div>
</div>
<div class="detail-row">
<div class="detail-label">Invoice Number</div>
<div class="detail-value">: ' . htmlspecialchars($order_id) . '</div>
</div>
</div>
<div class="invoice-right">
<div class="detail-row">
<div class="detail-label">Reference</div>
<div class="detail-value">: Online order</div>
</div>
<div class="detail-row">
<div class="detail-label">Order number</div>
<div class="detail-value">: ' . htmlspecialchars($order_id) . '</div>
</div>
</div>
</div>
<table class="items-table">
<thead>
<tr>
<th>Item code</th>
<th>Description</th>
<th class="qty-col">Quantity</th>
<th class="price-col">Price</th>
<th class="total-col">Total</th>
</tr>
</thead>
<tbody>';
foreach ($items as $item) {
$line_total = $item['price'] * $item['quantity'];
$html .= '<tr>
<td>SOFTWARE</td>
<td>' . htmlspecialchars($item['name']);
if ($item['serial_number'] !== 'N/A') {
$html .= '<br><small>Serial: ' . htmlspecialchars($item['serial_number']) . '</small>';
}
if ($item['license_key'] !== 'Pending') {
$html .= '<br><small>License: ' . htmlspecialchars($item['license_key']) . '</small>';
}
$html .= '</td>
<td class="qty-col">' . htmlspecialchars($item['quantity']) . ' </td>
<td class="price-col">€ ' . number_format($item['price'], 2) . '</td>
<td class="total-col">€ ' . number_format($line_total, 2) . '</td>
</tr>';
}
$html .= '</tbody>
</table>
<div class="totals-section">
<div class="total-row">
<div class="total-label">' . htmlspecialchars($lbl_subtotal) . '</div>
<div class="total-amount">€ ' . number_format($subtotal, 2) . '</div>
</div>';
if ($tax_amount > 0) {
$html .= '<div class="total-row">
<div class="total-label">' . htmlspecialchars($lbl_tax) . '</div>
<div class="total-amount">€ ' . number_format($tax_amount, 2) . '</div>
</div>';
} else {
$html .= '<div class="total-row">
<div class="total-label">VAT</div>
<div class="total-amount">included</div>
</div>';
}
$html .= '<div class="total-row final-total">
<div class="total-label">' . htmlspecialchars($lbl_total) . '</div>
<div class="total-amount">€ ' . number_format($payment_amount, 2) . '</div>
</div>
</div>
</body>
</html>';
return [$html, $customer_email, $order_id];
return [$message,$pdf,$customer_email, $order_id];
}
/**

View File

@@ -0,0 +1,138 @@
<?php
$message = '
<!DOCTYPE html>
<html lang="' . strtolower($language) . '">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>' . htmlspecialchars($lbl_invoice) . ' - Total Safety Solutions</title>
</head>
<body style="font-family: Arial, Helvetica, sans-serif; color: #000000; font-size: 14px; line-height: 1.6; margin: 0; padding: 0; background-color: #f4f4f4;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: #f4f4f4;">
<tr>
<td align="center" style="padding: 20px 0;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="600" style="background-color: #ffffff; max-width: 600px;">
<!-- Content -->
<tr>
<td style="padding: 30px 40px;">
<!-- Invoice Title -->
<h1 style="font-size: 24px; font-weight: bold; color: #2c5f5d; margin: 0 0 25px 0;">' . htmlspecialchars($lbl_invoice) . '</h1>
<!-- Company Header -->
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin-bottom: 25px;">
<tr>
<td width="50%" style="vertical-align: top; font-size: 13px; line-height: 1.5;">
<strong style="color: #2c5f5d; font-size: 14px;">Total Safety Solutions B.V.</strong><br>
Laarakkerweg 8<br>
5061 JR OISTERWIJK<br>
Nederland
</td>
<td width="50%" style="vertical-align: top; text-align: right;">
<!-- Contact details if needed -->
</td>
</tr>
</table>
<!-- Invoice Details -->
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin-bottom: 25px; font-size: 13px;">
<tr>
<td width="50%" style="vertical-align: top; padding-right: 10px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 3px 0;"><strong>Invoice Date:</strong></td>
<td style="padding: 3px 0;">' . htmlspecialchars(date('d-m-Y', strtotime($invoice_date))) . '</td>
</tr>
<tr>
<td style="padding: 3px 0;"><strong>Invoice Number:</strong></td>
<td style="padding: 3px 0;">' . htmlspecialchars($order_id) . '</td>
</tr>
</table>
</td>
<td width="50%" style="vertical-align: top; padding-left: 10px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 3px 0;"><strong>Reference:</strong></td>
<td style="padding: 3px 0;">Online order</td>
</tr>
<tr>
<td style="padding: 3px 0;"><strong>Order number:</strong></td>
<td style="padding: 3px 0;">' . htmlspecialchars($order_id) . '</td>
</tr>
</table>
</td>
</tr>
</table>
<!-- Items Table -->
<table role="presentation" cellspacing="0" cellpadding="8" border="0" width="100%" style="margin-bottom: 20px; border-bottom: 2px solid #999999;">
<thead>
<tr style="background-color: #f8f8f8;">
<th style="text-align: left; font-weight: bold; font-size: 12px; padding: 10px 8px; border-bottom: 1px solid #999999;">Item code</th>
<th style="text-align: left; font-weight: bold; font-size: 12px; padding: 10px 8px; border-bottom: 1px solid #999999;">Description</th>
<th style="text-align: center; font-weight: bold; font-size: 12px; padding: 10px 8px; border-bottom: 1px solid #999999;">Quantity</th>
<th style="text-align: right; font-weight: bold; font-size: 12px; padding: 10px 8px; border-bottom: 1px solid #999999;">Price</th>
<th style="text-align: right; font-weight: bold; font-size: 12px; padding: 10px 8px; border-bottom: 1px solid #999999;">Total</th>
</tr>
</thead>
<tbody>';
foreach ($items as $item) {
$line_total = $item['price'] * $item['quantity'];
$message .= '<tr>
<td style="padding: 10px 8px; border-bottom: 1px solid #dddddd; font-size: 13px;">SOFTWARE</td>
<td style="padding: 10px 8px; border-bottom: 1px solid #dddddd; font-size: 13px;">' . htmlspecialchars($item['name']);
if ($item['serial_number'] !== 'N/A') {
$message .= '<br><span style="font-size: 12px; color: #666666;">Serial: ' . htmlspecialchars($item['serial_number']) . '</span>';
}
if ($item['license_key'] !== 'Pending') {
$message .= '<br><span style="font-size: 12px; color: #666666;">License: ' . htmlspecialchars($item['license_key']) . '</span>';
}
$message .= '</td>
<td style="text-align: center; padding: 10px 8px; border-bottom: 1px solid #dddddd; font-size: 13px;">' . htmlspecialchars($item['quantity']) . '</td>
<td style="text-align: right; padding: 10px 8px; border-bottom: 1px solid #dddddd; font-size: 13px;">€ ' . number_format($item['price'], 2) . '</td>
<td style="text-align: right; padding: 10px 8px; border-bottom: 1px solid #dddddd; font-size: 13px;">€ ' . number_format($line_total, 2) . '</td>
</tr>';
}
$message .= '</tbody>
</table>
<!-- Totals -->
<table role="presentation" cellspacing="0" cellpadding="5" border="0" width="250" align="right" style="margin-top: 10px; font-size: 13px;">
<tr>
<td style="text-align: left; padding: 5px 0;">' . htmlspecialchars($lbl_subtotal) . '</td>
<td style="text-align: right; padding: 5px 0;">€ ' . number_format($subtotal, 2) . '</td>
</tr>';
if ($tax_amount > 0) {
$message .= '<tr>
<td style="text-align: left; padding: 5px 0;">' . htmlspecialchars($lbl_tax) . '</td>
<td style="text-align: right; padding: 5px 0;">€ ' . number_format($tax_amount, 2) . '</td>
</tr>';
} else {
$message .= '<tr>
<td style="text-align: left; padding: 5px 0;">VAT</td>
<td style="text-align: right; padding: 5px 0;">included</td>
</tr>';
}
$message .= '<tr style="border-top: 2px solid #000000;">
<td style="text-align: left; padding: 8px 0 5px 0;"><strong>' . htmlspecialchars($lbl_total) . '</strong></td>
<td style="text-align: right; padding: 8px 0 5px 0;"><strong>€ ' . number_format($payment_amount, 2) . '</strong></td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>';

View File

@@ -0,0 +1,306 @@
<?php
$pdf = '<!DOCTYPE html>
<html lang="' . strtolower($language) . '">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>' . htmlspecialchars($lbl_invoice) . ' - Total Safety Solutions</title>
<style>
@page {margin: 220px 50px; }
body {
font-family: "DejaVu Sans", Arial, sans-serif;
color: #000;
font-size: 11px;
line-height: 1.4;
margin: 10px auto;
padding: 0;
width:90%;
}
#header {
position: fixed;
left: -52px;
top: -220px;
right: -50px;
height: 200px;
text-align: center;
border-radius: 5px;
}
#header img {
width: 100%;
}
#footer {
position: fixed;
left: -50px;
bottom: -250px;
right: -50px;
height: 150px;
border-radius: 5px;
}
#footer img {
width: 100%;
}
.invoice-title {
font-size: 18px;
font-weight: bold;
color: #2c5f5d;
margin-bottom: 20px;
}
.company-header {
display: table;
width: 100%;
margin-bottom: 25px;
}
.company-info, .contact-details {
display: table-cell;
vertical-align: top;
width: 50%;
}
.company-info h3 {
font-weight: bold;
margin: 0 0 5px 0;
color: #2c5f5d;
font-size: 12px;
}
.company-info p, .contact-details p {
margin: 0;
line-height: 1.2;
}
.contact-details {
text-align: right;
}
.contact-details h3 {
font-weight: bold;
margin: 0 0 5px 0;
color: #2c5f5d;
font-size: 12px;
}
.invoice-details {
display: table;
width: 100%;
margin-bottom: 25px;
}
.invoice-left, .invoice-right {
display: table-cell;
vertical-align: top;
width: 50%;
}
.invoice-right {
text-align: left;
}
.detail-row {
margin-bottom: 2px;
display: table;
width: 100%;
}
.detail-label {
display: table-cell;
font-weight: normal;
width: 140px;
padding-right: 10px;
}
.detail-value {
display: table-cell;
}
.detail-value {
display: table-cell;
}
.items-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 30px;
border-bottom: 1px solid #999;
}
.items-table th {
padding: 8px 6px;
font-weight: bold;
text-align: left;
font-size: 10px;
}
.items-table td {
padding: 6px;
border-bottom: 1px solid #999;
font-size: 10px;
}
.items-table .qty-col {
text-align: center;
width: 70px;
}
.items-table .price-col, .items-table .total-col {
text-align: right;
width: 80px;
}
.items-table th.qty-col {
text-align: center;
}
.items-table th.price-col, .items-table th.total-col {
text-align: right;
}
.totals-section {
float: right;
width: 250px;
margin-top: 20px;
clear: both;
}
.total-row {
display: table;
width: 100%;
margin-bottom: 3px;
}
.total-label {
display: table-cell;
text-align: left;
width: 150px;
}
.total-amount {
display: table-cell;
text-align: right;
font-weight: normal;
}
.final-total {
border-top: 1px solid #000;
padding-top: 5px;
margin-top: 8px;
}
.final-total .total-amount {
font-weight: bold;
}
</style>
</head>
<body>
<div id="header">
<img src="https://'.$portalURL.'/assets/images/TSS_invoice_header.png" alt="Invoice header">
</div>
<div id="footer">
<img src="https://'.$portalURL.'/assets/images/TSS_invoice_footer.png" alt="Invoice footer">
</div>
<div class="invoice-title">' . htmlspecialchars($lbl_invoice) . '</div>
<div class="company-header">
<div class="company-info">
<h3>Total Safety Solutions B.V.</h3>
<p>Laarakkerweg 8</p>
<p>5061 JR OISTERWIJK</p>
<p>Nederland</p>
</div>
<div class="contact-details">
</div>
</div>
<div class="invoice-details">
<div class="invoice-left">
<div class="detail-row">
<div class="detail-label">Invoice Date</div>
<div class="detail-value">: ' . htmlspecialchars(date('d-m-Y', strtotime($invoice_date))) . '</div>
</div>
<div class="detail-row">
<div class="detail-label">Invoice Number</div>
<div class="detail-value">: ' . htmlspecialchars($order_id) . '</div>
</div>
</div>
<div class="invoice-right">
<div class="detail-row">
<div class="detail-label">Reference</div>
<div class="detail-value">: Online order</div>
</div>
<div class="detail-row">
<div class="detail-label">Order number</div>
<div class="detail-value">: ' . htmlspecialchars($order_id) . '</div>
</div>
</div>
</div>
<table class="items-table">
<thead>
<tr>
<th>Item code</th>
<th>Description</th>
<th class="qty-col">Quantity</th>
<th class="price-col">Price</th>
<th class="total-col">Total</th>
</tr>
</thead>
<tbody>';
foreach ($items as $item) {
$line_total = $item['price'] * $item['quantity'];
$pdf .= '<tr>
<td>SOFTWARE</td>
<td>' . htmlspecialchars($item['name']);
if ($item['serial_number'] !== 'N/A') {
$pdf .= '<br><small>Serial: ' . htmlspecialchars($item['serial_number']) . '</small>';
}
if ($item['license_key'] !== 'Pending') {
$pdf .= '<br><small>License: ' . htmlspecialchars($item['license_key']) . '</small>';
}
$pdf .= '</td>
<td class="qty-col">' . htmlspecialchars($item['quantity']) . ' </td>
<td class="price-col">€ ' . number_format($item['price'], 2) . '</td>
<td class="total-col">€ ' . number_format($line_total, 2) . '</td>
</tr>';
}
$pdf .= '</tbody>
</table>
<div class="totals-section">
<div class="total-row">
<div class="total-label">' . htmlspecialchars($lbl_subtotal) . '</div>
<div class="total-amount">€ ' . number_format($subtotal, 2) . '</div>
</div>';
if ($tax_amount > 0) {
$pdf .= '<div class="total-row">
<div class="total-label">' . htmlspecialchars($lbl_tax) . '</div>
<div class="total-amount">€ ' . number_format($tax_amount, 2) . '</div>
</div>';
} else {
$pdf .= '<div class="total-row">
<div class="total-label">VAT</div>
<div class="total-amount">included</div>
</div>';
}
$pdf .= '<div class="total-row final-total">
<div class="total-label">' . htmlspecialchars($lbl_total) . '</div>
<div class="total-amount">€ ' . number_format($payment_amount, 2) . '</div>
</div>
</div>
</body>
</html>';

View File

@@ -17,6 +17,13 @@ class MarketingFileManager {
this.folders = []; // Store folders data
this.loadRequestId = 0; // Track the latest load request
// Get permissions from PHP
this.permissions = window.marketingPermissions || {
canCreate: 0,
canUpdate: 0,
canDelete: 0
};
this.init();
}
@@ -105,6 +112,18 @@ class MarketingFileManager {
document.getElementById('saveEdit')?.addEventListener('click', () => {
this.saveEdit();
});
// Edit folder
document.getElementById('saveEditFolder')?.addEventListener('click', () => {
this.saveEditFolder();
});
// Delete folder
document.getElementById('deleteFolder')?.addEventListener('click', () => {
if (this.selectedFolder) {
this.deleteFolder(this.selectedFolder);
}
});
}
bindUploadEvents() {
@@ -317,15 +336,29 @@ class MarketingFileManager {
const hasChildren = folder.children && folder.children.length > 0;
const expandIcon = hasChildren ? '<i class="fa fa-chevron-right expand-icon"></i>' : '';
// Only show edit button if user has update permission
const editButton = this.permissions.canUpdate === 1
? `<button class="folder-edit-btn" title="Edit folder" data-folder-id="${folder.id}">
<i class="fa fa-edit"></i>
</button>`
: '';
folderItem.innerHTML = `
${expandIcon}
<i class="fa fa-folder"></i>
<span class="folder-name">${this.escapeHtml(folder.folder_name)}</span>
<span class="file-count">(${folder.file_count})</span>
${editButton}
`;
folderItem.addEventListener('click', () => {
this.selectFolder(folder.id);
folderItem.addEventListener('click', (e) => {
// Don't select folder if edit button was clicked
if (e.target.closest('.folder-edit-btn')) {
e.stopPropagation();
this.editFolder(folder);
} else {
this.selectFolder(folder.id);
}
});
container.appendChild(folderItem);
@@ -929,13 +962,20 @@ class MarketingFileManager {
const folderId = document.getElementById('editFolder').value;
const tags = document.getElementById('editTags').value.trim();
// Prepare update data
const updateData = {
file_id: this.selectedFile.id,
title: title || null,
folder_id: folderId || null,
tags: tags ? tags.split(',').map(tag => tag.trim()).filter(tag => tag) : []
};
// Compare with original values to detect changes
const originalTitle = this.selectedFile.title || '';
const originalFolderId = this.selectedFile.folder_id || '';
const originalTags = (this.selectedFile.tags || []).join(', ');
const hasChanges = title !== originalTitle ||
folderId !== originalFolderId ||
tags !== originalTags;
if (!hasChanges) {
this.showToast('No changes detected', 'info');
this.closeModal(document.getElementById('editModal'));
return;
}
// Show loading state
const saveBtn = document.getElementById('saveEdit');
@@ -943,22 +983,35 @@ class MarketingFileManager {
saveBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Saving...';
saveBtn.disabled = true;
// Prepare FormData with only changed fields
const formData = new FormData();
formData.append('file_id', this.selectedFile.id);
if (title !== originalTitle) {
formData.append('title', title);
}
if (folderId !== originalFolderId) {
formData.append('folder_id', folderId);
}
if (tags !== originalTags) {
formData.append('tags', tags);
}
// Send update request
fetch('./marketing.php?action=update_file', {
fetch('index.php?page=marketing&action=marketing_update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(updateData)
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
this.showToast('File updated successfully!', 'success');
this.closeModal(document.getElementById('editModal'));
this.loadFiles(); // Reload to show changes
// Reload window to reflect changes
setTimeout(() => {
window.location.reload();
}, 500);
} else {
throw new Error(data.message || 'Failed to update file');
throw new Error(data.error || data.message || 'Failed to update file');
}
})
.catch(error => {
@@ -994,6 +1047,193 @@ class MarketingFileManager {
this.renderUploadQueue();
document.getElementById('startUpload').disabled = this.uploadQueue.length === 0;
}
// Edit folder functionality
editFolder(folder) {
this.selectedFolder = folder;
this.showEditFolderModal();
this.populateEditFolderModal(folder);
}
showEditFolderModal() {
const modal = document.getElementById('editFolderModal');
if (modal) {
this.showModal(modal);
}
}
populateEditFolderModal(folder) {
// Populate folder name
const nameInput = document.getElementById('editFolderName');
if (nameInput) {
nameInput.value = folder.folder_name || '';
}
// Populate parent folder select
const parentSelect = document.getElementById('editParentFolder');
if (parentSelect) {
parentSelect.innerHTML = '<option value="">Root Folder</option>';
this.addFolderOptionsExcluding(parentSelect, this.folders, folder.id);
// Select current parent
if (folder.parent_id) {
parentSelect.value = folder.parent_id;
}
}
// Populate description
const descInput = document.getElementById('editFolderDescription');
if (descInput) {
descInput.value = folder.description || '';
}
}
addFolderOptionsExcluding(select, folders, excludeId, level = 0) {
// Add folders but exclude the current folder and its children
folders.forEach(folder => {
if (folder.id !== excludeId && !this.isFolderDescendant(folder, excludeId)) {
const option = document.createElement('option');
option.value = folder.id;
option.textContent = ' '.repeat(level) + folder.folder_name;
select.appendChild(option);
if (folder.children && folder.children.length > 0) {
this.addFolderOptionsExcluding(select, folder.children, excludeId, level + 1);
}
}
});
}
isFolderDescendant(folder, ancestorId) {
// Check if folder is a descendant of ancestorId
if (folder.id === ancestorId) return true;
if (folder.children) {
for (let child of folder.children) {
if (this.isFolderDescendant(child, ancestorId)) {
return true;
}
}
}
return false;
}
saveEditFolder() {
if (!this.selectedFolder) return;
const folderName = document.getElementById('editFolderName').value.trim();
const parentId = document.getElementById('editParentFolder').value;
const description = document.getElementById('editFolderDescription').value.trim();
// Compare with original values
const originalName = this.selectedFolder.folder_name || '';
const originalParentId = this.selectedFolder.parent_id || '';
const originalDescription = this.selectedFolder.description || '';
const hasChanges = folderName !== originalName ||
parentId !== originalParentId ||
description !== originalDescription;
if (!hasChanges) {
this.showToast('No changes detected', 'info');
this.closeModal(document.getElementById('editFolderModal'));
return;
}
if (!folderName) {
this.showToast('Folder name is required', 'error');
return;
}
// Show loading state
const saveBtn = document.getElementById('saveEditFolder');
const originalText = saveBtn.innerHTML;
saveBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Saving...';
saveBtn.disabled = true;
// Prepare FormData
const formData = new FormData();
formData.append('id', this.selectedFolder.id);
if (folderName !== originalName) {
formData.append('folder_name', folderName);
}
if (parentId !== originalParentId) {
formData.append('parent_id', parentId);
}
if (description !== originalDescription) {
formData.append('description', description);
}
// Send update request
fetch('index.php?page=marketing&action=marketing_folders', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
this.showToast('Folder updated successfully!', 'success');
// Reload window to reflect changes
setTimeout(() => {
window.location.reload();
}, 500);
} else {
throw new Error(data.error || data.message || 'Failed to update folder');
}
})
.catch(error => {
console.error('Update folder error:', error);
this.showToast('Error updating folder: ' + error.message, 'error');
})
.finally(() => {
// Restore button state
saveBtn.innerHTML = originalText;
saveBtn.disabled = false;
});
}
async deleteFolder(folder) {
if (!confirm(`Are you sure you want to delete the folder "${folder.folder_name}"? This action cannot be undone.`)) {
return;
}
try {
const formData = new FormData();
formData.append('id', folder.id);
formData.append('delete', 'true');
const response = await fetch('index.php?page=marketing&action=marketing_folders', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const text = await response.text();
if (!text || text.trim() === '') {
throw new Error('Empty response from server');
}
const data = JSON.parse(text);
if (data && (data.success || !data.error)) {
this.closeModal(document.getElementById('editFolderModal'));
this.showToast('Folder deleted successfully!', 'success');
// Reload window to reflect changes
setTimeout(() => {
window.location.reload();
}, 500);
} else if (data.error) {
throw new Error(data.error);
} else {
throw new Error('Unexpected response format');
}
} catch (error) {
this.showToast(error.message || 'Error deleting folder', 'error');
}
}
}
// Initialize when DOM is ready

View File

@@ -15,6 +15,71 @@ let reader;
let readableStreamClosed;
let keepReading = true;
// Browser compatibility check
let isSerialSupported = false;
// Check browser compatibility on page load
function checkBrowserCompatibility() {
isSerialSupported = 'serial' in navigator;
if (!isSerialSupported) {
// Show warning banner
showBrowserWarningBanner();
// Disable connect button
disableSerialFunctionality();
}
return isSerialSupported;
}
function showBrowserWarningBanner() {
const connectDevice = document.getElementById("connectdevice");
if (!connectDevice) return;
const warningBanner = document.createElement("div");
warningBanner.id = "browserWarningBanner";
warningBanner.style.cssText = `
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
color: white;
padding: 15px 20px;
border-radius: 8px;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 15px;
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.3);
`;
warningBanner.innerHTML = `
<i class="fa-solid fa-exclamation-triangle" style="font-size: 24px;"></i>
<div style="flex: 1;">
<strong style="display: block; margin-bottom: 5px; font-size: 16px;">Browser Not Supported</strong>
<p style="margin: 0; font-size: 14px; opacity: 0.95;">
Please use <strong>Chrome</strong>, <strong>Edge</strong>, or <strong>Opera</strong> to access device connectivity features.
</p>
</div>
`;
connectDevice.parentNode.insertBefore(warningBanner, connectDevice);
}
function disableSerialFunctionality() {
const connectButton = document.getElementById("connectButton");
if (connectButton) {
connectButton.disabled = true;
connectButton.style.opacity = "0.5";
connectButton.style.cursor = "not-allowed";
connectButton.title = "Browser is not supported. Please use Chrome, Edge, or Opera.";
}
}
// Call on page load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', checkBrowserCompatibility);
} else {
checkBrowserCompatibility();
}
// Function to log communication to API (reused from scripts.js)
async function logCommunication(data, direction) {
// Only log if debug mode is enabled
@@ -69,6 +134,13 @@ function progressBar(percentage, message, color){
// Connect device for software tool
async function connectDeviceForSoftware() {
// Browser compatibility check
if (!isSerialSupported) {
progressBar("100", "Browser not supported - Please use Chrome, Edge, or Opera", "#dc3545");
await logCommunication('Connection attempt failed: Web Serial API not supported in browser', 'error');
return;
}
//clear input
readBar.innerHTML = '';
serialResultsDiv.innerHTML = '';
@@ -162,11 +234,18 @@ async function connectDeviceForSoftware() {
} catch (error) {
await logCommunication(`Connection error: ${error.message}`, 'error');
// Check for specific "No port selected" error and show user-friendly message
if (error.message && error.message.includes('No port selected by the user')) {
progressBar("100", "No device selected, please try again", "#ff6666");
// Improved error messages with specific cases
if (error.name === 'NotSupportedError' || !navigator.serial) {
progressBar("100", "Browser not supported - Please use Chrome, Edge, or Opera", "#dc3545");
} else if (error.message && error.message.includes('No port selected by the user')) {
progressBar("100", "No device selected - Please try again", "#ff6666");
} else if (error.name === 'NetworkError') {
progressBar("100", "Connection failed - Please check device connection", "#ff6666");
} else if (error.name === 'InvalidStateError') {
progressBar("100", "Port already in use - Refreshing page...", "#ff9800");
setTimeout(() => location.reload(), 2000);
} else {
progressBar("100", "Error: " + error.message, "#ff6666");
progressBar("100", "Connection error: " + error.message, "#ff6666");
}
}
}