Add subcategory management to admin panel + Kala/Liha defaults

Categories now show their subcategories as tags with remove buttons,
plus an input field to add new subcategories directly from admin.
Added Kala and Liha as default subcategories for Reseptit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 12:48:01 +02:00
parent 44de6ef76d
commit 74c31f898c
2 changed files with 60 additions and 10 deletions

View File

@@ -159,6 +159,19 @@
.cat-info { flex: 1; font-family: Arial, sans-serif; font-size: 0.88rem; color: #3b2a1a; } .cat-info { flex: 1; font-family: Arial, sans-serif; font-size: 0.88rem; color: #3b2a1a; }
.cat-info span { color: #7a5c3e; font-size: 0.78rem; } .cat-info span { color: #7a5c3e; font-size: 0.78rem; }
/* Subcategory tags */
.subcat-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding-left: 38px; }
.subcat-tag {
display: inline-flex; align-items: center; gap: 4px;
background: #f5ece0; color: #7c4a1e; padding: 3px 10px; border-radius: 12px;
font-family: Arial, sans-serif; font-size: 0.78rem; font-weight: bold;
}
.subcat-remove {
background: none; border: none; color: #c0856a; cursor: pointer;
font-size: 0.7rem; padding: 0 2px; line-height: 1;
}
.subcat-remove:hover { color: #c04040; }
/* User list */ /* User list */
.user-table { width: 100%; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 0.88rem; } .user-table { width: 100%; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 0.88rem; }
.user-table th { text-align: left; color: #7a5c3e; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.5px; padding: 6px 10px; border-bottom: 2px solid #e8d5c0; } .user-table th { text-align: left; color: #7a5c3e; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.5px; padding: 6px 10px; border-bottom: 2px solid #e8d5c0; }
@@ -575,19 +588,54 @@
const cats = ADMIN.categories; const cats = ADMIN.categories;
const el = document.getElementById('catList'); const el = document.getElementById('catList');
if (!cats.length) { el.innerHTML = `<p class="empty-state">${at('cat_empty')}</p>`; return; } if (!cats.length) { el.innerHTML = `<p class="empty-state">${at('cat_empty')}</p>`; return; }
el.innerHTML = cats.map(c => ` el.innerHTML = cats.map(c => {
<div class="cat-item"> const subs = (c.subcategories || []).map(s =>
<span class="cat-emoji">${c.emoji || '📁'}</span> `<span class="subcat-tag">${s.fi} <button onclick="removeSubcat('${c.id}','${s.id}')" class="subcat-remove">✕</button></span>`
<div class="cat-info"> ).join('');
<strong>${c.fi}</strong> / ${c.en} return `
<span> · id: ${c.id}</span> <div class="cat-item" style="flex-wrap:wrap">
</div> <span class="cat-emoji">${c.emoji || '📁'}</span>
<button class="delete-btn" onclick="deleteCategory('${c.id}')">${at('poista')}</button> <div class="cat-info">
</div> <strong>${c.fi}</strong> / ${c.en}
`).join(''); <span> · id: ${c.id}</span>
</div>
<button class="delete-btn" onclick="deleteCategory('${c.id}')">${at('poista')}</button>
<div class="subcat-row" style="width:100%;margin-top:6px">
${subs || '<span style="color:#bbb;font-size:0.8rem">Ei alikategorioita</span>'}
<div class="subcat-add" style="display:flex;gap:6px;margin-top:6px">
<input type="text" id="newSub_${c.id}" placeholder="Uusi alikategoria..." style="flex:1;padding:5px 10px;border:1px solid #e8d5c0;border-radius:6px;font-size:0.82rem" />
<button onclick="addSubcat('${c.id}')" style="padding:5px 12px;background:#e07b39;color:#fff;border:none;border-radius:6px;font-size:0.82rem;cursor:pointer">+ Lisää</button>
</div>
</div>
</div>`;
}).join('');
populateCategorySelect(); populateCategorySelect();
} }
async function addSubcat(catId) {
const input = document.getElementById('newSub_' + catId);
const name = input.value.trim();
if (!name) return;
const cat = ADMIN.categories.find(c => c.id === catId);
if (!cat) return;
if (!cat.subcategories) cat.subcategories = [];
const id = name.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/\s+/g,'_').replace(/[^a-z0-9_]/g,'');
if (cat.subcategories.some(s => s.id === id)) { showToast('⚠️ Alikategoria on jo olemassa.'); return; }
cat.subcategories.push({ id, fi: name });
await apiPost('save_categories', { categories: ADMIN.categories });
renderCatList();
showToast('✅ Alikategoria lisätty!');
}
async function removeSubcat(catId, subId) {
const cat = ADMIN.categories.find(c => c.id === catId);
if (!cat) return;
cat.subcategories = (cat.subcategories || []).filter(s => s.id !== subId);
await apiPost('save_categories', { categories: ADMIN.categories });
renderCatList();
showToast('🗑️ Alikategoria poistettu.');
}
async function addCategory() { async function addCategory() {
const fi = document.getElementById('newCatFi').value.trim(); const fi = document.getElementById('newCatFi').value.trim();
const en = document.getElementById('newCatEn').value.trim(); const en = document.getElementById('newCatEn').value.trim();

View File

@@ -104,6 +104,8 @@ function defaultCategories(): array {
'subcategories' => [ 'subcategories' => [
['id' => 'kasvis', 'fi' => 'Kasvis'], ['id' => 'kasvis', 'fi' => 'Kasvis'],
['id' => 'vegaaniset', 'fi' => 'Vegaaniset'], ['id' => 'vegaaniset', 'fi' => 'Vegaaniset'],
['id' => 'kala', 'fi' => 'Kala'],
['id' => 'liha', 'fi' => 'Liha'],
['id' => 'jalkiruuat', 'fi' => 'Jälkiruuat'], ['id' => 'jalkiruuat', 'fi' => 'Jälkiruuat'],
['id' => 'muut', 'fi' => 'Muut'], ['id' => 'muut', 'fi' => 'Muut'],
]], ]],