This commit is contained in:
lfirmin
2026-05-10 18:07:51 +02:00
parent 50af2452b1
commit a5f888b3c1
59 changed files with 6682 additions and 108 deletions
+431
View File
@@ -0,0 +1,431 @@
{{define "title"}}{{if .IsNew}}Nouveau panel{{else}}Éditer — {{.Panel.Name}}{{end}}{{end}}
{{define "header"}}{{if .IsNew}}Créer un panel{{else}}Éditer {{.Panel.Name}}{{end}}{{end}}
{{define "content"}}
<form id="panelForm" method="POST" action="{{if .IsNew}}/panels/new{{else}}/panels/{{.Panel.ID}}/edit{{end}}">
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
{{if and .IsNew .Guilds}}
<div class="bg-discord-card rounded-lg border border-discord-border p-4 mb-4 flex items-center gap-4">
<label class="text-sm text-gray-300 flex-shrink-0">Serveur Discord :</label>
<select id="guildSelector" class="px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white text-sm focus:outline-none focus:border-indigo-500"
onchange="location.href='/panels/new?guild_id='+this.value">
{{range .Guilds}}
<option value="{{.ID}}" {{if eq .ID $.Panel.GuildID}}selected{{end}}>{{.Name}}</option>
{{end}}
</select>
<p class="text-xs text-gray-500">Les rôles et channels chargés correspondent au serveur sélectionné.</p>
</div>
{{end}}
<input type="hidden" name="guild_id" value="{{.Panel.GuildID}}">
<div class="grid grid-cols-1 xl:grid-cols-3 gap-6">
<!-- Left: form -->
<div class="xl:col-span-2 space-y-6">
<!-- Section 1: Infos panel -->
<div class="bg-discord-card rounded-lg border border-discord-border p-6">
<h2 class="font-semibold text-white mb-4">Informations du panel</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="sm:col-span-2">
<label class="block text-sm text-gray-300 mb-1">Nom interne <span class="text-red-400">*</span></label>
<input type="text" name="name" value="{{.Panel.Name}}" required {{if not .IsNew}}readonly{{end}}
class="w-full px-3 py-2 bg-gray-800 border {{if index .Errors "name"}}border-red-600{{else}}border-gray-600{{end}} rounded-lg text-white focus:outline-none focus:border-indigo-500 {{if not .IsNew}}opacity-60 cursor-not-allowed{{end}}">
{{if index .Errors "name"}}<p class="text-red-400 text-xs mt-1">{{index .Errors "name"}}</p>{{end}}
</div>
<div class="sm:col-span-2">
<label class="block text-sm text-gray-300 mb-1">Titre embed <span class="text-red-400">*</span></label>
<input type="text" name="embed_title" value="{{.Panel.EmbedTitle}}" required
hx-post="/panels/preview" hx-trigger="input changed delay:300ms" hx-include="#panelForm" hx-target="#preview"
class="w-full px-3 py-2 bg-gray-800 border {{if index .Errors "embed_title"}}border-red-600{{else}}border-gray-600{{end}} rounded-lg text-white focus:outline-none focus:border-indigo-500">
{{if index .Errors "embed_title"}}<p class="text-red-400 text-xs mt-1">{{index .Errors "embed_title"}}</p>{{end}}
</div>
<div class="sm:col-span-2">
<label class="block text-sm text-gray-300 mb-1">Description embed</label>
<textarea name="embed_description" rows="3"
hx-post="/panels/preview" hx-trigger="input changed delay:300ms" hx-include="#panelForm" hx-target="#preview"
class="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white focus:outline-none focus:border-indigo-500 resize-none">{{.Panel.EmbedDescription.String}}</textarea>
</div>
<div>
<label class="block text-sm text-gray-300 mb-1">Couleur embed</label>
<div class="flex items-center gap-3">
<input type="color" name="embed_color" value="{{.Panel.EmbedColor}}" id="embedColor"
hx-post="/panels/preview" hx-trigger="input changed delay:200ms" hx-include="#panelForm" hx-target="#preview"
class="h-9 w-16 rounded cursor-pointer bg-gray-800 border border-gray-600">
<span id="embedColorText" class="text-sm text-gray-400">{{.Panel.EmbedColor}}</span>
</div>
</div>
<div class="sm:col-span-2">
<label class="block text-sm text-gray-300 mb-1">Image URL (grande image en bas de l'embed)</label>
<input type="url" name="embed_image" value="{{.Panel.EmbedImage}}"
class="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white focus:outline-none focus:border-indigo-500"
placeholder="https://...">
</div>
<div>
<label class="block text-sm text-gray-300 mb-1">Channel Discord</label>
{{if .TextChans}}
<select name="channel_id" class="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white focus:outline-none focus:border-indigo-500">
<option value="">— Sélectionner —</option>
{{range .TextChans}}
<option value="{{.ID}}" {{if eq .ID $.Panel.ChannelID.String}}selected{{end}}>#{{.Name}}</option>
{{end}}
</select>
{{else}}
<input type="text" name="channel_id" value="{{.Panel.ChannelID.String}}"
class="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white focus:outline-none focus:border-indigo-500"
placeholder="ID du channel cible">
{{end}}
</div>
</div>
</div>
<!-- Section 2: Types de tickets -->
<div class="bg-discord-card rounded-lg border border-discord-border p-6">
<div class="flex items-center justify-between mb-4">
<h2 class="font-semibold text-white">Types de tickets</h2>
<button type="button" onclick="addType()"
class="px-3 py-1.5 text-sm bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg transition-colors">
+ Ajouter un type
</button>
</div>
<div id="typesList" class="space-y-4">
{{range $i, $t := .Panel.Types}}
{{template "type_block" dict "Type" $t "Index" $i "CSRFToken" $.CSRFToken "Roles" $.Roles "TextChans" $.TextChans "Categories" $.Categories}}
{{end}}
</div>
{{if eq (len .Panel.Types) 0}}
<p id="noTypesMsg" class="text-gray-500 text-sm text-center py-4">Aucun type. Cliquez sur "+ Ajouter un type".</p>
{{end}}
</div>
</div>
<!-- Right: preview + actions -->
<div class="space-y-6">
<div class="bg-discord-card rounded-lg border border-discord-border p-5">
<h3 class="font-semibold text-white mb-3">Aperçu</h3>
<div id="preview">
<div class="rounded-lg overflow-hidden border-l-4 bg-[#2b2d31] p-4" style="border-left-color:{{.Panel.EmbedColor}}">
<p class="font-semibold text-white mb-1">{{if .Panel.EmbedTitle}}{{.Panel.EmbedTitle}}{{else}}Titre du panel{{end}}</p>
{{if .Panel.EmbedDescription.Valid}}
<p class="text-gray-300 text-sm">{{.Panel.EmbedDescription.String}}</p>
{{end}}
{{if .Panel.Types}}
<div class="flex flex-wrap gap-2 mt-3">
{{range .Panel.Types}}
<span class="{{buttonClass .ButtonColor}} text-white text-sm px-3 py-1.5 rounded">{{.ButtonEmoji}} {{.ButtonLabel}}</span>
{{end}}
</div>
{{end}}
</div>
</div>
</div>
<div class="bg-discord-card rounded-lg border border-discord-border p-5 space-y-3">
<button type="submit" name="action" value="save"
class="w-full px-4 py-2.5 bg-indigo-600 hover:bg-indigo-500 text-white font-medium rounded-lg transition-colors">
Sauvegarder
</button>
<button type="submit" name="action" value="save_send"
class="w-full px-4 py-2.5 bg-green-700 hover:bg-green-600 text-white font-medium rounded-lg transition-colors">
Sauvegarder et envoyer sur Discord
</button>
<a href="/panels" class="block text-center px-4 py-2.5 text-gray-400 hover:text-white border border-gray-700 hover:border-gray-500 rounded-lg transition-colors text-sm">
Annuler
</a>
</div>
</div>
</div>
</form>
<!-- Emoji picker -->
<div id="emojiPicker" class="hidden fixed z-50 bg-gray-900 border border-gray-700 rounded-xl shadow-2xl w-72">
<div class="flex gap-1 p-2 border-b border-gray-700 overflow-x-auto">
<button type="button" onclick="showEmojiCat('smileys')" class="emoji-cat-btn px-2 py-1 text-sm rounded hover:bg-gray-700" data-cat="smileys">😀</button>
<button type="button" onclick="showEmojiCat('gestures')" class="emoji-cat-btn px-2 py-1 text-sm rounded hover:bg-gray-700" data-cat="gestures">👍</button>
<button type="button" onclick="showEmojiCat('activities')" class="emoji-cat-btn px-2 py-1 text-sm rounded hover:bg-gray-700" data-cat="activities">🎮</button>
<button type="button" onclick="showEmojiCat('objects')" class="emoji-cat-btn px-2 py-1 text-sm rounded hover:bg-gray-700" data-cat="objects">📝</button>
<button type="button" onclick="showEmojiCat('symbols')" class="emoji-cat-btn px-2 py-1 text-sm rounded hover:bg-gray-700" data-cat="symbols"></button>
</div>
<div id="emojiGrid" class="p-2 grid grid-cols-8 gap-0.5 max-h-48 overflow-y-auto"></div>
</div>
<!-- Type block template for JS -->
<template id="typeTpl">
<div class="type-block border border-gray-700 rounded-lg p-4 space-y-3 cursor-move" data-index="__IDX__">
<div class="flex items-center justify-between mb-1">
<span class="text-sm font-medium text-gray-300 flex items-center gap-2">
<svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8h16M4 16h16"/></svg>
Type <span class="type-num"></span>
</span>
<button type="button" onclick="removeType(this)" class="text-red-400 hover:text-red-300 text-sm">Supprimer</button>
</div>
<div class="grid grid-cols-2 gap-3">
<div><label class="block text-xs text-gray-400 mb-1">Nom interne</label>
<input type="text" name="types[__IDX__][name]" required class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500" placeholder="ex: support"></div>
<div><label class="block text-xs text-gray-400 mb-1">Label bouton</label>
<input type="text" name="types[__IDX__][button_label]"
hx-post="/panels/preview" hx-trigger="input changed delay:300ms" hx-include="#panelForm" hx-target="#preview"
class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500"></div>
<div><label class="block text-xs text-gray-400 mb-1">Couleur bouton</label>
<select name="types[__IDX__][button_color]"
hx-post="/panels/preview" hx-trigger="change" hx-include="#panelForm" hx-target="#preview"
class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500">
<option value="primary">🔵 Bleu (primary)</option>
<option value="secondary">⚪ Gris (secondary)</option>
<option value="success">🟢 Vert (success)</option>
<option value="danger">🔴 Rouge (danger)</option>
</select></div>
<div><label class="block text-xs text-gray-400 mb-1">Emoji bouton</label>
<div class="flex gap-1">
<input type="text" name="types[__IDX__][button_emoji]" readonly
hx-post="/panels/preview" hx-trigger="input changed delay:200ms" hx-include="#panelForm" hx-target="#preview"
class="flex-1 px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none cursor-pointer" placeholder="—" onclick="openEmojiPicker(this)">
<button type="button" onclick="openEmojiPicker(this.previousElementSibling)" class="px-2 py-1.5 bg-gray-700 hover:bg-gray-600 border border-gray-600 rounded text-sm">😀</button>
</div></div>
<div><label class="block text-xs text-gray-400 mb-1">Titre embed ticket</label>
<input type="text" name="types[__IDX__][embed_title]" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500"></div>
<div><label class="block text-xs text-gray-400 mb-1">Couleur embed</label>
<input type="color" name="types[__IDX__][embed_color]" value="#5865f2" class="h-9 w-full rounded cursor-pointer bg-gray-800 border border-gray-600"></div>
<div class="col-span-2"><label class="block text-xs text-gray-400 mb-1">Texte embed bienvenue</label>
<textarea name="types[__IDX__][embed_text]" rows="2" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500 resize-none"></textarea></div>
<div class="col-span-2"><label class="block text-xs text-gray-400 mb-1">Image URL (optionnel)</label>
<input type="url" name="types[__IDX__][image_url]" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500" placeholder="https://..."></div>
__ROLES_HTML__
__CATEGORIES_HTML__
__TEXTCHANS_HTML__
<div><label class="block text-xs text-gray-400 mb-1">Règle de fermeture</label>
<select name="types[__IDX__][close_rule]" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500">
<option value="staff_only">Staff uniquement</option>
<option value="author_if_unclaimed">Auteur si non claimé</option>
</select></div>
<div class="col-span-2"><label class="block text-xs text-gray-400 mb-1">Reup claim (minutes, 0 = global)</label>
<input type="number" name="types[__IDX__][claim_reup_minutes]" value="0" min="0" max="1440"
class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500"></div>
<div class="flex items-center gap-2">
<input type="hidden" name="types[__IDX__][claim_mode]" value="0">
<input type="checkbox" name="types[__IDX__][claim_mode]" value="1" class="rounded" id="cm__IDX__">
<label for="cm__IDX__" class="text-xs text-gray-400">Système de claim activé</label></div>
<div class="flex items-center gap-2">
<input type="hidden" name="types[__IDX__][modal_enabled]" value="1">
<input type="checkbox" name="types[__IDX__][modal_enabled]" value="1" checked class="rounded" id="me__IDX__">
<label for="me__IDX__" class="text-xs text-gray-400">Modal activé</label></div>
</div>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<script>
let typeCount = {{len .Panel.Types}};
// Emoji data
const emojiData = {
smileys: ['😀','😃','😄','😁','😆','😅','😂','🤣','😊','😇','🙂','😉','😌','😍','🥰','😘','😗','😙','😚','😋','😛','😜','🤪','😝','🤑','🤗','🤭','🤫','🤔','🤐','🤨','😐','😑','😶','😏','😒','🙄','😬','🤥','😌','😔','😪','🤤','😴','😷','🤒','🤕','🤢','🤧','🥵','🥶','🥴','😵','🤯','🤠','🥳','😎','🤓','🧐','😕','😟','🙁','☹️','😮','😯','😲','😳','🥺','😦','😧','😨','😰','😥','😢','😭','😱','😖','😣','😞','😓','😩','😫','🥱','😤','😡','😠','🤬','😈','👿','💀','☠️','💩','🤡','👹','👺','👻','👽','👾','🤖'],
gestures: ['👋','🤚','🖐','✋','🖖','👌','🤌','🤏','✌️','🤞','🤟','🤘','🤙','👈','👉','👆','🖕','👇','☝️','👍','👎','✊','👊','🤛','🤜','👏','🙌','👐','🤲','🤝','🙏','✍️','💅','🤳','💪','🦾','🦿','🦵','🦶','👂','🦻','👃','🫀','🫁','🧠','🦷','🦴','👀','👁','👅','👄'],
activities: ['⚽','🏀','🏈','⚾','🥎','🎾','🏐','🏉','🥏','🎱','🪀','🏓','🏸','🏒','🥍','🏏','🪃','🥅','⛳','🪁','🏹','🎣','🤿','🥊','🥋','🎽','🛹','🛷','⛸','🥌','🎿','⛷','🏂','🏋','🤼','🤸','⛹','🤺','🤾','🏌','🏇','🧘','🏄','🏊','🤽','🚣','🧗','🚵','🚴','🎖','🏆','🥇','🥈','🥉','🏅','🎪','🤹','🎭','🎨','🎬','🎤','🎧','🎼','🎵','🎶','🎷','🪗','🎸','🎹','🎺','🎻','🥁','🪘','🎙','🎚','🎛','📻','🎮','🕹','🎲','🎯','🎳','🎰','🎴','🀄','🎭','🎠','🎡','🎢','🎪'],
objects: ['📱','💻','🖥','🖨','⌨️','🖱','🖲','💽','💾','💿','📀','📷','📸','📹','🎥','📽','🎞','📞','☎️','📟','📠','📺','📻','🧭','⏱','⏲','⏰','🕰','⌚','⏳','📡','🔋','🔌','💡','🔦','🕯','🗑','🛢','💰','💴','💵','💶','💷','💸','💳','🪙','💹','📈','📉','📊','📋','📌','📍','✂️','🗃','🗂','🗄','🗑','🔧','🔨','⚒','🛠','⛏','🔩','🪛','🔑','🗝','🔐','🔏','🔒','🔓','🚪','🪑','🛋','🚽','🪠','🚿','🛁','🛒','🚬','⚰️','🪦','⚱️','🧿','💎','🔮','🧸','🪆','🖼','🪅','🎊','🎉','🎁','🎀','🎗','🎟','🎫','🎖','📝','📓','📔','📒','📕','📗','📘','📙','📚','📖','🔖','🏷','💉','🩸','💊','🩹','🩺','🔬','🔭','🩻','🧬','🦠','🧫','🧪','🌡','🧹','🧺','🧻','🪣','🧼','🫧','🪥','🧽','🧯','🛒'],
symbols: ['✅','❌','❎','🔰','⛔','🚫','📵','🔕','🚷','🚯','🚳','🚱','🔞','📵','🔇','🔈','🔉','🔊','📢','📣','🔔','🔕','🔋','🪫','🔌','💡','🔦','🕯','🪔','🧯','💰','💱','💲','♾','🔁','🔂','▶️','⏩','⏭','⏯','◀️','⏪','⏮','🔀','🔃','🎵','🎶','','','✖️','➗','🟰','♾','💲','💱','🏧','🔱','⚜️','🔰','♻️','✅','🆗','✔️','❎','🆘','❓','❔','❕','❗','⁉️','‼️','🔅','🔆','🔱','⚜️','💠','🌀','⭕','✖️','❌','✖','➰','➿','〽️','✳️','✴️','❇️','💯','☑️','🔘','🔲','🔳','▪️','▫️','◾','◽','◼️','◻️','🟥','🟧','🟨','🟩','🟦','🟪','⬛','⬜','🟫','🔶','🔷','🔸','🔹','🔺','🔻','💠','🔘','🔳','🔲']
};
let currentEmojiInput = null;
let currentCat = 'smileys';
function openEmojiPicker(input) {
currentEmojiInput = input;
const picker = document.getElementById('emojiPicker');
const rect = input.getBoundingClientRect();
picker.style.top = (window.scrollY + rect.bottom + 4) + 'px';
picker.style.left = rect.left + 'px';
picker.classList.remove('hidden');
showEmojiCat(currentCat);
}
function showEmojiCat(cat) {
currentCat = cat;
const grid = document.getElementById('emojiGrid');
grid.innerHTML = emojiData[cat].map(e =>
`<button type="button" onclick="pickEmoji('${e}')" class="text-xl p-1 hover:bg-gray-700 rounded transition-colors" title="${e}">${e}</button>`
).join('');
}
function pickEmoji(e) {
if (currentEmojiInput) {
currentEmojiInput.value = e;
currentEmojiInput.dispatchEvent(new Event('input', {bubbles: true}));
}
document.getElementById('emojiPicker').classList.add('hidden');
}
document.addEventListener('click', function(ev) {
const picker = document.getElementById('emojiPicker');
if (!picker.classList.contains('hidden') && !picker.contains(ev.target) &&
!ev.target.closest('[onclick*="openEmojiPicker"]') && !ev.target.closest('[onclick*="EmojiPicker"]')) {
picker.classList.add('hidden');
}
});
// Sync color picker
document.getElementById('embedColor').addEventListener('input', function() {
document.getElementById('embedColorText').textContent = this.value;
});
// Roles HTML for JS-added type blocks
const rolesHtml = `{{range .Roles}}<option value="{{.ID}}">{{.Name}}</option>{{end}}`;
const categoriesHtml = `{{range .Categories}}<option value="{{.ID}}">📁 {{.Name}}</option>{{end}}`;
const textChansHtml = `{{range .TextChans}}<option value="{{.ID}}"># {{.Name}}</option>{{end}}`;
function makeSelect(name, label, options, value = '') {
if (!options) return `<div><label class="block text-xs text-gray-400 mb-1">${label}</label><input type="text" name="${name}" value="${value}" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500"></div>`;
return `<div><label class="block text-xs text-gray-400 mb-1">${label}</label><select name="${name}" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500"><option value="">— Sélectionner —</option>${options}</select></div>`;
}
// SortableJS
new Sortable(document.getElementById('typesList'), {
animation: 150, handle: '.type-block',
onEnd: function() {
renumberTypes();
{{if not .IsNew}}
const ids = [...document.querySelectorAll('.type-block')].map(el => el.dataset.typeId).filter(Boolean);
if (ids.length) {
fetch('/panels/{{.Panel.ID}}/types/reorder', {
method: 'POST',
headers: {'X-CSRF-Token': '{{.CSRFToken}}', 'Content-Type': 'application/x-www-form-urlencoded'},
body: ids.map(id => 'ids[]=' + id).join('&')
});
}
{{end}}
}
});
function addType() {
document.getElementById('noTypesMsg')?.remove();
let html = document.getElementById('typeTpl').innerHTML.replaceAll('__IDX__', typeCount);
html = html.replace('__ROLES_HTML__', makeSelect(`types[${typeCount}][staff_role_id]`, 'Staff Role', rolesHtml));
html = html.replace('__CATEGORIES_HTML__', makeSelect(`types[${typeCount}][category_id]`, 'Catégorie', categoriesHtml));
html = html.replace('__TEXTCHANS_HTML__',
makeSelect(`types[${typeCount}][claim_channel_id]`, 'Claim Channel', textChansHtml) +
makeSelect(`types[${typeCount}][log_channel_id]`, 'Log Channel', textChansHtml));
const div = document.createElement('div');
div.innerHTML = html;
document.getElementById('typesList').appendChild(div.firstElementChild);
typeCount++;
renumberTypes();
htmx.process(document.getElementById('typesList'));
}
function removeType(btn) {
btn.closest('.type-block').remove();
renumberTypes();
}
function renumberTypes() {
document.querySelectorAll('.type-num').forEach((el, i) => el.textContent = i + 1);
}
</script>
{{end}}
{{define "type_block"}}
<div class="type-block border border-gray-700 rounded-lg p-4 space-y-3 cursor-move" data-index="{{.Index}}" data-type-id="{{.Type.ID}}">
<div class="flex items-center justify-between mb-1">
<span class="text-sm font-medium text-gray-300 flex items-center gap-2">
<svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8h16M4 16h16"/></svg>
Type <span class="type-num">{{add .Index 1}}</span>
</span>
<button type="button" onclick="removeType(this)" class="text-red-400 hover:text-red-300 text-sm">Supprimer</button>
</div>
<div class="grid grid-cols-2 gap-3">
<div><label class="block text-xs text-gray-400 mb-1">Nom interne</label>
<input type="text" name="types[{{.Index}}][name]" value="{{.Type.Name}}" required class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500"></div>
<div><label class="block text-xs text-gray-400 mb-1">Label bouton</label>
<input type="text" name="types[{{.Index}}][button_label]" value="{{.Type.ButtonLabel}}"
hx-post="/panels/preview" hx-trigger="input changed delay:300ms" hx-include="#panelForm" hx-target="#preview"
class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500"></div>
<div><label class="block text-xs text-gray-400 mb-1">Couleur bouton</label>
<select name="types[{{.Index}}][button_color]"
hx-post="/panels/preview" hx-trigger="change" hx-include="#panelForm" hx-target="#preview"
class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500">
<option value="primary" {{if eq .Type.ButtonColor "primary"}}selected{{end}}>🔵 Bleu (primary)</option>
<option value="secondary" {{if eq .Type.ButtonColor "secondary"}}selected{{end}}>⚪ Gris (secondary)</option>
<option value="success" {{if eq .Type.ButtonColor "success"}}selected{{end}}>🟢 Vert (success)</option>
<option value="danger" {{if eq .Type.ButtonColor "danger"}}selected{{end}}>🔴 Rouge (danger)</option>
</select></div>
<div><label class="block text-xs text-gray-400 mb-1">Emoji bouton</label>
<div class="flex gap-1">
<input type="text" name="types[{{.Index}}][button_emoji]" value="{{.Type.ButtonEmoji}}" readonly
hx-post="/panels/preview" hx-trigger="input changed delay:200ms" hx-include="#panelForm" hx-target="#preview"
class="flex-1 px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm cursor-pointer" placeholder="—" onclick="openEmojiPicker(this)">
<button type="button" onclick="openEmojiPicker(this.previousElementSibling)" class="px-2 py-1.5 bg-gray-700 hover:bg-gray-600 border border-gray-600 rounded text-sm">😀</button>
</div></div>
<div><label class="block text-xs text-gray-400 mb-1">Titre embed ticket</label>
<input type="text" name="types[{{.Index}}][embed_title]" value="{{.Type.EmbedTitle}}" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500"></div>
<div><label class="block text-xs text-gray-400 mb-1">Couleur embed</label>
<input type="color" name="types[{{.Index}}][embed_color]" value="{{.Type.EmbedColor}}" class="h-9 w-full rounded cursor-pointer bg-gray-800 border border-gray-600"></div>
<div class="col-span-2"><label class="block text-xs text-gray-400 mb-1">Texte embed bienvenue</label>
<textarea name="types[{{.Index}}][embed_text]" rows="2" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500 resize-none">{{.Type.EmbedText}}</textarea></div>
<div class="col-span-2"><label class="block text-xs text-gray-400 mb-1">Image URL (optionnel)</label>
<input type="url" name="types[{{.Index}}][image_url]" value="{{.Type.ImageURL}}" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500" placeholder="https://..."></div>
<div>
<label class="block text-xs text-gray-400 mb-1">Staff Role</label>
{{if .Roles}}
<select name="types[{{.Index}}][staff_role_id]" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500">
<option value="">— Sélectionner —</option>
{{range .Roles}}<option value="{{.ID}}" {{if eq .ID $.Type.StaffRoleID}}selected{{end}}>{{.Name}}</option>{{end}}
</select>
{{else}}
<input type="text" name="types[{{.Index}}][staff_role_id]" value="{{.Type.StaffRoleID}}" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500">
{{end}}
</div>
<div>
<label class="block text-xs text-gray-400 mb-1">Catégorie</label>
{{if .Categories}}
<select name="types[{{.Index}}][category_id]" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500">
<option value="">— Sélectionner —</option>
{{range .Categories}}<option value="{{.ID}}" {{if eq .ID $.Type.CategoryID}}selected{{end}}>📁 {{.Name}}</option>{{end}}
</select>
{{else}}
<input type="text" name="types[{{.Index}}][category_id]" value="{{.Type.CategoryID}}" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500">
{{end}}
</div>
<div>
<label class="block text-xs text-gray-400 mb-1">Claim Channel</label>
{{if .TextChans}}
<select name="types[{{.Index}}][claim_channel_id]" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500">
<option value="">— Sélectionner —</option>
{{range .TextChans}}<option value="{{.ID}}" {{if eq .ID $.Type.ClaimChannelID}}selected{{end}}># {{.Name}}</option>{{end}}
</select>
{{else}}
<input type="text" name="types[{{.Index}}][claim_channel_id]" value="{{.Type.ClaimChannelID}}" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500">
{{end}}
</div>
<div>
<label class="block text-xs text-gray-400 mb-1">Log Channel</label>
{{if .TextChans}}
<select name="types[{{.Index}}][log_channel_id]" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500">
<option value="">— Sélectionner —</option>
{{range .TextChans}}<option value="{{.ID}}" {{if eq .ID $.Type.LogChannelID}}selected{{end}}># {{.Name}}</option>{{end}}
</select>
{{else}}
<input type="text" name="types[{{.Index}}][log_channel_id]" value="{{.Type.LogChannelID}}" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500">
{{end}}
</div>
<div><label class="block text-xs text-gray-400 mb-1">Max tickets / user</label>
<input type="number" name="types[{{.Index}}][max_per_user]" value="{{.Type.MaxPerUser}}" min="1" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500"></div>
<div><label class="block text-xs text-gray-400 mb-1">Règle de close</label>
<select name="types[{{.Index}}][close_rule]" class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500">
<option value="staff_only" {{if eq .Type.CloseRule "staff_only"}}selected{{end}}>Staff uniquement</option>
<option value="author_if_unclaimed" {{if eq .Type.CloseRule "author_if_unclaimed"}}selected{{end}}>Auteur si non claimé</option>
</select></div>
<div class="flex items-center gap-2 pt-1">
<input type="hidden" name="types[{{.Index}}][claim_mode]" value="0">
<input type="checkbox" name="types[{{.Index}}][claim_mode]" value="1" {{if .Type.ClaimMode}}checked{{end}} class="rounded" id="cm{{.Index}}">
<label for="cm{{.Index}}" class="text-xs text-gray-400">Système de claim activé</label></div>
<div class="col-span-2"><label class="block text-xs text-gray-400 mb-1">Reup claim (minutes, 0 = global)</label>
<input type="number" name="types[{{.Index}}][claim_reup_minutes]" value="{{.Type.ClaimReupMinutes}}" min="0" max="1440"
class="w-full px-2 py-1.5 bg-gray-800 border border-gray-600 rounded text-white text-sm focus:outline-none focus:border-indigo-500"></div>
<div class="flex items-center gap-2 pt-1">
<input type="hidden" name="types[{{.Index}}][modal_enabled]" value="0">
<input type="checkbox" name="types[{{.Index}}][modal_enabled]" value="1" {{if .Type.ModalEnabled}}checked{{end}} class="rounded" id="me{{.Index}}">
<label for="me{{.Index}}" class="text-xs text-gray-400">Modal activé</label></div>
</div>
</div>
{{end}}