- Sidebar header: user menu dropdown (Settings, Switch workspace, Log out) avec avatar + nom + email, chevron animé, fond opaque #1E1E1E - Nouvelle page /settings avec gestion globale des tags: créer tag avec couleur, changer couleur, supprimer, liste avec compteurs - API tag management: POST/PUT/DELETE /api/settings/tags, GET /api/settings/tags/all - Preview panel: affichage images (img tag), PDF (lien viewer), détection format - workspace-chevron CSS: rotation 180° quand menu ouvert
152 lines
6.3 KiB
HTML
152 lines
6.3 KiB
HTML
{% extends "base.html" %}
|
||
{% block page_title %}Settings{% endblock %}
|
||
{% block page_icon %}⚙️{% endblock %}
|
||
|
||
{% block topbar %}
|
||
<header class="topbar">
|
||
<button class="hamburger-btn" @click="mobileSidebarOpen = true; sidebarCollapsed = false;" title="Menu">
|
||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||
</button>
|
||
<div class="topbar-left">
|
||
<span class="page-title-topbar">⚙️ Settings</span>
|
||
</div>
|
||
</header>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<style>
|
||
.settings-layout{max-width:720px;margin:0 auto;padding:32px 24px;}
|
||
.settings-section{margin-bottom:32px;}
|
||
.settings-section h2{font-size:13px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-tertiary);margin-bottom:12px;padding-bottom:6px;border-bottom:1px solid var(--border);}
|
||
.setting-row{display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--border);}
|
||
.setting-row:last-child{border-bottom:none;}
|
||
.setting-label{font-size:14px;color:var(--text);}
|
||
.setting-desc{font-size:12px;color:var(--text-tertiary);margin-top:2px;}
|
||
.setting-control{flex-shrink:0;margin-left:16px;}
|
||
|
||
/* Tags management */
|
||
.tag-mgmt-list{display:flex;flex-direction:column;gap:4px;}
|
||
.tag-mgmt-item{display:flex;align-items:center;gap:10px;padding:8px 10px;background:var(--bg-tertiary);border-radius:8px;}
|
||
.tag-mgmt-item .tag-color-dot{width:14px;height:14px;border-radius:50%;flex-shrink:0;}
|
||
.tag-mgmt-item .tag-name{flex:1;font-size:13px;color:var(--text);}
|
||
.tag-mgmt-item .tag-count{font-size:12px;color:var(--text-tertiary);}
|
||
.tag-mgmt-item .tag-actions{display:flex;gap:4px;}
|
||
.tag-mgmt-item .tag-del-btn{background:none;border:none;color:var(--text-tertiary);cursor:pointer;font-size:14px;padding:2px 6px;border-radius:4px;}
|
||
.tag-mgmt-item .tag-del-btn:hover{color:var(--danger);background:rgba(255,80,80,.1);}
|
||
|
||
/* Color picker grid */
|
||
.color-picker{display:flex;gap:4px;flex-wrap:wrap;}
|
||
.color-swatch{width:22px;height:22px;border-radius:50%;cursor:pointer;border:2px solid transparent;transition:all 120ms;}
|
||
.color-swatch:hover{transform:scale(1.15);}
|
||
.color-swatch.selected{border-color:var(--text);box-shadow:0 0 0 2px var(--accent);}
|
||
|
||
/* New tag form */
|
||
.new-tag-form{display:flex;align-items:center;gap:10px;margin-top:8px;padding:8px 10px;background:var(--bg-tertiary);border:1px dashed var(--border);border-radius:8px;}
|
||
.new-tag-form input{flex:1;background:none;border:none;color:var(--text);font-size:13px;outline:none;}
|
||
.new-tag-form input::placeholder{color:var(--text-tertiary);}
|
||
.new-tag-form .new-tag-color{margin-right:6px;}
|
||
</style>
|
||
|
||
<div class="settings-layout" x-data="settingsInit()">
|
||
<!-- Tags Management -->
|
||
<div class="settings-section">
|
||
<h2>🏷️ Tags Management</h2>
|
||
<p class="setting-desc" style="margin-bottom:12px;">Manage all tags across your workspace. Changes apply globally.</p>
|
||
<div class="tag-mgmt-list">
|
||
<template x-for="tag in allTags" :key="tag.id">
|
||
<div class="tag-mgmt-item">
|
||
<div class="tag-color-dot" :style="{background: tag.color}"></div>
|
||
<span class="tag-name" x-text="tag.name"></span>
|
||
<span class="tag-count" x-text="tag.count + ' items'"></span>
|
||
<div class="tag-actions">
|
||
<input type="color" :value="tag.color" @input="updateTagColor(tag.id, $event.target.value)" style="width:24px;height:24px;border:none;cursor:pointer;background:none;padding:0;" title="Change color">
|
||
<button class="tag-del-btn" @click="deleteTag(tag.id)" title="Delete tag">×</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<div x-show="allTags.length === 0" style="text-align:center;padding:16px;color:var(--text-tertiary);">No tags yet</div>
|
||
</div>
|
||
<!-- New tag form -->
|
||
<div class="new-tag-form">
|
||
<div class="color-picker new-tag-color" style="margin-right:8px;">
|
||
<template x-for="c in presetColors" :key="c">
|
||
<div class="color-swatch" :class="{ selected: newTagColor === c }" :style="{background: c}" @click="newTagColor=c"></div>
|
||
</template>
|
||
</div>
|
||
<input type="text" placeholder="New tag name..." x-model="newTagName" @keydown.enter="createTag()">
|
||
<button class="btn btn-primary" @click="createTag()" :disabled="!newTagName.trim()" style="padding:4px 12px;font-size:12px;">Add</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Workspace Info -->
|
||
<div class="settings-section">
|
||
<h2>📋 Workspace</h2>
|
||
<div class="setting-row">
|
||
<div>
|
||
<div class="setting-label">Workspace name</div>
|
||
<div class="setting-desc">Current: {{ workspaceName }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
document.addEventListener('alpine:init', function() {
|
||
Alpine.data('settingsInit', function() {
|
||
return {
|
||
allTags: [],
|
||
newTagName: '',
|
||
newTagColor: '#787774',
|
||
presetColors: ['#787774','#E03E3E','#D9730D','#DFAB01','#0F7B6C','#0B6E99','#6940A5','#AD1A72','#E16259','#D4A72C','#448361','#337EA9','#9065B0','#C94D8B'],
|
||
|
||
async init() {
|
||
await this.loadTags();
|
||
},
|
||
|
||
async loadTags() {
|
||
try {
|
||
var r = await fetch('/api/settings/tags/all');
|
||
var d = await r.json();
|
||
this.allTags = d.tags || [];
|
||
} catch(e) { this.allTags = []; }
|
||
},
|
||
|
||
async createTag() {
|
||
var n = this.newTagName.trim();
|
||
if (!n) return;
|
||
try {
|
||
var r = await fetch('/api/settings/tags', {
|
||
method: 'POST', headers: {'Content-Type':'application/json'},
|
||
body: JSON.stringify({name: n, color: this.newTagColor})
|
||
});
|
||
if (r.ok) {
|
||
this.newTagName = '';
|
||
this.newTagColor = '#787774';
|
||
await this.loadTags();
|
||
}
|
||
} catch(e) {}
|
||
},
|
||
|
||
async updateTagColor(id, color) {
|
||
try {
|
||
await fetch('/api/settings/tags/' + id, {
|
||
method: 'PUT', headers: {'Content-Type':'application/json'},
|
||
body: JSON.stringify({color: color})
|
||
});
|
||
await this.loadTags();
|
||
} catch(e) {}
|
||
},
|
||
|
||
async deleteTag(id) {
|
||
if (!confirm('Delete this tag?')) return;
|
||
try {
|
||
await fetch('/api/settings/tags/' + id, { method: 'DELETE' });
|
||
await this.loadTags();
|
||
} catch(e) {}
|
||
}
|
||
};
|
||
});
|
||
});
|
||
</script>
|
||
{% endblock %}
|