diff --git a/frontend/wishes.html b/frontend/wishes.html
index 6e2ec23..aaabf94 100644
--- a/frontend/wishes.html
+++ b/frontend/wishes.html
@@ -10,62 +10,120 @@
@@ -74,35 +132,41 @@
-
Пожелания по улучшению
-
Предложите, что улучшить в системе — мы это увидим и ответим.
+
+
+
+
+
Пожелания по улучшению
+
Есть идея, как сделать систему лучше? Расскажите — мы прочитаем и ответим.
+
+
+
-
-
-
-
-
-
-
-
-
@@ -116,81 +180,53 @@
LS.showBoardIfAllowed();
LS.notif.init();
- const CAT_LABEL = { ui: 'Интерфейс', content: 'Контент', feature: 'Новая функция', bug: 'Баг', other: 'Другое' };
- const ST_LABEL = { new: 'Новое', planned: 'Запланировано', in_progress: 'В работе', done: 'Готово', declined: 'Отклонено' };
- const ST_ORDER = ['new', 'planned', 'in_progress', 'done', 'declined'];
- let _statusFilter = null;
- let _wishes = [];
+ const CAT = {
+ feature: { label: 'Новая функция', icon: 'sparkles', color: '#9B5DE5' },
+ ui: { label: 'Интерфейс', icon: 'layout-panel-top', color: '#06B6D4' },
+ content: { label: 'Контент', icon: 'book-open', color: '#2563EB' },
+ bug: { label: 'Баг / ошибка', icon: 'bug', color: '#EF476F' },
+ other: { label: 'Другое', icon: 'message-circle', color: '#64748B' },
+ };
+ const CAT_ORDER = ['feature', 'ui', 'content', 'bug', 'other'];
+ const ST = {
+ new: { label: 'Новое', icon: 'sparkle', color: '#06aab3' },
+ planned: { label: 'Запланировано', icon: 'calendar-clock', color: '#9B5DE5' },
+ in_progress: { label: 'В работе', icon: 'loader', color: '#d97706' },
+ done: { label: 'Готово', icon: 'check-circle-2', color: '#059652' },
+ declined: { label: 'Отклонено', icon: 'x-circle', color: '#64748B' },
+ };
+ const ST_ORDER = ['new', 'planned', 'in_progress', 'done', 'declined'];
+
+ let _wishes = [], _statusFilter = null, _catFilter = null, _q = '', _formCat = 'feature', _formOpen = false;
function fmtDate(s) {
if (!s) return '';
const d = new Date(s.includes('T') ? s : s.replace(' ', 'T') + 'Z');
return d.toLocaleDateString('ru', { day: 'numeric', month: 'short', year: 'numeric' });
}
+ function icons() { if (window.lucide) lucide.createIcons(); }
- async function load() {
- try {
- const params = _statusFilter ? { status: _statusFilter } : {};
- const data = await LS.wishesList(params);
- _wishes = data.wishes || [];
- if (data.isAdmin) renderFilters(data.counts || {});
- render();
- } catch (e) {
- document.getElementById('w-list').innerHTML = `
Не удалось загрузить: ${esc(e.message || '')}
`;
- }
+ /* ── form ── */
+ function renderCatPick() {
+ document.getElementById('wq-cat-pick').innerHTML = CAT_ORDER.map(k =>
+ `
`).join('');
+ icons();
}
-
- function renderFilters(counts) {
- const el = document.getElementById('w-filters');
- el.style.display = '';
- const total = Object.values(counts).reduce((a, b) => a + b, 0);
- let html = `
`;
- html += ST_ORDER.map(s => counts[s]
- ? `
`
- : '').join('');
- el.innerHTML = html;
+ function pickCat(k) { _formCat = k; renderCatPick(); }
+ function updCounter() {
+ const n = document.getElementById('wf-title').value.length;
+ document.getElementById('wf-counter').textContent = n + ' / 200';
}
-
- function setFilter(s) { _statusFilter = s; load(); }
-
- function render() {
- const el = document.getElementById('w-list');
- if (!_wishes.length) {
- el.innerHTML = `
${isAdmin ? 'Пожеланий пока нет.' : 'Вы ещё не оставляли пожеланий. Поделитесь идеей выше!'}
`;
- if (window.lucide) lucide.createIcons();
- return;
- }
- el.innerHTML = _wishes.map(cardHtml).join('');
- if (window.lucide) lucide.createIcons();
- }
-
- const ST_COLOR = { new: '#06aab3', planned: '#9B5DE5', in_progress: '#d97706', done: '#059652', declined: '#94A3B8' };
-
- function cardHtml(w) {
- const author = (isAdmin && w.author_name) ? `
${esc(w.author_name)} · ` : '';
- const noteHtml = w.admin_note ? `
Ответ: ${esc(w.admin_note)}
` : '';
- let manage = '';
- if (isAdmin) {
- const opts = ST_ORDER.map(s => `
`).join('');
- manage = `
-
-
-
-
-
`;
- } else if (w.status === 'new') {
- manage = `
`;
- }
- return `
-
- ${esc(w.title)}
- ${ST_LABEL[w.status] || w.status}
-
-
${author}${CAT_LABEL[w.category] || w.category} · ${fmtDate(w.created_at)}
- ${w.body ? `
${esc(w.body)}
` : ''}
- ${noteHtml}
- ${manage}
-
`;
+ function toggleForm(forceOpen) {
+ _formOpen = forceOpen === undefined ? !_formOpen : forceOpen;
+ document.getElementById('wq-form').classList.toggle('collapsed', !_formOpen);
+ const btn = document.getElementById('wq-new-btn');
+ btn.classList.toggle('open', _formOpen);
+ document.getElementById('wq-new-lbl').textContent = _formOpen ? 'Свернуть' : 'Поделиться идеей';
+ btn.querySelector('i').setAttribute('data-lucide', _formOpen ? 'chevron-up' : 'plus');
+ icons();
+ if (_formOpen) setTimeout(() => document.getElementById('wf-title').focus(), 80);
}
async function submitWish() {
@@ -199,28 +235,133 @@
const btn = document.getElementById('wf-submit');
btn.disabled = true;
try {
- await LS.wishCreate({
- title,
- category: document.getElementById('wf-cat').value,
- body: document.getElementById('wf-body').value.trim(),
- });
+ const row = await LS.wishCreate({ title, category: _formCat, body: document.getElementById('wf-body').value.trim() });
+ if (isAdmin && user) { row.author_name = user.name; }
+ _wishes.unshift(row);
document.getElementById('wf-title').value = '';
document.getElementById('wf-body').value = '';
+ _formCat = 'feature'; renderCatPick(); updCounter();
+ toggleForm(false);
LS.toast('Пожелание отправлено — спасибо!', 'success');
- _statusFilter = null;
- await load();
+ _statusFilter = null; _catFilter = null;
+ renderAll();
} catch (e) { LS.toast(e.message || 'Ошибка', 'error'); }
finally { btn.disabled = false; }
}
+ /* ── load + render ── */
+ async function load() {
+ try {
+ const data = await LS.wishesList();
+ _wishes = data.wishes || [];
+ renderAll();
+ } catch (e) {
+ document.getElementById('w-list').innerHTML = `
Не удалось загрузить
${esc(e.message || '')}
`;
+ }
+ }
+
+ function counts() {
+ const c = {}; ST_ORDER.forEach(s => c[s] = 0);
+ _wishes.forEach(w => { c[w.status] = (c[w.status] || 0) + 1; });
+ return c;
+ }
+
+ function renderAll() { renderStats(); renderSubbar(); renderList(); }
+
+ function renderStats() {
+ const c = counts();
+ const total = _wishes.length;
+ let html = `
`;
+ html += ST_ORDER.filter(s => c[s] > 0).map(s =>
+ `
`).join('');
+ document.getElementById('wq-stats').innerHTML = html;
+ }
+
+ function renderSubbar() {
+ const cats = [...new Set(_wishes.map(w => w.category))];
+ const bar = document.getElementById('wq-subbar');
+ // показываем подбар только если есть смысл (несколько категорий или много пожеланий)
+ if (cats.length < 2 && _wishes.length < 4) { bar.style.display = 'none'; return; }
+ bar.style.display = '';
+ document.getElementById('wq-cats').innerHTML = CAT_ORDER.filter(k => cats.includes(k)).map(k =>
+ `
`).join('');
+ document.getElementById('wq-search').style.display = _wishes.length >= 4 ? '' : 'none';
+ icons();
+ }
+
+ function setStatus(s) { _statusFilter = (_statusFilter === s) ? null : s; renderAll(); }
+ function setCat(k) { _catFilter = (_catFilter === k) ? null : k; renderAll(); }
+ function onSearch(v) { _q = v.trim().toLowerCase(); renderList(); }
+
+ function renderList() {
+ const el = document.getElementById('w-list');
+ let list = _wishes;
+ if (_statusFilter) list = list.filter(w => w.status === _statusFilter);
+ if (_catFilter) list = list.filter(w => w.category === _catFilter);
+ if (_q) list = list.filter(w =>
+ (w.title || '').toLowerCase().includes(_q) ||
+ (w.body || '').toLowerCase().includes(_q) ||
+ (w.author_name || '').toLowerCase().includes(_q));
+
+ if (!list.length) {
+ const fresh = !_wishes.length;
+ el.innerHTML = `
+
+
${fresh ? (isAdmin ? 'Пожеланий пока нет' : 'У вас пока нет пожеланий') : 'Ничего не найдено'}
+
${fresh ? (isAdmin ? 'Они появятся здесь, когда пользователи их оставят.' : 'Поделитесь идеей — нажмите «Поделиться идеей» выше.') : 'Попробуйте изменить фильтр или запрос.'}
+
`;
+ icons();
+ return;
+ }
+ el.innerHTML = list.map(cardHtml).join('');
+ icons();
+ }
+
+ function cardHtml(w) {
+ const cat = CAT[w.category] || CAT.other;
+ const st = ST[w.status] || ST.new;
+ const author = (isAdmin && w.author_name) ? `
${esc(w.author_name)}·` : '';
+ const note = w.admin_note ? `
Ответ: ${esc(w.admin_note)}
` : '';
+ let manage = '';
+ if (isAdmin) {
+ const opts = ST_ORDER.map(s => `
`).join('');
+ manage = `
+
+
+
+
+
`;
+ } else if (w.status === 'new') {
+ manage = `
`;
+ }
+ return `
+
+
+
+ ${esc(w.title)}
+ ${st.label}
+
+
${author}${cat.label}·${fmtDate(w.created_at)}
+ ${w.body ? `
${esc(w.body)}
` : ''}
+ ${note}
+ ${manage}
+
+
`;
+ }
+
async function saveWish(id) {
try {
- await LS.wishUpdate(id, {
+ const upd = await LS.wishUpdate(id, {
status: document.getElementById('st-' + id).value,
admin_note: document.getElementById('note-' + id).value.trim(),
});
+ const i = _wishes.findIndex(w => w.id === id);
+ if (i >= 0) { _wishes[i] = { ..._wishes[i], ...upd }; }
LS.toast('Сохранено', 'success');
- await load();
+ renderAll();
} catch (e) { LS.toast(e.message || 'Ошибка', 'error'); }
}
@@ -229,12 +370,13 @@
try {
await LS.wishDelete(id);
_wishes = _wishes.filter(w => w.id !== id);
- render();
+ renderAll();
} catch (e) { LS.toast(e.message || 'Ошибка', 'error'); }
}
+ renderCatPick();
load();
- if (window.lucide) lucide.createIcons();
+ icons();