Files
flowdeck/app/templates/settings.html
T
bruno 004c47df34
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
feat: sidebar user menu Notion-style + settings page + tag colors + preview images/PDF
- 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
2026-07-12 12:35:17 -04:00

152 lines
6.3 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% 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 %}