feat(widgets): allow users to customize widget titles from settings gear dropdown
This commit is contained in:
+20
-1
@@ -42,8 +42,27 @@ try {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Valideer en sanitize layout items inclusief optionele custom widget titel
|
||||
$cleanedLayout = [];
|
||||
foreach ($layout as $item) {
|
||||
if (!is_array($item) || empty($item['id'])) continue;
|
||||
$cleaned = [
|
||||
'id' => substr(trim((string)$item['id']), 0, 64),
|
||||
'enabled' => !empty($item['enabled']),
|
||||
'col_span' => in_array((int)($item['col_span'] ?? 6), [4, 5, 6, 7, 8, 12]) ? (int)$item['col_span'] : 6,
|
||||
'order' => (int)($item['order'] ?? 1),
|
||||
];
|
||||
if (!empty($item['custom_title'])) {
|
||||
$customTitle = trim(strip_tags((string)$item['custom_title']));
|
||||
if ($customTitle !== '') {
|
||||
$cleaned['custom_title'] = substr($customTitle, 0, 50);
|
||||
}
|
||||
}
|
||||
$cleanedLayout[] = $cleaned;
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("UPDATE users SET fociz_widget_layout = ? WHERE id = ?");
|
||||
$stmt->execute([json_encode($layout), $user['id']]);
|
||||
$stmt->execute([json_encode($cleanedLayout, JSON_UNESCAPED_UNICODE), $user['id']]);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Widget indeling succesvol opgeslagen']);
|
||||
exit;
|
||||
|
||||
+131
-14
@@ -289,18 +289,34 @@ $savedLayoutJson = $currentUser['fociz_widget_layout'] ?? '';
|
||||
if (!empty($savedLayoutJson)) {
|
||||
$decoded = json_decode($savedLayoutJson, true);
|
||||
if (is_array($decoded)) {
|
||||
$savedOrder = 1;
|
||||
$seen = [];
|
||||
foreach ($decoded as $item) {
|
||||
$wId = $item['id'] ?? '';
|
||||
if (isset($userWidgetLayout[$wId])) {
|
||||
$userWidgetLayout[$wId]['enabled'] = !empty($item['enabled']);
|
||||
if (!empty($item['col_span'])) $userWidgetLayout[$wId]['col_span'] = (int)$item['col_span'];
|
||||
if (isset($item['order'])) $userWidgetLayout[$wId]['order'] = (int)$item['order'];
|
||||
$userWidgetLayout[$wId]['order'] = isset($item['order']) ? (int)$item['order'] : $savedOrder;
|
||||
if (!empty($item['custom_title'])) {
|
||||
$userWidgetLayout[$wId]['custom_title'] = trim($item['custom_title']);
|
||||
$userWidgetLayout[$wId]['title'] = trim($item['custom_title']);
|
||||
}
|
||||
$seen[$wId] = true;
|
||||
$savedOrder++;
|
||||
}
|
||||
}
|
||||
foreach ($userWidgetLayout as $wId => &$wData) {
|
||||
if (!isset($seen[$wId])) {
|
||||
$wData['order'] = $savedOrder++;
|
||||
}
|
||||
}
|
||||
unset($wData);
|
||||
}
|
||||
}
|
||||
uasort($userWidgetLayout, function($a, $b) {
|
||||
return ($a['order'] ?? 0) <=> ($b['order'] ?? 0);
|
||||
$diff = ($a['order'] ?? 0) <=> ($b['order'] ?? 0);
|
||||
if ($diff !== 0) return $diff;
|
||||
return strcmp($a['id'] ?? '', $b['id'] ?? '');
|
||||
});
|
||||
|
||||
// Bereken dagstatistieken
|
||||
@@ -545,19 +561,45 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
||||
<!-- Links: Drag Handle Grip + Widget Titel -->
|
||||
<div class="widget-drag-handle flex items-center space-x-1.5 cursor-grab active:cursor-grabbing text-slate-400 hover:text-indigo-600 dark:hover:text-indigo-400 py-0.5 px-1 -ml-1 rounded-lg hover:bg-slate-200/50 dark:hover:bg-slate-800/50 transition group/drag touch-none" data-widget-id="<?= htmlspecialchars($wId) ?>" title="Sleep om te verplaatsen">
|
||||
<i data-lucide="grip-vertical" class="w-3.5 h-3.5 text-slate-400 group-hover/drag:text-indigo-500 transition-colors flex-shrink-0"></i>
|
||||
<span class="text-xs font-bold text-slate-700 dark:text-slate-300 tracking-wide truncate max-w-[160px] sm:max-w-[240px]"><?= htmlspecialchars($widget['title']) ?></span>
|
||||
<span id="widgetTitleText_<?= htmlspecialchars($wId) ?>" class="text-xs font-bold text-slate-700 dark:text-slate-300 tracking-wide truncate max-w-[160px] sm:max-w-[240px]"><?= htmlspecialchars($widget['title']) ?></span>
|
||||
</div>
|
||||
|
||||
<!-- Rechts: Tandwieltje (Formaat & Verbergen) & Inklappen -->
|
||||
<!-- Rechts: Tandwieltje (Formaat, Naam & Verbergen) & Inklappen -->
|
||||
<div class="flex items-center space-x-1 relative">
|
||||
<!-- Tandwieltje Knop met Dropdown -->
|
||||
<div class="relative">
|
||||
<button type="button" onclick="toggleWidgetSettingsMenu('<?= $wId ?>', event)" class="widget-settings-btn p-1 rounded-lg text-slate-400 hover:text-slate-700 dark:hover:text-slate-200 hover:bg-slate-200/60 dark:hover:bg-slate-800/60 transition cursor-pointer" title="Widget instellingen (formaat en verbergen)">
|
||||
<button type="button" onclick="toggleWidgetSettingsMenu('<?= $wId ?>', event)" class="widget-settings-btn p-1 rounded-lg text-slate-400 hover:text-slate-700 dark:hover:text-slate-200 hover:bg-slate-200/60 dark:hover:bg-slate-800/60 transition cursor-pointer" title="Widget instellingen (naam, formaat en verbergen)">
|
||||
<i data-lucide="settings" class="w-3.5 h-3.5"></i>
|
||||
</button>
|
||||
|
||||
<!-- Dropdown Popover Menu -->
|
||||
<div id="widgetSettingsMenu_<?= htmlspecialchars($wId) ?>" class="widget-settings-dropdown hidden absolute right-0 top-full mt-1.5 z-50 w-52 p-2.5 rounded-2xl bg-white/95 dark:bg-[#111624]/95 backdrop-blur-md border border-slate-200 dark:border-slate-800 shadow-2xl space-y-2.5">
|
||||
<div id="widgetSettingsMenu_<?= htmlspecialchars($wId) ?>" class="widget-settings-dropdown hidden absolute right-0 top-full mt-1.5 z-50 w-64 p-3 rounded-2xl bg-white/95 dark:bg-[#111624]/95 backdrop-blur-md border border-slate-200 dark:border-slate-800 shadow-2xl space-y-3">
|
||||
<!-- 1. Naam / Titel Aanpassen -->
|
||||
<div class="space-y-1.5 pb-2.5 border-b border-slate-100 dark:border-slate-800/80">
|
||||
<div class="flex items-center justify-between">
|
||||
<label for="widgetTitleInput_<?= htmlspecialchars($wId) ?>" class="text-[10px] font-bold uppercase tracking-wider text-slate-400">Naam aanpassen</label>
|
||||
<button type="button" onclick="resetWidgetTitle('<?= $wId ?>', '<?= htmlspecialchars(addslashes($availableWidgets[$wId]['title'] ?? $widget['title']), ENT_QUOTES) ?>')" class="text-[10px] text-indigo-500 hover:text-indigo-600 dark:hover:text-indigo-400 font-medium cursor-pointer" title="Standaardnaam herstellen">Herstellen</button>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1.5">
|
||||
<input type="text"
|
||||
id="widgetTitleInput_<?= htmlspecialchars($wId) ?>"
|
||||
value="<?= htmlspecialchars($widget['title']) ?>"
|
||||
data-default-title="<?= htmlspecialchars($availableWidgets[$wId]['title'] ?? $widget['title']) ?>"
|
||||
placeholder="<?= htmlspecialchars($availableWidgets[$wId]['title'] ?? 'Widget titel') ?>"
|
||||
maxlength="50"
|
||||
onclick="event.stopPropagation();"
|
||||
onkeydown="if(event.key==='Enter'){event.preventDefault();saveWidgetTitle('<?= $wId ?>');}"
|
||||
class="flex-1 min-w-0 px-2.5 py-1.5 text-xs rounded-xl bg-slate-50 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-700 text-slate-800 dark:text-slate-100 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition">
|
||||
<button type="button"
|
||||
onclick="saveWidgetTitle('<?= $wId ?>')"
|
||||
class="px-2.5 py-1.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-bold text-xs transition flex-shrink-0 cursor-pointer shadow-2xs flex items-center justify-center"
|
||||
title="Naam opslaan">
|
||||
<i data-lucide="check" class="w-3.5 h-3.5"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. Breedte instellen -->
|
||||
<div>
|
||||
<span class="text-[10px] font-bold uppercase tracking-wider text-slate-400 block mb-1.5">Breedte instellen</span>
|
||||
<div class="grid grid-cols-4 gap-1">
|
||||
@@ -568,6 +610,7 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. Widget verbergen -->
|
||||
<div class="pt-1.5 border-t border-slate-100 dark:border-slate-800/80">
|
||||
<button type="button" onclick="hideWidgetOnScreen('<?= $wId ?>')" class="w-full flex items-center space-x-2 px-2 py-1.5 rounded-xl text-slate-600 dark:text-slate-400 hover:text-rose-600 dark:hover:text-rose-400 hover:bg-rose-50 dark:hover:bg-rose-500/10 transition cursor-pointer text-left text-xs font-semibold">
|
||||
<i data-lucide="eye-off" class="w-3.5 h-3.5 flex-shrink-0"></i>
|
||||
@@ -2250,12 +2293,18 @@ async function saveWidgetLayoutConfig() {
|
||||
focizWidgets.forEach((item, idx) => {
|
||||
item.order = idx + 1;
|
||||
});
|
||||
const payload = focizWidgets.map((w) => ({
|
||||
id: w.id,
|
||||
enabled: !!w.enabled,
|
||||
col_span: parseInt(w.col_span, 10) || 6,
|
||||
order: w.order
|
||||
}));
|
||||
const payload = focizWidgets.map((w) => {
|
||||
const obj = {
|
||||
id: w.id,
|
||||
enabled: !!w.enabled,
|
||||
col_span: parseInt(w.col_span, 10) || 6,
|
||||
order: w.order
|
||||
};
|
||||
if (w.custom_title) {
|
||||
obj.custom_title = w.custom_title;
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
|
||||
try {
|
||||
localStorage.setItem('fociz_widget_layout_cache', JSON.stringify(payload));
|
||||
@@ -2352,6 +2401,9 @@ function getActiveWidgetLayoutPayload() {
|
||||
col_span: colSpan,
|
||||
order: currentOrder++
|
||||
};
|
||||
if (item && item.custom_title) {
|
||||
widgetObj.custom_title = item.custom_title;
|
||||
}
|
||||
payload.push(widgetObj);
|
||||
if (item) {
|
||||
item.order = widgetObj.order;
|
||||
@@ -2366,12 +2418,16 @@ function getActiveWidgetLayoutPayload() {
|
||||
focizWidgets.forEach(w => {
|
||||
if (!seenIds.has(w.id)) {
|
||||
seenIds.add(w.id);
|
||||
payload.push({
|
||||
const hiddenObj = {
|
||||
id: w.id,
|
||||
enabled: !!w.enabled,
|
||||
col_span: parseInt(w.col_span, 10) || 6,
|
||||
order: currentOrder++
|
||||
});
|
||||
};
|
||||
if (w.custom_title) {
|
||||
hiddenObj.custom_title = w.custom_title;
|
||||
}
|
||||
payload.push(hiddenObj);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2453,6 +2509,67 @@ function setWidgetSpan(wId, span) {
|
||||
showWidgetToast(`Formaat aangepast naar ${spanDescriptions[span] || span + ' kolommen'}`, 'success');
|
||||
}
|
||||
|
||||
// --- Widget Naam / Titel Aanpassen ---
|
||||
function saveWidgetTitle(wId) {
|
||||
const input = document.getElementById(`widgetTitleInput_${wId}`);
|
||||
if (!input) return;
|
||||
|
||||
const defaultTitle = input.getAttribute('data-default-title') || '';
|
||||
let newTitle = input.value.trim();
|
||||
if (!newTitle) {
|
||||
newTitle = defaultTitle;
|
||||
input.value = defaultTitle;
|
||||
}
|
||||
|
||||
// Directe DOM-update op de titelbalk van de widget
|
||||
const titleEl = document.getElementById(`widgetTitleText_${wId}`);
|
||||
if (titleEl) {
|
||||
titleEl.textContent = newTitle;
|
||||
}
|
||||
|
||||
// Werk de in-memory widget catalogus bij
|
||||
const item = focizWidgets.find(x => x.id === wId);
|
||||
if (item) {
|
||||
if (newTitle !== defaultTitle) {
|
||||
item.custom_title = newTitle;
|
||||
item.title = newTitle;
|
||||
} else {
|
||||
delete item.custom_title;
|
||||
item.title = defaultTitle;
|
||||
}
|
||||
}
|
||||
|
||||
// Sluit het instellingen menu
|
||||
document.getElementById(`widgetSettingsMenu_${wId}`)?.classList.add('hidden');
|
||||
|
||||
// Automatisch opslaan naar database
|
||||
persistWidgetLayoutAuto();
|
||||
showWidgetToast(`Widgetnaam aangepast naar "${escapeHtml(newTitle)}"`, 'success');
|
||||
}
|
||||
|
||||
function resetWidgetTitle(wId, defaultTitle) {
|
||||
const input = document.getElementById(`widgetTitleInput_${wId}`);
|
||||
if (input) {
|
||||
input.value = defaultTitle;
|
||||
}
|
||||
|
||||
const titleEl = document.getElementById(`widgetTitleText_${wId}`);
|
||||
if (titleEl) {
|
||||
titleEl.textContent = defaultTitle;
|
||||
}
|
||||
|
||||
const item = focizWidgets.find(x => x.id === wId);
|
||||
if (item) {
|
||||
delete item.custom_title;
|
||||
item.title = defaultTitle;
|
||||
}
|
||||
|
||||
document.getElementById(`widgetSettingsMenu_${wId}`)?.classList.add('hidden');
|
||||
|
||||
persistWidgetLayoutAuto();
|
||||
showWidgetToast('Standaard widgetnaam hersteld', 'info');
|
||||
}
|
||||
|
||||
// --- Widget Instellingen Popover Menu (Tandwieltje) ---
|
||||
function toggleWidgetSettingsMenu(wId, event) {
|
||||
if (event) {
|
||||
|
||||
Reference in New Issue
Block a user