feat(branding): v2.7.0 logo specifique optionnel pour UCP avec repli sur logo principal ou NexusPBX
This commit is contained in:
1 parent
ddbaabb657
commit
ba04af32bb
7 files changed
+130
-32
No files matched your search
+40
-17
@@ -1802,30 +1802,53 @@ try {
|
||||
case 'save_company_branding':
|
||||
$company_name = trim((string)($payload['company_name'] ?? ''));
|
||||
$company_logo = trim((string)($payload['company_logo'] ?? ''));
|
||||
$ucp_logo = trim((string)($payload['ucp_logo'] ?? ''));
|
||||
|
||||
if ($company_logo !== '' && !preg_match('/^data:image\/(png|jpeg|jpg|webp|svg\+xml|gif);base64,/i', $company_logo)) {
|
||||
throw new InvalidArgumentException("Format d'image invalide (utilisez PNG, SVG, JPG ou WebP).");
|
||||
}
|
||||
if (strlen($company_logo) > 2 * 1024 * 1024) {
|
||||
throw new InvalidArgumentException("Le logo est trop volumineux (max 1.5 Mo).");
|
||||
foreach (['company_logo' => $company_logo, 'ucp_logo' => $ucp_logo] as $fieldKey => $logoVal) {
|
||||
if (!array_key_exists($fieldKey, $payload)) {
|
||||
continue;
|
||||
}
|
||||
if ($logoVal !== '' && !preg_match('/^data:image\/(png|jpeg|jpg|webp|svg\+xml|gif);base64,/i', $logoVal)) {
|
||||
throw new InvalidArgumentException("Format d'image invalide (utilisez PNG, SVG, JPG ou WebP).");
|
||||
}
|
||||
if (strlen($logoVal) > 2 * 1024 * 1024) {
|
||||
throw new InvalidArgumentException("Le logo est trop volumineux (max 1.5 Mo).");
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('company_name', $payload) && array_key_exists('company_logo', $payload)) {
|
||||
$stmt = $db->prepare('UPDATE settings SET company_name = :cn, company_logo = :cl, updated_at = CURRENT_TIMESTAMP WHERE id = 1');
|
||||
$stmt->execute([':cn' => $company_name, ':cl' => $company_logo]);
|
||||
} elseif (array_key_exists('company_logo', $payload)) {
|
||||
$stmt = $db->prepare('UPDATE settings SET company_logo = :cl, updated_at = CURRENT_TIMESTAMP WHERE id = 1');
|
||||
$stmt->execute([':cl' => $company_logo]);
|
||||
} elseif (array_key_exists('company_name', $payload)) {
|
||||
$stmt = $db->prepare('UPDATE settings SET company_name = :cn, updated_at = CURRENT_TIMESTAMP WHERE id = 1');
|
||||
$stmt->execute([':cn' => $company_name]);
|
||||
$sets = [];
|
||||
$params = [];
|
||||
if (array_key_exists('company_name', $payload)) {
|
||||
$sets[] = 'company_name = :cn';
|
||||
$params[':cn'] = $company_name;
|
||||
}
|
||||
if (array_key_exists('company_logo', $payload)) {
|
||||
$sets[] = 'company_logo = :cl';
|
||||
$params[':cl'] = $company_logo;
|
||||
}
|
||||
if (array_key_exists('ucp_logo', $payload)) {
|
||||
$sets[] = 'ucp_logo = :ul';
|
||||
$params[':ul'] = $ucp_logo;
|
||||
}
|
||||
|
||||
if ($sets) {
|
||||
$sets[] = 'updated_at = CURRENT_TIMESTAMP';
|
||||
$stmt = $db->prepare('UPDATE settings SET ' . implode(', ', $sets) . ' WHERE id = 1');
|
||||
$stmt->execute($params);
|
||||
}
|
||||
|
||||
$msg = 'Logo et identité entreprise mis à jour sur NexusPBX !';
|
||||
if (array_key_exists('ucp_logo', $payload) && !array_key_exists('company_logo', $payload) && !array_key_exists('company_name', $payload)) {
|
||||
$msg = $ucp_logo === ''
|
||||
? 'Logo UCP réinitialisé (hérite du logo principal).'
|
||||
: 'Logo spécifique UCP mis à jour !';
|
||||
} elseif (array_key_exists('company_logo', $payload) && $company_logo === '' && !array_key_exists('company_name', $payload) && !array_key_exists('ucp_logo', $payload)) {
|
||||
$msg = 'Logo entreprise réinitialisé.';
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => $company_logo === '' && !array_key_exists('company_name', $payload)
|
||||
? 'Logo entreprise réinitialisé.'
|
||||
: 'Logo et identité entreprise mis à jour sur NexusPBX !',
|
||||
'message' => $msg,
|
||||
]);
|
||||
break;
|
||||
|
||||
|
||||
+40
-4
@@ -243,6 +243,7 @@ function renderAll() {
|
||||
function renderCompanyBranding() {
|
||||
const s = appState.settings || {};
|
||||
const logo = (s.company_logo || '').trim();
|
||||
const ucpLogo = (s.ucp_logo || '').trim();
|
||||
const name = (s.company_name || '').trim();
|
||||
|
||||
const emptyBox = document.getElementById('sidebar-company-empty');
|
||||
@@ -270,6 +271,21 @@ function renderCompanyBranding() {
|
||||
}
|
||||
}
|
||||
|
||||
const ucpPreviewImg = document.getElementById('settings-ucp-preview-img');
|
||||
const ucpPreviewEmpty = document.getElementById('settings-ucp-preview-empty');
|
||||
if (ucpPreviewImg && ucpPreviewEmpty) {
|
||||
if (ucpLogo) {
|
||||
ucpPreviewImg.src = ucpLogo;
|
||||
ucpPreviewImg.classList.remove('hidden');
|
||||
ucpPreviewEmpty.classList.add('hidden');
|
||||
} else {
|
||||
ucpPreviewImg.src = '';
|
||||
ucpPreviewImg.classList.add('hidden');
|
||||
ucpPreviewEmpty.textContent = logo ? 'Hérite du logo principal' : 'NexusPBX par défaut';
|
||||
ucpPreviewEmpty.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
if (logo || name) {
|
||||
emptyBox?.classList.add('hidden');
|
||||
filledBox?.classList.remove('hidden');
|
||||
@@ -321,7 +337,11 @@ function triggerCompanyLogoPicker() {
|
||||
document.getElementById('company-logo-file-input')?.click();
|
||||
}
|
||||
|
||||
function handleCompanyLogoUpload(input) {
|
||||
function triggerUcpLogoPicker() {
|
||||
document.getElementById('ucp-logo-file-input')?.click();
|
||||
}
|
||||
|
||||
function uploadBrandingLogo(input, fieldName) {
|
||||
const file = input?.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
@@ -335,7 +355,7 @@ function handleCompanyLogoUpload(input) {
|
||||
reader.onload = async (ev) => {
|
||||
const dataUrl = String(ev.target?.result || '');
|
||||
try {
|
||||
const res = await apiCall('save_company_branding', { company_logo: dataUrl });
|
||||
const res = await apiCall('save_company_branding', { [fieldName]: dataUrl });
|
||||
showToast(res.message, 'success');
|
||||
await loadState();
|
||||
} catch (err) {
|
||||
@@ -347,9 +367,17 @@ function handleCompanyLogoUpload(input) {
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
async function clearCompanyLogo() {
|
||||
function handleCompanyLogoUpload(input) {
|
||||
uploadBrandingLogo(input, 'company_logo');
|
||||
}
|
||||
|
||||
function handleUcpLogoUpload(input) {
|
||||
uploadBrandingLogo(input, 'ucp_logo');
|
||||
}
|
||||
|
||||
async function clearBrandingLogo(fieldName) {
|
||||
try {
|
||||
const res = await apiCall('save_company_branding', { company_logo: '' });
|
||||
const res = await apiCall('save_company_branding', { [fieldName]: '' });
|
||||
showToast(res.message, 'info');
|
||||
await loadState();
|
||||
} catch (err) {
|
||||
@@ -357,6 +385,14 @@ async function clearCompanyLogo() {
|
||||
}
|
||||
}
|
||||
|
||||
function clearCompanyLogo() {
|
||||
clearBrandingLogo('company_logo');
|
||||
}
|
||||
|
||||
function clearUcpLogo() {
|
||||
clearBrandingLogo('ucp_logo');
|
||||
}
|
||||
|
||||
function renderSummaryCards() {
|
||||
const exts = appState.extensions || [];
|
||||
const grps = appState.groups || [];
|
||||
|
||||
@@ -1648,6 +1648,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<form id="settings-form" class="space-y-4">
|
||||
<!-- Identité & Logo de l'Exploitant -->
|
||||
<input type="file" id="company-logo-file-input" accept="image/png,image/svg+xml,image/jpeg,image/webp,image/gif" class="hidden" onchange="handleCompanyLogoUpload(this)">
|
||||
<input type="file" id="ucp-logo-file-input" accept="image/png,image/svg+xml,image/jpeg,image/webp,image/gif" class="hidden" onchange="handleUcpLogoUpload(this)">
|
||||
<div class="p-4 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-3.5">
|
||||
<div class="flex items-center justify-between pb-2 border-b border-anthracite-700/60">
|
||||
<div>
|
||||
@@ -1675,6 +1676,28 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logo spécifique UCP (optionnel, fond clair) -->
|
||||
<div class="pt-3 border-t border-anthracite-800 flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<div id="settings-ucp-preview-slot" class="w-32 h-12 rounded-lg bg-white/95 border border-dashed border-slate-300 flex items-center justify-center p-1.5 shrink-0">
|
||||
<img id="settings-ucp-preview-img" src="" alt="Aperçu Logo UCP" class="max-h-9 max-w-full object-contain hidden">
|
||||
<span id="settings-ucp-preview-empty" class="text-[10px] text-slate-500 italic text-center leading-tight">Hérite du logo principal</span>
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<div class="text-xs font-semibold text-slate-200">Logo UCP (Portail Utilisateur — optionnel)</div>
|
||||
<p class="text-[11px] text-slate-400">Si vide, l'UCP réutilise le logo principal (ou NexusPBX). Utile pour importer un logo foncé adapté au thème clair UCP.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<button type="button" onclick="triggerUcpLogoPicker()" class="px-3 py-1.5 rounded-lg bg-slate-700 hover:bg-slate-600 text-xs text-white font-semibold transition">
|
||||
Importer Logo UCP
|
||||
</button>
|
||||
<button type="button" onclick="clearUcpLogo()" class="px-2.5 py-1.5 rounded-lg bg-anthracite-800 hover:bg-red-500/20 border border-anthracite-700 text-xs text-slate-300 hover:text-red-300 transition">
|
||||
Effacer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
|
||||
Reference in new issue
Block a user